diff --git a/.Doxyfile b/.Doxyfile index d67edcf2ff..edd47781df 100644 --- a/.Doxyfile +++ b/.Doxyfile @@ -5,7 +5,7 @@ #--------------------------------------------------------------------------- DOXYFILE_ENCODING = UTF-8 PROJECT_NAME = libskycoin -PROJECT_NUMBER = 0.24.1-develop +PROJECT_NUMBER = 0.25.0-rc1-develop PROJECT_BRIEF = "Skycoin C client library" PROJECT_LOGO = ./docs/assets/sky.libc.jpg OUTPUT_DIRECTORY = ./docs/libc diff --git a/.gitignore b/.gitignore index 40d3912098..3e6cb73981 100644 --- a/.gitignore +++ b/.gitignore @@ -102,6 +102,8 @@ electron/node_modules electron/.gox_output electron/.electron_output electron/.standalone_output +electron/.daemon_output +electron/.cli_output # Do not ignore the icons folder !electron/build !electron/build/icons diff --git a/.travis.yml b/.travis.yml index cf34b49621..ad42d0544e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,12 +7,12 @@ go: matrix: include: - os: linux - env: VERSION_UPGRADE_TEST_WAIT_TIMEOUT=5s + env: VERSION_UPGRADE_TEST_WAIT_TIMEOUT=45s - os: osx # Do not start osx build for PR if: type != pull_request osx_image: xcode8 - env: VERSION_UPGRADE_TEST_WAIT_TIMEOUT=30s + env: VERSION_UPGRADE_TEST_WAIT_TIMEOUT=60s before_install: - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test && sudo apt-get update -qq; fi @@ -48,8 +48,9 @@ install: - cd $GOPATH/src/github.com/skycoin/skycoin - go get -t ./... - make install-linters - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install -qq g++-6 && sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-6 90; fi - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update ; echo 'Available versions (gcc)' && brew list --versions gcc ; brew list gcc@6 &>/dev/null || brew install gcc@6 ; fi + # Install pinned golangci-lint, overriding the latest version install by make install-linters + - VERSION=1.10.2 ./ci-scripts/install-golangci-lint.sh + - ./ci-scripts/install-travis-gcc.sh - make install-deps-libc - nvm install 8.11.0 - nvm use 8.11.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d78d3a29f..ba65b13b2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,15 +11,24 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. In the v0.26.0 these features and functions will be removed. If you have a need for any of these features, let us know. - JSON-RPC 2.0 interface (this is no longer used by the CLI tool, and the REST API supports everything the JSON-RPC 2.0 API does). See https://github.com/skycoin/skycoin/blob/develop/src/api/README.md#migrating-from-the-jsonrpc-api -- `/api/v1/wallet/spend` endpoint (use `POST /api/v1/wallet/transaction` followed by `POST /api/v1/injectTransaction` instead) -- The unversioned REST API (the `-enable-unversioned-api` option will be removed, prefix your API requests with `/api/v1`) +- `/api/v1/wallet/spend` endpoint (use `POST /api/v1/wallet/transaction` followed by `POST /api/v1/injectTransaction` instead). See https://github.com/skycoin/skycoin/blob/develop/src/api/README.md#migrating-from--api-v1-spend +- The unversioned REST API (the `-enable-unversioned-api` option will be removed, prefix your API requests with `/api/v1`). See https://github.com/skycoin/skycoin/blob/develop/src/api/README.md#migrating-from-the-unversioned-api +- `/api/v1/explorer/address` endpoint (use `GET /api/v1/transactions?verbose=1` instead). See https://github.com/skycoin/skycoin/blob/develop/src/api/README.md#migrating-from--api-v1-explorer-address + +### Notice + +Nodes v0.23.0 and earlier will not be able to connect to v0.25.0 due to a change in the introduction packet message. + +Nodes v0.24.1 and earlier will not be able to connect to v0.26.0 due to a similar change. + +Make sure to upgrade to v0.25.0 so that your node will continue to connect once v0.26.0 is released. ### Added - Add `-csv` option to `cli send` and `cli createRawTransaction`, which will send coins to multiple addresses defined in a csv file - Add `-disable-default-peers` option to disable the default hardcoded peers and mark all cached peers as untrusted - Add `-custom-peers-file` to load peers from disk. This peers file is a newline separate list of `ip:port` strings -- Add `csrf_enabled`, `csp_enabled`, `wallet_api_enabled`, `unversioned_api_enabled`, `gui_enabled` and `json_rpc_enabled` configuration settings to the `/api/v1/health` endpoint response +- Add `user_agent`, `coin`, `csrf_enabled`, `csp_enabled`, `wallet_api_enabled`, `unversioned_api_enabled`, `gui_enabled` and `json_rpc_enabled`, `coinhour_burn_factor` configuration settings to the `/api/v1/health` endpoint response - Add `verbose` flag to `/api/v1/block`, `/api/v1/blocks`, `/api/v1/last_blocks`, `/api/v1/pendingTxs`, `/api/v1/transaction`, `/api/v1/transactions`, `/api/v1/wallet/transactions` to return verbose block data, which includes the address, coins, hours and calculcated_hours of the block's transaction's inputs - Add `encoded` flag to `/api/v1/transaction` to return an encoded transaction - Add `-http-prof-host` option to choose the HTTP profiler's bind hostname (defaults to `localhost:6060`) @@ -35,7 +44,13 @@ In the v0.26.0 these features and functions will be removed. If you have a need - Go application metrics exported at `/api/v2/metrics` (API set `PROMETHEUS`) in Prometheus format - Add `/api/v2/wallet/recover` to recover an encrypted wallet by providing the seed - Add `fiberAddressGen` CLI command to generate distribution addresses for fiber coins -- Coinhour burn factor can be configured at runtime with `COINHOUR_BURN_FACTOR` envvar +- Coinhour burn factor when creating transactions can be configured at runtime with `USER_BURN_FACTOR` envvar +- Max transaction size when creating transactions can be configured at runtime with `MAX_USER_TXN_SIZE` envvar +- Daemon configured builds will be available on the [releases](https://github.com/skycoin/skycoin/releases) page. The builds available for previous versions are configured for desktop client use. +- `skycoin-cli` builds will be available on the [releases](https://github.com/skycoin/skycoin/releases) page. +- A user agent string is sent in the wire protocol's introduction packet +- `-max-connections` option to control total max connections +- `/api/v1/network/disconnect` to disconnect a peer ### Fixed @@ -49,6 +64,7 @@ In the v0.26.0 these features and functions will be removed. If you have a need - `POST /api/v1/wallet/newAddress` and `POST /api/v1/wallet/spend` will correctly fail if the wallet is not encrypted but a password is provided - Return `503` error for `/api/v1/injectTransaction` for all message broadcast failures (note that it is still possible for broadcast to fail but no error to be returned, in certain conditions) - Fixed autogenerated HTTPS certs. Certs are now self-signed ECDSA certs, valid for 10 years, valid for localhost and all public interfaces found on the machine. The default cert and key are renamed from cert.pem, key.pem to skycoind.cert, skycoind.key +- `/api/v1/resendUnconfirmedTxns` will return `503 Service Unavailable` is no connections are available for broadcast ### Changed @@ -68,11 +84,24 @@ In the v0.26.0 these features and functions will be removed. If you have a need - `cli addressGen` arguments have changed - `cli generateWallet` renamed to `cli walletCreate` - `cli generateAddresses` renamed to `cli walletAddAddresses` +- `/api/v1/explorer/address` is deprecated in favor of `/api/v1/transactions?verbose=1` +- `/api/v1/balance`, `/api/v1/transactions`, `/api/v1/outputs` and `/api/v1/blocks` accept the `POST` method so that large request bodies can be sent to the server, which would not fit in a `GET` query string +- Send new `DISC` disconnect packet to peer before disconnecting +- `/api/v1/health` `"open_connections"` value now includes incoming connections. Added `"outgoing_connections"` and `"incoming_connections"` fields to separate the two. +- `run.sh` is now `run-client.sh` and a new `run-daemon.sh` script is added for running in server daemon mode +- `/api/v1/network/connection*` connection object's field `"introduced"` replaced with field `"state"` which may have the values `"pending"`, `"connected"` or `"introduced"` +- `/api/v1/network/connection*` field `"is_trusted_peer"` added to connection object to indicate if the peer is in the hardcoded list of default peers +- `/api/v1/network/connection*` field `"connected_at"` added to connection object +- `/api/v1/network/connections` now includes incoming connections. Filters are added to query connections by state and direction +- `/api/v1/resendUnconfirmedTxns` is now a `POST` method, previously was a `GET` method +- Transactions that violation soft constraints will propagate through the network +- Node will send more peers before disconnecting due to a full peer list ### Removed - Remove `USE_CSRF` envvar from the CLI tool. It uses the REST API client now, which will automatically detect CSRF as needed, so no additional configuration is necessary. Operators may still wish to disable CSRF on their remote node to reduce request overhead. - Remove `-enable-wallet-api` and `-enable-seed-api` in place of including `WALLET` and `INSECURE_WALLET_SEED` in `-enable-api-sets`. +- Copies of the source code removed from release builds due to build artifact size ## [0.24.1] - 2018-07-30 @@ -382,7 +411,7 @@ In the v0.26.0 these features and functions will be removed. If you have a need - #383 Error during installation from skycoin source code - #375 Node can't recovery from zero connections - #376 Explorer api `/explorer/address` does not return spend transactions -- #373 Master node will be closed if there're no transactions need to execute +- #373 Block publisher node will be closed if there're no transactions need to execute - #360 Node will crash when do ctrl+c while downloading blocks - #350 Wallet name always 'undefined' after loading wallet from seed diff --git a/INSTALLATION.md b/INSTALLATION.md index 7ef426d19f..7fa6f5db95 100644 --- a/INSTALLATION.md +++ b/INSTALLATION.md @@ -53,8 +53,8 @@ In China, use `--source=https://github.com/golang/go` to bypass firewall when fe gvm install go1.4 --source=https://github.com/golang/go gvm use go1.4 -gvm install go1.10.2 -gvm use go1.10.2 --default +gvm install go1.11.1 +gvm use go1.11.1 --default ``` #### Installation issues @@ -62,7 +62,7 @@ If you open up new a terminal and the `go` command is not found then add this to ```sh [[ -s "$HOME/.gvm/scripts/gvm" ]] && source "$HOME/.gvm/scripts/gvm" -gvm use go1.10.2 >/dev/null +gvm use go1.11.1 >/dev/null ``` ## Install Go manually @@ -72,7 +72,7 @@ Let's go to home directory and declare `go`'s version that you want to download. ```sh cd ~ -export GOV=1.10.2 # golang version +export GOV=1.11.1 # golang version ``` After that, let's download and uncompress golang source. diff --git a/INTEGRATION.md b/INTEGRATION.md index ecf219bb62..e4d5506980 100644 --- a/INTEGRATION.md +++ b/INTEGRATION.md @@ -32,6 +32,7 @@ and to use the CLI tool for wallet operations (seed and address generation, tran +- [Running the skycoin node](#running-the-skycoin-node) - [API Documentation](#api-documentation) - [Wallet REST API](#wallet-rest-api) - [Skycoin command line interface](#skycoin-command-line-interface) @@ -67,6 +68,9 @@ and to use the CLI tool for wallet operations (seed and address generation, tran +## Running the skycoin node + +For integrations, the skycoin node should be run from source with `./run-daemon.sh`. This requires go1.10+ to be installed. ## API Documentation diff --git a/Makefile b/Makefile index a70b3ed0fe..ed706a3bf5 100644 --- a/Makefile +++ b/Makefile @@ -23,6 +23,7 @@ BUILDLIB_DIR = $(BUILD_DIR)/libskycoin LIB_DIR = lib LIB_FILES = $(shell find ./lib/cgo -type f -name "*.go") SRC_FILES = $(shell find ./src -type f -name "*.go") +HEADER_FILES = $(shell find ./include -type f -name "*.h") BIN_DIR = bin DOC_DIR = docs INCLUDE_DIR = include @@ -61,8 +62,11 @@ else LDFLAGS=$(LIBC_FLAGS) endif -run: ## Run the skycoin node. To add arguments, do 'make ARGS="--foo" run'. - ./run.sh ${ARGS} +run-client: ## Run skycoin with desktop client configuration. To add arguments, do 'make ARGS="--foo" run'. + ./run-client.sh ${ARGS} + +run-daemon: ## Run skycoin with server daemon configuration. To add arguments, do 'make ARGS="--foo" run'. + ./run-daemon.sh ${ARGS} run-help: ## Show skycoin node help @go run cmd/$(COIN)/$(COIN).go --help @@ -90,21 +94,23 @@ configure-build: mkdir -p $(BUILD_DIR)/usr/tmp $(BUILD_DIR)/usr/lib $(BUILD_DIR)/usr/include mkdir -p $(BUILDLIB_DIR) $(BIN_DIR) $(INCLUDE_DIR) -$(BUILDLIB_DIR)/libskycoin.so: $(LIB_FILES) $(SRC_FILES) +$(BUILDLIB_DIR)/libskycoin.so: $(LIB_FILES) $(SRC_FILES) $(HEADER_FILES) rm -Rf $(BUILDLIB_DIR)/libskycoin.so go build -buildmode=c-shared -o $(BUILDLIB_DIR)/libskycoin.so $(LIB_FILES) mv $(BUILDLIB_DIR)/libskycoin.h $(INCLUDE_DIR)/ -$(BUILDLIB_DIR)/libskycoin.a: $(LIB_FILES) $(SRC_FILES) +$(BUILDLIB_DIR)/libskycoin.a: $(LIB_FILES) $(SRC_FILES) $(HEADER_FILES) rm -Rf $(BUILDLIB_DIR)/libskycoin.a go build -buildmode=c-archive -o $(BUILDLIB_DIR)/libskycoin.a $(LIB_FILES) mv $(BUILDLIB_DIR)/libskycoin.h $(INCLUDE_DIR)/ +## Build libskycoin C static library build-libc-static: $(BUILDLIB_DIR)/libskycoin.a +## Build libskycoin C shared library build-libc-shared: $(BUILDLIB_DIR)/libskycoin.so -## Build libskycoin C client library +## Build libskycoin C client libraries build-libc: configure-build build-libc-static build-libc-shared ## Build libskycoin C client library and executable C test suites @@ -209,20 +215,33 @@ build-ui: ## Builds the UI build-ui-travis: ## Builds the UI for travis cd $(GUI_STATIC_DIR) && npm run build-travis -release: ## Build electron and standalone apps. Use osarch=${osarch} to specify the platform. Example: 'make release osarch=darwin/amd64', multiple platform can be supported in this way: 'make release osarch="darwin/amd64 windows/amd64"'. Supported architectures are: darwin/amd64 windows/amd64 windows/386 linux/amd64 linux/arm, the builds are located in electron/release folder. +release: ## Build electron, standalone and daemon apps. Use osarch=${osarch} to specify the platform. Example: 'make release osarch=darwin/amd64', multiple platform can be supported in this way: 'make release osarch="darwin/amd64 windows/amd64"'. Supported architectures are: darwin/amd64 windows/amd64 windows/386 linux/amd64 linux/arm, the builds are located in electron/release folder. cd $(ELECTRON_DIR) && ./build.sh ${osarch} @echo release files are in the folder of electron/release -release-bin: ## Build standalone apps. Use osarch=${osarch} to specify the platform. Example: 'make release-bin osarch=darwin/amd64' Supported architectures are the same as 'release' command. +release-standalone: ## Build standalone apps. Use osarch=${osarch} to specify the platform. Example: 'make release-standalone osarch=darwin/amd64' Supported architectures are the same as 'release' command. cd $(ELECTRON_DIR) && ./build-standalone-release.sh ${osarch} @echo release files are in the folder of electron/release -release-gui: ## Build electron apps. Use osarch=${osarch} to specify the platform. Example: 'make release-gui osarch=darwin/amd64' Supported architectures are the same as 'release' command. +release-electron: ## Build electron apps. Use osarch=${osarch} to specify the platform. Example: 'make release-electron osarch=darwin/amd64' Supported architectures are the same as 'release' command. cd $(ELECTRON_DIR) && ./build-electron-release.sh ${osarch} @echo release files are in the folder of electron/release -clean-release: ## Clean dist files and delete all builds in electron/release - rm $(ELECTRON_DIR)/release/* +release-daemon: ## Build daemon apps. Use osarch=${osarch} to specify the platform. Example: 'make release-daemon osarch=darwin/amd64' Supported architectures are the same as 'release' command. + cd $(ELECTRON_DIR) && ./build-daemon-release.sh ${osarch} + @echo release files are in the folder of electron/release + +release-cli: ## Build CLI apps. Use osarch=${osarch} to specify the platform. Example: 'make release-cli osarch=darwin/amd64' Supported architectures are the same as 'release' command. + cd $(ELECTRON_DIR) && ./build-cli-release.sh ${osarch} + @echo release files are in the folder of electron/release + +clean-release: ## Remove all electron build artifacts + rm -rf $(ELECTRON_DIR)/release + rm -rf $(ELECTRON_DIR)/.gox_output + rm -rf $(ELECTRON_DIR)/.daemon_output + rm -rf $(ELECTRON_DIR)/.cli_output + rm -rf $(ELECTRON_DIR)/.standalone_output + rm -rf $(ELECTRON_DIR)/.electron_output clean-coverage: ## Remove coverage output files rm -rf ./coverage/ diff --git a/README.md b/README.md index 36ecc859e9..9017585474 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,9 @@ scratch, to remedy the rough edges in the Bitcoin design. - [Contributing a node to the network](#contributing-a-node-to-the-network) - [Creating a new coin](#creating-a-new-coin) - [Running with a custom coin hour burn factor](#running-with-a-custom-coin-hour-burn-factor) +- [Running with a custom max transaction size](#running-with-a-custom-max-transaction-size) - [URI Specification](#uri-specification) +- [Wire protocol user agent](#wire-protocol-user-agent) - [Development](#development) - [Modules](#modules) - [Client libraries](#client-libraries) @@ -81,7 +83,7 @@ scratch, to remedy the rough edges in the Bitcoin design. - [Rules](#rules) - [Management](#management) - [Configuration Modes](#configuration-modes) - - [Development Desktop Daemon Mode](#development-desktop-daemon-mode) + - [Development Desktop Client Mode](#development-desktop-client-mode) - [Server Daemon Mode](#server-daemon-mode) - [Electron Desktop Client Mode](#electron-desktop-client-mode) - [Standalone Desktop Client Mode](#standalone-desktop-client-mode) @@ -221,12 +223,30 @@ See the [newcoin tool README](./cmd/newcoin/README.md) The coin hour burn factor is the denominator in the ratio of coinhours that must be burned by a transaction. For example, a burn factor of 2 means 1/2 of hours must be burned. A burn factor of 10 means 1/10 of coin hours must be burned. -The coin hour burn factor can be configured with a `COINHOUR_BURN_FACTOR` envvar. It cannot be configured through the command line. +The coin hour burn factor can be configured with a `USER_BURN_FACTOR` envvar. It cannot be configured through the command line. ```sh -COINHOUR_BURN_FACTOR=999 ./run.sh +USER_BURN_FACTOR=999 ./run-client.sh ``` +This burn factor applies to user-created transactions. + +To control the burn factor in other scenarios, use `-burn-factor-unconfirmed` and `-burn-factor-create-block`. + +## Running with a custom max transaction size + +```sh +MAX_USER_TXN_SIZE=1024 ./run-client.sh +``` + +This maximum transaction size applies to user-created transactions. + +To control the transaction size for unconfirmed transactions, use `-unconfirmed-txn-size`. + +To control the max block size, use `-block-size`. + +Transaction and block size are measured in bytes. + ## URI Specification Skycoin URIs obey the same rules as specified in Bitcoin's [BIP21](https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki). @@ -238,6 +258,10 @@ Example Skycoin URIs: * `skycoin:2hYbwYudg34AjkJJCRVRcMeqSWHUixjkfwY?amount=123.456&hours=70` * `skycoin:2hYbwYudg34AjkJJCRVRcMeqSWHUixjkfwY?amount=123.456&hours=70&label=friend&message=Birthday%20Gift` +## Wire protocol user agent + +[Wire protocol user agent description](https://github.com/skycoin/skycoin/wiki/Wire-protocol-user-agent) + ## Development We have two branches: `master` and `develop`. @@ -309,7 +333,7 @@ The live integration tests run against a live runnning skycoin node, so before r need to start a skycoin node: ```sh -./run.sh -launch-browser=false +./run-daemon.sh ``` After the skycoin node is up, run the following command to start the live tests: @@ -519,17 +543,24 @@ There are 4 configuration modes in which you can run a skycoin node: - Electron Desktop Client - Standalone Desktop Client -#### Development Desktop Daemon Mode -This mode is configured via `run.sh` +#### Development Desktop Client Mode +This mode is configured via `run-client.sh` ```bash -$ ./run.sh +$ ./run-client.sh ``` #### Server Daemon Mode The default settings for a skycoin node are chosen for `Server Daemon`, which is typically run from source. This mode is usually preferred to be run with security options, though `-disable-csrf` is normal for server daemon mode, it is left enabled by default. + +```bash +$ ./run-daemon.sh +``` + +To disable CSRF: + ```bash -$ go run cmd/skycoin/skycoin.go +$ ./run-daemon.sh -disable-csrf ``` #### Electron Desktop Client Mode @@ -561,13 +592,13 @@ Instructions for doing this: 0. Follow the steps in [pre-release testing](#pre-release-testing) 0. Make a PR merging `develop` into `master` 0. Review the PR and merge it -0. Tag the master branch with the version number. Version tags start with `v`, e.g. `v0.20.0`. +0. Tag the `master` branch with the version number. Version tags start with `v`, e.g. `v0.20.0`. Sign the tag. If you have your GPG key in github, creating a release on the Github website will automatically tag the release. It can be tagged from the command line with `git tag -as v0.20.0 $COMMIT_ID`, but Github will not recognize it as a "release". 0. Make sure that the client runs properly from the `master` branch 0. Release builds are created and uploaded by travis. To do it manually, checkout the `master` branch and follow the [create release builds](electron/README.md) instructions. -If there are problems discovered after merging to master, start over, and increment the 3rd version number. +If there are problems discovered after merging to `master`, start over, and increment the 3rd version number. For example, `v0.20.0` becomes `v0.20.1`, for minor fixes. #### Pre-release testing @@ -577,7 +608,7 @@ Performs these actions before releasing: * `make check` * `make integration-test-live` (see [live integration tests](#live-integration-tests)) both with an unencrypted and encrypted wallet, and once with `-networking-disabled` * `go run cmd/cli/cli.go checkdb` against a synced node -* On all OSes, make sure that the client runs properly from the command line (`./run.sh`) +* On all OSes, make sure that the client runs properly from the command line (`./run-client.sh` and `./run-daemon.sh`) * Build the releases and make sure that the Electron client runs properly on Windows, Linux and macOS. * Use a clean data directory with no wallets or database to sync from scratch and verify the wallet setup wizard. * Load a test wallet with nonzero balance from seed to confirm wallet loading works diff --git a/ci-scripts/install-golangci-lint.sh b/ci-scripts/install-golangci-lint.sh new file mode 100755 index 0000000000..6c796c7dc5 --- /dev/null +++ b/ci-scripts/install-golangci-lint.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +if [ -z "$VERSION" ]; then + echo "VERSION must be set" + exit 1 +fi + +# binary will be $GOPATH/bin/golangci-lint +curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $GOPATH/bin v$VERSION + +# or install it into ./bin/ +curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s vX.Y.Z + +# In alpine linux (as it does not come with curl by default) +wget -O - -q https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s vX.Y.Z + +golangci-lint --version diff --git a/ci-scripts/install-travis-gcc.sh b/ci-scripts/install-travis-gcc.sh new file mode 100755 index 0000000000..8ac0f7421e --- /dev/null +++ b/ci-scripts/install-travis-gcc.sh @@ -0,0 +1,21 @@ + +# Install gcc6 (6.4.0-2 on Mac OS) for Travis builds + +if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then + sudo apt-get install -qq g++-6 && sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-6 90; +fi + +if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then + echo 'Updating packages database' + brew update + echo 'Available versions (gcc)' + brew list --versions gcc + echo 'Creating gcc@64 formula' + cd "$(brew --repository)/Library/Taps/homebrew/homebrew-core" + git show 42d31bba7772fb01f9ba442d9ee98b33a6e7a055:Formula/gcc\@6.rb > Formula/gcc\@6.rb + echo 'Installing gcc@6 (6.4.0-2)' + brew install gcc\@6 || brew link --overwrite gcc\@6 +fi + +cd $TRAVIS_BUILD_DIR + diff --git a/ci-scripts/integration-test-stable.sh b/ci-scripts/integration-test-stable.sh index e482e04c3f..08d822b636 100755 --- a/ci-scripts/integration-test-stable.sh +++ b/ci-scripts/integration-test-stable.sh @@ -116,7 +116,8 @@ set +e if [[ -z $TEST || $TEST = "api" ]]; then -SKYCOIN_INTEGRATION_TESTS=1 SKYCOIN_INTEGRATION_TEST_MODE=$MODE SKYCOIN_NODE_HOST=$HOST USE_CSRF=$USE_CSRF DB_NO_UNCONFIRMED=$DB_NO_UNCONFIRMED \ +SKYCOIN_INTEGRATION_TESTS=1 SKYCOIN_INTEGRATION_TEST_MODE=$MODE SKYCOIN_NODE_HOST=$HOST \ + USE_CSRF=$USE_CSRF DB_NO_UNCONFIRMED=$DB_NO_UNCONFIRMED COIN=$COIN \ go test ./src/api/integration/... $UPDATE -timeout=3m $VERBOSE $RUN_TESTS API_FAIL=$? @@ -125,7 +126,8 @@ fi if [[ -z $TEST || $TEST = "cli" ]]; then -SKYCOIN_INTEGRATION_TESTS=1 SKYCOIN_INTEGRATION_TEST_MODE=$MODE RPC_ADDR=$RPC_ADDR USE_CSRF=$USE_CSRF DB_NO_UNCONFIRMED=$DB_NO_UNCONFIRMED \ +SKYCOIN_INTEGRATION_TESTS=1 SKYCOIN_INTEGRATION_TEST_MODE=$MODE RPC_ADDR=$RPC_ADDR \ + USE_CSRF=$USE_CSRF DB_NO_UNCONFIRMED=$DB_NO_UNCONFIRMED COIN=$COIN \ go test ./src/cli/integration/... $UPDATE -timeout=3m $VERBOSE $RUN_TESTS CLI_FAIL=$? diff --git a/cmd/cli/README.md b/cmd/cli/README.md index 841a1cb836..caf93f1408 100644 --- a/cmd/cli/README.md +++ b/cmd/cli/README.md @@ -129,7 +129,7 @@ USAGE: skycoin-cli [global options] command [command options] [arguments...] VERSION: - 0.24.1 + 0.25.0-rc1 COMMANDS: addPrivateKey Add a private key to specific wallet @@ -1630,7 +1630,11 @@ $ skycoin-cli status "commit": "620405485d3276c16c0379bc3b88b588e34c45e1", "branch": "develop" }, + "coin": "skycoin", + "user_agent": "skycoin:0.25.0-rc1", "open_connections": 8, + "outgoing_connections": 5, + "incoming_connections": 3, "uptime": "4h1m23.697072461s", "csrf_enabled": true, "csp_enabled": true, diff --git a/cmd/newcoin/newcoin.go b/cmd/newcoin/newcoin.go index fc183b71be..1acc340691 100644 --- a/cmd/newcoin/newcoin.go +++ b/cmd/newcoin/newcoin.go @@ -5,6 +5,7 @@ package main import ( "fmt" + "regexp" "os" "path/filepath" @@ -14,6 +15,7 @@ import ( "github.com/skycoin/skycoin/src/skycoin" "github.com/skycoin/skycoin/src/util/logging" + "github.com/skycoin/skycoin/src/util/useragent" ) const ( @@ -21,22 +23,6 @@ const ( Version = "0.1" ) -// CoinTemplateParameters represents parameters used to generate the new coin files. -type CoinTemplateParameters struct { - Version string - PeerListURL string - Port int - WebInterfacePort int - DataDirectory string - GenesisSignatureStr string - GenesisAddressStr string - BlockchainPubkeyStr string - BlockchainSeckeyStr string - GenesisTimestamp uint64 - GenesisCoinVolume uint64 - DefaultConnections []string -} - var ( app = cli.NewApp() log = logging.MustGetLogger("newcoin") @@ -90,9 +76,9 @@ func createCoinCommand() cli.Command { Value: "coin_test.template", }, cli.StringFlag{ - Name: "visor-template-file, vt", - Usage: "visor template file", - Value: "visor.template", + Name: "params-template-file, pt", + Usage: "params template file", + Value: "params.template", }, cli.StringFlag{ Name: "config-dir, cd", @@ -108,11 +94,17 @@ func createCoinCommand() cli.Command { Action: func(c *cli.Context) error { // -- parse flags -- // + coinName := c.String("coin") + + if err := validateCoinName(coinName); err != nil { + return err + } + templateDir := c.String("template-dir") coinTemplateFile := c.String("coin-template-file") coinTestTemplateFile := c.String("coin-test-template-file") - visorTemplateFile := c.String("visor-template-file") + paramsTemplateFile := c.String("params-template-file") // check that the coin template file exists if _, err := os.Stat(filepath.Join(templateDir, coinTemplateFile)); os.IsNotExist(err) { @@ -122,8 +114,8 @@ func createCoinCommand() cli.Command { if _, err := os.Stat(filepath.Join(templateDir, coinTestTemplateFile)); os.IsNotExist(err) { return err } - // check that the visor template file exists - if _, err := os.Stat(filepath.Join(templateDir, visorTemplateFile)); os.IsNotExist(err) { + // check that the params template file exists + if _, err := os.Stat(filepath.Join(templateDir, paramsTemplateFile)); os.IsNotExist(err) { return err } @@ -136,9 +128,7 @@ func createCoinCommand() cli.Command { return err } - coinName := c.String("coin") - - // -- parse template and create new coin.go and visor parameters.go -- // + // -- parse template and create new coin.go and config blockchain.go -- // config, err := skycoin.NewParameters(configFile, configDir) if err != nil { @@ -172,12 +162,13 @@ func createCoinCommand() cli.Command { } defer coinTestFile.Close() - visorParamsFile, err := os.Create("./src/visor/parameters.go") + paramsFilePath := "./src/params/params.go" + paramsFile, err := os.Create(paramsFilePath) if err != nil { - log.Errorf("failed to create new visor parameters.go") + log.Errorf("failed to create new file %s", paramsFilePath) return err } - defer visorParamsFile.Close() + defer paramsFile.Close() // change dir so that text/template can parse the file err = os.Chdir(templateDir) @@ -189,7 +180,7 @@ func createCoinCommand() cli.Command { templateFiles := []string{ coinTemplateFile, coinTestTemplateFile, - visorTemplateFile, + paramsTemplateFile, } t := template.New(coinTemplateFile) @@ -199,19 +190,10 @@ func createCoinCommand() cli.Command { return err } - err = t.ExecuteTemplate(coinFile, coinTemplateFile, CoinTemplateParameters{ - PeerListURL: config.Node.PeerListURL, - Port: config.Node.Port, - WebInterfacePort: config.Node.WebInterfacePort, - DataDirectory: "$HOME/." + coinName, - GenesisSignatureStr: config.Node.GenesisSignatureStr, - GenesisAddressStr: config.Node.GenesisAddressStr, - BlockchainPubkeyStr: config.Node.BlockchainPubkeyStr, - BlockchainSeckeyStr: config.Node.BlockchainSeckeyStr, - GenesisTimestamp: config.Node.GenesisTimestamp, - GenesisCoinVolume: config.Node.GenesisCoinVolume, - DefaultConnections: config.Node.DefaultConnections, - }) + config.Node.CoinName = coinName + config.Node.DataDirectory = "$HOME/." + coinName + + err = t.ExecuteTemplate(coinFile, coinTemplateFile, config.Node) if err != nil { log.Error("failed to parse coin template variables") return err @@ -223,9 +205,9 @@ func createCoinCommand() cli.Command { return err } - err = t.ExecuteTemplate(visorParamsFile, visorTemplateFile, config.Visor) + err = t.ExecuteTemplate(paramsFile, paramsTemplateFile, config.Params) if err != nil { - log.Error("failed to parse visor params template variables") + log.Error("failed to parse params template variables") return err } @@ -234,6 +216,14 @@ func createCoinCommand() cli.Command { } } +func validateCoinName(s string) error { + x := regexp.MustCompile(fmt.Sprintf(`^%s$`, useragent.NamePattern)) + if !x.MatchString(s) { + return fmt.Errorf("invalid coin name. must only contain the characters %s", useragent.NamePattern) + } + return nil +} + func main() { if e := app.Run(os.Args); e != nil { log.Fatal(e) diff --git a/cmd/skycoin/skycoin.go b/cmd/skycoin/skycoin.go index 00f8e50d69..5666f8785d 100644 --- a/cmd/skycoin/skycoin.go +++ b/cmd/skycoin/skycoin.go @@ -20,7 +20,7 @@ import ( var ( // Version of the node. Can be set by -ldflags - Version = "0.24.1" + Version = "0.25.0-rc1" // Commit ID. Can be set by -ldflags Commit = "" // Branch name. Can be set by -ldflags @@ -32,6 +32,9 @@ var ( logger = logging.MustGetLogger("main") + // CoinName name of coin + CoinName = "skycoin" + // GenesisSignatureStr hex string of genesis signature GenesisSignatureStr = "eb10468d10054d15f2b6f8946cd46797779aa20a7617ceb4be884189f219bc9a164e56a5b9f7bec392a804ff3740210348d73db77a37adb542a8e08d429ac92700" // GenesisAddressStr genesis address string @@ -58,17 +61,22 @@ var ( } nodeConfig = skycoin.NewNodeConfig(ConfigMode, skycoin.NodeParameters{ - GenesisSignatureStr: GenesisSignatureStr, - GenesisAddressStr: GenesisAddressStr, - GenesisCoinVolume: GenesisCoinVolume, - GenesisTimestamp: GenesisTimestamp, - BlockchainPubkeyStr: BlockchainPubkeyStr, - BlockchainSeckeyStr: BlockchainSeckeyStr, - DefaultConnections: DefaultConnections, - PeerListURL: "https://downloads.skycoin.net/blockchain/peers.txt", - Port: 6000, - WebInterfacePort: 6420, - DataDirectory: "$HOME/.skycoin", + CoinName: CoinName, + GenesisSignatureStr: GenesisSignatureStr, + GenesisAddressStr: GenesisAddressStr, + GenesisCoinVolume: GenesisCoinVolume, + GenesisTimestamp: GenesisTimestamp, + BlockchainPubkeyStr: BlockchainPubkeyStr, + BlockchainSeckeyStr: BlockchainSeckeyStr, + DefaultConnections: DefaultConnections, + PeerListURL: "https://downloads.skycoin.net/blockchain/peers.txt", + Port: 6000, + WebInterfacePort: 6420, + DataDirectory: "$HOME/.skycoin", + UnconfirmedBurnFactor: 2, + CreateBlockBurnFactor: 2, + MaxBlockSize: 32768, + MaxUnconfirmedTransactionSize: 32768, }) parseFlags = true diff --git a/docker/images/mainnet/README.md b/docker/images/mainnet/README.md index 7a562a811e..122774e621 100644 --- a/docker/images/mainnet/README.md +++ b/docker/images/mainnet/README.md @@ -114,15 +114,15 @@ To get a full list of skycoin's parameters, just run $ docker run --rm skycoin/skycoin:develop -help ``` -To run multiple nodes concurrently in the same host, it is highly recommended to create separate volumes for each node. For example, in order to run a master node along with the one launched above, it is necessary to execute +To run multiple nodes concurrently in the same host, it is highly recommended to create separate volumes for each node. For example, in order to run a block publisher node along with the one launched above, it is necessary to execute ```sh -$ docker volume create skycoin-master-data -$ docker volume create skycoin-master-wallet -$ docker run -d -v skycoin-master-data:/data/.skycoin \ - -v skycoin-master-wallet:/wallet \ +$ docker volume create skycoin-block-publisher-data +$ docker volume create skycoin-block-publisher-wallet +$ docker run -d -v skycoin-block-publisher-data:/data/.skycoin \ + -v skycoin-block-publisher-wallet:/wallet \ -p 6001:6000 -p 6421:6420 \ - --name skycoin-master-stable skycoin/skycoin -master + --name skycoin-block-publisher-stable skycoin/skycoin -block-publisher ``` Notice that the host's port must be changed since collisions of two services listening at the same port are not allowed by the low-level operating system socket libraries. diff --git a/electron/build-cli-release.sh b/electron/build-cli-release.sh new file mode 100755 index 0000000000..edccb5755f --- /dev/null +++ b/electron/build-cli-release.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -e -o pipefail + +GOX_OSARCH="$@" + +. build-conf.sh "$GOX_OSARCH" + +SKIP_COMPILATION=${SKIP_COMPILATION:-0} + +SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +pushd "$SCRIPTDIR" >/dev/null + +if [ $SKIP_COMPILATION -ne 1 ]; then + CMD="cli" CONFIG_MODE= ./gox.sh "$GOX_OSARCH" "$GOX_CLI_OUTPUT_DIR" "$GOX_CLI_OUTPUT_NAME" +fi + +echo +echo "===========================" +echo "Packaging cli release" +./package-cli-release.sh "$GOX_OSARCH" + +echo "------------------------------" +echo "Compressing cli release" +./compress-cli-release.sh "$GOX_OSARCH" + +popd >/dev/null diff --git a/electron/build-conf.sh b/electron/build-conf.sh index 9faad32837..e92c2ff0d0 100755 --- a/electron/build-conf.sh +++ b/electron/build-conf.sh @@ -15,7 +15,7 @@ PDT_NAME=`grep productName package.json | sed 's/[,\", ]//g' | awk '{split($0,s, ELN_VERSION="v1.4.13" ELN_OUTPUT_BASE=".electron_output" -ELN_OUTPUT="${ELN_OUTPUT_BASE}/${ELN_VERSION}" +ELN_OUTPUT_DIR="${ELN_OUTPUT_BASE}/${ELN_VERSION}" if [ -n "$1" ]; then @@ -24,61 +24,91 @@ else GOX_OSARCH="linux/amd64 linux/arm windows/amd64 windows/386 darwin/amd64" fi -GOX_OUTPUT=".gox_output" +GOX_OUTPUT_DIR=".gox_output" +GOX_GUI_OUTPUT_DIR="${GOX_OUTPUT_DIR}/gui" +GOX_DMN_OUTPUT_DIR="${GOX_OUTPUT_DIR}/daemon" +GOX_CLI_OUTPUT_DIR="${GOX_OUTPUT_DIR}/cli" +GOX_CLI_OUTPUT_NAME="${PKG_NAME}-cli" -STL_OUTPUT=".standalone_output" +STL_OUTPUT_DIR=".standalone_output" +DMN_OUTPUT_DIR=".daemon_output" +CLI_OUTPUT_DIR=".cli_output" -FINAL_OUTPUT="release" +FINAL_OUTPUT_DIR="release" GUI_DIST_DIR="../src/gui/static/dist" # Do not append "/" to this path # Variable suffix guide: # _APP -- name of the OS X app # _ELN_PLT -- directory name created by electron for its build of this platform -# _ELN -- our name for electron/gui releases -# _ELN_ZIP -- our compressed name for electron/gui releases -# _STL -- our name for standalone/non-gui releases -# _STL_ZIP -- our compressed name for standalone/non-gui releases +# _ELN -- our name for electron gui releases +# _ELN_ZIP -- our compressed name for electron gui releases +# _STL -- our name for standalone gui releases +# _STL_ZIP -- our compressed name for standalone gui releases +# _DMN -- our name for daemon releases +# _DMN_ZIP -- our compressed name for daemon releases +# _CLI -- our name for cli releases +# _CLI_ZIP -- our compressed name for cli releases if [[ $GOX_OSARCH == *"darwin/amd64"* ]]; then OSX64_APP="${PDT_NAME}.app" OSX64_ELN_PLT="darwin-x64" - OSX64_ELN="${PKG_NAME}-${APP_VERSION}-gui-osx-darwin-x64" + OSX64_ELN="${PKG_NAME}-${APP_VERSION}-gui-electron-osx-darwin-x64" OSX64_ELN_ZIP="${OSX64_ELN}.zip" - OSX64_STL="${PKG_NAME}-${APP_VERSION}-bin-osx-darwin-x64" + OSX64_STL="${PKG_NAME}-${APP_VERSION}-gui-standalone-osx-darwin-x64" OSX64_STL_ZIP="${OSX64_STL}.zip" + OSX64_DMN="${PKG_NAME}-${APP_VERSION}-daemon-osx-darwin-x64" + OSX64_DMN_ZIP="${OSX64_DMN}.zip" + OSX64_CLI="${PKG_NAME}-${APP_VERSION}-cli-osx-darwin-x64" + OSX64_CLI_ZIP="${OSX64_CLI}.zip" OSX64_OUT="mac_x64" fi if [[ $GOX_OSARCH == *"linux/amd64"* ]]; then - LNX64_ELN="${PKG_NAME}-${APP_VERSION}-gui-linux-x64" + LNX64_ELN="${PKG_NAME}-${APP_VERSION}-gui-electron-linux-x64" LNX64_ELN_PLT="linux-x64" LNX64_ELN_ZIP="${LNX64_ELN}.tar.gz" - LNX64_STL="${PKG_NAME}-${APP_VERSION}-bin-linux-x64" + LNX64_STL="${PKG_NAME}-${APP_VERSION}-gui-standalone-linux-x64" LNX64_STL_ZIP="${LNX64_STL}.tar.gz" + LNX64_DMN="${PKG_NAME}-${APP_VERSION}-daemon-linux-x64" + LNX64_DMN_ZIP="${LNX64_DMN}.tar.gz" + LNX64_CLI="${PKG_NAME}-${APP_VERSION}-cli-linux-x64" + LNX64_CLI_ZIP="${LNX64_CLI}.tar.gz" LNX64_OUT="linux_x64" fi if [[ $GOX_OSARCH == *"windows/amd64"* ]]; then - WIN64_ELN="${PKG_NAME}-${APP_VERSION}-gui-win-x64" + WIN64_ELN="${PKG_NAME}-${APP_VERSION}-gui-electron-win-x64" WIN64_ELN_PLT="win32-x64" WIN64_ELN_ZIP="${WIN64_ELN}.zip" - WIN64_STL="${PKG_NAME}-${APP_VERSION}-bin-win-x64" + WIN64_STL="${PKG_NAME}-${APP_VERSION}-gui-standalone-win-x64" WIN64_STL_ZIP="${WIN64_STL}.zip" + WIN64_DMN="${PKG_NAME}-${APP_VERSION}-daemon-win-x64" + WIN64_DMN_ZIP="${WIN64_DMN}.zip" + WIN64_CLI="${PKG_NAME}-${APP_VERSION}-cli-win-x64" + WIN64_CLI_ZIP="${WIN64_CLI}.zip" WIN64_OUT="win_x64" fi if [[ $GOX_OSARCH == *"windows/386"* ]]; then - WIN32_ELN="${PKG_NAME}-${APP_VERSION}-gui-win-x86" + WIN32_ELN="${PKG_NAME}-${APP_VERSION}-gui-electron-win-x86" WIN32_ELN_PLT="win32-ia32" WIN32_ELN_ZIP="${WIN32_ELN}.zip" - WIN32_STL="${PKG_NAME}-${APP_VERSION}-bin-win-x86" + WIN32_STL="${PKG_NAME}-${APP_VERSION}-gui-standalone-win-x86" WIN32_STL_ZIP="${WIN32_STL}.zip" + WIN32_DMN="${PKG_NAME}-${APP_VERSION}-daemon-win-x86" + WIN32_DMN_ZIP="${WIN32_DMN}.zip" + WIN32_CLI="${PKG_NAME}-${APP_VERSION}-cli-win-x86" + WIN32_CLI_ZIP="${WIN32_CLI}.zip" WIN32_OUT="win_ia32" fi if [[ $GOX_OSARCH == *"linux/arm"* ]]; then - LNX_ARM_STL="${PKG_NAME}-${APP_VERSION}-bin-linux-arm" + LNX_ARM_STL="${PKG_NAME}-${APP_VERSION}-gui-standalone-linux-arm" LNX_ARM_STL_ZIP="${LNX_ARM_STL}.tar.gz" + LNX_ARM_DMN="${PKG_NAME}-${APP_VERSION}-daemon-linux-arm" + LNX_ARM_DMN_ZIP="${LNX_ARM_DMN}.tar.gz" + LNX_ARM_CLI="${PKG_NAME}-${APP_VERSION}-cli-linux-arm" + LNX_ARM_CLI_ZIP="${LNX_ARM_CLI}.tar.gz" LNX_ARM_OUT="linux_arm" fi diff --git a/electron/build-daemon-release.sh b/electron/build-daemon-release.sh new file mode 100755 index 0000000000..218cbae9d6 --- /dev/null +++ b/electron/build-daemon-release.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -e -o pipefail + +GOX_OSARCH="$@" + +. build-conf.sh "$GOX_OSARCH" + +SKIP_COMPILATION=${SKIP_COMPILATION:-0} + +SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +pushd "$SCRIPTDIR" >/dev/null + +if [ $SKIP_COMPILATION -ne 1 ]; then + CONFIG_MODE= ./gox.sh "$GOX_OSARCH" "$GOX_DMN_OUTPUT_DIR" +fi + +echo +echo "===========================" +echo "Packaging daemon release" +./package-daemon-release.sh "$GOX_OSARCH" + +echo "------------------------------" +echo "Compressing daemon release" +./compress-daemon-release.sh "$GOX_OSARCH" + +popd >/dev/null diff --git a/electron/build-electron-release.sh b/electron/build-electron-release.sh index de8daf6cdb..642513b8e4 100755 --- a/electron/build-electron-release.sh +++ b/electron/build-electron-release.sh @@ -23,11 +23,11 @@ SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" pushd "$SCRIPTDIR" >/dev/null if [ $SKIP_COMPILATION -ne 1 ]; then - ./gox.sh "$GOX_OSARCH" "$GOX_OUTPUT" + CONFIG_MODE=STANDALONE_CLIENT ./gox.sh "$GOX_OSARCH" "$GOX_GUI_OUTPUT_DIR" fi -if [ -e "$ELN_OUTPUT" ]; then - rm -r "$ELN_OUTPUT" +if [ -e "$ELN_OUTPUT_DIR" ]; then + rm -r "$ELN_OUTPUT_DIR" fi if [ ! -z "$WIN64_ELN" ] && [ ! -z "$WIN32_ELN" ]; then @@ -53,16 +53,16 @@ if [ ! -z "$OSX64_ELN" ]; then fi fi -pushd "$FINAL_OUTPUT" >/dev/null +pushd "$FINAL_OUTPUT_DIR" >/dev/null if [ -e "mac" ]; then pushd "mac" >/dev/null if [ -e "${PDT_NAME}-${APP_VERSION}.dmg" ]; then - mv "${PDT_NAME}-${APP_VERSION}.dmg" "../${PKG_NAME}-${APP_VERSION}-gui-osx-x64.dmg" + mv "${PDT_NAME}-${APP_VERSION}.dmg" "../${PKG_NAME}-${APP_VERSION}-gui-electron-osx-x64.dmg" elif [ -e "${PDT_NAME}.app" ]; then if [[ "$OSTYPE" == "darwin"* ]]; then - tar czf "../${PKG_NAME}-${APP_VERSION}-gui-osx-x64.zip" "${PDT_NAME}.app" + tar czf "../${PKG_NAME}-${APP_VERSION}-gui-electron-osx-x64.zip" "${PDT_NAME}.app" elif [[ "$OSTYPE" == "linux"* ]]; then - tar czf "../${PKG_NAME}-${APP_VERSION}-gui-osx-x64.zip" --owner=0 --group=0 "${PDT_NAME}.app" + tar czf "../${PKG_NAME}-${APP_VERSION}-gui-electron-osx-x64.zip" --owner=0 --group=0 "${PDT_NAME}.app" fi fi popd >/dev/null @@ -70,7 +70,7 @@ if [ -e "mac" ]; then fi IMG="${PKG_NAME}-${APP_VERSION}-x86_64.AppImage" -DEST_IMG="${PKG_NAME}-${APP_VERSION}-gui-linux-x64.AppImage" +DEST_IMG="${PKG_NAME}-${APP_VERSION}-gui-electron-linux-x64.AppImage" if [ -e $IMG ]; then mv "$IMG" "$DEST_IMG" chmod +x "$DEST_IMG" @@ -79,7 +79,7 @@ fi EXE="${PDT_NAME} Setup ${APP_VERSION}.exe" if [ -e "$EXE" ]; then if [ ! -z $WIN32_ELN ] && [ ! -z $WIN64_ELN ]; then - mv "$EXE" "${PKG_NAME}-${APP_VERSION}-gui-win-setup.exe" + mv "$EXE" "${PKG_NAME}-${APP_VERSION}-gui-electron-win-setup.exe" elif [ ! -z $WIN32_ELN ]; then mv "$EXE" "${WIN32_ELN}.exe" elif [ ! -z $WIN64_ELN ]; then @@ -90,7 +90,7 @@ fi # rename dmg file name DMG="${PKG_NAME}-${APP_VERSION}.dmg" if [ -e "$DMG" ]; then - mv "$DMG" "${PKG_NAME}-${APP_VERSION}-gui-osx.dmg" + mv "$DMG" "${PKG_NAME}-${APP_VERSION}-gui-electron-osx.dmg" fi # delete app zip file diff --git a/electron/build-standalone-release.sh b/electron/build-standalone-release.sh index ce2b5bec54..01a671ef69 100755 --- a/electron/build-standalone-release.sh +++ b/electron/build-standalone-release.sh @@ -12,7 +12,7 @@ SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" pushd "$SCRIPTDIR" >/dev/null if [ $SKIP_COMPILATION -ne 1 ]; then - ./gox.sh "$GOX_OSARCH" "$GOX_OUTPUT" + CONFIG_MODE=STANDALONE_CLIENT ./gox.sh "$GOX_OSARCH" "$GOX_GUI_OUTPUT_DIR" fi echo diff --git a/electron/build.sh b/electron/build.sh index 17fb0ae0fc..dd13502b4d 100755 --- a/electron/build.sh +++ b/electron/build.sh @@ -13,8 +13,8 @@ pushd "$SCRIPTDIR" >/dev/null echo "Compiling with gox" pwd -# Build with gox here and make the other scripts skip it -./gox.sh "$GOX_OSARCH" "$GOX_OUTPUT" +# Build the client mode with gox here so that the standalone and electron releases don't need to compile twice +CONFIG_MODE=STANDALONE_CLIENT ./gox.sh "$GOX_OSARCH" "$GOX_GUI_OUTPUT_DIR" echo "Installing node modules" ./install-node-modules.sh @@ -31,4 +31,16 @@ echo "Building electron release" SKIP_COMPILATION=1 ./build-electron-release.sh "$GOX_OSARCH" +echo +echo "===========================" +echo "Building daemon release" + +./build-daemon-release.sh "$GOX_OSARCH" + +echo +echo "===========================" +echo "Building cli release" + +./build-cli-release.sh "$GOX_OSARCH" + popd >/dev/null diff --git a/electron/clean.sh b/electron/clean.sh index 3b927e5a11..783faf5dbe 100755 --- a/electron/clean.sh +++ b/electron/clean.sh @@ -16,7 +16,7 @@ function rmnofail { done } -rmnofail "$ELN_OUTPUT_BASE" "$STL_OUTPUT" "$GOX_OUTPUT" "$FINAL_OUTPUT" +rmnofail "$ELN_OUTPUT_BASE" "$STL_OUTPUT_DIR" "$GOX_OUTPUT_DIR" "$FINAL_OUTPUT_DIR" # don't remove the electron cache by default, most of the time when we want # to clean up build artifacts we don't want to clean this up, and downloading diff --git a/electron/compress-cli-release.sh b/electron/compress-cli-release.sh new file mode 100755 index 0000000000..5d0c3c63ae --- /dev/null +++ b/electron/compress-cli-release.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash +set -e -o pipefail + +# Compresses packaged cli release after +# ./package-cli-release.sh is done + +GOX_OSARCH="$@" + +. build-conf.sh "$GOX_OSARCH" + +SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +pushd "$SCRIPTDIR" >/dev/null + +# Compress archives +pushd "$CLI_OUTPUT_DIR" >/dev/null + +FINALS=() + +# OS X +if [ -e "$OSX64_CLI" ]; then + if [ -e "$OSX64_CLI_ZIP" ]; then + echo "Removing old $OSX64_CLI_ZIP" + rm "$OSX64_CLI_ZIP" + fi + echo "Zipping $OSX64_CLI_ZIP" + # -y preserves symlinks, + # so that the massive .framework library isn't duplicated + zip -r -y --quiet "$OSX64_CLI_ZIP" "$OSX64_CLI" + FINALS+=("$OSX64_CLI_ZIP") +fi + +# Windows 64bit +if [ -e "$WIN64_CLI" ]; then + if [ -e "$WIN64_CLI_ZIP" ]; then + echo "Removing old $WIN64_CLI_ZIP" + rm "$WIN64_CLI_ZIP" + fi + echo "Zipping $WIN64_CLI_ZIP" + if [[ "$OSTYPE" == "linux"* ]]; then + zip -r --quiet -X "$WIN64_CLI_ZIP" "$WIN64_CLI" + elif [[ "$OSTYPE" == "darwin"* ]]; then + zip -r --quiet "$WIN64_CLI_ZIP" "$WIN64_CLI" + elif [[ "$OSTYPE" == "msys"* ]]; then + 7z a "$WIN64_CLI_ZIP" "$WIN64_CLI" + fi + FINALS+=("$WIN64_CLI_ZIP") +fi + +# Windows 32bit +if [ -e "$WIN32_CLI" ]; then + if [ -e "$WIN32_CLI_ZIP" ]; then + echo "Removing old $WIN32_CLI_ZIP" + rm "$WIN32_CLI_ZIP" + fi + echo "Zipping $WIN32_CLI_ZIP" + if [[ "$OSTYPE" == "linux"* ]]; then + zip -r --quiet -X "$WIN32_CLI_ZIP" "$WIN32_CLI" + elif [[ "$OSTYPE" == "darwin"* ]]; then + zip -r --quiet "$WIN32_CLI_ZIP" "$WIN32_CLI" + elif [[ "$OSTYPE" == "msys"* ]]; then + 7z a "$WIN32_CLI_ZIP" "$WIN32_CLI" + fi + FINALS+=("$WIN32_CLI_ZIP") +fi + +# Linux +if [ -e "$LNX64_CLI" ]; then + if [ -e "$LNX64_CLI_ZIP" ]; then + echo "Removing old $LNX64_CLI_ZIP" + rm "$LNX64_CLI_ZIP" + fi + echo "Zipping $LNX64_CLI_ZIP" + if [[ "$OSTYPE" == "linux"* ]]; then + tar czf "$LNX64_CLI_ZIP" --owner=0 --group=0 "$LNX64_CLI" + elif [[ "$OSTYPE" == "darwin"* ]]; then + tar czf "$LNX64_CLI_ZIP" "$LNX64_CLI" + fi + FINALS+=("$LNX64_CLI_ZIP") +fi + +# Linux arm +if [ -e "$LNX_ARM_CLI" ]; then + if [ -e "$LNX_ARM_CLI_ZIP" ]; then + echo "Removing old $LNX_ARM_CLI_ZIP" + rm "$LNX_ARM_CLI_ZIP" + fi + echo "Zipping $LNX_ARM_CLI_ZIP" + if [[ "$OSTYPE" == "linux"* ]]; then + tar czf "$LNX_ARM_CLI_ZIP" --owner=0 --group=0 "$LNX_ARM_CLI" + elif [[ "$OSTYPE" == "darwin"* ]]; then + tar czf "$LNX_ARM_CLI_ZIP" "$LNX_ARM_CLI" + fi + FINALS+=("$LNX_ARM_CLI_ZIP") +fi + +popd >/dev/null + +# Move to final release dir +mkdir -p "$FINAL_OUTPUT_DIR" +for var in "${FINALS[@]}"; do + mv "${CLI_OUTPUT_DIR}/${var}" "$FINAL_OUTPUT_DIR" +done + +popd >/dev/null diff --git a/electron/compress-daemon-release.sh b/electron/compress-daemon-release.sh new file mode 100755 index 0000000000..72adc31c0c --- /dev/null +++ b/electron/compress-daemon-release.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash +set -e -o pipefail + +# Compresses packaged daemon release after +# ./package-daemon-release.sh is done + +GOX_OSARCH="$@" + +. build-conf.sh "$GOX_OSARCH" + +SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +pushd "$SCRIPTDIR" >/dev/null + +# Compress archives +pushd "$DMN_OUTPUT_DIR" >/dev/null + +FINALS=() + +# OS X +if [ -e "$OSX64_DMN" ]; then + if [ -e "$OSX64_DMN_ZIP" ]; then + echo "Removing old $OSX64_DMN_ZIP" + rm "$OSX64_DMN_ZIP" + fi + echo "Zipping $OSX64_DMN_ZIP" + # -y preserves symlinks, + # so that the massive .framework library isn't duplicated + zip -r -y --quiet "$OSX64_DMN_ZIP" "$OSX64_DMN" + FINALS+=("$OSX64_DMN_ZIP") +fi + +# Windows 64bit +if [ -e "$WIN64_DMN" ]; then + if [ -e "$WIN64_DMN_ZIP" ]; then + echo "Removing old $WIN64_DMN_ZIP" + rm "$WIN64_DMN_ZIP" + fi + echo "Zipping $WIN64_DMN_ZIP" + if [[ "$OSTYPE" == "linux"* ]]; then + zip -r --quiet -X "$WIN64_DMN_ZIP" "$WIN64_DMN" + elif [[ "$OSTYPE" == "darwin"* ]]; then + zip -r --quiet "$WIN64_DMN_ZIP" "$WIN64_DMN" + elif [[ "$OSTYPE" == "msys"* ]]; then + 7z a "$WIN64_DMN_ZIP" "$WIN64_DMN" + fi + FINALS+=("$WIN64_DMN_ZIP") +fi + +# Windows 32bit +if [ -e "$WIN32_DMN" ]; then + if [ -e "$WIN32_DMN_ZIP" ]; then + echo "Removing old $WIN32_DMN_ZIP" + rm "$WIN32_DMN_ZIP" + fi + echo "Zipping $WIN32_DMN_ZIP" + if [[ "$OSTYPE" == "linux"* ]]; then + zip -r --quiet -X "$WIN32_DMN_ZIP" "$WIN32_DMN" + elif [[ "$OSTYPE" == "darwin"* ]]; then + zip -r --quiet "$WIN32_DMN_ZIP" "$WIN32_DMN" + elif [[ "$OSTYPE" == "msys"* ]]; then + 7z a "$WIN32_DMN_ZIP" "$WIN32_DMN" + fi + FINALS+=("$WIN32_DMN_ZIP") +fi + +# Linux +if [ -e "$LNX64_DMN" ]; then + if [ -e "$LNX64_DMN_ZIP" ]; then + echo "Removing old $LNX64_DMN_ZIP" + rm "$LNX64_DMN_ZIP" + fi + echo "Zipping $LNX64_DMN_ZIP" + if [[ "$OSTYPE" == "linux"* ]]; then + tar czf "$LNX64_DMN_ZIP" --owner=0 --group=0 "$LNX64_DMN" + elif [[ "$OSTYPE" == "darwin"* ]]; then + tar czf "$LNX64_DMN_ZIP" "$LNX64_DMN" + fi + FINALS+=("$LNX64_DMN_ZIP") +fi + +# Linux arm +if [ -e "$LNX_ARM_DMN" ]; then + if [ -e "$LNX_ARM_DMN_ZIP" ]; then + echo "Removing old $LNX_ARM_DMN_ZIP" + rm "$LNX_ARM_DMN_ZIP" + fi + echo "Zipping $LNX_ARM_DMN_ZIP" + if [[ "$OSTYPE" == "linux"* ]]; then + tar czf "$LNX_ARM_DMN_ZIP" --owner=0 --group=0 "$LNX_ARM_DMN" + elif [[ "$OSTYPE" == "darwin"* ]]; then + tar czf "$LNX_ARM_DMN_ZIP" "$LNX_ARM_DMN" + fi + FINALS+=("$LNX_ARM_DMN_ZIP") +fi + +popd >/dev/null + +# Move to final release dir +mkdir -p "$FINAL_OUTPUT_DIR" +for var in "${FINALS[@]}"; do + mv "${DMN_OUTPUT_DIR}/${var}" "$FINAL_OUTPUT_DIR" +done + +popd >/dev/null diff --git a/electron/compress-electron-release.sh b/electron/compress-electron-release.sh index bebfe2c822..b29565bcfe 100755 --- a/electron/compress-electron-release.sh +++ b/electron/compress-electron-release.sh @@ -13,7 +13,7 @@ SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" pushd "$SCRIPTDIR" >/dev/null # Compress archives -pushd "$ELN_OUTPUT" >/dev/null +pushd "$ELN_OUTPUT_DIR" >/dev/null FINALS=() @@ -81,9 +81,9 @@ fi popd >/dev/null # Move to final release dir -mkdir -p "$FINAL_OUTPUT" +mkdir -p "$FINAL_OUTPUT_DIR" for var in "${FINALS[@]}"; do - mv "${ELN_OUTPUT}/${var}" "$FINAL_OUTPUT" + mv "${ELN_OUTPUT_DIR}/${var}" "$FINAL_OUTPUT_DIR" done popd >/dev/null diff --git a/electron/compress-standalone-release.sh b/electron/compress-standalone-release.sh index ecddc0dcd2..92c5ac1c44 100755 --- a/electron/compress-standalone-release.sh +++ b/electron/compress-standalone-release.sh @@ -13,7 +13,7 @@ SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" pushd "$SCRIPTDIR" >/dev/null # Compress archives -pushd "$STL_OUTPUT" >/dev/null +pushd "$STL_OUTPUT_DIR" >/dev/null FINALS=() @@ -97,9 +97,9 @@ fi popd >/dev/null # Move to final release dir -mkdir -p "$FINAL_OUTPUT" +mkdir -p "$FINAL_OUTPUT_DIR" for var in "${FINALS[@]}"; do - mv "${STL_OUTPUT}/${var}" "$FINAL_OUTPUT" + mv "${STL_OUTPUT_DIR}/${var}" "$FINAL_OUTPUT_DIR" done popd >/dev/null diff --git a/electron/gox.sh b/electron/gox.sh index 4b1232da79..79497e241d 100755 --- a/electron/gox.sh +++ b/electron/gox.sh @@ -15,39 +15,44 @@ if it does not exist. Defaults to the working directory. SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" CMDDIR="../cmd" # relative to compile/electron/ -CMD="${PKG_NAME}" +CMD="${CMD:-$PKG_NAME}" # name of folder in ../cmd to build. defaults to PKG_NAME which is the name of the coin OSARCH="$1" -OUTPUT="$2" +OUTPUT_DIR="$2" +BIN_NAME="${3:-$CMD}" + +CONFIG_MODE=${CONFIG_MODE:-} if [ -z "$OSARCH" ]; then echo "$USAGE" exit 1 fi -case "$OUTPUT" in +case "$OUTPUT_DIR" in "") ;; */) ;; *) - OUTPUT+="/" + OUTPUT_DIR+="/" ;; esac pushd "$SCRIPTDIR" >/dev/null -if [ -n "$OUTPUT" ]; then - mkdir -p "$OUTPUT" +if [ -n "$OUTPUT_DIR" ]; then + mkdir -p "$OUTPUT_DIR" fi COMMIT=`git rev-parse HEAD` +CLI_IMPORT_PATH=`go list ../src/cli` + gox -osarch="$OSARCH" \ -gcflags="-trimpath=${HOME}" \ -asmflags="-trimpath=${HOME}" \ - -ldflags="-X main.Version=${APP_VERSION} -X main.Commit=${COMMIT} -X main.ConfigMode=STANDALONE_CLIENT" \ - -output="${OUTPUT}{{.Dir}}_{{.OS}}_{{.Arch}}" \ + -ldflags="-X main.Version=${APP_VERSION} -X main.Commit=${COMMIT} -X main.ConfigMode=${CONFIG_MODE} -X ${CLI_IMPORT_PATH}.Version=${APP_VERSION}" \ + -output="${OUTPUT_DIR}{{.Dir}}_{{.OS}}_{{.Arch}}" \ "${CMDDIR}/${CMD}" # move the executable files into ${os}_${arch} folders, electron-builder will pack @@ -62,33 +67,33 @@ do case "${s[0]}" in "windows") if [ "${s[1]}" = "386" ]; then - OUT="${OUTPUT}${WIN32_OUT}" + OUT="${OUTPUT_DIR}${WIN32_OUT}" echo "mkdir $OUT" mkdir -p "$OUT" - mv "${OUTPUT}${PKG_NAME}_${s[0]}_${s[1]}.exe" "${OUT}/${PKG_NAME}.exe" + mv "${OUTPUT_DIR}${CMD}_${s[0]}_${s[1]}.exe" "${OUT}/${BIN_NAME}.exe" else - OUT="${OUTPUT}${WIN64_OUT}" + OUT="${OUTPUT_DIR}${WIN64_OUT}" mkdir -p "${OUT}" - mv "${OUTPUT}${PKG_NAME}_${s[0]}_${s[1]}.exe" "${OUT}/${PKG_NAME}.exe" + mv "${OUTPUT_DIR}${CMD}_${s[0]}_${s[1]}.exe" "${OUT}/${BIN_NAME}.exe" fi ;; "darwin") - OUT="${OUTPUT}${OSX64_OUT}" + OUT="${OUTPUT_DIR}${OSX64_OUT}" echo "mkdir ${OUT}" mkdir -p "${OUT}" - mv "${OUTPUT}${PKG_NAME}_${s[0]}_${s[1]}" "${OUT}/${PKG_NAME}" + mv "${OUTPUT_DIR}${CMD}_${s[0]}_${s[1]}" "${OUT}/${BIN_NAME}" ;; "linux") if [ "${s[1]}" = "amd64" ]; then - OUT="${OUTPUT}${LNX64_OUT}" + OUT="${OUTPUT_DIR}${LNX64_OUT}" echo "mkdir ${OUT}" mkdir -p "${OUT}" - mv "${OUTPUT}${PKG_NAME}_${s[0]}_${s[1]}" "${OUT}/${PKG_NAME}" + mv "${OUTPUT_DIR}${CMD}_${s[0]}_${s[1]}" "${OUT}/${BIN_NAME}" elif [ "${s[1]}" = "arm" ]; then - OUT="${OUTPUT}${LNX_ARM_OUT}" + OUT="${OUTPUT_DIR}${LNX_ARM_OUT}" echo "mkdir ${OUT}" mkdir -p "${OUT}" - mv "${OUTPUT}${PKG_NAME}_${s[0]}_${s[1]}" "${OUT}/${PKG_NAME}" + mv "${OUTPUT_DIR}${CMD}_${s[0]}_${s[1]}" "${OUT}/${BIN_NAME}" fi ;; esac diff --git a/electron/package-cli-release.sh b/electron/package-cli-release.sh new file mode 100755 index 0000000000..84e85867eb --- /dev/null +++ b/electron/package-cli-release.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env bash +set -e -o pipefail + +# Builds the cli release + +GOX_OSARCH="$@" + +echo "In package cli release: $GOX_OSARCH" + +. build-conf.sh "$GOX_OSARCH" + +SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +pushd "$SCRIPTDIR" >/dev/null + +DESTSRCS=() + +function copy_if_exists { + if [ -z "$1" -o -z "$2" -o -z "$3" ]; then + echo "copy_if_exists requires 3 args" + exit 1 + fi + + BIN="${GOX_CLI_OUTPUT_DIR}/${1}" + DESTDIR="$2" + DESTSRC="$3" + + if [ -f "$BIN" ]; then + if [ -e "$DESTDIR" ]; then + rm -r "$DESTDIR" + fi + mkdir -p "$DESTDIR" + + # Copy binary to app + echo "Copying $BIN to $DESTDIR" + cp "$BIN" "$DESTDIR" + + # Copy changelog to app + echo "Copying CHANGELOG.md to $DESTDIR" + cp ../CHANGELOG.md "$DESTDIR" + + # Copy cmd/cli/README.md to app + echo "Copying cmd/cli/README.md to $DESTDIR" + cp ../cmd/cli/README.md "$DESTDIR" + + echo "Adding $DESTSRC to package-source.sh list" + DESTSRCS+=("$DESTSRC") + else + echo "$BIN does not exsit" + fi +} + +echo "Copying ${GOX_CLI_OUTPUT_NAME} binaries" + +# OS X +if [ ! -z "$OSX64_CLI" ]; then + OSX64="${CLI_OUTPUT_DIR}/${OSX64_CLI}" + OSX64_SRC="${OSX64}/src" + copy_if_exists "${OSX64_OUT}/${GOX_CLI_OUTPUT_NAME}" "$OSX64" "$OSX64_SRC" +fi + +# Linux amd64 +if [ ! -z "$LNX64_CLI" ]; then + LNX64="${CLI_OUTPUT_DIR}/${LNX64_CLI}" + LNX64_SRC="${LNX64}/src" + copy_if_exists "${LNX64_OUT}/${GOX_CLI_OUTPUT_NAME}" "$LNX64" "$LNX64_SRC" +fi + +# Linux arm +if [ ! -z "$LNX_ARM_CLI" ]; then + LNX_ARM="${CLI_OUTPUT_DIR}/${LNX_ARM_CLI}" + LNX_ARM_SRC="${LNX_ARM}/src" + copy_if_exists "${LNX_ARM_OUT}/${GOX_CLI_OUTPUT_NAME}" "$LNX_ARM" "$LNX_ARM_SRC" +fi + +# Windows amd64 +if [ ! -z "$WIN64_CLI" ]; then + WIN64="${CLI_OUTPUT_DIR}/${WIN64_CLI}" + WIN64_SRC="${WIN64}/src" + copy_if_exists "${WIN64_OUT}/${GOX_CLI_OUTPUT_NAME}.exe" "$WIN64" "$WIN64_SRC" +fi + +# Windows 386 +if [ ! -z "$WIN32_CLI" ]; then + WIN32="${CLI_OUTPUT_DIR}/${WIN32_CLI}" + WIN32_SRC="${WIN32}/src" + copy_if_exists "${WIN32_OUT}/${GOX_CLI_OUTPUT_NAME}.exe" "$WIN32" "$WIN32_SRC" +fi + +# # Copy the source for reference +# # tar it with filters, move it, then untar in order to do this +# echo "Copying source snapshot" + +# ./package-source.sh "${DESTSRCS[@]}" diff --git a/electron/package-daemon-release.sh b/electron/package-daemon-release.sh new file mode 100755 index 0000000000..989046bfef --- /dev/null +++ b/electron/package-daemon-release.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +set -e -o pipefail + +# Builds the daemon release + +GOX_OSARCH="$@" + +echo "In package daemon release: $GOX_OSARCH" + +. build-conf.sh "$GOX_OSARCH" + +SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +pushd "$SCRIPTDIR" >/dev/null + +DESTSRCS=() + +function copy_if_exists { + if [ -z "$1" -o -z "$2" -o -z "$3" ]; then + echo "copy_if_exists requires 3 args" + exit 1 + fi + + BIN="${GOX_DMN_OUTPUT_DIR}/${1}" + DESTDIR="$2" + DESTSRC="$3" + + if [ -f "$BIN" ]; then + if [ -e "$DESTDIR" ]; then + rm -r "$DESTDIR" + fi + mkdir -p "$DESTDIR" + + # Copy binary to app + echo "Copying $BIN to $DESTDIR" + cp "$BIN" "$DESTDIR" + + # Copy changelog to app + echo "Copying CHANGELOG.md to $DESTDIR" + cp ../CHANGELOG.md "$DESTDIR" + + echo "Adding $DESTSRC to package-source.sh list" + DESTSRCS+=("$DESTSRC") + else + echo "$BIN does not exsit" + fi +} + +echo "Copying ${PKG_NAME} binaries" + +# OS X +if [ ! -z "$OSX64_DMN" ]; then + OSX64="${DMN_OUTPUT_DIR}/${OSX64_DMN}" + OSX64_SRC="${OSX64}/src" + copy_if_exists "${OSX64_OUT}/${PKG_NAME}" "$OSX64" "$OSX64_SRC" +fi + +# Linux amd64 +if [ ! -z "$LNX64_DMN" ]; then + LNX64="${DMN_OUTPUT_DIR}/${LNX64_DMN}" + LNX64_SRC="${LNX64}/src" + copy_if_exists "${LNX64_OUT}/${PKG_NAME}" "$LNX64" "$LNX64_SRC" +fi + +# Linux arm +if [ ! -z "$LNX_ARM_DMN" ]; then + LNX_ARM="${DMN_OUTPUT_DIR}/${LNX_ARM_DMN}" + LNX_ARM_SRC="${LNX_ARM}/src" + copy_if_exists "${LNX_ARM_OUT}/${PKG_NAME}" "$LNX_ARM" "$LNX_ARM_SRC" +fi + +# Windows amd64 +if [ ! -z "$WIN64_DMN" ]; then + WIN64="${DMN_OUTPUT_DIR}/${WIN64_DMN}" + WIN64_SRC="${WIN64}/src" + copy_if_exists "${WIN64_OUT}/${PKG_NAME}.exe" "$WIN64" "$WIN64_SRC" +fi + +# Windows 386 +if [ ! -z "$WIN32_DMN" ]; then + WIN32="${DMN_OUTPUT_DIR}/${WIN32_DMN}" + WIN32_SRC="${WIN32}/src" + copy_if_exists "${WIN32_OUT}/${PKG_NAME}.exe" "$WIN32" "$WIN32_SRC" +fi + +# # Copy the source for reference +# # tar it with filters, move it, then untar in order to do this +# echo "Copying source snapshot" + +# ./package-source.sh "${DESTSRCS[@]}" diff --git a/electron/package-electron-release.sh b/electron/package-electron-release.sh index 4323743744..a88add7e4d 100755 --- a/electron/package-electron-release.sh +++ b/electron/package-electron-release.sh @@ -12,10 +12,10 @@ SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" pushd "$SCRIPTDIR" >/dev/null -OSX64="${ELN_OUTPUT}/${OSX64_ELN_PLT}" -WIN64="${ELN_OUTPUT}/${WIN64_ELN_PLT}" -WIN32="${ELN_OUTPUT}/${WIN32_ELN_PLT}" -LNX64="${ELN_OUTPUT}/${LNX64_ELN_PLT}" +OSX64="${ELN_OUTPUT_DIR}/${OSX64_ELN_PLT}" +WIN64="${ELN_OUTPUT_DIR}/${WIN64_ELN_PLT}" +WIN32="${ELN_OUTPUT_DIR}/${WIN32_ELN_PLT}" +LNX64="${ELN_OUTPUT_DIR}/${LNX64_ELN_PLT}" OSX64_RES="${OSX64}/${OSX64_APP}/Contents/Resources/app" WIN64_RES="${WIN64}/resources/app" @@ -40,7 +40,7 @@ function copy_if_exists { exit 1 fi - BIN="${GOX_OUTPUT}/${1}" + BIN="${GOX_GUI_OUTPUT_DIR}/${1}" DESTDIR="$2" DESTBIN="${DESTDIR}/${3}" DESTSRC="$4" @@ -55,6 +55,10 @@ function copy_if_exists { echo "Copying $GUI_DIST_DIR to $DESTDIR" cp -R "$GUI_DIST_DIR" "$DESTDIR" + # Copy changelog to app + echo "Copying CHANGELOG.md to $DESTDIR" + cp ../CHANGELOG.md "$DESTDIR" + DESTSRCS+=("$DESTSRC") else echo "$BIN does not exist" @@ -68,8 +72,8 @@ copy_if_exists "${PKG_NAME}_windows_amd64.exe" "$WIN64_RES" "${PKG_NAME}.exe" "$ copy_if_exists "${PKG_NAME}_windows_386.exe" "$WIN32_RES" "${PKG_NAME}.exe" "$WIN32_SRC" copy_if_exists "${PKG_NAME}_linux_amd64" "$LNX64_RES" "${PKG_NAME}" "$LNX64_SRC" -# Copy the source for reference -# tar it with filters, move it, then untar in order to do this -echo "Copying source snapshot" +# # Copy the source for reference +# # tar it with filters, move it, then untar in order to do this +# echo "Copying source snapshot" -./package-source.sh "${DESTSRCS[@]}" +# ./package-source.sh "${DESTSRCS[@]}" diff --git a/electron/package-lock.json b/electron/package-lock.json index 55fceada87..30dc94d2df 100644 --- a/electron/package-lock.json +++ b/electron/package-lock.json @@ -1,6 +1,6 @@ { "name": "skycoin", - "version": "0.24.1", + "version": "0.25.0-rc1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/electron/package-source.sh b/electron/package-source.sh index fe6a5d57dc..a10149db55 100755 --- a/electron/package-source.sh +++ b/electron/package-source.sh @@ -10,17 +10,17 @@ pushd "${SCRIPTDIR}" if [[ "$OSTYPE" == "linux"* ]]; then tar -C .. -cvPf "${SRC_TAR}" --owner=0 --group=0 --exclude=electron \ --exclude=node_modules --exclude=_deprecated --exclude='.*' \ - src cmd run.sh README.md INSTALLATION.md CHANGELOG.md INTEGRATION.md \ + src cmd run-client.sh run-daemon.sh README.md INSTALLATION.md CHANGELOG.md INTEGRATION.md \ >/dev/null elif [[ "$OSTYPE" == "darwin"* ]]; then tar -C .. -cvf "${SRC_TAR}" --exclude=electron \ --exclude=node_modules --exclude=_deprecated --exclude='.*' \ - src cmd run.sh README.md INSTALLATION.md CHANGELOG.md INTEGRATION.md \ + src cmd run-client.sh run-daemon.sh README.md INSTALLATION.md CHANGELOG.md INTEGRATION.md \ >/dev/null elif [[ "$OSTYPE" == "msys"* ]]; then tar -C .. -cvPf "${SRC_TAR}" --owner=0 --group=0 --exclude=electron \ --exclude=node_modules --exclude=_deprecated --exclude='.*' \ - src cmd run.sh README.md INSTALLATION.md CHANGELOG.md INTEGRATION.md \ + src cmd run-client.sh run-daemon.sh README.md INSTALLATION.md CHANGELOG.md INTEGRATION.md \ >/dev/null fi diff --git a/electron/package-standalone-release.sh b/electron/package-standalone-release.sh index ae01c4cbaa..605021adc3 100755 --- a/electron/package-standalone-release.sh +++ b/electron/package-standalone-release.sh @@ -21,7 +21,7 @@ function copy_if_exists { exit 1 fi - BIN="${GOX_OUTPUT}/${1}" + BIN="${GOX_GUI_OUTPUT_DIR}/${1}" DESTDIR="$2" DESTSRC="$3" @@ -40,6 +40,10 @@ function copy_if_exists { mkdir -p "${DESTDIR}/src/gui/static" cp -R "$GUI_DIST_DIR" "${DESTDIR}/src/gui/static" + # Copy changelog to app + echo "Copying CHANGELOG.md to $DESTDIR" + cp ../CHANGELOG.md "$DESTDIR" + echo "Adding $DESTSRC to package-source.sh list" DESTSRCS+=("$DESTSRC") else @@ -49,43 +53,43 @@ function copy_if_exists { echo "Copying ${PKG_NAME} binaries" -# OS X +# OS X if [ ! -z "$OSX64_STL" ]; then - OSX64="${STL_OUTPUT}/${OSX64_STL}" + OSX64="${STL_OUTPUT_DIR}/${OSX64_STL}" OSX64_SRC="${OSX64}/src" copy_if_exists "${OSX64_OUT}/${PKG_NAME}" "$OSX64" "$OSX64_SRC" fi # Linux amd64 if [ ! -z "$LNX64_STL" ]; then - LNX64="${STL_OUTPUT}/${LNX64_STL}" + LNX64="${STL_OUTPUT_DIR}/${LNX64_STL}" LNX64_SRC="${LNX64}/src" copy_if_exists "${LNX64_OUT}/${PKG_NAME}" "$LNX64" "$LNX64_SRC" fi # Linux arm if [ ! -z "$LNX_ARM_STL" ]; then - LNX_ARM="${STL_OUTPUT}/${LNX_ARM_STL}" + LNX_ARM="${STL_OUTPUT_DIR}/${LNX_ARM_STL}" LNX_ARM_SRC="${LNX_ARM}/src" copy_if_exists "${LNX_ARM_OUT}/${PKG_NAME}" "$LNX_ARM" "$LNX_ARM_SRC" fi # Windows amd64 if [ ! -z "$WIN64_STL" ]; then - WIN64="${STL_OUTPUT}/${WIN64_STL}" + WIN64="${STL_OUTPUT_DIR}/${WIN64_STL}" WIN64_SRC="${WIN64}/src" copy_if_exists "${WIN64_OUT}/${PKG_NAME}.exe" "$WIN64" "$WIN64_SRC" fi # Windows 386 if [ ! -z "$WIN32_STL" ]; then - WIN32="${STL_OUTPUT}/${WIN32_STL}" + WIN32="${STL_OUTPUT_DIR}/${WIN32_STL}" WIN32_SRC="${WIN32}/src" copy_if_exists "${WIN32_OUT}/${PKG_NAME}.exe" "$WIN32" "$WIN32_SRC" fi -# Copy the source for reference -# tar it with filters, move it, then untar in order to do this -echo "Copying source snapshot" +# # Copy the source for reference +# # tar it with filters, move it, then untar in order to do this +# echo "Copying source snapshot" -./package-source.sh "${DESTSRCS[@]}" \ No newline at end of file +# ./package-source.sh "${DESTSRCS[@]}" diff --git a/electron/package.json b/electron/package.json index b8ef8daf9f..bb934ac2e9 100644 --- a/electron/package.json +++ b/electron/package.json @@ -3,7 +3,7 @@ "productName": "Skycoin", "author": "skycoin", "main": "src/electron-main.js", - "version": "0.24.1", + "version": "0.25.0-rc1", "description": "skycoin wallet", "license": "MIT", "build": { @@ -16,7 +16,7 @@ "category": "public.app-category.productivity", "extraFiles": [ { - "from": ".gox_output/${os}_${arch}", + "from": ".gox_output/gui/${os}_${arch}", "to": "./Resources/app" } ] @@ -25,7 +25,7 @@ "target": "nsis", "extraFiles": [ { - "from": ".gox_output/${os}_${arch}", + "from": ".gox_output/gui/${os}_${arch}", "to": "./resources/app" } ] @@ -34,7 +34,7 @@ "category": "Network", "extraFiles": [ { - "from": ".gox_output/${os}_${arch}", + "from": ".gox_output/gui/${os}_${arch}", "to": "./resources/app" }, { diff --git a/electron/skycoin/current-skycoin.json b/electron/skycoin/current-skycoin.json index 381e17e55d..400f1dcc6f 100644 --- a/electron/skycoin/current-skycoin.json +++ b/electron/skycoin/current-skycoin.json @@ -1 +1 @@ -versionData='{ "version": "0.24.1" }'; +versionData='{ "version": "0.25.0-rc1" }'; diff --git a/electron/src/electron-main.js b/electron/src/electron-main.js index 0d55831446..13339d9ba7 100644 --- a/electron/src/electron-main.js +++ b/electron/src/electron-main.js @@ -232,13 +232,13 @@ function createWindow(url) { }, { label: 'Edit', submenu: [ - { label: 'Undo', accelerator: 'CmdOrCtrl+Z', selector: 'undo:' }, - { label: 'Redo', accelerator: 'Shift+CmdOrCtrl+Z', selector: 'redo:' }, + { label: 'Undo', accelerator: 'CmdOrCtrl+Z', role: 'undo' }, + { label: 'Redo', accelerator: 'Shift+CmdOrCtrl+Z', role: 'redo' }, { type: 'separator' }, - { label: 'Cut', accelerator: 'CmdOrCtrl+X', selector: 'cut:' }, - { label: 'Copy', accelerator: 'CmdOrCtrl+C', selector: 'copy:' }, - { label: 'Paste', accelerator: 'CmdOrCtrl+V', selector: 'paste:' }, - { label: 'Select All', accelerator: 'CmdOrCtrl+A', selector: 'selectAll:' } + { label: 'Cut', accelerator: 'CmdOrCtrl+X', role: 'cut' }, + { label: 'Copy', accelerator: 'CmdOrCtrl+C', role: 'copy' }, + { label: 'Paste', accelerator: 'CmdOrCtrl+V', role: 'paste' }, + { label: 'Select All', accelerator: 'CmdOrCtrl+A', role: 'selectall' } ] }, { label: 'Show', diff --git a/fiber.toml b/fiber.toml index b9fd846120..f93ba8a704 100644 --- a/fiber.toml +++ b/fiber.toml @@ -18,17 +18,22 @@ default_connections = [ "139.162.7.132:6000", ] peer_list_url = "https://downloads.skycoin.net/blockchain/peers.txt" -#port = 6000 -#web_interface_port = 6420 +# port = 6000 +# web_interface_port = 6420 +# unconfirmed_burn_factor = 2 +# create_block_burn_factor = 2 +# max_block_size = 32 * 1024 +# max_unconfirmed_transaction_size = 32 * 1024 -[visor] -#max_coin_supply = 1e8 -#distribution_addresses_total = 100 -#initial_unlocked_count = 25 -#unlock_addresss_rate = 5 -#unlocked_time_internal = 60 * 60 * 24 * 365 -#max_droplet_precision = 3 -#default_max_block_size = 32 * 1024 +[params] +# max_coin_supply = 1e8 +# distribution_addresses_total = 100 +# initial_unlocked_count = 25 +# unlock_addresss_rate = 5 +# unlocked_time_internal = 60 * 60 * 24 * 365 +# max_droplet_precision = 3 +# max_user_transaction_size = 32 * 1024 +# user_burn_factor = 2 distribution_addresses = [ "R6aHqKWSQfvpdo2fGSrq4F1RYXkBWR9HHJ", "2EYM4WFHe4Dgz6kjAdUkM6Etep7ruz2ia6h", diff --git a/include/api.client.go.h b/include/api.client.go.h new file mode 100644 index 0000000000..58838440ff --- /dev/null +++ b/include/api.client.go.h @@ -0,0 +1,11 @@ +typedef struct{ + GoInt_ N; + BOOL IncludeDistribution; +} api__RichlistParams; + +typedef struct{ + // Comma separated list of connection states ("pending", "connected", "introduced") + GoString_ States; + GoString_ Direction; +} api__NetworkConnectionsFilter; + diff --git a/include/base64.h b/include/base64.h index 75211ccf4e..c27c6e0805 100644 --- a/include/base64.h +++ b/include/base64.h @@ -11,7 +11,7 @@ Thank you for inspiration: #include //Base64 char table function - used internally for decoding -unsigned int b64_int(unsigned int ch); +int b64_int(unsigned int ch); // in_size : the number bytes to be encoded. // Returns the recommended memory size to be allocated for the output buffer excluding the null byte @@ -31,7 +31,7 @@ unsigned int b64_encode(const unsigned char* in, unsigned int in_len, unsigned c // in_len : number of bytes to be decoded. // out : pointer to buffer with enough memory, user is responsible for memory allocation, receives "raw" binary // returns size of output excluding null byte -unsigned int b64_decode(const unsigned char* in, unsigned int in_len, unsigned char* out); +int b64_decode(const unsigned char* in, unsigned int in_len, unsigned char* out); // file-version b64_encode // Input : filenames @@ -41,5 +41,4 @@ unsigned int b64_encodef(char *InFile, char *OutFile); // file-version b64_decode // Input : filenames // returns size of output -unsigned int b64_decodef(char *InFile, char *OutFile); - +int b64_decodef(char *InFile, char *OutFile); diff --git a/include/cipher.address.go.h b/include/cipher.address.go.h index 462feb9816..a074642724 100644 --- a/include/cipher.address.go.h +++ b/include/cipher.address.go.h @@ -1,14 +1,14 @@ /** * Integrity checksum, 4-bytes long. */ -typedef unsigned char cipher__Checksum[4]; +typedef GoUint8_ cipher__Checksum[4]; /** * Addresses of SKY accounts */ -typedef struct { - unsigned char Version; ///< Address version identifier. - ///< Used to differentiate testnet - ///< vs mainnet addresses, for instance. - cipher__Ripemd160 Key; ///< Address hash identifier. +typedef struct{ + GoUint8_ Version; ///< Address version identifier. + ///< Used to differentiate testnet + ///< vs mainnet addresses, for ins + cipher__Ripemd160 Key; ///< Address hash identifier. } cipher__Address; diff --git a/include/cipher.bitcoin.go.h b/include/cipher.bitcoin.go.h index 0cb3b6ffc4..b08b11fe38 100644 --- a/include/cipher.bitcoin.go.h +++ b/include/cipher.bitcoin.go.h @@ -2,8 +2,8 @@ * Addresses of Bitcoin accounts */ typedef struct { - unsigned char Version; ///< Address version identifier. + GoUint8_ Version; ///< Address version identifier. ///< Used to differentiate testnet ///< vs mainnet addresses, for instance. - cipher__Ripemd160 Key; ///< Address hash identifier. + cipher__Ripemd160 Key; ///< Address hash identifier. } cipher__BitcoinAddress; diff --git a/include/cipher.crypto.go.h b/include/cipher.crypto.go.h index 0ab62df3a9..27b41c067e 100644 --- a/include/cipher.crypto.go.h +++ b/include/cipher.crypto.go.h @@ -1,14 +1,14 @@ /** * Hash signed using a secret key, 65 bytes long. */ -typedef unsigned char cipher__Sig[65]; +typedef GoUint8_ cipher__Sig[65]; /** * Public key, 33-bytes long. */ -typedef unsigned char cipher__PubKey[33]; +typedef GoUint8_ cipher__PubKey[33]; /** * Secret key, 32 bytes long. */ -typedef unsigned char cipher__SecKey[32]; +typedef GoUint8_ cipher__SecKey[32]; diff --git a/include/cipher.encrypt.scrypt_chacha20poly1305.go.h b/include/cipher.encrypt.scrypt_chacha20poly1305.go.h new file mode 100644 index 0000000000..07e1dcd759 --- /dev/null +++ b/include/cipher.encrypt.scrypt_chacha20poly1305.go.h @@ -0,0 +1,6 @@ +typedef struct{ + GoInt_ N; + GoInt_ R; + GoInt_ P; + GoInt_ KeyLen; +} encrypt__ScryptChacha20poly1305; diff --git a/include/cipher.hash.go.h b/include/cipher.hash.go.h index 87465693c6..2b801ca668 100644 --- a/include/cipher.hash.go.h +++ b/include/cipher.hash.go.h @@ -1,9 +1,9 @@ /** * Hash obtained using SHA256 algorithm, 32 bytes long. */ -typedef unsigned char cipher__SHA256[32]; +typedef GoUint8_ cipher__SHA256[32]; /** * RIPEMD-160 hash. */ -typedef unsigned char cipher__Ripemd160[20]; +typedef GoUint8_ cipher__Ripemd160[20]; diff --git a/include/cipher.secp256k1-go.secp256k1-go2.field.go.h b/include/cipher.secp256k1-go.secp256k1-go2.field.go.h new file mode 100644 index 0000000000..6345d5e4ac --- /dev/null +++ b/include/cipher.secp256k1-go.secp256k1-go2.field.go.h @@ -0,0 +1,3 @@ +typedef struct{ + GoUint32_ n[10]; +} secp256k1go__Field; diff --git a/include/cipher.secp256k1-go.secp256k1-go2.xy.go.h b/include/cipher.secp256k1-go.secp256k1-go2.xy.go.h new file mode 100644 index 0000000000..780f923486 --- /dev/null +++ b/include/cipher.secp256k1-go.secp256k1-go2.xy.go.h @@ -0,0 +1,5 @@ +typedef struct{ + secp256k1go__Field X; + secp256k1go__Field Y; + BOOL Infinity; +} secp256k1go__XY; diff --git a/include/cipher.secp256k1-go.secp256k1-go2.xyz.go.h b/include/cipher.secp256k1-go.secp256k1-go2.xyz.go.h new file mode 100644 index 0000000000..bd16b90ab8 --- /dev/null +++ b/include/cipher.secp256k1-go.secp256k1-go2.xyz.go.h @@ -0,0 +1,6 @@ +typedef struct{ + secp256k1go__Field X; + secp256k1go__Field Y; + secp256k1go__Field Z; + BOOL Infinity; +} secp256k1go__XYZ; diff --git a/include/cipher.testsuite.testsuite.go.h b/include/cipher.testsuite.testsuite.go.h index 0802f980dc..c56c207748 100644 --- a/include/cipher.testsuite.testsuite.go.h +++ b/include/cipher.testsuite.testsuite.go.h @@ -21,7 +21,7 @@ #define FILEPATH_SEPARATOR "/" #define TEST_DATA_DIR "src/cipher/testsuite/testdata/" #define MANY_ADDRESSES_FILENAME "many-addresses.golden" -#define INPUT_HASHES_FILENAME "input-hashes.golden" +#define INPUT_HASHES_FILENAME "input-hashes.golden" #define SEED_FILE_REGEX "seed-\d+.golden" //------------------------------------------------------------------------------ diff --git a/include/cli.cli.go.h b/include/cli.cli.go.h new file mode 100644 index 0000000000..126a0e3c8c --- /dev/null +++ b/include/cli.cli.go.h @@ -0,0 +1 @@ +typedef GoSlice_ cli__PasswordFromBytes; diff --git a/include/cli.create_rawtx.go.h b/include/cli.create_rawtx.go.h index 48dfa5c9b4..5d41be93df 100644 --- a/include/cli.create_rawtx.go.h +++ b/include/cli.create_rawtx.go.h @@ -2,6 +2,6 @@ * Structure used to specify amounts transferred in a transaction. */ typedef struct { - GoString_ Addr; ///< Sender / receipient address. - GoInt64_ Coins; ///< Amount transferred (e.g. measured in SKY) + GoString_ Addr; ///< Sender / receipient address. + GoInt64_ Coins; ///< Amount transferred (e.g. measured in SKY) } cli__SendAmount; diff --git a/include/coin.block.go.h b/include/coin.block.go.h new file mode 100644 index 0000000000..69ff0660de --- /dev/null +++ b/include/coin.block.go.h @@ -0,0 +1,21 @@ +typedef struct{ + GoUint32_ Version; + GoUint64_ Time; + GoUint64_ BkSeq; + GoUint64_ Fee; + cipher__SHA256 PrevHash; + cipher__SHA256 BodyHash; + cipher__SHA256 UxHash; +} coin__BlockHeader; +typedef struct{ + coin__Transactions Transactions; +} coin__BlockBody; +typedef struct{ + coin__BlockHeader Head; + coin__BlockBody Body; +} coin__Block; + +typedef struct{ + coin__Block _unnamed; + cipher__Sig Sig; +} coin__SignedBlock; diff --git a/include/coin.transactions.go.h b/include/coin.transactions.go.h index 0fa66cac15..2eed03ea26 100644 --- a/include/coin.transactions.go.h +++ b/include/coin.transactions.go.h @@ -1,25 +1,26 @@ -/** - * Skycoin transaction output. - * - * Instances are integral part of transactions included in blocks. - */ -typedef struct { - cipher__Address Address; ///< Receipient address. - GoInt64_ Coins; ///< Amount sent to the receipient address. - GoInt64_ Hours; ///< Amount of Coin Hours sent to the receipient address. -} coin__TransactionOutput; - +typedef GoSlice_ coin__Transactions; /** * Skycoin transaction. * * Instances of this struct are included in blocks. */ typedef struct { - GoInt32_ Length; ///< Current transaction's length expressed in bytes. - GoInt8_ Type; ///< Transaction's version. When a node tries to process a transaction, it must verify whether it supports the transaction's type. This is intended to provide a way to update skycoin clients and servers without crashing the network. If the transaction is not compatible with the node, it should not process it. - cipher__SHA256 InnerHash; ///< It's a SHA256 hash of the inputs and outputs of the transaction. It is used to protect against transaction mutability. This means that the transaction cannot be altered after its creation. + GoInt32_ Length; ///< Current transaction's length expressed in bytes. + GoInt8_ Type; ///< Transaction's version. When a node tries to process a transaction, it must verify whether it supports the transaction's type. This is intended to provide a way to update skycoin clients and servers without crashing the network. If the transaction is not compatible with the node, it should not process it. + cipher__SHA256 InnerHash; ///< It's a SHA256 hash of the inputs and outputs of the transaction. It is used to protect against transaction mutability. This means that the transaction cannot be altered after its creation. - GoSlice_ Sigs; ///< A list of digital signiatures generated by the skycoin client using the private key. It is used by Skycoin servers to verify the authenticy of the transaction. Each input requires a different signature. - GoSlice_ In; ///< A list of references to unspent transaction outputs. Unlike other cryptocurrencies, such as Bitcoin, Skycoin unspent transaction outputs (UX) and Skycoin transactions (TX) are separated in the blockchain protocol, allowing for lighter transactions, thus reducing the broadcasting costs across the network. - GoSlice_ Out; ///< Outputs: A list of outputs created by the client, that will be recorded in the blockchain if transactions are confirmed. An output consists of a data structure representing an UTXT, which is composed by a Skycoin address to be sent to, the amount in Skycoin to be sent, and the amount of Coin Hours to be sent, and the SHA256 hash of the previous fields. + GoSlice_ Sigs; ///< A list of digital signiatures generated by the skycoin client using the private key. It is used by Skycoin servers to verify the authenticy of the transaction. Each input requires a different signature. + GoSlice_ In; ///< A list of references to unspent transaction outputs. Unlike other cryptocurrencies, such as Bitcoin, Skycoin unspent transaction outputs (UX) and Skycoin transactions (TX) are separated in the blockchain protocol, allowing for lighter transactions, thus reducing the broadcasting costs across the network. + GoSlice_ Out; ///< Outputs: A list of outputs created by the client, that will be recorded in the blockchain if transactions are confirmed. An output consists of a data structure representing an UTXT, which is composed by a Skycoin address to be sent to, the amount in Skycoin to be sent, and the amount of Coin Hours to be sent, and the SHA256 hash of the previous fields. } coin__Transaction; + +/** + * Skycoin transaction output. + * + * Instances are integral part of transactions included in blocks. + */ +typedef struct{ + cipher__Address Address; ///< Receipient address. + GoUint64_ Coins; ///< Amount sent to the receipient address. + GoUint64_ Hours; ///< Amount of Coin Hours sent to the receipient address. +} coin__TransactionOutput; diff --git a/include/skycriterion.h b/include/skycriterion.h index ecdf227c5c..91f5aa1c64 100644 --- a/include/skycriterion.h +++ b/include/skycriterion.h @@ -28,9 +28,43 @@ extern int cr_user_GoSlice_eq(GoSlice *slice1, GoSlice *slice2); extern char *cr_user_GoSlice_tostr(GoSlice *slice1); extern int cr_user_GoSlice_noteq(GoSlice *slice1, GoSlice *slice2); + +extern int cr_user_GoSlice__eq(GoSlice_ *slice1, GoSlice_ *slice2); +extern char *cr_user_GoSlice__tostr(GoSlice_ *slice1); +extern int cr_user_GoSlice__noteq(GoSlice_ *slice1, GoSlice_ *slice2); + extern int cr_user_cipher__SHA256_noteq(cipher__SHA256 *sh1, cipher__SHA256 *sh2); extern int cr_user_cipher__SHA256_eq(cipher__SHA256 *sh1, cipher__SHA256 *sh2); extern char *cr_user_cipher__SHA256_tostr(cipher__SHA256 *sh1); +extern int cr_user_secp256k1go__Field_eq(secp256k1go__Field* f1, secp256k1go__Field* f2); + +extern int cr_user_coin__BlockBody_eq(coin__BlockBody *b1, coin__BlockBody *b2); +extern int cr_user_coin__BlockBody_noteq(coin__BlockBody *b1, coin__BlockBody *b2); +extern char *cr_user_coin__BlockBody_tostr(coin__BlockBody *b); + +extern int cr_user_coin__UxOut_eq(coin__UxOut *x1, coin__UxOut *x2); +extern int cr_user_coin__UxOut_noteq(coin__UxOut *x1, coin__UxOut *x2); +extern char* cr_user_coin__UxOut_tostr(coin__UxOut *x1); + +extern int cr_user_coin__UxArray_eq(coin__UxArray *x1, coin__UxArray *x2); +extern int cr_user_coin__UxArray_noteq(coin__UxArray *x1, coin__UxArray *x2); +extern char* cr_user_coin__UxArray_tostr(coin__UxArray *x1); + +extern int cr_user_coin__Transaction_eq(coin__Transaction *x1, coin__Transaction *x2); +extern int cr_user_coin__Transaction_noteq(coin__Transaction *x1, coin__Transaction *x2); +extern char* cr_user_coin__Transaction_tostr(coin__Transaction *x1); + +extern int cr_user_coin__Transactions_eq(coin__Transactions *x1, coin__Transactions *x2); +extern int cr_user_coin__Transactions_noteq(coin__Transactions *x1, coin__Transactions *x2); +extern char* cr_user_coin__Transactions_tostr(coin__Transactions *x1); + +extern int cr_user_coin__TransactionOutput_eq(coin__TransactionOutput *x1, coin__TransactionOutput *x2); +extern int cr_user_coin__TransactionOutput_noteq(coin__TransactionOutput *x1, coin__TransactionOutput *x2); +extern char* cr_user_coin__TransactionOutput_tostr(coin__TransactionOutput *x1); + +extern int cr_user_Number_eq(Number *n1, Number *n2); +extern int cr_user_Number_noteq(Number *n1, Number *n2); +extern char* cr_user_Number_tostr(Number *n1); #endif //LIBCRITERION_H diff --git a/include/skyerrors.h b/include/skyerrors.h index 1053e3c529..cea60a1edb 100644 --- a/include/skyerrors.h +++ b/include/skyerrors.h @@ -1,10 +1,25 @@ +#include + +#if __APPLE__ +#include "TargetConditionals.h" +#endif + +#if __linux__ +#define SKY_ABORT .signal = SIGABRT +#elif __APPLE__ +#if TARGET_OS_MAC +#define SKY_ABORT .exit_code = 2 +#endif +#endif #ifndef SKY_ERRORS_H #define SKY_ERRORS_H // Generic error conditions -#define SKY_OK 0 -#define SKY_ERROR 0x7FFFFFFF +#define SKY_OK 0 +#define SKY_ERROR 0x7FFFFFFF +#define SKY_BAD_HANDLE 0x7F000001 +#define SKY_INVALID_TIMESTRING 0x7F000002 // Package error code prefix list #define SKY_PKG_API 0x01000000 @@ -18,6 +33,7 @@ #define SKY_PKG_UTIL 0x09000000 #define SKY_PKG_VISOR 0x0A000000 #define SKY_PKG_WALLET 0x0B000000 +#define SKY_PKG_LIBCGO 0x7F000000 #define SKY_PKG_LIBCGO 0x7F000000 @@ -49,7 +65,7 @@ #define SKY_ErrInvalidBytesLength 0x02000015 #define SKY_ErrInvalidPubKey 0x02000016 #define SKY_ErrInvalidSecKey 0x02000017 -#define SKY_ErrInvalidSigForPubKey 0x02000018 +#define SKY_ErrInvalidSigPubKeyRecovery 0x02000018 #define SKY_ErrInvalidSecKeyHex 0x02000019 #define SKY_ErrInvalidAddressForSig 0x0200001A #define SKY_ErrInvalidHashForSig 0x0200001B @@ -68,6 +84,16 @@ #define SKY_ErrBitcoinWIFInvalidChecksum 0x02000028 #define SKY_ErrEmptySeed 0x02000029 #define SKY_ErrInvalidSig 0x0200002A +#define SKY_ErrMissingPassword 0x0200002B +#define SKY_ErrDataTooLarge 0x0200002C +#define SKY_ErrInvalidChecksumLength 0x0200002D +#define SKY_ErrInvalidChecksum 0x0200002E +#define SKY_ErrInvalidNonceLength 0x0200002F +#define SKY_ErrInvalidBlockSize 0x02000030 +#define SKY_ErrReadDataHashFailed 0x02000031 +#define SKY_ErrInvalidPassword 0x02000032 +#define SKY_ErrReadDataLengthFailed 0x02000033 +#define SKY_ErrInvalidDataLength 0x02000034 // cli error codes #define SKY_ErrTemporaryInsufficientBalance 0x03000000 @@ -92,40 +118,37 @@ #define SKY_ErrNotExternalIP 0x06000003 #define SKY_ErrPortTooLow 0x06000004 #define SKY_ErrBlacklistedAddress 0x06000005 -#define SKY_ErrDisconnectReadFailed 0x06000006 +// #define SKY_ErrDisconnectReadFailed 0x06000006 #define SKY_ErrDisconnectWriteFailed 0x06000007 #define SKY_ErrDisconnectSetReadDeadlineFailed 0x06000008 #define SKY_ErrDisconnectInvalidMessageLength 0x06000009 #define SKY_ErrDisconnectMalformedMessage 0x0600000A #define SKY_ErrDisconnectUnknownMessage 0x0600000B -#define SKY_ErrDisconnectUnexpectedError 0x0600000C #define SKY_ErrConnectionPoolClosed 0x0600000D #define SKY_ErrWriteQueueFull 0x0600000E #define SKY_ErrNoReachableConnections 0x0600000F #define SKY_ErrMaxDefaultConnectionsReached 0x06000010 -#define SKY_ErrDisconnectInvalidVersion 0x06000011 +#define SKY_ErrDisconnectVersionNotSupported 0x06000011 #define SKY_ErrDisconnectIntroductionTimeout 0x06000012 -#define SKY_ErrDisconnectVersionSendFailed 0x06000013 #define SKY_ErrDisconnectIsBlacklisted 0x06000014 #define SKY_ErrDisconnectSelf 0x06000015 #define SKY_ErrDisconnectConnectedTwice 0x06000016 #define SKY_ErrDisconnectIdle 0x06000017 #define SKY_ErrDisconnectNoIntroduction 0x06000018 #define SKY_ErrDisconnectIPLimitReached 0x06000019 -#define SKY_ErrDisconnectOtherError 0x0600001A #define SKY_ErrDisconnectMaxDefaultConnectionReached 0x0600001B #define SKY_ErrDisconnectMaxOutgoingConnectionsReached 0x0600001C #define SKY_ConnectionError 0x0600001D // util error codes -#define SKY_ErrTxnNoFee 0x09000000 -#define SKY_ErrTxnInsufficientFee 0x09000001 -#define SKY_ErrTxnInsufficientCoinHours 0x09000002 -#define SKY_ErrNegativeValue 0x09000003 -#define SKY_ErrTooManyDecimals 0x09000004 -#define SKY_ErrTooLarge 0x09000005 -#define SKY_ErrEmptyDirectoryName 0x09000006 -#define SKY_ErrDotDirectoryName 0x09000007 +#define SKY_ErrTxnNoFee 0x09000000 +#define SKY_ErrTxnInsufficientFee 0x09000001 +#define SKY_ErrTxnInsufficientCoinHours 0x09000002 +#define SKY_ErrNegativeValue 0x09000003 +#define SKY_ErrTooManyDecimals 0x09000004 +#define SKY_ErrTooLarge 0x09000005 +#define SKY_ErrEmptyDirectoryName 0x09000006 +#define SKY_ErrDotDirectoryName 0x09000007 // visor error codes #define SKY_ErrHistoryDBCorrupted 0x0A000000 @@ -141,48 +164,50 @@ #define SKY_ErrTxnViolatesUserConstraint 0x0A000009 // wallet error codes -#define SKY_ErrInsufficientBalance 0x0B000000 -#define SKY_ErrInsufficientHours 0x0B000001 -#define SKY_ErrZeroSpend 0x0B000002 -#define SKY_ErrSpendingUnconfirmed 0x0B000003 -#define SKY_ErrInvalidEncryptedField 0x0B000004 -#define SKY_ErrWalletEncrypted 0x0B000005 -#define SKY_ErrWalletNotEncrypted 0x0B000006 -#define SKY_ErrMissingPassword 0x0B000007 -#define SKY_ErrMissingEncrypt 0x0B000008 -#define SKY_ErrInvalidPassword 0x0B000009 -#define SKY_ErrMissingSeed 0x0B00000A -#define SKY_ErrMissingAuthenticated 0x0B00000B -#define SKY_ErrWrongCryptoType 0x0B00000C -#define SKY_ErrWalletNotExist 0x0B00000D -#define SKY_ErrSeedUsed 0x0B00000E -#define SKY_ErrWalletAPIDisabled 0x0B00000F -#define SKY_ErrSeedAPIDisabled 0x0B000010 -#define SKY_ErrWalletNameConflict 0x0B000011 -#define SKY_ErrInvalidHoursSelectionMode 0x0B000012 -#define SKY_ErrInvalidHoursSelectionType 0x0B000013 -#define SKY_ErrUnknownAddress 0x0B000014 -#define SKY_ErrUnknownUxOut 0x0B000015 -#define SKY_ErrNoUnspents 0x0B000016 -#define SKY_ErrNullChangeAddress 0x0B000017 -#define SKY_ErrMissingTo 0x0B000018 -#define SKY_ErrZeroCoinsTo 0x0B000019 -#define SKY_ErrNullAddressTo 0x0B00001A -#define SKY_ErrDuplicateTo 0x0B00001B -#define SKY_ErrMissingWalletID 0x0B00001C -#define SKY_ErrIncludesNullAddress 0x0B00001D -#define SKY_ErrDuplicateAddresses 0x0B00001E -#define SKY_ErrZeroToHoursAuto 0x0B00001F -#define SKY_ErrMissingModeAuto 0x0B000020 -#define SKY_ErrInvalidHoursSelMode 0x0B000021 -#define SKY_ErrInvalidModeManual 0x0B000022 -#define SKY_ErrInvalidHoursSelType 0x0B000023 -#define SKY_ErrMissingShareFactor 0x0B000024 -#define SKY_ErrInvalidShareFactor 0x0B000025 -#define SKY_ErrShareFactorOutOfRange 0x0B000026 -#define SKY_ErrWalletConstraint 0x0B000027 -#define SKY_ErrDuplicateUxOuts 0x0B000028 -#define SKY_ErrUnknownWalletID 0x0B000029 +#define SKY_ErrInsufficientBalance 0x0B000000 +#define SKY_ErrInsufficientHours 0x0B000001 +#define SKY_ErrZeroSpend 0x0B000002 +#define SKY_ErrSpendingUnconfirmed 0x0B000003 +#define SKY_ErrInvalidEncryptedField 0x0B000004 +#define SKY_ErrWalletEncrypted 0x0B000005 +#define SKY_ErrWalletNotEncrypted 0x0B000006 +#define SKY_ErrWalletMissingPassword 0x0B000007 +#define SKY_ErrMissingEncrypt 0x0B000008 +#define SKY_ErrWalletInvalidPassword 0x0B000009 +#define SKY_ErrMissingSeed 0x0B00000A +#define SKY_ErrMissingAuthenticated 0x0B00000B +#define SKY_ErrWrongCryptoType 0x0B00000C +#define SKY_ErrWalletNotExist 0x0B00000D +#define SKY_ErrSeedUsed 0x0B00000E +#define SKY_ErrWalletAPIDisabled 0x0B00000F +#define SKY_ErrSeedAPIDisabled 0x0B000010 +#define SKY_ErrWalletNameConflict 0x0B000011 +#define SKY_ErrInvalidHoursSelectionMode 0x0B000012 +#define SKY_ErrInvalidHoursSelectionType 0x0B000013 +#define SKY_ErrUnknownAddress 0x0B000014 +#define SKY_ErrUnknownUxOut 0x0B000015 +#define SKY_ErrNoUnspents 0x0B000016 +#define SKY_ErrNullChangeAddress 0x0B000017 +#define SKY_ErrMissingTo 0x0B000018 +#define SKY_ErrZeroCoinsTo 0x0B000019 +#define SKY_ErrNullAddressTo 0x0B00001A +#define SKY_ErrDuplicateTo 0x0B00001B +#define SKY_ErrMissingWalletID 0x0B00001C +#define SKY_ErrIncludesNullAddress 0x0B00001D +#define SKY_ErrDuplicateAddresses 0x0B00001E +#define SKY_ErrZeroToHoursAuto 0x0B00001F +#define SKY_ErrMissingModeAuto 0x0B000020 +#define SKY_ErrInvalidHoursSelMode 0x0B000021 +#define SKY_ErrInvalidModeManual 0x0B000022 +#define SKY_ErrInvalidHoursSelType 0x0B000023 +#define SKY_ErrMissingShareFactor 0x0B000024 +#define SKY_ErrInvalidShareFactor 0x0B000025 +#define SKY_ErrShareFactorOutOfRange 0x0B000026 +#define SKY_ErrWalletConstraint 0x0B000027 +#define SKY_ErrDuplicateUxOuts 0x0B000028 +#define SKY_ErrUnknownWalletID 0x0B000029 +#define SKY_ErrVerifySignatureInvalidInputsNils 0x0B000033 +#define SKY_ErrVerifySignatureInvalidSigLength 0x0B000034 +#define SKY_ErrVerifySignatureInvalidPubkeysLength 0x0B000035 #endif - diff --git a/include/skyfee.h b/include/skyfee.h new file mode 100644 index 0000000000..1370de56ba --- /dev/null +++ b/include/skyfee.h @@ -0,0 +1,6 @@ +#ifndef CALLFEECALCULATOR +#define CALLFEECALCULATOR +static inline GoUint32_ callFeeCalculator(FeeCalculator* feeCalc, Transaction__Handle handle, GoUint64_* pFee){ + return feeCalc->callback(handle, pFee, feeCalc->context); +} +#endif diff --git a/include/skystring.h b/include/skystring.h index 124cae3bc4..59fec254b3 100644 --- a/include/skystring.h +++ b/include/skystring.h @@ -11,7 +11,20 @@ extern void bytesnhex(unsigned char* buf, char *str, int n); extern void strnhex(unsigned char* buf, char *str, int n); -extern void strhex(unsigned char* buf, char *str); +extern void strnhexlower(unsigned char* buf, char *str, int n); +extern int hexnstr(const char* hex, unsigned char* str, int n); + +extern void bin2hex(unsigned char* buf, char *str, int n); + +extern int cmpGoSlice_GoSlice(GoSlice *slice1, GoSlice_ *slice2); + +extern void bin2hex(unsigned char* buf, char *str, int n); + +extern int string_has_suffix(const char* str, const char* suffix); + +extern int string_has_prefix(const char* str, const char* prefix); + +extern int count_words(const char* str, int length); #endif //LIBSKY_STRING_H diff --git a/include/skytest.h b/include/skytest.h index ebd25d9bb1..25faccab23 100644 --- a/include/skytest.h +++ b/include/skytest.h @@ -1,7 +1,7 @@ #include - #include "json.h" +#include "skytypes.h" #include "skycriterion.h" #ifndef LIBSKY_TESTING_H @@ -11,38 +11,68 @@ * I/O *---------------------------------------------------------------------- */ + void fprintbuff(FILE *f, void *buff, size_t n); -json_value* loadJsonFile(const char* filename); /*---------------------------------------------------------------------- * Memory handling *---------------------------------------------------------------------- */ + void * registerMemCleanup(void *p); -extern void toGoString(GoString_ *s, GoString *r); + +int registerJsonFree(void *p); + +void freeRegisteredJson(void *p); + +int registerHandleClose(Handle handle); + +void closeRegisteredHandle(Handle handle); + +void freeRegisteredMemCleanup(void *p); + +int registerWalletClean(Client__Handle clientHandle, + WalletResponse__Handle walletHandle); + +void cleanRegisteredWallet( + Client__Handle client, + WalletResponse__Handle wallet); + +int copySlice(GoSlice_* pdest, GoSlice_* psource, int elem_size); + +int cutSlice(GoSlice_* slice, int start, int end, int elem_size, GoSlice_* result); + +int concatSlices(GoSlice_* slice1, GoSlice_* slice2, int elem_size, GoSlice_* result); /*---------------------------------------------------------------------- * JSON helpers *---------------------------------------------------------------------- */ + +json_value* loadJsonFile(const char* filename); + json_value* json_get_string(json_value* value, const char* key); + int json_set_string(json_value* value, const char* new_string_value); -int registerJsonFree(void *p); -void freeRegisteredJson(void *p); -json_value* loadJsonFile(const char* filename); int compareJsonValues(json_value* value1, json_value* value2); + json_value* get_json_value(json_value* node, const char* path, - json_type type); + json_type type); + json_value* get_json_value_not_strict(json_value* node, const char* path, - json_type type, int allow_null); + json_type type, int allow_null); + +int compareJsonValuesWithIgnoreList(json_value* value1, json_value* value2, const char* ignoreList); + +int parseBoolean(const char* str, int length); /*---------------------------------------------------------------------- - * JSON helpers + * Test infrastructure *---------------------------------------------------------------------- */ + void setup(void); void teardown(void); #endif - diff --git a/include/skytxn.h b/include/skytxn.h new file mode 100644 index 0000000000..acadff5163 --- /dev/null +++ b/include/skytxn.h @@ -0,0 +1,42 @@ + +#include +#include + +#include +#include + +#include "libskycoin.h" +#include "skyerrors.h" +#include "skystring.h" +#include "skytest.h" +#include "skytypes.h" + +GoUint32_ zeroFeeCalculator(Transaction__Handle handle, GoUint64_ *pFee, void* context); + +int makeKeysAndAddress(cipher__PubKey* ppubkey, cipher__SecKey* pseckey, cipher__Address* paddress); + +int makeUxBodyWithSecret(coin__UxBody* puxBody, cipher__SecKey* pseckey); + +int makeUxOutWithSecret(coin__UxOut* puxOut, cipher__SecKey* pseckey); + +int makeUxBody(coin__UxBody* puxBody); + +int makeUxOut(coin__UxOut* puxOut); + +int makeAddress(cipher__Address* paddress); + +coin__Transaction* makeTransactionFromUxOut(coin__UxOut* puxOut, cipher__SecKey* pseckey, Transaction__Handle* handle); + +coin__Transaction* makeTransaction(Transaction__Handle* handle); + +coin__Transaction* makeEmptyTransaction(Transaction__Handle* handle); + +int makeTransactions(int n, Transactions__Handle* handle); + +coin__Transaction* copyTransaction(Transaction__Handle handle, Transaction__Handle* handle2); + +void makeRandHash(cipher__SHA256* phash); + +int makeUxArray(coin__UxArray* parray, int n); + +int sortTransactions(Transactions__Handle txns_handle, Transactions__Handle* sorted_txns_handle); diff --git a/include/skytypes.gen.h b/include/skytypes.gen.h new file mode 100644 index 0000000000..0e38a3865c --- /dev/null +++ b/include/skytypes.gen.h @@ -0,0 +1,29 @@ + +#include "api.client.go.h" + +#include "cipher.hash.go.h" +#include "cipher.address.go.h" +#include "cipher.bitcoin.go.h" +#include "cipher.crypto.go.h" +#include "cipher.encrypt.scrypt_chacha20poly1305.go.h" + +#include "cipher.secp256k1-go.secp256k1-go2.field.go.h" +#include "cipher.secp256k1-go.secp256k1-go2.xy.go.h" +#include "cipher.secp256k1-go.secp256k1-go2.xyz.go.h" + +#include "cli.cli.go.h" +#include "cli.create_rawtx.go.h" + +#include "coin.transactions.go.h" +#include "coin.block.go.h" +#include "coin.outputs.go.h" + +#include "util.http.json.go.h" + +#include "visor.readable.go.h" + +#include "wallet.balance.go.h" +#include "wallet.entry.go.h" +#include "wallet.notes.go.h" +#include "wallet.wallet.go.h" + diff --git a/include/skytypes.h b/include/skytypes.h index c276282285..1b55b7056b 100644 --- a/include/skytypes.h +++ b/include/skytypes.h @@ -2,6 +2,10 @@ #ifndef SKYTYPES_H #define SKYTYPES_H +#ifndef __SIZE_TYPE__ +#define __SIZE_TYPE__ unsigned int +#endif + /** * Go 8-bit signed integer values. */ @@ -59,11 +63,13 @@ typedef double GoFloat64_; /** * Instances of Go `complex` type. */ -typedef float _Complex GoComplex64_; +typedef struct{float real; float imaginary;} GoComplex64_; /** * Instances of Go `complex` type. */ -typedef double _Complex GoComplex128_; +typedef struct{double real; double imaginary;} GoComplex128_; +typedef unsigned int BOOL; +typedef unsigned int error; /* static assertion to make sure the file is being used on architecture @@ -83,10 +89,94 @@ typedef struct { * Instances of Go `map` type. */ typedef void *GoMap_; + /** * Instances of Go `chan` channel types. */ typedef void *GoChan_; + +/** + * Memory handles returned back to the caller and manipulated + * internally by API functions. Usually used to avoid type dependencies + * with internal implementation types. + */ +typedef GoInt64_ Handle; + +/** + * Webrpc Client Handle +*/ +typedef Handle WebRpcClient__Handle; + +/** + * Wallet Handle +*/ +typedef Handle Wallet__Handle; + +/** + * ReadableWallet Handle +*/ +typedef Handle ReadableWallet__Handle; + +/** + * ReadableEntry Handle +*/ +typedef Handle ReadableEntry__Handle; + +/** + * Options Handle +*/ +typedef Handle Options__Handle; + + +/** + * Config Handle +*/ +typedef Handle Config__Handle; + +/** + * App Handle +*/ +typedef Handle App__Handle; + +/** + * Gcli Context Handle +*/ +typedef Handle Context__Handle; + +/** + * API Client Handle +*/ +typedef Handle Client__Handle; + +/** + * Wallet Response Handle +*/ +typedef Handle WalletResponse__Handle; + +/** + * Create Transaction Request Handle +*/ +typedef Handle CreateTransactionRequest__Handle; + +/** + * String Slice Handle +*/ +typedef Handle Strings__Handle; + +/** + * Instances of Go `map` type, deal map[string] as handle + */ +typedef Handle GoStringMap_; + +/** + * Wallets Handle, slice of Wallet +*/ +typedef Handle Wallets__Handle; + +/** + * ReadableOutputSet Handle + * */ +typedef Handle ReadableOutputSet_Handle; /** * Instances of Go interface types. */ @@ -107,13 +197,29 @@ typedef struct { ///< size. } GoSlice_; +typedef struct { + BOOL neg; + GoSlice_ nat; +} Number; + +typedef struct { + //TODO: stdevEclipse Define Signature + Number R; + Number S; +} Signature; + +#include "skytypes.gen.h" /** - * Memory handles returned back to the caller and manipulated - * internally by API functions. Usually used to avoid type dependencies - * with internal implementation types. + * Internal representation of a Skycoin wallet. */ -typedef GoInt64_ Handle; +typedef struct { + GoMap_ Meta; ///< Records items that are not deterministic, like filename, lable, wallet type, secrets, etc. + GoSlice_ Entries; ///< Entries field stores the address entries that are deterministically generated from seed. +} Wallet; + +typedef GoUint8_ poly1305__Mac[16]; +typedef GoUint8_ poly1305__Key[32]; /** * Memory handle for internal object retrieving password to read @@ -141,16 +247,141 @@ typedef Handle Options__Handle; * Memory handle to access to Skycoin CLI configuration */ typedef Handle Config__Handle; +/** + * Memory handle to access to coin.Transaction + */ +typedef Handle Transaction__Handle; -#include "cipher.hash.go.h" -#include "cipher.crypto.go.h" -#include "cipher.address.go.h" -#include "cipher.bitcoin.go.h" -#include "cli.create_rawtx.go.h" -#include "coin.outputs.go.h" -#include "coin.transactions.go.h" -#include "wallet.entry.go.h" -#include "wallet.wallet.go.h" +/** + * Memory handle to access to coin.Transactions + */ +typedef Handle Transactions__Handle; -#endif +/** + * Memory handle to access to api.CreatedTransaction + */ +typedef Handle CreatedTransaction__Handle; +/** + * Memory handle to access to api.CreatedTransactionOutput + */ +typedef Handle CreatedTransactionOutput__Handle; + +/** + * Memory handle to access to api.CreatedTransactionInput + */ +typedef Handle CreatedTransactionInput__Handle; + +/** + * Memory handle to access to api.CreateTransactionResponse + */ +typedef Handle CreateTransactionResponse__Handle; + +/** + * Memory handle to access to coin.Block + */ +typedef Handle Block__Handle; + +/** + * Memory handle to access to coin.SignedBlock + */ +typedef Handle SignedBlock__Handle; + +/** + * Memory handle to access to coin.BlockBody + */ +typedef Handle BlockBody__Handle; + +/** + * Memory handle to access to cli.BalanceResult + */ + +typedef Handle BalanceResult_Handle; + + +/** + * Memory handle to access to api.SpendResult + */ + +typedef Handle SpendResult_Handle; + +/** + * Memory handle to access to coin.Transactions + */ + +typedef Handle TransactionResult_Handle; + +/** + * Memory handle to access to coin.SortableTransactions + */ + +typedef Handle SortableTransactionResult_Handle; + +/** + * Memory handle to access to wallet.Notes + */ + +typedef Handle WalletNotes_Handle; + +/** + * Memory handle to access to wallet.ReadableNotes + */ + +typedef Handle WalletReadableNotes_Handle; + +/** + * Memory handle to access to webrpc.OutputsResult + */ + +typedef Handle OutputsResult_Handle; + +/** + * Memory handle to access to webrpc.StatusResult + */ + +typedef Handle StatusResult_Handle; + +/** + * Memory handle to access to coin.AddressUxOuts + */ + +typedef Handle AddressUxOuts_Handle; + +/** + * Memory handle to access to readable.BuildInfo (BuildInfo) + */ + +typedef Handle BuildInfo_Handle; + +/** + * Memory handle to access to readable.UnspentOutputsSummary (UnspentOutputsSummary) + */ + +typedef Handle ReadableUnspentOutputsSummary_Handle; + +/** + * Memory handle for hash (ripemd160.digest) + */ + +typedef Handle Hash_Handle; + +/** +* Handle for Number type +*/ + +typedef Handle Number_Handle; + +/** +* Handle for Signature type +*/ + +typedef Handle Signature_Handle; + +typedef GoUint32_ (*FeeCalcFunc)(Transaction__Handle handle, GoUint64_* pFee, void* context); + +typedef struct { + FeeCalcFunc callback; + void* context; +} FeeCalculator ; + +#endif diff --git a/include/util.http.json.go.h b/include/util.http.json.go.h new file mode 100644 index 0000000000..84cb0e0361 --- /dev/null +++ b/include/util.http.json.go.h @@ -0,0 +1,5 @@ +typedef GoUint64_ httphelper__Coins; +typedef GoUint64_ httphelper__Hours; +typedef struct{ + cipher__Address _unnamed; +} httphelper__Address; diff --git a/include/visor.readable.go.h b/include/visor.readable.go.h new file mode 100644 index 0000000000..46587f7c42 --- /dev/null +++ b/include/visor.readable.go.h @@ -0,0 +1 @@ +typedef GoSlice_ visor__ReadableOutputs; diff --git a/include/wallet.balance.go.h b/include/wallet.balance.go.h new file mode 100644 index 0000000000..7c5fb1f493 --- /dev/null +++ b/include/wallet.balance.go.h @@ -0,0 +1,8 @@ +typedef struct{ + GoUint64_ Coins; + GoUint64_ Hours; +} wallet__Balance; +typedef struct{ + wallet__Balance Confirmed; + wallet__Balance Predicted; +} wallet__BalancePair; diff --git a/include/wallet.entry.go.h b/include/wallet.entry.go.h index 362bb76494..fa43ea31d3 100644 --- a/include/wallet.entry.go.h +++ b/include/wallet.entry.go.h @@ -2,7 +2,7 @@ * Wallet entry. */ typedef struct { - cipher__Address Address; ///< Wallet address. - cipher__PubKey Public; ///< Public key used to generate address. - cipher__SecKey Secret; ///< Secret key used to generate address. + cipher__Address Address; ///< Wallet address. + cipher__PubKey Public; ///< Public key used to generate address. + cipher__SecKey Secret; ///< Secret key used to generate address. } wallet__Entry; diff --git a/include/wallet.notes.go.h b/include/wallet.notes.go.h new file mode 100644 index 0000000000..2c77c0d365 --- /dev/null +++ b/include/wallet.notes.go.h @@ -0,0 +1,8 @@ +typedef struct{ + GoString_ TxID; + GoString_ Value; +} wallet__Note; +typedef struct{ + GoString_ TransactionID; + GoString_ ActualNote; +} wallet__ReadableNote; diff --git a/include/wallet.wallet.go.h b/include/wallet.wallet.go.h index 2605cde0f9..1aef1b164a 100644 --- a/include/wallet.wallet.go.h +++ b/include/wallet.wallet.go.h @@ -1,19 +1,12 @@ + /** * Intermediate representation of a UxOut for sorting and spend choosing. */ typedef struct { - cipher__SHA256 Hash; ///< Hash of underlying UxOut. - GoInt64_ BkSeq; ///< Block height corresponding to the + cipher__SHA256 Hash; ///< Hash of underlying UxOut. + GoInt64_ BkSeq; ///< Block height corresponding to the ///< moment balance calculation is performed at. - cipher__Address Address; ///< Account holder address. - GoInt64_ Coins; ///< Coins amount (e.g. in SKY). - GoInt64_ Hours; ///< Balance of Coin Hours generated by underlying UxOut, depending on UxOut's head time. + cipher__Address Address; ///< Account holder address. + GoInt64_ Coins; ///< Coins amount (e.g. in SKY). + GoInt64_ Hours; ///< Balance of Coin Hours generated by underlying UxOut, depending on UxOut's head time. } wallet__UxBalance; - -/** - * Internal representation of a Skycoin wallet. - */ -typedef struct { - GoMap_ Meta; ///< Records items that are not deterministic, like filename, lable, wallet type, secrets, etc. - GoSlice_ Entries; ///< Entries field stores the address entries that are deterministically generated from seed. -} wallet__Wallet; diff --git a/lib/cgo/README.md b/lib/cgo/README.md index b23dc114a0..20208aa57f 100644 --- a/lib/cgo/README.md +++ b/lib/cgo/README.md @@ -56,6 +56,10 @@ classes and objects. This makes it suitable for writing third-party applications and integrations. The notable differences between go lang and C languages have consequences for the consumers of the API. +The following subsets of the golang API are not available in the C client library: + +- `cipher.encrypt.sha256xor` + ### Data types Skycoin core objects may not be passed across API boundaries. Therefore @@ -63,6 +67,23 @@ equivalent C types are defined for each Skycoin core struct that might be needed by developers. The result of this translation is available in [skytpes.h](../../include/skytypes.h). +#### Instances of `time.Time` + +Instances of `time.Time` will be formatted as RFC3339 strings before crossing API boundaries. + +#### Interface types + +At present there is limited support for functions with arguments +of interface types or collections of such types. + +#### Callback functions + +Given the fact that most widely used C language toolchains have no support for +function closures, signatures of API functions with callback parameters differ +from the originals in that they include an additional `void *` parameter +callers can use to supply context information. The very same pointer is passed +in to the callback function itself in a similar manner. + ### Memory management Caller is responsible for allocating memory for objects meant to be @@ -99,6 +120,26 @@ invocation. The caller will be responsible for [reallocating another memory buffer](http://en.cppreference.com/w/c/memory/realloc) using a higher `cap` and retry. +#### Memory handles + +Complex objects represent a challenge to proper memory management, +especially when mutable values move across API boundaries. Hence some objects +always remain managed by `libskycoin` C API. Client applications can refer +to them using memory handles created by multiple functions distributed all over +the API. The memory associated to these objects remains allocated until +`SKY_handle_close` API function is applied upon the corresponding handle +value. + +Opening and closing handles can lead to memory leaks under certain circumstances, +including but not limited to nested scopes, and recursive function calls. +In order to cope with this, the API provides the means to duplicate references to +the same complex object by applying `SKY_handle_copy` function upon an existing +(valid) handle pointing at the object. There are no copy semantics involved for +the object. After the call a new handle reference is created pointing at the same +object referred to by the original handle value. The target will remain allocated +in memory (at least) until all open handles pointing at it will be closed by +invoking `SKY_handle_close` API function. + ## Generating documentation Follow these steps to generate API documentation. diff --git a/lib/cgo/api.client.go b/lib/cgo/api.client.go new file mode 100644 index 0000000000..bc9f4c8ad8 --- /dev/null +++ b/lib/cgo/api.client.go @@ -0,0 +1,800 @@ +package main + +import ( + "reflect" + "strings" + "unsafe" + + api "github.com/skycoin/skycoin/src/api" + daemon "github.com/skycoin/skycoin/src/daemon" +) + +/* + + #include + #include + + #include "skytypes.h" +*/ +import "C" + +//export SKY_api_NewClient +func SKY_api_NewClient(_addr string, _arg1 *C.Client__Handle) (____error_code uint32) { + addr := _addr + __arg1 := api.NewClient(addr) + *_arg1 = registerClientHandle(__arg1) + return +} + +//export SKY_api_Client_CSRF +func SKY_api_Client_CSRF(_c C.Client__Handle, _arg0 *C.GoString_) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + __arg0, ____return_err := c.CSRF() + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + copyString(__arg0, _arg0) + } + return +} + +//export SKY_api_Client_Version +func SKY_api_Client_Version(_c C.Client__Handle, _arg0 *C.Handle) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + __arg0, ____return_err := c.Version() + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg0 = registerBuildInfoHandle(__arg0) + } + return +} + +//export SKY_api_Client_Outputs +func SKY_api_Client_Outputs(_c C.Client__Handle, _arg0 *C.Handle) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + __arg0, ____return_err := c.Outputs() + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg0 = registerHandle(__arg0) + } + return +} + +//export SKY_api_Client_OutputsForAddresses +func SKY_api_Client_OutputsForAddresses(_c C.Client__Handle, _addrs []string, _arg1 *C.Handle) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + addrs := *(*[]string)(unsafe.Pointer(&_addrs)) + __arg1, ____return_err := c.OutputsForAddresses(addrs) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg1 = registerHandle(__arg1) + } + return +} + +//export SKY_api_Client_OutputsForHashes +func SKY_api_Client_OutputsForHashes(_c C.Client__Handle, _hashes []string, _arg1 *C.Handle) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + hashes := *(*[]string)(unsafe.Pointer(&_hashes)) + __arg1, ____return_err := c.OutputsForHashes(hashes) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg1 = registerHandle(__arg1) + } + return +} + +//export SKY_api_Client_CoinSupply +func SKY_api_Client_CoinSupply(_c C.Client__Handle, _arg0 *C.Handle) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + __arg0, ____return_err := c.CoinSupply() + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg0 = registerHandle(__arg0) + } + return +} + +//export SKY_api_Client_BlockByHash +func SKY_api_Client_BlockByHash(_c C.Client__Handle, _hash string, _arg1 *C.Handle) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + hash := _hash + __arg1, ____return_err := c.BlockByHash(hash) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg1 = registerHandle(__arg1) + } + return +} + +//export SKY_api_Client_BlockBySeq +func SKY_api_Client_BlockBySeq(_c C.Client__Handle, _seq uint64, _arg1 *C.Handle) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + seq := _seq + __arg1, ____return_err := c.BlockBySeq(seq) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg1 = registerHandle(__arg1) + } + return +} + +//export SKY_api_Client_Blocks +func SKY_api_Client_Blocks(_c C.Client__Handle, _seqs []uint64, _arg1 *C.Handle) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + __arg1, ____return_err := c.Blocks(_seqs) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg1 = registerHandle(__arg1) + } + return +} + +//export SKY_api_Client_LastBlocks +func SKY_api_Client_LastBlocks(_c C.Client__Handle, _n uint64, _arg1 *C.Handle) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + n := _n + __arg1, ____return_err := c.LastBlocks(n) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg1 = registerHandle(__arg1) + } + return +} + +//export SKY_api_Client_BlockchainMetadata +func SKY_api_Client_BlockchainMetadata(_c C.Client__Handle, _arg0 *C.Handle) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + __arg0, ____return_err := c.BlockchainMetadata() + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg0 = registerHandle(__arg0) + } + return +} + +//export SKY_api_Client_BlockchainProgress +func SKY_api_Client_BlockchainProgress(_c C.Client__Handle, _arg0 *C.Handle) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + __arg0, ____return_err := c.BlockchainProgress() + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg0 = registerHandle(__arg0) + } + return +} + +//export SKY_api_Client_Balance +func SKY_api_Client_Balance(_c C.Client__Handle, _addrs []string, _arg1 *C.wallet__BalancePair) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + addrs := *(*[]string)(unsafe.Pointer(&_addrs)) + __arg1, ____return_err := c.Balance(addrs) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg1 = *(*C.wallet__BalancePair)(unsafe.Pointer(__arg1)) + } + return +} + +//export SKY_api_Client_UxOut +func SKY_api_Client_UxOut(_c C.Client__Handle, _uxID string, _arg1 *C.Handle) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + uxID := _uxID + __arg1, ____return_err := c.UxOut(uxID) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg1 = registerHandle(__arg1) + } + return +} + +//export SKY_api_Client_AddressUxOuts +func SKY_api_Client_AddressUxOuts(_c C.Client__Handle, _addr string, _arg1 *C.Handle) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + addr := _addr + __arg1, ____return_err := c.AddressUxOuts(addr) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg1 = registerHandle(__arg1) + } + return +} + +//export SKY_api_Client_Wallet +func SKY_api_Client_Wallet(_c C.Client__Handle, _id string, _arg1 *C.WalletResponse__Handle) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + id := _id + __arg1, ____return_err := c.Wallet(id) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg1 = registerWalletResponseHandle(__arg1) + } + return +} + +//export SKY_api_Client_Wallets +func SKY_api_Client_Wallets(_c C.Client__Handle, _arg0 *C.Wallets__Handle) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + __arg0, ____return_err := c.Wallets() + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg0 = registerWalletsHandle(&__arg0) + } + return +} + +//export SKY_api_Client_CreateUnencryptedWallet +func SKY_api_Client_CreateUnencryptedWallet(_c C.Client__Handle, _seed, _label string, _scanN int, _arg2 *C.WalletResponse__Handle) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + seed := _seed + label := _label + scanN := _scanN + __arg2, ____return_err := c.CreateUnencryptedWallet(seed, label, scanN) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg2 = registerWalletResponseHandle(__arg2) + } + return +} + +//export SKY_api_Client_CreateEncryptedWallet +func SKY_api_Client_CreateEncryptedWallet(_c C.Client__Handle, _seed, _label, _password string, _scanN int, _arg2 *C.WalletResponse__Handle) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + seed := _seed + label := _label + password := _password + scanN := _scanN + __arg2, ____return_err := c.CreateEncryptedWallet(seed, label, password, scanN) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg2 = registerWalletResponseHandle(__arg2) + } + return +} + +//export SKY_api_Client_NewWalletAddress +func SKY_api_Client_NewWalletAddress(_c C.Client__Handle, _id string, _n int, _password string, _arg3 *C.Strings__Handle) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + id := _id + n := _n + password := _password + __arg3, ____return_err := c.NewWalletAddress(id, n, password) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg3 = (C.Strings__Handle)(registerHandle(__arg3)) + } + return +} + +//export SKY_api_Client_WalletBalance +func SKY_api_Client_WalletBalance(_c C.Client__Handle, _id string, _arg1 *C.wallet__BalancePair) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + id := _id + __arg1, ____return_err := c.WalletBalance(id) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg1 = *(*C.wallet__BalancePair)(unsafe.Pointer(__arg1)) + } + return +} + +//export SKY_api_Client_Spend +func SKY_api_Client_Spend(_c C.Client__Handle, _id, _dst string, _coins uint64, _password string, _arg3 *C.SpendResult_Handle) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + id := _id + dst := _dst + coins := _coins + password := _password + __arg3, ____return_err := c.Spend(id, dst, coins, password) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg3 = registerSpendResultHandle(__arg3) + } + return +} + +//export SKY_api_Client_CreateTransaction +func SKY_api_Client_CreateTransaction(_c C.Client__Handle, _req *C.Handle, _arg1 *C.CreateTransactionResponse__Handle) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + req, okreq := lookupCreateTransactionRequestHandle(C.CreateTransactionRequest__Handle(*_req)) + if !okreq { + ____error_code = SKY_BAD_HANDLE + return + } + __arg1, ____return_err := c.CreateTransaction(*req) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg1 = registerCreateTransactionResponseHandle(__arg1) + } + return +} + +//export SKY_api_Client_UpdateWallet +func SKY_api_Client_UpdateWallet(_c C.Client__Handle, _id, _label string) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + id := _id + label := _label + ____return_err := c.UpdateWallet(id, label) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + } + return +} + +//export SKY_api_Client_WalletFolderName +func SKY_api_Client_WalletFolderName(_c C.Client__Handle, _arg0 *C.Handle) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + __arg0, ____return_err := c.WalletFolderName() + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg0 = registerHandle(__arg0) + } + return +} + +//export SKY_api_Client_NewSeed +func SKY_api_Client_NewSeed(_c C.Client__Handle, _entropy int, _arg1 *C.GoString_) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + entropy := _entropy + __arg1, ____return_err := c.NewSeed(entropy) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + copyString(__arg1, _arg1) + } + return +} + +//export SKY_api_Client_WalletSeed +func SKY_api_Client_WalletSeed(_c C.Client__Handle, _id string, _password string, _arg2 *C.GoString_) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + id := _id + password := _password + __arg2, ____return_err := c.WalletSeed(id, password) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + copyString(__arg2, _arg2) + } + return +} + +//export SKY_api_Client_NetworkConnection +func SKY_api_Client_NetworkConnection(_c C.Client__Handle, _addr string, _arg1 *C.Handle) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + addr := _addr + __arg1, ____return_err := c.NetworkConnection(addr) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg1 = registerHandle(__arg1) + } + return +} + +type _networkConnectionsFilter struct { + States string + Direction string +} + +func parseNetworkConnectionsFilter(_filter *C.api__NetworkConnectionsFilter, filter *api.NetworkConnectionsFilter) { + __filter := (*_networkConnectionsFilter)(unsafe.Pointer(_filter)) + states := strings.Split(string(__filter.States), ",") + filter.States = make([]daemon.ConnectionState, len(states)) + for i, state := range states { + filter.States[i] = daemon.ConnectionState(state) + } + filter.Direction = string(__filter.Direction) +} + +//export SKY_api_Client_NetworkConnections +func SKY_api_Client_NetworkConnections(_c C.Client__Handle, _filters *C.api__NetworkConnectionsFilter, _arg0 *C.Handle) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + var filters api.NetworkConnectionsFilter + parseNetworkConnectionsFilter(_filters, &filters) + __arg0, ____return_err := c.NetworkConnections(&filters) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg0 = registerHandle(__arg0) + } + return +} + +//export SKY_api_Client_NetworkDefaultPeers +func SKY_api_Client_NetworkDefaultPeers(_c C.Client__Handle, _arg0 *C.GoSlice_) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + __arg0, ____return_err := c.NetworkDefaultPeers() + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + copyToGoSlice(reflect.ValueOf(__arg0), _arg0) + } + return +} + +//export SKY_api_Client_NetworkTrustedPeers +func SKY_api_Client_NetworkTrustedPeers(_c C.Client__Handle, _arg0 *C.GoSlice_) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + __arg0, ____return_err := c.NetworkTrustedPeers() + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + copyToGoSlice(reflect.ValueOf(__arg0), _arg0) + } + return +} + +//export SKY_api_Client_NetworkExchangedPeers +func SKY_api_Client_NetworkExchangedPeers(_c C.Client__Handle, _arg0 *C.GoSlice_) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + __arg0, ____return_err := c.NetworkExchangedPeers() + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + copyToGoSlice(reflect.ValueOf(__arg0), _arg0) + } + return +} + +//export SKY_api_Client_PendingTransactions +func SKY_api_Client_PendingTransactions(_c C.Client__Handle, _arg0 *C.Handle) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + __arg0, ____return_err := c.PendingTransactions() + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg0 = registerHandle(__arg0) + } + return +} + +//export SKY_api_Client_Transaction +func SKY_api_Client_Transaction(_c C.Client__Handle, _txid string, _arg1 *C.Handle) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + txid := _txid + __arg1, ____return_err := c.Transaction(txid) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg1 = registerHandle(__arg1) + } + return +} + +//export SKY_api_Client_Transactions +func SKY_api_Client_Transactions(_c C.Client__Handle, _addrs []string, _arg1 *C.Handle) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + addrs := *(*[]string)(unsafe.Pointer(&_addrs)) + __arg1, ____return_err := c.Transactions(addrs) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg1 = registerHandle(__arg1) + } + return +} + +//export SKY_api_Client_ConfirmedTransactions +func SKY_api_Client_ConfirmedTransactions(_c C.Client__Handle, _addrs []string, _arg1 *C.Handle) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + addrs := *(*[]string)(unsafe.Pointer(&_addrs)) + __arg1, ____return_err := c.ConfirmedTransactions(addrs) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg1 = registerHandle(__arg1) + } + return +} + +//export SKY_api_Client_UnconfirmedTransactions +func SKY_api_Client_UnconfirmedTransactions(_c C.Client__Handle, _addrs []string, _arg1 *C.Handle) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + addrs := *(*[]string)(unsafe.Pointer(&_addrs)) + __arg1, ____return_err := c.UnconfirmedTransactions(addrs) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg1 = registerHandle(__arg1) + } + return +} + +//export SKY_api_Client_InjectTransaction +func SKY_api_Client_InjectTransaction(_c C.Client__Handle, _rawTx C.Transaction__Handle, _arg1 *C.GoString_) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + rawTx, okt := lookupTransactionHandle(_rawTx) + if !okt { + ____error_code = SKY_BAD_HANDLE + return + } + + __arg1, ____return_err := c.InjectTransaction(rawTx) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + copyString(__arg1, _arg1) + } + return +} + +//export SKY_api_Client_ResendUnconfirmedTransactions +func SKY_api_Client_ResendUnconfirmedTransactions(_c C.Client__Handle, _arg0 *C.Handle) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + __arg0, ____return_err := c.ResendUnconfirmedTransactions() + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg0 = registerHandle(__arg0) + } + return +} + +//export SKY_api_Client_RawTransaction +func SKY_api_Client_RawTransaction(_c C.Client__Handle, _txid string, _arg1 *C.GoString_) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + txid := _txid + __arg1, ____return_err := c.RawTransaction(txid) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + copyString(__arg1, _arg1) + } + return +} + +//export SKY_api_Client_AddressTransactions +func SKY_api_Client_AddressTransactions(_c C.Client__Handle, _addr string, _arg1 *C.Handle) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + addr := _addr + __arg1, ____return_err := c.AddressTransactions(addr) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg1 = registerHandle(__arg1) + } + return +} + +//export SKY_api_Client_Richlist +func SKY_api_Client_Richlist(_c C.Client__Handle, _params *C.api__RichlistParams, _arg1 *C.Handle) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + params := (*api.RichlistParams)(unsafe.Pointer(_params)) + __arg1, ____return_err := c.Richlist(params) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg1 = registerHandle(__arg1) + } + return +} + +//export SKY_api_Client_AddressCount +func SKY_api_Client_AddressCount(_c C.Client__Handle, _arg0 *uint64) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + __arg0, ____return_err := c.AddressCount() + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg0 = __arg0 + } + return +} + +//export SKY_api_Client_UnloadWallet +func SKY_api_Client_UnloadWallet(_c C.Client__Handle, _id string) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + id := _id + ____return_err := c.UnloadWallet(id) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + } + return +} + +//export SKY_api_Client_Health +func SKY_api_Client_Health(_c C.Client__Handle, _arg0 *C.Handle) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + __arg0, ____return_err := c.Health() + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg0 = registerHandle(__arg0) + } + return +} + +//export SKY_api_Client_EncryptWallet +func SKY_api_Client_EncryptWallet(_c C.Client__Handle, _id string, _password string, _arg2 *C.WalletResponse__Handle) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + id := _id + password := _password + __arg2, ____return_err := c.EncryptWallet(id, password) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg2 = registerWalletResponseHandle(__arg2) + } + return +} + +//export SKY_api_Client_DecryptWallet +func SKY_api_Client_DecryptWallet(_c C.Client__Handle, _id string, _password string, _arg2 *C.WalletResponse__Handle) (____error_code uint32) { + c, okc := lookupClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + id := _id + password := _password + __arg2, ____return_err := c.DecryptWallet(id, password) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg2 = registerWalletResponseHandle(__arg2) + } + return +} diff --git a/lib/cgo/api.spend.go b/lib/cgo/api.spend.go new file mode 100644 index 0000000000..4f38bac1d8 --- /dev/null +++ b/lib/cgo/api.spend.go @@ -0,0 +1,89 @@ +package main + +import ( + "unsafe" + + api "github.com/skycoin/skycoin/src/api" + cipher "github.com/skycoin/skycoin/src/cipher" + coin "github.com/skycoin/skycoin/src/coin" + wallet "github.com/skycoin/skycoin/src/wallet" +) + +/* + + #include + #include + + #include "skytypes.h" +*/ +import "C" + +//export SKY_api_NewCreateTransactionResponse +func SKY_api_NewCreateTransactionResponse(_txn C.Transaction__Handle, _inputs []C.wallet__UxBalance, _arg2 *C.CreateTransactionResponse__Handle) (____error_code uint32) { + txn, ok := lookupTransactionHandle(_txn) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + inputs := *(*[]wallet.UxBalance)(unsafe.Pointer(&_inputs)) + __arg2, ____return_err := api.NewCreateTransactionResponse(txn, inputs) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg2 = registerCreateTransactionResponseHandle(__arg2) + } + return +} + +//export SKY_api_NewCreatedTransaction +func SKY_api_NewCreatedTransaction(_txn C.Transaction__Handle, _inputs []C.wallet__UxBalance, _arg2 *C.CreatedTransaction__Handle) (____error_code uint32) { + txn, ok := lookupTransactionHandle(_txn) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + inputs := *(*[]wallet.UxBalance)(unsafe.Pointer(&_inputs)) + __arg2, ____return_err := api.NewCreatedTransaction(txn, inputs) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg2 = registerCreatedTransactionHandle(__arg2) + } + return +} + +//export SKY_api_CreatedTransaction_ToTransaction +func SKY_api_CreatedTransaction_ToTransaction(_r C.CreatedTransaction__Handle, _arg0 *C.Transaction__Handle) (____error_code uint32) { + r, ok := lookupCreatedTransactionHandle(_r) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + __arg0, ____return_err := r.ToTransaction() + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg0 = registerTransactionHandle(__arg0) + } + return +} + +//export SKY_api_NewCreatedTransactionOutput +func SKY_api_NewCreatedTransactionOutput(_out *C.coin__TransactionOutput, _txid *C.cipher__SHA256, _arg2 *C.CreatedTransactionOutput__Handle) (____error_code uint32) { + out := *(*coin.TransactionOutput)(unsafe.Pointer(_out)) + txid := *(*cipher.SHA256)(unsafe.Pointer(_txid)) + __arg2, ____return_err := api.NewCreatedTransactionOutput(out, txid) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg2 = registerCreatedTransactionOutputHandle(__arg2) + } + return +} + +//export SKY_api_NewCreatedTransactionInput +func SKY_api_NewCreatedTransactionInput(_out *C.wallet__UxBalance, _arg1 *C.CreatedTransactionInput__Handle) (____error_code uint32) { + out := *(*wallet.UxBalance)(unsafe.Pointer(_out)) + __arg1, ____return_err := api.NewCreatedTransactionInput(out) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg1 = registerCreatedTransactionInputHandle(__arg1) + } + return +} diff --git a/lib/cgo/api.wallet.go b/lib/cgo/api.wallet.go new file mode 100644 index 0000000000..90b218dc52 --- /dev/null +++ b/lib/cgo/api.wallet.go @@ -0,0 +1,27 @@ +package main + +import api "github.com/skycoin/skycoin/src/api" + +/* + + #include + #include + + #include "skytypes.h" +*/ +import "C" + +//export SKY_api_NewWalletResponse +func SKY_api_NewWalletResponse(_w C.Wallet__Handle, _arg1 *C.WalletResponse__Handle) (____error_code uint32) { + w, okw := lookupWalletHandle(_w) + if !okw { + ____error_code = SKY_BAD_HANDLE + return + } + __arg1, ____return_err := api.NewWalletResponse(w) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg1 = registerWalletResponseHandle(__arg1) + } + return +} diff --git a/lib/cgo/api.webrpc.client.go b/lib/cgo/api.webrpc.client.go index bc2a4726ca..55ceed0caa 100644 --- a/lib/cgo/api.webrpc.client.go +++ b/lib/cgo/api.webrpc.client.go @@ -1,14 +1,18 @@ package main import ( + "reflect" + "unsafe" + webrpc "github.com/skycoin/skycoin/src/api/webrpc" ) /* + #include #include - #include "skytypes.h" + #include "skytypes.h" */ import "C" @@ -36,3 +40,119 @@ func SKY_webrpc_Client_CSRF(_c C.WebRpcClient__Handle, _arg0 *C.GoString_) (____ } return } + +//export SKY_webrpc_Client_InjectTransaction +func SKY_webrpc_Client_InjectTransaction(_c C.WebRpcClient__Handle, _tx C.Transaction__Handle, _arg1 *C.GoString_) (____error_code uint32) { + c, okc := lookupWebRpcClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + tx, ok := lookupTransactionHandle(_tx) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + __arg1, ____return_err := c.InjectTransaction(tx) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + copyString(__arg1, _arg1) + } + return +} + +//export SKY_webrpc_Client_GetStatus +func SKY_webrpc_Client_GetStatus(_c C.WebRpcClient__Handle, _arg0 *C.StatusResult_Handle) (____error_code uint32) { + c, okc := lookupWebRpcClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + __arg0, ____return_err := c.GetStatus() + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg0 = registerStatusResultHandle(__arg0) + } + return +} + +//export SKY_webrpc_Client_GetTransactionByID +func SKY_webrpc_Client_GetTransactionByID(_c C.WebRpcClient__Handle, _txid string, _arg1 *C.TransactionResult_Handle) (____error_code uint32) { + c, okc := lookupWebRpcClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + txid := _txid + __arg1, ____return_err := c.GetTransactionByID(txid) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg1 = registerTransactionResultHandle(__arg1) + } + return +} + +//export SKY_webrpc_Client_GetAddressUxOuts +func SKY_webrpc_Client_GetAddressUxOuts(_c C.WebRpcClient__Handle, _addrs []string, _arg1 *C.GoSlice_) (____error_code uint32) { + c, okc := lookupWebRpcClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + addrs := *(*[]string)(unsafe.Pointer(&_addrs)) + __arg1, ____return_err := c.GetAddressUxOuts(addrs) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + copyToGoSlice(reflect.ValueOf(__arg1), _arg1) + } + return +} + +//export SKY_webrpc_Client_GetBlocksInRange +func SKY_webrpc_Client_GetBlocksInRange(_c C.WebRpcClient__Handle, _start, _end uint64, _arg1 *C.GoSlice_) (____error_code uint32) { + c, okc := lookupWebRpcClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + start := _start + end := _end + __arg1, ____return_err := c.GetBlocksInRange(start, end) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + copyToGoSlice(reflect.ValueOf(__arg1.Blocks), _arg1) + } + return +} + +//export SKY_webrpc_Client_GetBlocksBySeq +func SKY_webrpc_Client_GetBlocksBySeq(_c C.WebRpcClient__Handle, _ss []uint64, _arg1 *C.GoSlice_) (____error_code uint32) { + c, okc := lookupWebRpcClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + ss := *(*[]uint64)(unsafe.Pointer(&_ss)) + __arg1, ____return_err := c.GetBlocksBySeq(ss) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + copyToGoSlice(reflect.ValueOf(__arg1.Blocks), _arg1) + } + return +} + +//export SKY_webrpc_Client_GetLastBlocks +func SKY_webrpc_Client_GetLastBlocks(_c C.WebRpcClient__Handle, _n uint64, _arg1 *C.GoSlice_) (____error_code uint32) { + c, okc := lookupWebRpcClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + n := _n + __arg1, ____return_err := c.GetLastBlocks(n) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + copyToGoSlice(reflect.ValueOf(__arg1.Blocks), _arg1) + } + return +} diff --git a/lib/cgo/cipher.address.go b/lib/cgo/cipher.address.go index e293e94457..60a2880b4d 100644 --- a/lib/cgo/cipher.address.go +++ b/lib/cgo/cipher.address.go @@ -1,24 +1,19 @@ package main -/* -#include -#include - -#include "skytypes.h" - -*/ -import "C" - import ( "reflect" "unsafe" - "github.com/skycoin/skycoin/src/cipher" + cipher "github.com/skycoin/skycoin/src/cipher" ) -/** - * Functions in github.com/skycoin/skycoin/src/cipher/address.go - */ +/* + +#include +#include +#include "skytypes.h" +*/ +import "C" //export SKY_cipher_DecodeBase58Address func SKY_cipher_DecodeBase58Address(_addr string, _arg1 *C.cipher__Address) (____error_code uint32) { @@ -61,6 +56,15 @@ func SKY_cipher_AddressFromSecKey(_secKey *C.cipher__SecKey, _arg1 *C.cipher__Ad return } +//export SKY_cipher_Address_Null +func SKY_cipher_Address_Null(_addr *C.cipher__Address, _arg0 *bool) (____error_code uint32) { + + addr := *inplaceAddress(_addr) + __arg0 := addr.Null() + *_arg0 = __arg0 + return +} + //export SKY_cipher_Address_Bytes func SKY_cipher_Address_Bytes(_addr *C.cipher__Address, _arg0 *C.GoSlice_) (____error_code uint32) { addr := (*cipher.Address)(unsafe.Pointer(_addr)) diff --git a/lib/cgo/cipher.base58.base58.go b/lib/cgo/cipher.base58.base58.go new file mode 100644 index 0000000000..2e7b1c8186 --- /dev/null +++ b/lib/cgo/cipher.base58.base58.go @@ -0,0 +1,117 @@ +package main + +import ( + "encoding/hex" + "reflect" + "unsafe" + + base58 "github.com/skycoin/skycoin/src/cipher/base58" +) + +/* + + #include + #include + + #include "skytypes.h" +*/ +import "C" + +//export SKY_base58_String2Hex +func SKY_base58_String2Hex(_s string, _arg1 *C.GoSlice_) (____error_code uint32) { + s := _s + __arg1, ____return_err := hex.DecodeString(s) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + copyToGoSlice(reflect.ValueOf(__arg1), _arg1) + } + + return +} + +//export SKY_base58_Base58_ToInt +func SKY_base58_Base58_ToInt(_b string, _arg0 *int) (____error_code uint32) { + b := (base58.Base58)(_b) + __arg0, ____return_err := b.ToInt() + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg0 = __arg0 + } + return +} + +//export SKY_base58_Base58_ToHex +func SKY_base58_Base58_ToHex(_b string, _arg0 *C.GoSlice_) (____error_code uint32) { + b := (base58.Base58)(_b) + __arg0, ____return_err := b.ToHex() + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + copyToGoSlice(reflect.ValueOf(__arg0), _arg0) + } + return +} + +//export SKY_base58_Base58_Base582Int +func SKY_base58_Base58_Base582Int(_b string, _arg0 *int) (____error_code uint32) { + b := (base58.Base58)(_b) + __arg0, ____return_err := b.Base582Int() + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg0 = __arg0 + } + return +} + +//export SKY_base58_Base582Hex +func SKY_base58_Base582Hex(_b string, _arg1 *C.GoSlice_) (____error_code uint32) { + b := _b + __arg1, ____return_err := base58.Base582Hex(b) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + copyToGoSlice(reflect.ValueOf(__arg1), _arg1) + } + return +} + +//export SKY_base58_Base58_BitHex +func SKY_base58_Base58_BitHex(_b string, _arg0 *C.GoSlice_) (____error_code uint32) { + b := (base58.Base58)(_b) + __arg0, ____return_err := b.BitHex() + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + copyToGoSlice(reflect.ValueOf(__arg0), _arg0) + } + return +} + +//export SKY_base58_Int2Base58 +func SKY_base58_Int2Base58(_val int, _arg1 *C.GoString_) (____error_code uint32) { + val := _val + __arg1 := base58.Int2Base58(val) + copyString(string(__arg1), _arg1) + return +} + +//export SKY_base58_Hex2Base58 +func SKY_base58_Hex2Base58(_val []byte, _arg1 *C.GoString_) (____error_code uint32) { + val := *(*[]byte)(unsafe.Pointer(&_val)) + __arg1 := base58.Hex2Base58(val) + copyString(string(__arg1), _arg1) + return +} + +//export SKY_base58_Hex2Base58String +func SKY_base58_Hex2Base58String(_val []byte, _arg1 *C.GoString_) (____error_code uint32) { + val := *(*[]byte)(unsafe.Pointer(&_val)) + __arg1 := base58.Hex2Base58String(val) + copyString(__arg1, _arg1) + return +} + +//export SKY_base58_Hex2Base58Str +func SKY_base58_Hex2Base58Str(_val []byte, _arg1 *C.GoString_) (____error_code uint32) { + val := *(*[]byte)(unsafe.Pointer(&_val)) + __arg1 := base58.Hex2Base58Str(val) + copyString(__arg1, _arg1) + return +} diff --git a/lib/cgo/cipher.crypto.go b/lib/cgo/cipher.crypto.go index 6ee5515710..dfd6468a73 100644 --- a/lib/cgo/cipher.crypto.go +++ b/lib/cgo/cipher.crypto.go @@ -1,10 +1,10 @@ package main import ( - cipher "github.com/skycoin/skycoin/src/cipher" - "reflect" "unsafe" + + cipher "github.com/skycoin/skycoin/src/cipher" ) /* @@ -67,6 +67,7 @@ func SKY_cipher_PubKeyFromSig(_sig *C.cipher__Sig, _hash *C.cipher__SHA256, _arg errcode := libErrorCode(err) if err == nil { copyToBuffer(reflect.ValueOf(pubkey[:]), unsafe.Pointer(_arg2), uint(SizeofPubKey)) + } ____error_code = errcode return @@ -87,7 +88,7 @@ func SKY_cipher_PubKey_Hex(_pk *C.cipher__PubKey, _arg1 *C.GoString_) (____error pk := (*cipher.PubKey)(unsafe.Pointer(_pk)) s := pk.Hex() copyString(s, _arg1) - return + return SKY_OK } //export SKY_cipher_PubKeyRipemd160 @@ -154,7 +155,6 @@ func SKY_cipher_NewSig(_b []byte, _arg1 *C.cipher__Sig) (____error_code uint32) if err == nil { copyToBuffer(reflect.ValueOf(s[:]), unsafe.Pointer(_arg1), uint(SizeofSig)) } - ____error_code = libErrorCode(err) return } @@ -189,13 +189,13 @@ func SKY_cipher_SignHash(_hash *C.cipher__SHA256, _sec *C.cipher__SecKey, _arg2 return } -//export SKY_cipher_ChkSig -func SKY_cipher_ChkSig(_address *C.cipher__Address, _hash *C.cipher__SHA256, _sig *C.cipher__Sig) (____error_code uint32) { +//export SKY_cipher_VerifyAddressSignedHash +func SKY_cipher_VerifyAddressSignedHash(_address *C.cipher__Address, _sig *C.cipher__Sig, _hash *C.cipher__SHA256) (____error_code uint32) { address := inplaceAddress(_address) hash := (*cipher.SHA256)(unsafe.Pointer(_hash)) sig := (*cipher.Sig)(unsafe.Pointer(_sig)) - err := cipher.ChkSig(*address, *hash, *sig) + err := cipher.VerifyAddressSignedHash(*address, *sig, *hash) ____error_code = libErrorCode(err) return } @@ -210,13 +210,13 @@ func SKY_cipher_VerifySignedHash(_sig *C.cipher__Sig, _hash *C.cipher__SHA256) ( return } -//export SKY_cipher_VerifySignature -func SKY_cipher_VerifySignature(_pubkey *C.cipher__PubKey, _sig *C.cipher__Sig, _hash *C.cipher__SHA256) (____error_code uint32) { +//export SKY_cipher_VerifyPubKeySignedHash +func SKY_cipher_VerifyPubKeySignedHash(_pubkey *C.cipher__PubKey, _sig *C.cipher__Sig, _hash *C.cipher__SHA256) (____error_code uint32) { pubkey := (*cipher.PubKey)(unsafe.Pointer(_pubkey)) sig := (*cipher.Sig)(unsafe.Pointer(_sig)) hash := (*cipher.SHA256)(unsafe.Pointer(_hash)) - err := cipher.VerifySignature(*pubkey, *sig, *hash) + err := cipher.VerifyPubKeySignedHash(*pubkey, *sig, *hash) ____error_code = libErrorCode(err) return } diff --git a/lib/cgo/cipher.encrypt.scrypt_chacha20poly1305.go b/lib/cgo/cipher.encrypt.scrypt_chacha20poly1305.go new file mode 100644 index 0000000000..26195ec61e --- /dev/null +++ b/lib/cgo/cipher.encrypt.scrypt_chacha20poly1305.go @@ -0,0 +1,43 @@ +package main + +import ( + "reflect" + "unsafe" + + encrypt "github.com/skycoin/skycoin/src/cipher/encrypt" +) + +/* + + #include + #include + + #include "skytypes.h" +*/ +import "C" + +//export SKY_encrypt_ScryptChacha20poly1305_Encrypt +func SKY_encrypt_ScryptChacha20poly1305_Encrypt(_s *C.encrypt__ScryptChacha20poly1305, _data []byte, _password []byte, _arg1 *C.GoSlice_) (____error_code uint32) { + s := *(*encrypt.ScryptChacha20poly1305)(unsafe.Pointer(_s)) + data := *(*[]byte)(unsafe.Pointer(&_data)) + password := *(*[]byte)(unsafe.Pointer(&_password)) + __arg1, ____return_err := s.Encrypt(data, password) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + copyToGoSlice(reflect.ValueOf(__arg1), _arg1) + } + return +} + +//export SKY_encrypt_ScryptChacha20poly1305_Decrypt +func SKY_encrypt_ScryptChacha20poly1305_Decrypt(_s *C.encrypt__ScryptChacha20poly1305, _data []byte, _password []byte, _arg1 *C.GoSlice_) (____error_code uint32) { + s := *(*encrypt.ScryptChacha20poly1305)(unsafe.Pointer(_s)) + data := *(*[]byte)(unsafe.Pointer(&_data)) + password := *(*[]byte)(unsafe.Pointer(&_password)) + __arg1, ____return_err := s.Decrypt(data, password) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + copyToGoSlice(reflect.ValueOf(__arg1), _arg1) + } + return +} diff --git a/lib/cgo/cipher.go-bip39.bip39.go b/lib/cgo/cipher.go-bip39.bip39.go new file mode 100644 index 0000000000..a0a88c00ce --- /dev/null +++ b/lib/cgo/cipher.go-bip39.bip39.go @@ -0,0 +1,68 @@ +package main + +import ( + "reflect" + "unsafe" + + gobip39 "github.com/skycoin/skycoin/src/cipher/go-bip39" +) + +/* + + #include + #include + + #include "skytypes.h" +*/ +import "C" + +//export SKY_bip39_NewDefaultMnemomic +func SKY_bip39_NewDefaultMnemomic(_arg0 *C.GoString_) (____error_code uint32) { + __arg0, ____return_err := gobip39.NewDefaultMnemonic() + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + copyString(__arg0, _arg0) + } + return +} + +//export SKY_bip39_NewEntropy +func SKY_bip39_NewEntropy(_bitSize int, _arg1 *C.GoSlice_) (____error_code uint32) { + bitSize := _bitSize + __arg1, ____return_err := gobip39.NewEntropy(bitSize) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + copyToGoSlice(reflect.ValueOf(__arg1), _arg1) + } + return +} + +//export SKY_bip39_NewMnemonic +func SKY_bip39_NewMnemonic(_entropy []byte, _arg1 *C.GoString_) (____error_code uint32) { + entropy := *(*[]byte)(unsafe.Pointer(&_entropy)) + __arg1, ____return_err := gobip39.NewMnemonic(entropy) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + copyString(__arg1, _arg1) + } + return +} + +//export SKY_bip39_MnemonicToByteArray +func SKY_bip39_MnemonicToByteArray(_mnemonic string, _arg1 *C.GoSlice_) (____error_code uint32) { + mnemonic := _mnemonic + __arg1, ____return_err := gobip39.MnemonicToByteArray(mnemonic) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + copyToGoSlice(reflect.ValueOf(__arg1), _arg1) + } + return +} + +//export SKY_bip39_IsMnemonicValid +func SKY_bip39_IsMnemonicValid(_mnemonic string, _arg1 *bool) (____error_code uint32) { + mnemonic := _mnemonic + __arg1 := gobip39.IsMnemonicValid(mnemonic) + *_arg1 = __arg1 + return +} diff --git a/lib/cgo/cipher.hash.go b/lib/cgo/cipher.hash.go index b646e07adf..3509e2e155 100644 --- a/lib/cgo/cipher.hash.go +++ b/lib/cgo/cipher.hash.go @@ -1,10 +1,10 @@ package main import ( - cipher "github.com/skycoin/skycoin/src/cipher" - "reflect" "unsafe" + + cipher "github.com/skycoin/skycoin/src/cipher" ) /* @@ -101,3 +101,10 @@ func SKY_cipher_Merkle(_h0 *[]C.cipher__SHA256, _arg1 *C.cipher__SHA256) (____er copyToBuffer(reflect.ValueOf(h[:]), unsafe.Pointer(_arg1), uint(SizeofSHA256)) return } + +//export SKY_cipher_SHA256_Null +func SKY_cipher_SHA256_Null(_g *C.cipher__SHA256, _arg0 *bool) (____error_code uint32) { + g := (*cipher.SHA256)(unsafe.Pointer(_g)) + *_arg0 = g.Null() + return +} diff --git a/lib/cgo/cli.add_private_key.go b/lib/cgo/cli.add_private_key.go new file mode 100644 index 0000000000..2620fe7aa0 --- /dev/null +++ b/lib/cgo/cli.add_private_key.go @@ -0,0 +1,45 @@ +package main + +import ( + cli "github.com/skycoin/skycoin/src/cli" +) + +/* + + #include + #include + + #include "skytypes.h" +*/ +import "C" + +//export SKY_cli_AddPrivateKey +func SKY_cli_AddPrivateKey(_wlt C.Wallet__Handle, _key string) (____error_code uint32) { + wlt, okwlt := lookupWalletHandle(_wlt) + if !okwlt { + ____error_code = SKY_BAD_HANDLE + return + } + key := _key + ____return_err := cli.AddPrivateKey(wlt, key) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + } + return +} + +//export SKY_cli_AddPrivateKeyToFile +func SKY_cli_AddPrivateKeyToFile(_walletFile, _key string, pwd C.PasswordReader__Handle) (____error_code uint32) { + walletFile := _walletFile + key := _key + pr, okc := lookupPasswordReaderHandle(pwd) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + ____return_err := cli.AddPrivateKeyToFile(walletFile, key, *pr) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + } + return +} diff --git a/lib/cgo/cli.check_balance.go b/lib/cgo/cli.check_balance.go new file mode 100644 index 0000000000..d69ec3e57b --- /dev/null +++ b/lib/cgo/cli.check_balance.go @@ -0,0 +1,48 @@ +package main + +import ( + "unsafe" + + cli "github.com/skycoin/skycoin/src/cli" +) + +/* + + #include + #include + + #include "skytypes.h" +*/ +import "C" + +//export SKY_cli_CheckWalletBalance +func SKY_cli_CheckWalletBalance(_c C.WebRpcClient__Handle, _walletFile string, _arg2 *C.BalanceResult_Handle) (____error_code uint32) { + c, okc := lookupWebRpcClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + walletFile := _walletFile + __arg2, ____return_err := cli.CheckWalletBalance(c, walletFile) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg2 = registerBalanceResultHandle(__arg2) + } + return +} + +//export SKY_cli_GetBalanceOfAddresses +func SKY_cli_GetBalanceOfAddresses(_c C.WebRpcClient__Handle, _addrs []string, _arg2 *C.BalanceResult_Handle) (____error_code uint32) { + c, okc := lookupWebRpcClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + addrs := *(*[]string)(unsafe.Pointer(&_addrs)) + __arg2, ____return_err := cli.GetBalanceOfAddresses(c, addrs) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg2 = registerBalanceResultHandle(__arg2) + } + return +} diff --git a/lib/cgo/cli.cli.go b/lib/cgo/cli.cli.go new file mode 100644 index 0000000000..2911f82f60 --- /dev/null +++ b/lib/cgo/cli.cli.go @@ -0,0 +1,122 @@ +package main + +import ( + "reflect" + "unsafe" + + "github.com/skycoin/skycoin/src/api/webrpc" + cli "github.com/skycoin/skycoin/src/cli" +) + +/* + + #include + #include + + #include "skytypes.h" +*/ +import "C" + +//export SKY_cli_LoadConfig +func SKY_cli_LoadConfig(_arg0 *C.Config__Handle) (____error_code uint32) { + __arg0, ____return_err := cli.LoadConfig() + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg0 = registerConfigHandle(&__arg0) + } + return +} + +//export SKY_cli_Config_FullWalletPath +func SKY_cli_Config_FullWalletPath(_c C.Config__Handle, _arg0 *C.GoString_) (____error_code uint32) { + __c, okc := lookupConfigHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + c := *__c + __arg0 := c.FullWalletPath() + copyString(__arg0, _arg0) + return +} + +//export SKY_cli_Config_FullDBPath +func SKY_cli_Config_FullDBPath(_c C.Config__Handle, _arg0 *C.GoString_) (____error_code uint32) { + __c, okc := lookupConfigHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + c := *__c + __arg0 := c.FullDBPath() + copyString(__arg0, _arg0) + return +} + +//export SKY_cli_NewApp +func SKY_cli_NewApp(_cfg C.Config__Handle, _arg1 *C.App__Handle) (____error_code uint32) { + __cfg, okcfg := lookupConfigHandle(_cfg) + if !okcfg { + ____error_code = SKY_BAD_HANDLE + return + } + cfg := *__cfg + __arg1, ____return_err := cli.NewApp(cfg) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg1 = registerAppHandle(__arg1) + } + return +} + +//export SKY_cli_RPCClientFromContext +func SKY_cli_RPCClientFromContext(_c C.Context__Handle, _arg1 *C.WebRpcClient__Handle) (____error_code uint32) { + c, okc := lookupContextHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + webrpcClient := c.App.Metadata["rpc"].(*webrpc.Client) + *_arg1 = registerWebRpcClientHandle(webrpcClient) + return +} + +//export SKY_cli_ConfigFromContext +func SKY_cli_ConfigFromContext(_c C.Context__Handle, _arg1 *C.Config__Handle) (____error_code uint32) { + c, okc := lookupContextHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + config := c.App.Metadata["config"].(cli.Config) + *_arg1 = registerConfigHandle(&config) + return +} + +func SKY_cli_NewPasswordReader(_password []byte, passwordReader *C.PasswordReader__Handle) { + password := *(*[]byte)(unsafe.Pointer(&_password)) + pr := cli.NewPasswordReader(password) + *passwordReader = registerPasswordReaderHandle(&pr) +} + +//export SKY_cli_PasswordFromBytes_Password +func SKY_cli_PasswordFromBytes_Password(_p *C.cli__PasswordFromBytes, _arg0 *C.GoSlice_) (____error_code uint32) { + p := *(*cli.PasswordFromBytes)(unsafe.Pointer(_p)) + __arg0, ____return_err := p.Password() + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + copyToGoSlice(reflect.ValueOf(__arg0), _arg0) + } + return +} + +//export SKY_cli_PasswordFromTerm_Password +func SKY_cli_PasswordFromTerm_Password(_arg0 *C.GoSlice_) (____error_code uint32) { + p := cli.PasswordFromTerm{} + __arg0, ____return_err := p.Password() + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + copyToGoSlice(reflect.ValueOf(__arg0), _arg0) + } + return +} diff --git a/lib/cgo/cli.create_rawtx.go b/lib/cgo/cli.create_rawtx.go index 19211b82c0..66d664ceda 100644 --- a/lib/cgo/cli.create_rawtx.go +++ b/lib/cgo/cli.create_rawtx.go @@ -14,31 +14,36 @@ import ( #include #include - #include "../../include/skytypes.h" + #include "skytypes.h" */ import "C" //export SKY_cli_CreateRawTxFromWallet -func SKY_cli_CreateRawTxFromWallet(_c C.WebRpcClient__Handle, _walletFile, _chgAddr string, _toAddrs []C.cli__SendAmount, pwd string, _arg4 *C.coin__Transaction) (____error_code uint32) { +func SKY_cli_CreateRawTxFromWallet(_c C.WebRpcClient__Handle, _walletFile, _chgAddr string, _toAddrs []C.cli__SendAmount, pwd C.PasswordReader__Handle, _arg4 *C.Transaction__Handle) (____error_code uint32) { c, okc := lookupWebRpcClientHandle(_c) if !okc { ____error_code = SKY_BAD_HANDLE return } + walletFile := _walletFile chgAddr := _chgAddr toAddrs := *(*[]cli.SendAmount)(unsafe.Pointer(&_toAddrs)) - pr := cli.NewPasswordReader([]byte(pwd)) - __arg4, ____return_err := cli.CreateRawTxFromWallet(c, walletFile, chgAddr, toAddrs, pr) + pr, okp := lookupPasswordReaderHandle(pwd) + if !okp { + ____error_code = SKY_BAD_HANDLE + return + } + __arg4, ____return_err := cli.CreateRawTxFromWallet(c, walletFile, chgAddr, toAddrs, *pr) ____error_code = libErrorCode(____return_err) if ____return_err == nil { - *_arg4 = *(*C.coin__Transaction)(unsafe.Pointer(__arg4)) + *_arg4 = registerTransactionHandle(__arg4) } return } //export SKY_cli_CreateRawTxFromAddress -func SKY_cli_CreateRawTxFromAddress(_c C.WebRpcClient__Handle, _addr, _walletFile, _chgAddr string, _toAddrs []C.cli__SendAmount, pwd string, _arg4 *C.coin__Transaction) (____error_code uint32) { +func SKY_cli_CreateRawTxFromAddress(_c C.WebRpcClient__Handle, _addr, _walletFile, _chgAddr string, _toAddrs []C.cli__SendAmount, pwd C.PasswordReader__Handle, _arg4 *C.Transaction__Handle) (____error_code uint32) { c, okc := lookupWebRpcClientHandle(_c) if !okc { ____error_code = SKY_BAD_HANDLE @@ -48,17 +53,21 @@ func SKY_cli_CreateRawTxFromAddress(_c C.WebRpcClient__Handle, _addr, _walletFil walletFile := _walletFile chgAddr := _chgAddr toAddrs := *(*[]cli.SendAmount)(unsafe.Pointer(&_toAddrs)) - pr := cli.NewPasswordReader([]byte(pwd)) - __arg4, ____return_err := cli.CreateRawTxFromAddress(c, addr, walletFile, chgAddr, toAddrs, pr) + pr, okp := lookupPasswordReaderHandle(pwd) + if !okp { + ____error_code = SKY_BAD_HANDLE + return + } + __arg4, ____return_err := cli.CreateRawTxFromAddress(c, addr, walletFile, chgAddr, toAddrs, *pr) ____error_code = libErrorCode(____return_err) if ____return_err == nil { - *_arg4 = *(*C.coin__Transaction)(unsafe.Pointer(__arg4)) + *_arg4 = registerTransactionHandle(__arg4) } return } //export SKY_cli_CreateRawTx -func SKY_cli_CreateRawTx(_c C.WebRpcClient__Handle, _wlt C.Wallet__Handle, _inAddrs []string, _chgAddr string, _toAddrs []C.cli__SendAmount, _password []byte, _arg6 *C.coin__Transaction) (____error_code uint32) { +func SKY_cli_CreateRawTx(_c C.WebRpcClient__Handle, _wlt C.Wallet__Handle, _inAddrs []string, _chgAddr string, _toAddrs []C.cli__SendAmount, _password []byte, _arg6 *C.Transaction__Handle) (____error_code uint32) { c, okc := lookupWebRpcClientHandle(_c) if !okc { ____error_code = SKY_BAD_HANDLE @@ -77,17 +86,17 @@ func SKY_cli_CreateRawTx(_c C.WebRpcClient__Handle, _wlt C.Wallet__Handle, _inAd __arg6, ____return_err := cli.CreateRawTx(c, wlt, inAddrs, chgAddr, toAddrs, password) ____error_code = libErrorCode(____return_err) if ____return_err == nil { - *_arg6 = *(*C.coin__Transaction)(unsafe.Pointer(__arg6)) + *_arg6 = registerTransactionHandle(__arg6) } return } //export SKY_cli_NewTransaction -func SKY_cli_NewTransaction(_utxos []C.wallet__UxBalance, _keys []C.cipher__SecKey, _outs []C.coin__TransactionOutput, _arg3 *C.coin__Transaction) (____error_code uint32) { +func SKY_cli_NewTransaction(_utxos []C.wallet__UxBalance, _keys []C.cipher__SecKey, _outs []C.coin__TransactionOutput, _arg3 *C.Transaction__Handle) (____error_code uint32) { utxos := *(*[]wallet.UxBalance)(unsafe.Pointer(&_utxos)) keys := *(*[]cipher.SecKey)(unsafe.Pointer(&_keys)) outs := *(*[]coin.TransactionOutput)(unsafe.Pointer(&_outs)) __arg3 := cli.NewTransaction(utxos, keys, outs) - *_arg3 = *(*C.coin__Transaction)(unsafe.Pointer(__arg3)) + *_arg3 = registerTransactionHandle(__arg3) return } diff --git a/lib/cgo/cli.generate_addrs.go b/lib/cgo/cli.generate_addrs.go new file mode 100644 index 0000000000..da13765e61 --- /dev/null +++ b/lib/cgo/cli.generate_addrs.go @@ -0,0 +1,65 @@ +package main + +import ( + "reflect" + "unsafe" + + cipher "github.com/skycoin/skycoin/src/cipher" + cli "github.com/skycoin/skycoin/src/cli" +) + +/* + + #include + #include + + #include "skytypes.h" +*/ +import "C" + +//export SKY_cli_GenerateAddressesInFile +func SKY_cli_GenerateAddressesInFile(_walletFile string, _num uint64, pwd C.PasswordReader__Handle, _arg3 *C.GoSlice_) (____error_code uint32) { + walletFile := _walletFile + num := _num + pr, okc := lookupPasswordReaderHandle(pwd) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + __arg3, ____return_err := cli.GenerateAddressesInFile(walletFile, num, *pr) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + copyToGoSlice(reflect.ValueOf(__arg3), _arg3) + } + return +} + +//export SKY_cli_FormatAddressesAsJSON +func SKY_cli_FormatAddressesAsJSON(_addrs []C.cipher__Address, _arg1 *C.GoString_) (____error_code uint32) { + addrs := *(*[]cipher.Address)(unsafe.Pointer(&_addrs)) + __addrs := toAddresserArray(addrs) + __arg1, ____return_err := cli.FormatAddressesAsJSON(__addrs) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + copyString(__arg1, _arg1) + } + return +} + +//export SKY_cli_FormatAddressesAsJoinedArray +func SKY_cli_FormatAddressesAsJoinedArray(_addrs []C.cipher__Address, _arg1 *C.GoString_) (____error_code uint32) { + addrs := *(*[]cipher.Address)(unsafe.Pointer(&_addrs)) + __addrs := toAddresserArray(addrs) + __arg1 := cli.FormatAddressesAsJoinedArray(__addrs) + copyString(__arg1, _arg1) + return +} + +//export SKY_cli_AddressesToStrings +func SKY_cli_AddressesToStrings(_addrs []C.cipher__Address, _arg1 *C.GoSlice_) (____error_code uint32) { + addrs := *(*[]cipher.Address)(unsafe.Pointer(&_addrs)) + __addrs := toAddresserArray(addrs) + __arg1 := cli.AddressesToStrings(__addrs) + copyToGoSlice(reflect.ValueOf(__arg1), _arg1) + return +} diff --git a/lib/cgo/cli.generate_wallet.go b/lib/cgo/cli.generate_wallet.go new file mode 100644 index 0000000000..48714c9900 --- /dev/null +++ b/lib/cgo/cli.generate_wallet.go @@ -0,0 +1,39 @@ +package main + +import ( + cli "github.com/skycoin/skycoin/src/cli" +) + +/* + + #include + #include + + #include "skytypes.h" +*/ +import "C" + +//export SKY_cli_GenerateWallet +func SKY_cli_GenerateWallet(_walletFile string, _opts *C.Options__Handle, _numAddrs uint64, _arg3 *C.Wallet__Handle) (____error_code uint32) { + walletFile := _walletFile + __opts, okopts := lookupOptionsHandle(*_opts) + if !okopts { + ____error_code = SKY_BAD_HANDLE + return + } + opts := *__opts + numAddrs := _numAddrs + __arg3, ____return_err := cli.GenerateWallet(walletFile, opts, numAddrs) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg3 = registerWalletHandle(__arg3) + } + return +} + +//export SKY_cli_MakeAlphanumericSeed +func SKY_cli_MakeAlphanumericSeed(_arg0 *C.GoString_) (____error_code uint32) { + __arg0 := cli.MakeAlphanumericSeed() + copyString(__arg0, _arg0) + return +} diff --git a/lib/cgo/cli.outputs.go b/lib/cgo/cli.outputs.go new file mode 100644 index 0000000000..8d4ce1f66c --- /dev/null +++ b/lib/cgo/cli.outputs.go @@ -0,0 +1,50 @@ +package main + +import ( + cli "github.com/skycoin/skycoin/src/cli" +) + +/* + + #include + #include + + #include "skytypes.h" +*/ +import "C" + +//export SKY_cli_GetWalletOutputsFromFile +func SKY_cli_GetWalletOutputsFromFile(_c C.WebRpcClient__Handle, _walletFile string, _arg2 *C.ReadableUnspentOutputsSummary_Handle) (____error_code uint32) { + c, okc := lookupWebRpcClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + walletFile := _walletFile + __arg2, ____return_err := cli.GetWalletOutputsFromFile(c, walletFile) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg2 = registerReadableUnspentOutputsSummaryHandle(__arg2) + } + return +} + +//export SKY_cli_GetWalletOutputs +func SKY_cli_GetWalletOutputs(_c C.WebRpcClient__Handle, _wlt *C.Wallet__Handle, _arg2 *C.ReadableUnspentOutputsSummary_Handle) (____error_code uint32) { + c, okc := lookupWebRpcClientHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + wlt, okwlt := lookupWalletHandle(*_wlt) + if !okwlt { + ____error_code = SKY_BAD_HANDLE + return + } + __arg2, ____return_err := cli.GetWalletOutputs(c, wlt) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg2 = registerReadableUnspentOutputsSummaryHandle(__arg2) + } + return +} diff --git a/lib/cgo/cli_helper.go b/lib/cgo/cli_helper.go new file mode 100644 index 0000000000..50f3408e60 --- /dev/null +++ b/lib/cgo/cli_helper.go @@ -0,0 +1,85 @@ +package main + +import ( + "os" + + "github.com/skycoin/skycoin/src/api/webrpc" +) + +/* + + #include + #include + + #include "skytypes.h" +*/ +import "C" + +//export SKY_cli_App_Run +func SKY_cli_App_Run(_app C.App__Handle, _args string) (____error_code uint32) { + app, okapp := lookupAppHandle(_app) + if !okapp { + ____error_code = SKY_BAD_HANDLE + return + } + args := splitCliArgs(_args) + ____return_err := app.Run(args) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + } + return +} + +//export SKY_cli_Config_GetCoin +func SKY_cli_Config_GetCoin(_c C.Config__Handle, _arg0 *C.GoString_) (____error_code uint32) { + __c, okc := lookupConfigHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + c := *__c + __arg0 := c.Coin + copyString(__arg0, _arg0) + return +} + +//export SKY_cli_Config_GetRPCAddress +func SKY_cli_Config_GetRPCAddress(_c C.Config__Handle, _arg0 *C.GoString_) (____error_code uint32) { + __c, okc := lookupConfigHandle(_c) + if !okc { + ____error_code = SKY_BAD_HANDLE + return + } + c := *__c + __arg0 := c.RPCAddress + copyString(__arg0, _arg0) + return +} + +//export SKY_cli_RPCClientFromApp +func SKY_cli_RPCClientFromApp(_app C.App__Handle, _arg1 *C.WebRpcClient__Handle) (____error_code uint32) { + app, okapp := lookupAppHandle(_app) + if !okapp { + ____error_code = SKY_BAD_HANDLE + return + } + __arg1 := app.App.Metadata["rpc"].(*webrpc.Client) + *_arg1 = registerWebRpcClientHandle(__arg1) + return +} + +//export SKY_cli_Getenv +func SKY_cli_Getenv(varname string, _arg0 *C.GoString_) (____error_code uint32) { + __arg0 := os.Getenv(varname) + copyString(__arg0, _arg0) + return +} + +//export SKY_cli_Setenv +func SKY_cli_Setenv(varname string, value string) (____error_code uint32) { + ____return_err := os.Setenv(varname, value) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + } + return +} diff --git a/lib/cgo/coin.block.go b/lib/cgo/coin.block.go new file mode 100644 index 0000000000..23419f2006 --- /dev/null +++ b/lib/cgo/coin.block.go @@ -0,0 +1,331 @@ +package main + +import ( + "errors" + "reflect" + "unsafe" + + cipher "github.com/skycoin/skycoin/src/cipher" + coin "github.com/skycoin/skycoin/src/coin" +) + +/* + + #include + #include + + #include "skytypes.h" + #include "skyfee.h" +*/ +import "C" + +//export SKY_coin_NewBlock +func SKY_coin_NewBlock(_b C.Block__Handle, _currentTime uint64, _hash *C.cipher__SHA256, _txns C.Transactions__Handle, pFeeCalc *C.FeeCalculator, _arg2 *C.Block__Handle) (____error_code uint32) { + feeCalc := func(pTx *coin.Transaction) (uint64, error) { + var fee C.GoUint64_ + handle := registerTransactionHandle(pTx) + result := C.callFeeCalculator(pFeeCalc, handle, &fee) + closeHandle(Handle(handle)) + if result == SKY_OK { + return uint64(fee), nil + } else { + err := errorFromLibCode(uint32(result)) + if err == nil { + err = errors.New("Error in libskycoin fee calculator") + } + return 0, err + } + } + + b, ok := lookupBlockHandle(_b) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + hash := *(*cipher.SHA256)(unsafe.Pointer(_hash)) + txns, ok := lookupTransactionsHandle(_txns) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + __arg2, ____return_err := coin.NewBlock(*b, _currentTime, hash, *txns, feeCalc) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg2 = registerBlockHandle(__arg2) + } + return +} + +//export SKY_coin_SignedBlock_VerifySignature +func SKY_coin_SignedBlock_VerifySignature(_b *C.coin__SignedBlock, _pubkey *C.cipher__PubKey) (____error_code uint32) { + b := *(*coin.SignedBlock)(unsafe.Pointer(_b)) + pubkey := *(*cipher.PubKey)(unsafe.Pointer(_pubkey)) + ____return_err := b.VerifySignature(pubkey) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + } + return +} + +//export SKY_coin_NewGenesisBlock +func SKY_coin_NewGenesisBlock(_genesisAddr *C.cipher__Address, _genesisCoins, _timestamp uint64, _arg2 *C.Block__Handle) (____error_code uint32) { + genesisAddr := *(*cipher.Address)(unsafe.Pointer(_genesisAddr)) + genesisCoins := _genesisCoins + timestamp := _timestamp + __arg2, ____return_err := coin.NewGenesisBlock(genesisAddr, genesisCoins, timestamp) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg2 = registerBlockHandle(__arg2) + } + return +} + +//export SKY_coin_Block_HashHeader +func SKY_coin_Block_HashHeader(_b C.Block__Handle, _arg0 *C.cipher__SHA256) (____error_code uint32) { + b, ok := lookupBlockHandle(_b) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + __arg0 := b.HashHeader() + *_arg0 = *(*C.cipher__SHA256)(unsafe.Pointer(&__arg0)) + return +} + +//export SKY_coin_Block_PreHashHeader +func SKY_coin_Block_PreHashHeader(_b C.Block__Handle, _arg0 *C.cipher__SHA256) (____error_code uint32) { + b, ok := lookupBlockHandle(_b) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + __arg0 := b.PreHashHeader() + *_arg0 = *(*C.cipher__SHA256)(unsafe.Pointer(&__arg0)) + return +} + +//export SKY_coin_Block_Time +func SKY_coin_Block_Time(_b C.Block__Handle, _arg0 *uint64) (____error_code uint32) { + b, ok := lookupBlockHandle(_b) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + __arg0 := b.Time() + *_arg0 = __arg0 + return +} + +//export SKY_coin_Block_Seq +func SKY_coin_Block_Seq(_b C.Block__Handle, _arg0 *uint64) (____error_code uint32) { + b, ok := lookupBlockHandle(_b) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + __arg0 := b.Seq() + *_arg0 = __arg0 + return +} + +//export SKY_coin_Block_HashBody +func SKY_coin_Block_HashBody(_b C.Block__Handle, _arg0 *C.cipher__SHA256) (____error_code uint32) { + b, ok := lookupBlockHandle(_b) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + __arg0 := b.HashBody() + *_arg0 = *(*C.cipher__SHA256)(unsafe.Pointer(&__arg0)) + return +} + +//export SKY_coin_Block_Size +func SKY_coin_Block_Size(_b C.Block__Handle, _arg0 *int) (____error_code uint32) { + b, ok := lookupBlockHandle(_b) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + __arg0 := b.Size() + *_arg0 = __arg0 + return +} + +//export SKY_coin_Block_String +func SKY_coin_Block_String(_b C.Block__Handle, _arg0 *C.GoString_) (____error_code uint32) { + b, ok := lookupBlockHandle(_b) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + __arg0 := b.String() + copyString(__arg0, _arg0) + return +} + +//export SKY_coin_Block_GetTransaction +func SKY_coin_Block_GetTransaction(_b C.Block__Handle, _txHash *C.cipher__SHA256, _arg1 *C.Transaction__Handle, _arg2 *bool) (____error_code uint32) { + b, ok := lookupBlockHandle(_b) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + txHash := *(*cipher.SHA256)(unsafe.Pointer(_txHash)) + __arg1, __arg2 := b.GetTransaction(txHash) + *_arg1 = registerTransactionHandle(&__arg1) + *_arg2 = __arg2 + return +} + +//export SKY_coin_NewBlockHeader +func SKY_coin_NewBlockHeader(_prev *C.coin__BlockHeader, _uxHash *C.cipher__SHA256, _currentTime, _fee uint64, _body C.BlockBody__Handle, _arg4 *C.coin__BlockHeader) (____error_code uint32) { + prev := *(*coin.BlockHeader)(unsafe.Pointer(_prev)) + uxHash := *(*cipher.SHA256)(unsafe.Pointer(_uxHash)) + currentTime := _currentTime + fee := _fee + body, ok := lookupBlockBodyHandle(_body) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + __arg4 := coin.NewBlockHeader(prev, uxHash, currentTime, fee, *body) + *_arg4 = *(*C.coin__BlockHeader)(unsafe.Pointer(&__arg4)) + return +} + +//export SKY_coin_BlockHeader_Hash +func SKY_coin_BlockHeader_Hash(_bh *C.coin__BlockHeader, _arg0 *C.cipher__SHA256) (____error_code uint32) { + bh := *(*coin.BlockHeader)(unsafe.Pointer(_bh)) + __arg0 := bh.Hash() + *_arg0 = *(*C.cipher__SHA256)(unsafe.Pointer(&__arg0)) + return +} + +//export SKY_coin_BlockHeader_Bytes +func SKY_coin_BlockHeader_Bytes(_bh *C.coin__BlockHeader, _arg0 *C.GoSlice_) (____error_code uint32) { + bh := *(*coin.BlockHeader)(unsafe.Pointer(_bh)) + __arg0 := bh.Bytes() + copyToGoSlice(reflect.ValueOf(__arg0), _arg0) + return +} + +//export SKY_coin_BlockHeader_String +func SKY_coin_BlockHeader_String(_bh *C.coin__BlockHeader, _arg0 *C.GoString_) (____error_code uint32) { + bh := *(*coin.BlockHeader)(unsafe.Pointer(_bh)) + __arg0 := bh.String() + copyString(__arg0, _arg0) + return +} + +//export SKY_coin_BlockBody_Hash +func SKY_coin_BlockBody_Hash(_body C.BlockBody__Handle, _arg0 *C.cipher__SHA256) (____error_code uint32) { + body, ok := lookupBlockBodyHandle(_body) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + __arg0 := body.Hash() + *_arg0 = *(*C.cipher__SHA256)(unsafe.Pointer(&__arg0)) + return +} + +//export SKY_coin_BlockBody_Size +func SKY_coin_BlockBody_Size(_bb *C.BlockBody__Handle, _arg0 *int) (____error_code uint32) { + bb, ok := lookupBlockBodyHandle(*_bb) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + __arg0 := bb.Size() + *_arg0 = __arg0 + return +} + +//export SKY_coin_BlockBody_Bytes +func SKY_coin_BlockBody_Bytes(_bb C.BlockBody__Handle, _arg0 *C.GoSlice_) (____error_code uint32) { + bb, ok := lookupBlockBodyHandle(_bb) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + __arg0 := bb.Bytes() + copyToGoSlice(reflect.ValueOf(__arg0), _arg0) + return +} + +//export SKY_coin_CreateUnspents +func SKY_coin_CreateUnspents(_bh *C.coin__BlockHeader, _tx C.Transaction__Handle, _arg2 *C.coin__UxArray) (____error_code uint32) { + bh := *(*coin.BlockHeader)(unsafe.Pointer(_bh)) + tx, ok := lookupTransactionHandle(_tx) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + __arg2 := coin.CreateUnspents(bh, *tx) + copyToGoSlice(reflect.ValueOf(__arg2), _arg2) + return +} + +//export SKY_coin_CreateUnspent +func SKY_coin_CreateUnspent(_bh *C.coin__BlockHeader, _tx C.Transaction__Handle, _outIndex int, _arg3 *C.coin__UxOut) (____error_code uint32) { + bh := *(*coin.BlockHeader)(unsafe.Pointer(_bh)) + tx, ok := lookupTransactionHandle(_tx) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + outIndex := _outIndex + __arg3, ____return_err := coin.CreateUnspent(bh, *tx, outIndex) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg3 = *(*C.coin__UxOut)(unsafe.Pointer(&__arg3)) + } + return +} + +//export SKY_coin_GetBlockObject +func SKY_coin_GetBlockObject(_b C.Block__Handle, _p **C.coin__Block) (____error_code uint32) { + b, ok := lookupBlockHandle(_b) + if !ok { + ____error_code = SKY_BAD_HANDLE + } else { + *_p = (*C.coin__Block)(unsafe.Pointer(b)) + } + return +} + +//export SKY_coin_GetBlockBody +func SKY_coin_GetBlockBody(_b C.Block__Handle, _p *C.BlockBody__Handle) (____error_code uint32) { + b, ok := lookupBlockHandle(_b) + if !ok { + ____error_code = SKY_BAD_HANDLE + } else { + *_p = registerBlockBodyHandle(&b.Body) + } + return +} + +//export SKY_coin_NewEmptyBlock +func SKY_coin_NewEmptyBlock(_txns C.Transactions__Handle, handle *C.Block__Handle) (____error_code uint32) { + txns, ok := lookupTransactionsHandle(_txns) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + body := coin.BlockBody{ + Transactions: *txns, + } + block := coin.Block{ + Body: body, + Head: coin.BlockHeader{ + Version: 0x02, + Time: 100, + BkSeq: 0, + Fee: 10, + PrevHash: cipher.SHA256{}, + BodyHash: body.Hash(), + }} + *handle = registerBlockHandle(&block) + return +} diff --git a/lib/cgo/coin.math.go b/lib/cgo/coin.math.go new file mode 100644 index 0000000000..0e423e7342 --- /dev/null +++ b/lib/cgo/coin.math.go @@ -0,0 +1,46 @@ +package main + +import coin "github.com/skycoin/skycoin/src/coin" + +/* + + #include + #include + + #include "skytypes.h" +*/ +import "C" + +//export SKY_coin_AddUint64 +func SKY_coin_AddUint64(_a, _b uint64, _arg1 *uint64) (____error_code uint32) { + a := _a + b := _b + __arg1, ____return_err := coin.AddUint64(a, b) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg1 = __arg1 + } + return +} + +//export SKY_coin_Uint64ToInt64 +func SKY_coin_Uint64ToInt64(_a uint64, _arg1 *int64) (____error_code uint32) { + a := _a + __arg1, ____return_err := coin.Uint64ToInt64(a) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg1 = __arg1 + } + return +} + +//export SKY_coin_Int64ToUint64 +func SKY_coin_Int64ToUint64(_a int64, _arg1 *uint64) (____error_code uint32) { + a := _a + __arg1, ____return_err := coin.Int64ToUint64(a) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg1 = __arg1 + } + return +} diff --git a/lib/cgo/coin.outputs.go b/lib/cgo/coin.outputs.go index ccfdf91031..76cc0da513 100644 --- a/lib/cgo/coin.outputs.go +++ b/lib/cgo/coin.outputs.go @@ -4,10 +4,12 @@ import ( "reflect" "unsafe" + "github.com/skycoin/skycoin/src/cipher" coin "github.com/skycoin/skycoin/src/coin" ) /* + #include #include @@ -122,7 +124,7 @@ func SKY_coin_UxArray_Sub(_ua *C.coin__UxArray, _other *C.coin__UxArray, _arg1 * ua := *(*coin.UxArray)(unsafe.Pointer(_ua)) other := *(*coin.UxArray)(unsafe.Pointer(_other)) __arg1 := ua.Sub(other) - *_arg1 = *(*C.coin__UxArray)(unsafe.Pointer(&__arg1)) + copyToGoSlice(reflect.ValueOf(__arg1), _arg1) return } @@ -131,6 +133,150 @@ func SKY_coin_UxArray_Add(_ua *C.coin__UxArray, _other *C.coin__UxArray, _arg1 * ua := *(*coin.UxArray)(unsafe.Pointer(_ua)) other := *(*coin.UxArray)(unsafe.Pointer(_other)) __arg1 := ua.Add(other) - *_arg1 = *(*C.coin__UxArray)(unsafe.Pointer(&__arg1)) + copyToGoSlice(reflect.ValueOf(__arg1), _arg1) + return +} + +//export SKY_coin_NewAddressUxOuts +func SKY_coin_NewAddressUxOuts(_ua *C.coin__UxArray, _address_outs *C.AddressUxOuts_Handle) (____error_code uint32) { + ua := *(*coin.UxArray)(unsafe.Pointer(_ua)) + address_outs := coin.NewAddressUxOuts(ua) + *_address_outs = registerAddressUxOutHandle(&address_outs) + return +} + +//export SKY_coin_AddressUxOuts_Keys +func SKY_coin_AddressUxOuts_Keys(_address_outs C.AddressUxOuts_Handle, _keys *C.GoSlice_) (____error_code uint32) { + address_outs, ok := lookupAddressUxOutHandle(_address_outs) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + keys := (*address_outs).Keys() + copyToGoSlice(reflect.ValueOf(keys), _keys) + return +} + +//export SKY_coin_AddressUxOuts_Flatten +func SKY_coin_AddressUxOuts_Flatten(_address_outs C.AddressUxOuts_Handle, _ua *C.coin__UxArray) (____error_code uint32) { + address_outs, ok := lookupAddressUxOutHandle(_address_outs) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + ux := (*address_outs).Flatten() + copyToGoSlice(reflect.ValueOf(ux), _ua) + return +} + +//export SKY_coin_AddressUxOuts_Sub +func SKY_coin_AddressUxOuts_Sub(_auo1 C.AddressUxOuts_Handle, _auo2 C.AddressUxOuts_Handle, _auo_result *C.AddressUxOuts_Handle) (____error_code uint32) { + auo1, ok := lookupAddressUxOutHandle(_auo1) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + auo2, ok := lookupAddressUxOutHandle(_auo2) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + auo_result := (*auo1).Sub(*auo2) + *_auo_result = registerAddressUxOutHandle(&auo_result) + return +} + +//export SKY_coin_AddressUxOuts_Add +func SKY_coin_AddressUxOuts_Add(_auo1 C.AddressUxOuts_Handle, _auo2 C.AddressUxOuts_Handle, _auo_result *C.AddressUxOuts_Handle) (____error_code uint32) { + auo1, ok := lookupAddressUxOutHandle(_auo1) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + auo2, ok := lookupAddressUxOutHandle(_auo2) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + auo_result := (*auo1).Add(*auo2) + *_auo_result = registerAddressUxOutHandle(&auo_result) + return +} + +//export SKY_coin_AddressUxOuts_Get +func SKY_coin_AddressUxOuts_Get(handle C.AddressUxOuts_Handle, _key *C.cipher__Address, _uxOuts *C.coin__UxArray) (____error_code uint32) { + a, ok := lookupAddressUxOutHandle(handle) + if ok { + key := *(*cipher.Address)(unsafe.Pointer(_key)) + uxOuts, found := (*a)[key] + if found { + copyToGoSlice(reflect.ValueOf(uxOuts), _uxOuts) + ____error_code = SKY_OK + } + } else { + ____error_code = SKY_BAD_HANDLE + } + return +} + +//export SKY_coin_AddressUxOuts_HasKey +func SKY_coin_AddressUxOuts_HasKey(handle C.AddressUxOuts_Handle, _key *C.cipher__Address, _hasKey *bool) (____error_code uint32) { + a, ok := lookupAddressUxOutHandle(handle) + if ok { + key := *(*cipher.Address)(unsafe.Pointer(_key)) + _, found := (*a)[key] + *_hasKey = found + ____error_code = SKY_OK + } else { + ____error_code = SKY_BAD_HANDLE + } + return +} + +//export SKY_coin_AddressUxOuts_GetOutputLength +func SKY_coin_AddressUxOuts_GetOutputLength(handle C.AddressUxOuts_Handle, _key *C.cipher__Address, _length *int) (____error_code uint32) { + a, ok := lookupAddressUxOutHandle(handle) + if ok { + key := *(*cipher.Address)(unsafe.Pointer(_key)) + uxOuts, found := (*a)[key] + if found { + *_length = len(uxOuts) + ____error_code = SKY_OK + } + } else { + ____error_code = SKY_BAD_HANDLE + } + return +} + +//export SKY_coin_AddressUxOuts_Length +func SKY_coin_AddressUxOuts_Length(handle C.AddressUxOuts_Handle, _length *int) (____error_code uint32) { + a, ok := lookupAddressUxOutHandle(handle) + if ok { + *_length = len(*a) + ____error_code = SKY_OK + } else { + ____error_code = SKY_BAD_HANDLE + } + return +} + +//export SKY_coin_AddressUxOuts_Set +func SKY_coin_AddressUxOuts_Set(handle C.AddressUxOuts_Handle, _key *C.cipher__Address, _uxOuts *C.coin__UxArray) (____error_code uint32) { + a, ok := lookupAddressUxOutHandle(handle) + if ok { + key := *(*cipher.Address)(unsafe.Pointer(_key)) + //Copy the slice because it is going to be kept + //We can't hold memory allocated outside Go + tempUxOuts := *(*coin.UxArray)(unsafe.Pointer(_uxOuts)) + uxOuts := make(coin.UxArray, 0, len(tempUxOuts)) + for _, ux := range tempUxOuts { + uxOuts = append(uxOuts, ux) + } + (*a)[key] = uxOuts + ____error_code = SKY_OK + } else { + ____error_code = SKY_BAD_HANDLE + } return } diff --git a/lib/cgo/coin.transactions.go b/lib/cgo/coin.transactions.go new file mode 100644 index 0000000000..b0f5f61117 --- /dev/null +++ b/lib/cgo/coin.transactions.go @@ -0,0 +1,677 @@ +package main + +import ( + "errors" + "reflect" + "strconv" + "unsafe" + + cipher "github.com/skycoin/skycoin/src/cipher" + coin "github.com/skycoin/skycoin/src/coin" +) + +/* + + #include + #include + + #include "skytypes.h" + #include "skyfee.h" +*/ +import "C" + +//export SKY_coin_Create_Transaction +func SKY_coin_Create_Transaction(handle *C.Transaction__Handle) (____error_code uint32) { + tx := coin.Transaction{} + *handle = registerTransactionHandle(&tx) + return +} + +//export SKY_coin_Transaction_Copy +func SKY_coin_Transaction_Copy(handle C.Transaction__Handle, handle2 *C.Transaction__Handle) (____error_code uint32) { + tx, ok := lookupTransactionHandle(handle) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + ntx := coin.Transaction{} + ntx.Length = tx.Length + ntx.Type = tx.Type + ntx.InnerHash = tx.InnerHash + ntx.Sigs = make([]cipher.Sig, 0) + ntx.Sigs = append(ntx.Sigs, tx.Sigs...) + ntx.In = make([]cipher.SHA256, 0) + ntx.In = append(ntx.In, tx.In...) + ntx.Out = make([]coin.TransactionOutput, 0) + ntx.Out = append(ntx.Out, tx.Out...) + *handle2 = registerTransactionHandle(&ntx) + return +} + +//export SKY_coin_GetTransactionObject +func SKY_coin_GetTransactionObject(handle C.Transaction__Handle, _pptx **C.coin__Transaction) (____error_code uint32) { + ptx, ok := lookupTransactionHandle(handle) + if !ok { + ____error_code = SKY_BAD_HANDLE + } else { + *_pptx = (*C.coin__Transaction)(unsafe.Pointer(ptx)) + } + return +} + +//export SKY_coin_Transaction_ResetInputs +func SKY_coin_Transaction_ResetInputs(handle C.Transaction__Handle, count int) (____error_code uint32) { + txn, ok := lookupTransactionHandle(handle) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + txn.In = make([]cipher.SHA256, count) + return +} + +//export SKY_coin_Transaction_GetInputsCount +func SKY_coin_Transaction_GetInputsCount(handle C.Transaction__Handle, length *int) (____error_code uint32) { + txn, ok := lookupTransactionHandle(handle) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + *length = len(txn.In) + return +} + +//export SKY_coin_Transaction_GetInputAt +func SKY_coin_Transaction_GetInputAt(handle C.Transaction__Handle, i int, input *C.cipher__SHA256) (____error_code uint32) { + txn, ok := lookupTransactionHandle(handle) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + if i >= len(txn.In) { + ____error_code = SKY_BAD_HANDLE + return + } + *input = *(*C.cipher__SHA256)(unsafe.Pointer(&txn.In[i])) + return +} + +//export SKY_coin_Transaction_SetInputAt +func SKY_coin_Transaction_SetInputAt(handle C.Transaction__Handle, i int, input *C.cipher__SHA256) (____error_code uint32) { + txn, ok := lookupTransactionHandle(handle) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + if i >= len(txn.In) { + ____error_code = SKY_BAD_HANDLE + return + } + *(*C.cipher__SHA256)(unsafe.Pointer(&txn.In[i])) = *input + return +} + +//export SKY_coin_Transaction_GetOutputsCount +func SKY_coin_Transaction_GetOutputsCount(handle C.Transaction__Handle, length *int) (____error_code uint32) { + txn, ok := lookupTransactionHandle(handle) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + *length = len(txn.Out) + return +} + +//export SKY_coin_Transaction_GetOutputAt +func SKY_coin_Transaction_GetOutputAt(handle C.Transaction__Handle, i int, output *C.coin__TransactionOutput) (____error_code uint32) { + txn, ok := lookupTransactionHandle(handle) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + if i >= len(txn.Out) { + ____error_code = SKY_ERROR + return + } + *output = *(*C.coin__TransactionOutput)(unsafe.Pointer(&txn.Out[i])) + return +} + +//export SKY_coin_Transaction_SetOutputAt +func SKY_coin_Transaction_SetOutputAt(handle C.Transaction__Handle, i int, output *C.coin__TransactionOutput) (____error_code uint32) { + txn, ok := lookupTransactionHandle(handle) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + if i >= len(txn.Out) { + ____error_code = SKY_ERROR + return + } + *(*C.coin__TransactionOutput)(unsafe.Pointer(&txn.Out[i])) = *output + return +} + +//export SKY_coin_Transaction_GetSignaturesCount +func SKY_coin_Transaction_GetSignaturesCount(handle C.Transaction__Handle, length *int) (____error_code uint32) { + txn, ok := lookupTransactionHandle(handle) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + *length = len(txn.Sigs) + return +} + +//export SKY_coin_Transaction_GetSignatureAt +func SKY_coin_Transaction_GetSignatureAt(handle C.Transaction__Handle, i int, sig *C.cipher__Sig) (____error_code uint32) { + txn, ok := lookupTransactionHandle(handle) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + if i >= len(txn.Sigs) { + ____error_code = SKY_BAD_HANDLE + return + } + *sig = *(*C.cipher__Sig)(unsafe.Pointer(&txn.Sigs[i])) + return +} + +//export SKY_coin_Transaction_SetSignatureAt +func SKY_coin_Transaction_SetSignatureAt(handle C.Transaction__Handle, i int, sig *C.cipher__Sig) (____error_code uint32) { + txn, ok := lookupTransactionHandle(handle) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + if i >= len(txn.Sigs) { + ____error_code = SKY_BAD_HANDLE + return + } + *(*C.cipher__Sig)(unsafe.Pointer(&txn.Sigs[i])) = *sig + return +} + +//export SKY_coin_Transaction_PushSignature +func SKY_coin_Transaction_PushSignature(handle C.Transaction__Handle, _sig *C.cipher__Sig) (____error_code uint32) { + txn, ok := lookupTransactionHandle(handle) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + sig := *(*cipher.Sig)(unsafe.Pointer(_sig)) + txn.Sigs = append(txn.Sigs, sig) + return +} + +//export SKY_coin_Transaction_ResetOutputs +func SKY_coin_Transaction_ResetOutputs(handle C.Transaction__Handle, count int) (____error_code uint32) { + txn, ok := lookupTransactionHandle(handle) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + txn.Out = make([]coin.TransactionOutput, count) + return +} + +//export SKY_coin_Transaction_ResetSignatures +func SKY_coin_Transaction_ResetSignatures(handle C.Transaction__Handle, count int) (____error_code uint32) { + txn, ok := lookupTransactionHandle(handle) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + txn.Sigs = make([]cipher.Sig, count) + return +} + +//export SKY_coin_Transaction_Verify +func SKY_coin_Transaction_Verify(handle C.Transaction__Handle) (____error_code uint32) { + txn, ok := lookupTransactionHandle(handle) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + ____return_err := txn.Verify() + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + } + return +} + +//export SKY_coin_Transaction_VerifyInput +func SKY_coin_Transaction_VerifyInput(handle C.Transaction__Handle, _uxIn *C.coin__UxArray) (____error_code uint32) { + txn, ok := lookupTransactionHandle(handle) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + uxIn := *(*coin.UxArray)(unsafe.Pointer(_uxIn)) + ____return_err := txn.VerifyInput(uxIn) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + } + return +} + +//export SKY_coin_Transaction_PushInput +func SKY_coin_Transaction_PushInput(handle C.Transaction__Handle, _uxOut *C.cipher__SHA256, _arg1 *uint16) (____error_code uint32) { + txn, ok := lookupTransactionHandle(handle) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + uxOut := *(*cipher.SHA256)(unsafe.Pointer(_uxOut)) + __arg1 := txn.PushInput(uxOut) + *_arg1 = __arg1 + return +} + +//export SKY_coin_TransactionOutput_UxID +func SKY_coin_TransactionOutput_UxID(_txOut *C.coin__TransactionOutput, _txID *C.cipher__SHA256, _arg1 *C.cipher__SHA256) (____error_code uint32) { + txOut := *(*coin.TransactionOutput)(unsafe.Pointer(_txOut)) + txID := *(*cipher.SHA256)(unsafe.Pointer(_txID)) + __arg1 := txOut.UxID(txID) + *_arg1 = *(*C.cipher__SHA256)(unsafe.Pointer(&__arg1)) + return +} + +//export SKY_coin_Transaction_PushOutput +func SKY_coin_Transaction_PushOutput(handle C.Transaction__Handle, _dst *C.cipher__Address, _coins, _hours uint64) (____error_code uint32) { + txn, ok := lookupTransactionHandle(handle) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + dst := *(*cipher.Address)(unsafe.Pointer(_dst)) + coins := _coins + hours := _hours + txn.PushOutput(dst, coins, hours) + return +} + +//export SKY_coin_Transaction_SignInputs +func SKY_coin_Transaction_SignInputs(handle C.Transaction__Handle, _keys []C.cipher__SecKey) (____error_code uint32) { + txn, ok := lookupTransactionHandle(handle) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + keys := *(*[]cipher.SecKey)(unsafe.Pointer(&_keys)) + txn.SignInputs(keys) + return +} + +//export SKY_coin_Transaction_Size +func SKY_coin_Transaction_Size(handle C.Transaction__Handle, _arg0 *int) (____error_code uint32) { + txn, ok := lookupTransactionHandle(handle) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + __arg0 := txn.Size() + *_arg0 = __arg0 + return +} + +//export SKY_coin_Transaction_Hash +func SKY_coin_Transaction_Hash(handle C.Transaction__Handle, _arg0 *C.cipher__SHA256) (____error_code uint32) { + txn, ok := lookupTransactionHandle(handle) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + __arg0 := txn.Hash() + *_arg0 = *(*C.cipher__SHA256)(unsafe.Pointer(&__arg0)) + return +} + +//export SKY_coin_Transaction_SizeHash +func SKY_coin_Transaction_SizeHash(handle C.Transaction__Handle, _arg0 *int, _arg1 *C.cipher__SHA256) (____error_code uint32) { + txn, ok := lookupTransactionHandle(handle) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + __arg0, __arg1 := txn.SizeHash() + *_arg0 = __arg0 + *_arg1 = *(*C.cipher__SHA256)(unsafe.Pointer(&__arg1)) + return +} + +//export SKY_coin_Transaction_TxID +func SKY_coin_Transaction_TxID(handle C.Transaction__Handle, _arg0 *C.GoSlice_) (____error_code uint32) { + txn, ok := lookupTransactionHandle(handle) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + __arg0 := txn.TxID() + copyToGoSlice(reflect.ValueOf(__arg0), _arg0) + return +} + +//export SKY_coin_Transaction_TxIDHex +func SKY_coin_Transaction_TxIDHex(handle C.Transaction__Handle, _arg0 *C.GoString_) (____error_code uint32) { + txn, ok := lookupTransactionHandle(handle) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + __arg0 := txn.TxIDHex() + copyString(__arg0, _arg0) + return +} + +//export SKY_coin_Transaction_UpdateHeader +func SKY_coin_Transaction_UpdateHeader(handle C.Transaction__Handle) (____error_code uint32) { + txn, ok := lookupTransactionHandle(handle) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + txn.UpdateHeader() + return +} + +//export SKY_coin_Transaction_HashInner +func SKY_coin_Transaction_HashInner(handle C.Transaction__Handle, _arg0 *C.cipher__SHA256) (____error_code uint32) { + txn, ok := lookupTransactionHandle(handle) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + __arg0 := txn.HashInner() + *_arg0 = *(*C.cipher__SHA256)(unsafe.Pointer(&__arg0)) + return +} + +//export SKY_coin_Transaction_Serialize +func SKY_coin_Transaction_Serialize(handle C.Transaction__Handle, _arg0 *C.GoSlice_) (____error_code uint32) { + txn, ok := lookupTransactionHandle(handle) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + __arg0 := txn.Serialize() + copyToGoSlice(reflect.ValueOf(__arg0), _arg0) + return +} + +//export SKY_coin_TransactionDeserialize +func SKY_coin_TransactionDeserialize(_b []byte, _arg1 *C.Transaction__Handle) (____error_code uint32) { + b := *(*[]byte)(unsafe.Pointer(&_b)) + __arg1, ____return_err := coin.TransactionDeserialize(b) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg1 = registerTransactionHandle(&__arg1) + } + return +} + +//export SKY_coin_Transaction_OutputHours +func SKY_coin_Transaction_OutputHours(handle C.Transaction__Handle, _arg0 *uint64) (____error_code uint32) { + txn, ok := lookupTransactionHandle(handle) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + __arg0, ____return_err := txn.OutputHours() + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg0 = __arg0 + } + return +} + +//export SKY_coin_Create_Transactions +func SKY_coin_Create_Transactions(handle *C.Transactions__Handle) (____error_code uint32) { + txs := make(coin.Transactions, 0, 0) + *handle = registerTransactionsHandle(&txs) + return SKY_OK +} + +//export SKY_coin_GetTransactionsObject +func SKY_coin_GetTransactionsObject(handle C.Transactions__Handle, _pptx **C.coin__Transactions) (____error_code uint32) { + ptx, ok := lookupTransactionsHandle(handle) + if !ok { + ____error_code = SKY_BAD_HANDLE + } else { + *_pptx = (*C.coin__Transactions)(unsafe.Pointer(ptx)) + } + return +} + +//export SKY_coin_Transactions_Length +func SKY_coin_Transactions_Length(handle C.Transactions__Handle, _length *int) (____error_code uint32) { + txns, ok := lookupTransactionsHandle(handle) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + *_length = len(*txns) + return +} + +//export SKY_coin_Transactions_Add +func SKY_coin_Transactions_Add(tsh C.Transactions__Handle, th C.Transaction__Handle) (____error_code uint32) { + txns, ok := lookupTransactionsHandle(tsh) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + tx, okt := lookupTransactionHandle(th) + if !okt { + ____error_code = SKY_BAD_HANDLE + return + } + *txns = append(*txns, *tx) + result := overwriteHandle(tsh, txns) + if !result { + ____error_code = SKY_ERROR + } + return +} + +//export SKY_coin_Transactions_Fees +func SKY_coin_Transactions_Fees(tsh C.Transactions__Handle, pFeeCalc *C.FeeCalculator, _result *uint64) (____error_code uint32) { + feeCalc := func(pTx *coin.Transaction) (uint64, error) { + var fee C.GoUint64_ + handle := registerTransactionHandle(pTx) + result := C.callFeeCalculator(pFeeCalc, handle, &fee) + closeHandle(Handle(handle)) + if result == SKY_OK { + return uint64(fee), nil + } else { + return 0, errors.New("Error calculating fee") + } + } + txns, ok := lookupTransactionsHandle(tsh) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + result, err := txns.Fees(feeCalc) + if err != nil { + ____error_code = SKY_ERROR + } else { + *_result = result + } + return +} + +//export SKY_coin_Transactions_GetAt +func SKY_coin_Transactions_GetAt(tsh C.Transactions__Handle, n int, th *C.Transaction__Handle) (____error_code uint32) { + txns, ok := lookupTransactionsHandle(tsh) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + if n >= len(*txns) { + ____error_code = SKY_ERROR + return + } + tx := (*txns)[n] + *th = registerTransactionHandle(&tx) + return +} + +//export SKY_coin_Transactions_Hashes +func SKY_coin_Transactions_Hashes(tsh C.Transactions__Handle, _arg0 *C.GoSlice_) (____error_code uint32) { + txns, ok := lookupTransactionsHandle(tsh) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + __arg0 := txns.Hashes() + copyToGoSlice(reflect.ValueOf(__arg0), _arg0) + return +} + +//export SKY_coin_Transactions_Size +func SKY_coin_Transactions_Size(tsh C.Transactions__Handle, _arg0 *int) (____error_code uint32) { + txns, ok := lookupTransactionsHandle(tsh) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + __arg0 := txns.Size() + *_arg0 = __arg0 + return +} + +//export SKY_coin_Transactions_TruncateBytesTo +func SKY_coin_Transactions_TruncateBytesTo(tsh C.Transactions__Handle, _size int, _arg1 *C.Transactions__Handle) (____error_code uint32) { + txns, ok := lookupTransactionsHandle(tsh) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + size := _size + __arg1 := txns.TruncateBytesTo(size) + *_arg1 = registerTransactionsHandle(&__arg1) + return +} + +//export SKY_coin_SortTransactions +func SKY_coin_SortTransactions(tsh C.Transactions__Handle, pFeeCalc *C.FeeCalculator, ptsh *C.Transactions__Handle) (____error_code uint32) { + feeCalc := func(pTx *coin.Transaction) (uint64, error) { + var fee C.GoUint64_ + handle := registerTransactionHandle(pTx) + errorcode := C.callFeeCalculator(pFeeCalc, handle, &fee) + closeHandle(Handle(handle)) + if errorcode != SKY_OK { + if err, exists := codeToErrorMap[uint32(errorcode)]; exists { + return 0, err + } + return 0, errors.New("C fee calculator failed code=" + strconv.Itoa(int(errorcode))) + } + return uint64(fee), nil + } + txns, ok := lookupTransactionsHandle(tsh) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + sorted := coin.SortTransactions(*txns, feeCalc) + *ptsh = registerTransactionsHandle(&sorted) + return +} + +//export SKY_coin_NewSortableTransactions +func SKY_coin_NewSortableTransactions(tsh C.Transactions__Handle, pFeeCalc *C.FeeCalculator, ptsh *C.SortableTransactionResult_Handle) (____error_code uint32) { + feeCalc := func(pTx *coin.Transaction) (uint64, error) { + var fee C.GoUint64_ + handle := registerTransactionHandle(pTx) + result := C.callFeeCalculator(pFeeCalc, handle, &fee) + closeHandle(Handle(handle)) + if result == SKY_OK { + return uint64(fee), nil + } else { + return 0, errors.New("Error calculating fee") + } + } + txns, ok := lookupTransactionsHandle(tsh) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + sorted := coin.NewSortableTransactions(*txns, feeCalc) + *ptsh = registerSortableTransactiontHandle(&sorted) + return SKY_OK +} + +//export SKY_coin_SortableTransactions_Sort +func SKY_coin_SortableTransactions_Sort(_txns C.SortableTransactionResult_Handle) (____error_code uint32) { + txns, ok := lookupSortableTransactionHandle(_txns) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + txns.Sort() + return +} + +//export SKY_coin_SortableTransactions_Len +func SKY_coin_SortableTransactions_Len(_txns C.SortableTransactionResult_Handle, _arg0 *int) (____error_code uint32) { + txns, ok := lookupSortableTransactionHandle(_txns) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + __arg0 := txns.Len() + *_arg0 = __arg0 + return +} + +//export SKY_coin_SortableTransactions_Less +func SKY_coin_SortableTransactions_Less(_txns C.SortableTransactionResult_Handle, _i, _j int, _arg1 *bool) (____error_code uint32) { + txns, ok := lookupSortableTransactionHandle(_txns) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + i := _i + j := _j + __arg1 := txns.Less(i, j) + *_arg1 = __arg1 + return +} + +//export SKY_coin_SortableTransactions_Swap +func SKY_coin_SortableTransactions_Swap(_txns C.SortableTransactionResult_Handle, _i, _j int) (____error_code uint32) { + txns, ok := lookupSortableTransactionHandle(_txns) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + i := _i + j := _j + txns.Swap(i, j) + return +} + +//export SKY_coin_VerifyTransactionCoinsSpending +func SKY_coin_VerifyTransactionCoinsSpending(_uxIn *C.coin__UxArray, _uxOut *C.coin__UxArray) (____error_code uint32) { + uxIn := *(*coin.UxArray)(unsafe.Pointer(_uxIn)) + uxOut := *(*coin.UxArray)(unsafe.Pointer(_uxOut)) + ____return_err := coin.VerifyTransactionCoinsSpending(uxIn, uxOut) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + } + return +} + +//export SKY_coin_VerifyTransactionHoursSpending +func SKY_coin_VerifyTransactionHoursSpending(_headTime uint64, _uxIn *C.coin__UxArray, _uxOut *C.coin__UxArray) (____error_code uint32) { + headTime := _headTime + uxIn := *(*coin.UxArray)(unsafe.Pointer(_uxIn)) + uxOut := *(*coin.UxArray)(unsafe.Pointer(_uxOut)) + ____return_err := coin.VerifyTransactionHoursSpending(headTime, uxIn, uxOut) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + } + return +} diff --git a/lib/cgo/libsky_error.go b/lib/cgo/libsky_error.go index 8d60e7ed6d..206a58f0ea 100644 --- a/lib/cgo/libsky_error.go +++ b/lib/cgo/libsky_error.go @@ -6,6 +6,7 @@ import ( "github.com/skycoin/skycoin/src/cipher" "github.com/skycoin/skycoin/src/cipher/base58" "github.com/skycoin/skycoin/src/cipher/encoder" + "github.com/skycoin/skycoin/src/cipher/encrypt" "github.com/skycoin/skycoin/src/cli" "github.com/skycoin/skycoin/src/coin" "github.com/skycoin/skycoin/src/daemon" @@ -33,9 +34,12 @@ const ( const ( // SKY_BAD_HANDLE invalid handle argument SKY_BAD_HANDLE = SKY_PKG_LIBCGO + iota + 1 + // SKY_INVALID_TIMESTRING invalid time value + SKY_INVALID_TIMESTRING ) // Package prefixes for error codes +//nolint megacheck const ( // Error code prefix for api package SKY_PKG_API = (1 + iota) << 24 // nolint megacheck @@ -62,6 +66,7 @@ const ( ) // Error codes defined in cipher package +//nolint megacheck const ( // SKY_ErrAddressInvalidLength Unexpected size of address bytes buffer SKY_ErrAddressInvalidLength = SKY_PKG_CIPHER + iota @@ -111,8 +116,8 @@ const ( SKY_ErrInvalidPubKey // SKY_ErrInvalidSecKey Invalid public key SKY_ErrInvalidSecKey - // SKY_ErrInvalidSigForPubKey Invalig sig: PubKey recovery failed - SKY_ErrInvalidSigForPubKey + // SKY_ErrInvalidSigPubKeyRecovery Invalig sig: PubKey recovery failed + SKY_ErrInvalidSigPubKeyRecovery // SKY_ErrInvalidSecKeyHex Invalid SecKey: not valid hex SKY_ErrInvalidSecKeyHex // nolint megacheck // SKY_ErrInvalidAddressForSig Invalid sig: address does not match output address @@ -121,9 +126,9 @@ const ( SKY_ErrInvalidHashForSig // SKY_ErrPubKeyRecoverMismatch Recovered pubkey does not match pubkey SKY_ErrPubKeyRecoverMismatch - // SKY_ErrInvalidSigInvalidPubKey VerifySignature, secp256k1.VerifyPubkey failed + // SKY_ErrInvalidSigInvalidPubKey VerifySignedHash, secp256k1.VerifyPubkey failed SKY_ErrInvalidSigInvalidPubKey - // SKY_ErrInvalidSigValidity VerifySignature, VerifySignatureValidity failed + // SKY_ErrInvalidSigValidity VerifySignedHash, VerifySignatureValidity failed SKY_ErrInvalidSigValidity // SKY_ErrInvalidSigForMessage Invalid signature for this message SKY_ErrInvalidSigForMessage @@ -149,9 +154,30 @@ const ( SKY_ErrEmptySeed // SKY_ErrInvalidSig Invalid signature SKY_ErrInvalidSig + // SKY_ErrMissingPassword missing password + SKY_ErrMissingPassword + // SKY_SKY_ErrDataTooLarge data length overflowed, it must <= math.MaxUint32(4294967295) + SKY_ErrDataTooLarge + // SKY_ErrInvalidChecksumLength invalid checksum length + SKY_ErrInvalidChecksumLength + // SKY_ErrInvalidChecksum invalid data, checksum is not matched + SKY_ErrInvalidChecksum + // SKY_ErrInvalidNonceLength invalid nonce length + SKY_ErrInvalidNonceLength + // SKY_ErrInvalidBlockSize invalid block size, must be multiple of 32 bytes + SKY_ErrInvalidBlockSize + // SKY_ErrReadDataHashFailed read data hash failed: read length != 32 + SKY_ErrReadDataHashFailed + // SKY_ErrInvalidPassword invalid password SHA256or + SKY_ErrInvalidPassword + // SKY_ErrReadDataLengthFailed read data length failed + SKY_ErrReadDataLengthFailed + // SKY_ErrInvalidDataLength invalid data length + SKY_ErrInvalidDataLength ) // Error codes defined in cli package +// nolint megacheck const ( // SKY_ErrTemporaryInsufficientBalance is returned if a wallet does not have // enough balance for a spend, but will have enough after unconfirmed transactions confirm @@ -169,6 +195,7 @@ const ( ) // Error codes defined in coin package +// nolint megacheck const ( // ErrAddEarnedCoinHoursAdditionOverflow is returned by UxOut.CoinHours() // if during the addition of base coin @@ -189,6 +216,7 @@ const ( ) // Error codes defined in daemon package +// nolint megacheck const ( // SKY_ErrPeerlistFull is returned when the Pex is at a maximum SKY_ErrPeerlistFull = SKY_PKG_DAEMON + iota @@ -202,10 +230,10 @@ const ( SKY_ErrPortTooLow // SKY_ErrBlacklistedAddress returned when attempting to add a blacklisted peer SKY_ErrBlacklistedAddress - // SKY_ErrDisconnectReadFailed also includes a remote closed socket - SKY_ErrDisconnectReadFailed + // // SKY_ErrDisconnectReadFailed also includes a remote closed socket + // SKY_ErrDisconnectReadFailed // SKY_ErrDisconnectWriteFailed write faile - SKY_ErrDisconnectWriteFailed + // SKY_ErrDisconnectWriteFailed // SKY_ErrDisconnectSetReadDeadlineFailed set read deadline failed SKY_ErrDisconnectSetReadDeadlineFailed // SKY_ErrDisconnectInvalidMessageLength invalid message length @@ -214,8 +242,6 @@ const ( SKY_ErrDisconnectMalformedMessage // SKY_ErrDisconnectUnknownMessage unknow message SKY_ErrDisconnectUnknownMessage - // SKY_ErrDisconnectUnexpectedError unexpected error - SKY_ErrDisconnectUnexpectedError // SKY_ErrConnectionPoolClosed error message indicates the connection pool is closed SKY_ErrConnectionPoolClosed // SKY_ErrWriteQueueFull write queue is full @@ -225,11 +251,9 @@ const ( // SKY_ErrMaxDefaultConnectionsReached returns when maximum number of default connections is reached SKY_ErrMaxDefaultConnectionsReached // nolint megacheck // SKY_ErrDisconnectReasons invalid version - SKY_ErrDisconnectInvalidVersion + SKY_ErrDisconnectVersionNotSupported // SKY_ErrDisconnectIntroductionTimeout timeout SKY_ErrDisconnectIntroductionTimeout - // SKY_ErrDisconnectVersionSendFailed version send failed - SKY_ErrDisconnectVersionSendFailed // SKY_ErrDisconnectIsBlacklisted is blacklisted SKY_ErrDisconnectIsBlacklisted // SKY_ErrDisconnectSelf self connnect @@ -242,8 +266,6 @@ const ( SKY_ErrDisconnectNoIntroduction // SKY_ErrDisconnectIPLimitReached ip limit reached SKY_ErrDisconnectIPLimitReached - // SKY_ErrDisconnectOtherError this is returned when a seemingly impossible error is encountered - SKY_ErrDisconnectOtherError // SKY_ErrDisconnectMaxDefaultConnectionReached Maximum number of default connections was reached SKY_ErrDisconnectMaxDefaultConnectionReached // nolint megacheck // SKY_ErrDisconnectMaxOutgoingConnectionsReached is returned when connection pool size is greater than the maximum allowed @@ -253,6 +275,7 @@ const ( ) // Error codes defined in util package +// nolint megacheck const ( // ErrTxnNoFee is returned if a transaction has no coinhour fee SKY_ErrTxnNoFee = SKY_PKG_UTIL + iota @@ -273,6 +296,7 @@ const ( ) // Error codes defined in visor package +// nolint megacheck const ( // SKY_ErrHistoryDBCorrupted Internal format error in HistoryDB database SKY_ErrHistoryDBCorrupted = SKY_PKG_VISOR + iota @@ -299,6 +323,7 @@ const ( ) // Error codes defined in wallet package +// nolint megacheck const ( // SKY_ErrInsufficientBalance is returned if a wallet does not have enough balance for a spend SKY_ErrInsufficientBalance = SKY_PKG_WALLET + iota @@ -314,12 +339,12 @@ const ( SKY_ErrWalletEncrypted // SKY_ErrWalletNotEncrypted is returned when trying to decrypt unencrypted wallet SKY_ErrWalletNotEncrypted - // SKY_ErrMissingPassword is returned when trying to create wallet with encryption, but password is not provided. - SKY_ErrMissingPassword + // SKY_ErrWalletMissingPassword is returned when trying to create wallet with encryption, but password is not provided. + SKY_ErrWalletMissingPassword // SKY_ErrMissingEncrypt is returned when trying to create wallet with password, but options.Encrypt is not set. SKY_ErrMissingEncrypt - // SKY_ErrInvalidPassword is returned if decrypts secrets failed - SKY_ErrInvalidPassword + // SKY_ErrWalletInvalidPassword is returned if decrypts secrets failed + SKY_ErrWalletInvalidPassword // SKY_ErrMissingSeed is returned when trying to create wallet without a seed SKY_ErrMissingSeed // SKY_ErrMissingAuthenticated is returned if try to decrypt a scrypt chacha20poly1305 encrypted wallet, and find no authenticated metadata. @@ -384,6 +409,12 @@ const ( SKY_ErrDuplicateUxOuts // SKY_ErrUnknownWalletID params.Wallet.ID does not match wallet SKY_ErrUnknownWalletID + // SKY_ErrVerifySignatureInvalidInputsNils VerifySignature, ERROR: invalid input, nils + SKY_ErrVerifySignatureInvalidInputsNils + // SKY_ErrVerifySignatureInvalidSigLength + SKY_ErrVerifySignatureInvalidSigLength + // SKY_ErrVerifySignatureInvalidPubkeysLength + SKY_ErrVerifySignatureInvalidPubkeysLength ) var ( @@ -391,37 +422,42 @@ var ( ErrorBadHandle = errors.New("Invalid or unknown handle value") // ErrorUnknown unexpected error ErrorUnknown = errors.New("Unexpected error") + // ErrorInvalidTimeString time string does not match expected time format + // More precise errors conditions can be found in the logs + ErrorInvalidTimeString = errors.New("Invalid time value") + codeToErrorMap = make(map[uint32]error) errorToCodeMap = map[error]uint32{ // libcgo - ErrorBadHandle: SKY_BAD_HANDLE, - ErrorUnknown: SKY_ERROR, + ErrorBadHandle: SKY_BAD_HANDLE, + ErrorUnknown: SKY_ERROR, + ErrorInvalidTimeString: SKY_INVALID_TIMESTRING, // cipher - cipher.ErrAddressInvalidLength: SKY_ErrAddressInvalidLength, - cipher.ErrAddressInvalidChecksum: SKY_ErrAddressInvalidChecksum, - cipher.ErrAddressInvalidVersion: SKY_ErrAddressInvalidVersion, - cipher.ErrAddressInvalidPubKey: SKY_ErrAddressInvalidPubKey, - cipher.ErrAddressInvalidFirstByte: SKY_ErrAddressInvalidFirstByte, - cipher.ErrAddressInvalidLastByte: SKY_ErrAddressInvalidLastByte, - encoder.ErrBufferUnderflow: SKY_ErrBufferUnderflow, - encoder.ErrInvalidOmitEmpty: SKY_ErrInvalidOmitEmpty, - cipher.ErrInvalidLengthPubKey: SKY_ErrInvalidLengthPubKey, - cipher.ErrPubKeyFromNullSecKey: SKY_ErrPubKeyFromNullSecKey, - cipher.ErrPubKeyFromBadSecKey: SKY_ErrPubKeyFromBadSecKey, - cipher.ErrInvalidLengthSecKey: SKY_ErrInvalidLengthSecKey, - cipher.ErrECHDInvalidPubKey: SKY_ErrECHDInvalidPubKey, - cipher.ErrECHDInvalidSecKey: SKY_ErrECHDInvalidSecKey, - cipher.ErrInvalidLengthSig: SKY_ErrInvalidLengthSig, - cipher.ErrInvalidLengthRipemd160: SKY_ErrInvalidLengthRipemd160, - cipher.ErrInvalidLengthSHA256: SKY_ErrInvalidLengthSHA256, - base58.ErrInvalidBase58Char: SKY_ErrInvalidBase58Char, - base58.ErrInvalidBase58String: SKY_ErrInvalidBase58String, - base58.ErrInvalidBase58Length: SKY_ErrInvalidBase58Length, - cipher.ErrInvalidHexLength: SKY_ErrInvalidHexLength, - cipher.ErrInvalidBytesLength: SKY_ErrInvalidBytesLength, - cipher.ErrInvalidPubKey: SKY_ErrInvalidPubKey, - cipher.ErrInvalidSecKey: SKY_ErrInvalidSecKey, - cipher.ErrInvalidSigForPubKey: SKY_ErrInvalidSigForPubKey, + cipher.ErrAddressInvalidLength: SKY_ErrAddressInvalidLength, + cipher.ErrAddressInvalidChecksum: SKY_ErrAddressInvalidChecksum, + cipher.ErrAddressInvalidVersion: SKY_ErrAddressInvalidVersion, + cipher.ErrAddressInvalidPubKey: SKY_ErrAddressInvalidPubKey, + cipher.ErrAddressInvalidFirstByte: SKY_ErrAddressInvalidFirstByte, + cipher.ErrAddressInvalidLastByte: SKY_ErrAddressInvalidLastByte, + encoder.ErrBufferUnderflow: SKY_ErrBufferUnderflow, + encoder.ErrInvalidOmitEmpty: SKY_ErrInvalidOmitEmpty, + cipher.ErrInvalidLengthPubKey: SKY_ErrInvalidLengthPubKey, + cipher.ErrPubKeyFromNullSecKey: SKY_ErrPubKeyFromNullSecKey, + cipher.ErrPubKeyFromBadSecKey: SKY_ErrPubKeyFromBadSecKey, + cipher.ErrInvalidLengthSecKey: SKY_ErrInvalidLengthSecKey, + cipher.ErrECHDInvalidPubKey: SKY_ErrECHDInvalidPubKey, + cipher.ErrECHDInvalidSecKey: SKY_ErrECHDInvalidSecKey, + cipher.ErrInvalidLengthSig: SKY_ErrInvalidLengthSig, + cipher.ErrInvalidLengthRipemd160: SKY_ErrInvalidLengthRipemd160, + cipher.ErrInvalidLengthSHA256: SKY_ErrInvalidLengthSHA256, + base58.ErrInvalidBase58Char: SKY_ErrInvalidBase58Char, + base58.ErrInvalidBase58String: SKY_ErrInvalidBase58String, + base58.ErrInvalidBase58Length: SKY_ErrInvalidBase58Length, + cipher.ErrInvalidHexLength: SKY_ErrInvalidHexLength, + cipher.ErrInvalidBytesLength: SKY_ErrInvalidBytesLength, + cipher.ErrInvalidPubKey: SKY_ErrInvalidPubKey, + cipher.ErrInvalidSecKey: SKY_ErrInvalidSecKey, + cipher.ErrInvalidSigPubKeyRecovery: SKY_ErrInvalidSigPubKeyRecovery, // Removed in ea0aafbffb76 // cipher.ErrInvalidSecKeyHex: SKY_ErrInvalidSecKeyHex, cipher.ErrInvalidAddressForSig: SKY_ErrInvalidAddressForSig, @@ -441,6 +477,17 @@ var ( cipher.ErrBitcoinWIFInvalidChecksum: SKY_ErrBitcoinWIFInvalidChecksum, cipher.ErrEmptySeed: SKY_ErrEmptySeed, cipher.ErrInvalidSig: SKY_ErrInvalidSig, + encrypt.ErrMissingPassword: SKY_ErrMissingPassword, + encrypt.ErrDataTooLarge: SKY_ErrDataTooLarge, + encrypt.ErrInvalidChecksumLength: SKY_ErrInvalidChecksumLength, + encrypt.ErrInvalidChecksum: SKY_ErrInvalidChecksum, + encrypt.ErrInvalidNonceLength: SKY_ErrInvalidNonceLength, + encrypt.ErrInvalidBlockSize: SKY_ErrInvalidBlockSize, + encrypt.ErrReadDataHashFailed: SKY_ErrReadDataHashFailed, + encrypt.ErrInvalidPassword: SKY_ErrInvalidPassword, + encrypt.ErrReadDataLengthFailed: SKY_ErrReadDataLengthFailed, + encrypt.ErrInvalidDataLength: SKY_ErrInvalidDataLength, + // cli cli.ErrTemporaryInsufficientBalance: SKY_ErrTemporaryInsufficientBalance, cli.ErrAddress: SKY_ErrAddress, @@ -456,32 +503,29 @@ var ( // daemon // Removed in 34ad39ddb350 // gnet.ErrMaxDefaultConnectionsReached: SKY_ErrMaxDefaultConnectionsReached, - pex.ErrPeerlistFull: SKY_ErrPeerlistFull, - pex.ErrInvalidAddress: SKY_ErrInvalidAddress, - pex.ErrNoLocalhost: SKY_ErrNoLocalhost, - pex.ErrNotExternalIP: SKY_ErrNotExternalIP, - pex.ErrPortTooLow: SKY_ErrPortTooLow, - pex.ErrBlacklistedAddress: SKY_ErrBlacklistedAddress, - gnet.ErrDisconnectReadFailed: SKY_ErrDisconnectReadFailed, - gnet.ErrDisconnectWriteFailed: SKY_ErrDisconnectWriteFailed, + pex.ErrPeerlistFull: SKY_ErrPeerlistFull, + pex.ErrInvalidAddress: SKY_ErrInvalidAddress, + pex.ErrNoLocalhost: SKY_ErrNoLocalhost, + pex.ErrNotExternalIP: SKY_ErrNotExternalIP, + pex.ErrPortTooLow: SKY_ErrPortTooLow, + pex.ErrBlacklistedAddress: SKY_ErrBlacklistedAddress, + // gnet.ErrDisconnectReadFailed: SKY_ErrDisconnectReadFailed, + // gnet.ErrDisconnectWriteFailed: SKY_ErrDisconnectWriteFailed, gnet.ErrDisconnectSetReadDeadlineFailed: SKY_ErrDisconnectSetReadDeadlineFailed, gnet.ErrDisconnectInvalidMessageLength: SKY_ErrDisconnectInvalidMessageLength, gnet.ErrDisconnectMalformedMessage: SKY_ErrDisconnectMalformedMessage, gnet.ErrDisconnectUnknownMessage: SKY_ErrDisconnectUnknownMessage, - gnet.ErrDisconnectUnexpectedError: SKY_ErrDisconnectUnexpectedError, gnet.ErrConnectionPoolClosed: SKY_ErrConnectionPoolClosed, gnet.ErrWriteQueueFull: SKY_ErrWriteQueueFull, gnet.ErrNoReachableConnections: SKY_ErrNoReachableConnections, - daemon.ErrDisconnectInvalidVersion: SKY_ErrDisconnectInvalidVersion, + daemon.ErrDisconnectVersionNotSupported: SKY_ErrDisconnectVersionNotSupported, daemon.ErrDisconnectIntroductionTimeout: SKY_ErrDisconnectIntroductionTimeout, - daemon.ErrDisconnectVersionSendFailed: SKY_ErrDisconnectVersionSendFailed, daemon.ErrDisconnectIsBlacklisted: SKY_ErrDisconnectIsBlacklisted, daemon.ErrDisconnectSelf: SKY_ErrDisconnectSelf, daemon.ErrDisconnectConnectedTwice: SKY_ErrDisconnectConnectedTwice, daemon.ErrDisconnectIdle: SKY_ErrDisconnectIdle, daemon.ErrDisconnectNoIntroduction: SKY_ErrDisconnectNoIntroduction, daemon.ErrDisconnectIPLimitReached: SKY_ErrDisconnectIPLimitReached, - daemon.ErrDisconnectOtherError: SKY_ErrDisconnectOtherError, // Removed // daemon.ErrDisconnectMaxDefaultConnectionReached: SKY_ErrDisconnectMaxDefaultConnectionReached, daemon.ErrDisconnectMaxOutgoingConnectionsReached: SKY_ErrDisconnectMaxOutgoingConnectionsReached, @@ -505,9 +549,9 @@ var ( wallet.ErrInvalidEncryptedField: SKY_ErrInvalidEncryptedField, wallet.ErrWalletEncrypted: SKY_ErrWalletEncrypted, wallet.ErrWalletNotEncrypted: SKY_ErrWalletNotEncrypted, - wallet.ErrMissingPassword: SKY_ErrMissingPassword, + wallet.ErrMissingPassword: SKY_ErrWalletMissingPassword, wallet.ErrMissingEncrypt: SKY_ErrMissingEncrypt, - wallet.ErrInvalidPassword: SKY_ErrInvalidPassword, + wallet.ErrInvalidPassword: SKY_ErrWalletInvalidPassword, wallet.ErrMissingSeed: SKY_ErrMissingSeed, wallet.ErrMissingAuthenticated: SKY_ErrMissingAuthenticated, wallet.ErrWrongCryptoType: SKY_ErrWrongCryptoType, @@ -578,3 +622,53 @@ func libErrorCode(err error) uint32 { } return SKY_ERROR } + +func errorFromLibCode(errcode uint32) error { + if err, exists := codeToErrorMap[errcode]; exists { + return err + } + + // FIXME: Be more specific and encode type, sub-error in error code + err := errors.New("libskycoin error") + if errcode == SKY_WalletLoadError { + return cli.WalletLoadError{} + } + if errcode == SKY_WalletSaveError { + return cli.WalletSaveError{} + } + if errcode == SKY_ErrHistoryDBCorrupted { + return historydb.NewErrHistoryDBCorrupted(err) + } + if errcode == SKY_ErrUxOutNotExist { + return historydb.ErrUxOutNotExist{UxID: ""} + } + if errcode == SKY_ErrUnspentNotExist { + return blockdb.ErrUnspentNotExist{UxID: ""} + } + if errcode == SKY_ErrMissingSignature { + return blockdb.NewErrMissingSignature(nil) + } + if errcode == SKY_ErrCreateBucketFailed { + return dbutil.ErrCreateBucketFailed{Bucket: "", Err: nil} + } + if errcode == SKY_ErrBucketNotExist { + return dbutil.ErrBucketNotExist{Bucket: ""} + } + if errcode == SKY_ErrTxnViolatesHardConstraint { + return visor.ErrTxnViolatesHardConstraint{Err: err} + } + if errcode == SKY_ErrTxnViolatesSoftConstraint { + return visor.ErrTxnViolatesSoftConstraint{Err: err} + } + if errcode == SKY_ErrTxnViolatesUserConstraint { + return visor.ErrTxnViolatesUserConstraint{Err: err} + } + return nil +} + +func init() { + // Init reverse error code map + for _err := range errorToCodeMap { + codeToErrorMap[errorToCodeMap[_err]] = _err + } +} diff --git a/lib/cgo/libsky_handle.go b/lib/cgo/libsky_handle.go index 698bce7cfc..82bff3d3b7 100644 --- a/lib/cgo/libsky_handle.go +++ b/lib/cgo/libsky_handle.go @@ -5,42 +5,58 @@ package main #include #include + #include "skytypes.h" */ import "C" import ( - "unsafe" + "hash" + api "github.com/skycoin/skycoin/src/api" webrpc "github.com/skycoin/skycoin/src/api/webrpc" cli "github.com/skycoin/skycoin/src/cli" + "github.com/skycoin/skycoin/src/coin" + "github.com/skycoin/skycoin/src/readable" wallet "github.com/skycoin/skycoin/src/wallet" + gcli "github.com/urfave/cli" ) type Handle uint64 var ( - handleMap = make(map[Handle]interface{}) + handlesCounter uint64 = 0 + handleMap = make(map[Handle]interface{}) ) -func registerHandle(obj interface{}) Handle { - ptr := &obj - handle := *(*Handle)(unsafe.Pointer(&ptr)) - handleMap[handle] = obj - return handle +func registerHandle(obj interface{}) C.Handle { + handlesCounter++ + handle := handlesCounter + //handle := *(*Handle)(unsafe.Pointer(&obj)) + handleMap[Handle(handle)] = obj + return (C.Handle)(handle) } -func lookupHandle(handle Handle) (interface{}, bool) { - obj, ok := handleMap[handle] +func lookupHandle(handle C.Handle) (interface{}, bool) { + obj, ok := handleMap[Handle(handle)] return obj, ok } +func overwriteHandle(handle C.Handle, obj interface{}) bool { + _, ok := handleMap[Handle(handle)] + if ok { + handleMap[Handle(handle)] = obj + return true + } + return false +} + func registerWebRpcClientHandle(obj *webrpc.Client) C.WebRpcClient__Handle { return (C.WebRpcClient__Handle)(registerHandle(obj)) } func lookupWebRpcClientHandle(handle C.WebRpcClient__Handle) (*webrpc.Client, bool) { - obj, ok := lookupHandle(Handle(handle)) + obj, ok := lookupHandle(C.Handle(handle)) if ok { if obj, isOK := (obj).(*webrpc.Client); isOK { return obj, true @@ -54,7 +70,7 @@ func registerWalletHandle(obj *wallet.Wallet) C.Wallet__Handle { } func lookupWalletHandle(handle C.Wallet__Handle) (*wallet.Wallet, bool) { - obj, ok := lookupHandle(Handle(handle)) + obj, ok := lookupHandle(C.Handle(handle)) if ok { if obj, isOK := (obj).(*wallet.Wallet); isOK { return obj, true @@ -63,12 +79,40 @@ func lookupWalletHandle(handle C.Wallet__Handle) (*wallet.Wallet, bool) { return nil, false } +func registerReadableWalletHandle(obj *wallet.ReadableWallet) C.ReadableWallet__Handle { + return (C.ReadableWallet__Handle)(registerHandle(obj)) +} + +func lookupReadableWalletHandle(handle C.ReadableWallet__Handle) (*wallet.ReadableWallet, bool) { + obj, ok := lookupHandle(C.Handle(handle)) + if ok { + if obj, isOK := (obj).(*wallet.ReadableWallet); isOK { + return obj, true + } + } + return nil, false +} + +func registerReadableEntryHandle(obj *wallet.ReadableEntry) C.ReadableEntry__Handle { + return (C.ReadableEntry__Handle)(registerHandle(obj)) +} + +func lookupReadableEntryHandle(handle C.ReadableEntry__Handle) (*wallet.ReadableEntry, bool) { + obj, ok := lookupHandle(C.Handle(handle)) + if ok { + if obj, isOK := (obj).(*wallet.ReadableEntry); isOK { + return obj, true + } + } + return nil, false +} + func registerOptionsHandle(obj *wallet.Options) C.Options__Handle { return (C.Options__Handle)(registerHandle(obj)) } func lookupOptionsHandle(handle C.Options__Handle) (*wallet.Options, bool) { - obj, ok := lookupHandle(Handle(handle)) + obj, ok := lookupHandle(C.Handle(handle)) if ok { if obj, isOK := (obj).(*wallet.Options); isOK { return obj, true @@ -82,7 +126,7 @@ func registerConfigHandle(obj *cli.Config) C.Config__Handle { } func lookupConfigHandle(handle C.Config__Handle) (*cli.Config, bool) { - obj, ok := lookupHandle(Handle(handle)) + obj, ok := lookupHandle(C.Handle(handle)) if ok { if obj, isOK := (obj).(*cli.Config); isOK { return obj, true @@ -91,14 +135,336 @@ func lookupConfigHandle(handle C.Config__Handle) (*cli.Config, bool) { return nil, false } -func registerPasswordReaderHandle(obj cli.PasswordReader) C.PasswordReader__Handle { +func registerAppHandle(obj *cli.App) C.App__Handle { + return (C.App__Handle)(registerHandle(obj)) +} + +func lookupAppHandle(handle C.App__Handle) (*cli.App, bool) { + obj, ok := lookupHandle(C.Handle(handle)) + if ok { + if obj, isOK := (obj).(*cli.App); isOK { + return obj, true + } + } + return nil, false +} + +func registerContextHandle(obj *gcli.Context) C.Context__Handle { + return (C.Context__Handle)(registerHandle(obj)) +} + +func lookupContextHandle(handle C.Context__Handle) (*gcli.Context, bool) { + obj, ok := lookupHandle(C.Handle(handle)) + if ok { + if obj, isOK := (obj).(*gcli.Context); isOK { + return obj, true + } + } + return nil, false +} + +func registerClientHandle(obj *api.Client) C.Client__Handle { + return (C.Client__Handle)(registerHandle(obj)) +} + +func lookupClientHandle(handle C.Client__Handle) (*api.Client, bool) { + obj, ok := lookupHandle(C.Handle(handle)) + if ok { + if obj, isOK := (obj).(*api.Client); isOK { + return obj, true + } + } + return nil, false +} + +func registerWalletsHandle(obj *[]api.WalletResponse) C.Wallets__Handle { + return (C.Wallets__Handle)(registerHandle(obj)) +} + +func lookupWalletsHandle(handle C.Wallets__Handle) ([]*api.WalletResponse, bool) { + obj, ok := lookupHandle(C.Handle(handle)) + if ok { + if obj, isOK := (obj).([]*api.WalletResponse); isOK { + return obj, true + } + } + return nil, false +} + +func registerWalletResponseHandle(obj *api.WalletResponse) C.WalletResponse__Handle { + return (C.WalletResponse__Handle)(registerHandle(obj)) +} + +func lookupWalletResponseHandle(handle C.WalletResponse__Handle) (*api.WalletResponse, bool) { + obj, ok := lookupHandle(C.Handle(handle)) + if ok { + if obj, isOK := (obj).(*api.WalletResponse); isOK { + return obj, true + } + } + return nil, false +} + +func registerCreateTransactionRequestHandle(obj *api.CreateTransactionRequest) C.CreateTransactionRequest__Handle { + return (C.CreateTransactionRequest__Handle)(registerHandle(obj)) +} + +func lookupCreateTransactionRequestHandle(handle C.CreateTransactionRequest__Handle) (*api.CreateTransactionRequest, bool) { + obj, ok := lookupHandle(C.Handle(handle)) + if ok { + if obj, isOK := (obj).(*api.CreateTransactionRequest); isOK { + return obj, true + } + } + return nil, false +} + +func registerPasswordReaderHandle(obj *cli.PasswordReader) C.PasswordReader__Handle { return (C.PasswordReader__Handle)(registerHandle(obj)) } -func lookupPasswordReaderHandle(handle C.PasswordReader__Handle) (cli.PasswordReader, bool) { - obj, ok := lookupHandle(Handle(handle)) +func lookupPasswordReaderHandle(handle C.PasswordReader__Handle) (*cli.PasswordReader, bool) { + obj, ok := lookupHandle(C.Handle(handle)) + if ok { + if obj, isOK := (obj).(*cli.PasswordReader); isOK { + return obj, true + } + } + return nil, false +} + +func registerTransactionHandle(obj *coin.Transaction) C.Transaction__Handle { + return (C.Transaction__Handle)(registerHandle(obj)) +} + +func lookupTransactionHandle(handle C.Transaction__Handle) (*coin.Transaction, bool) { + obj, ok := lookupHandle(C.Handle(handle)) + if ok { + if obj, isOK := (obj).(*coin.Transaction); isOK { + return obj, true + } + } + return nil, false +} + +func registerTransactionsHandle(obj *coin.Transactions) C.Transactions__Handle { + return (C.Transactions__Handle)(registerHandle(obj)) +} + +func lookupTransactionsHandle(handle C.Transactions__Handle) (*coin.Transactions, bool) { + obj, ok := lookupHandle(C.Handle(handle)) + if ok { + if obj, isOK := (obj).(*coin.Transactions); isOK { + return obj, true + } + } + return nil, false +} + +func registerBlockHandle(obj *coin.Block) C.Block__Handle { + return (C.Block__Handle)(registerHandle(obj)) +} + +func lookupBlockHandle(handle C.Block__Handle) (*coin.Block, bool) { + obj, ok := lookupHandle(C.Handle(handle)) + if ok { + if obj, isOK := (obj).(*coin.Block); isOK { + return obj, true + } + } + return nil, false +} + +func registerSignedBlockHandle(obj *coin.SignedBlock) C.SignedBlock__Handle { + return (C.SignedBlock__Handle)(registerHandle(obj)) +} + +func lookupSignedBlockHandle(handle C.SignedBlock__Handle) (*coin.SignedBlock, bool) { + obj, ok := lookupHandle(C.Handle(handle)) + if ok { + if obj, isOK := (obj).(*coin.SignedBlock); isOK { + return obj, true + } + } + return nil, false +} + +func registerBlockBodyHandle(obj *coin.BlockBody) C.BlockBody__Handle { + return (C.BlockBody__Handle)(registerHandle(obj)) +} + +func lookupBlockBodyHandle(handle C.BlockBody__Handle) (*coin.BlockBody, bool) { + obj, ok := lookupHandle(C.Handle(handle)) + if ok { + if obj, isOK := (obj).(*coin.BlockBody); isOK { + return obj, true + } + } + return nil, false +} + +func registerCreatedTransactionHandle(obj *api.CreatedTransaction) C.CreatedTransaction__Handle { + return (C.CreatedTransaction__Handle)(registerHandle(obj)) +} + +func lookupCreatedTransactionHandle(handle C.CreatedTransaction__Handle) (*api.CreatedTransaction, bool) { + obj, ok := lookupHandle(C.Handle(handle)) + if ok { + if obj, isOK := (obj).(*api.CreatedTransaction); isOK { + return obj, true + } + } + return nil, false +} + +func registerCreatedTransactionOutputHandle(obj *api.CreatedTransactionOutput) C.CreatedTransactionOutput__Handle { + return (C.CreatedTransactionOutput__Handle)(registerHandle(obj)) +} + +func lookupCreatedTransactionOutputHandle(handle C.CreatedTransactionOutput__Handle) (*api.CreatedTransactionOutput, bool) { + obj, ok := lookupHandle(C.Handle(handle)) + if ok { + if obj, isOK := (obj).(*api.CreatedTransactionOutput); isOK { + return obj, true + } + } + return nil, false +} + +func registerCreatedTransactionInputHandle(obj *api.CreatedTransactionInput) C.CreatedTransactionInput__Handle { + return (C.CreatedTransactionInput__Handle)(registerHandle(obj)) +} + +func lookupCreatedTransactionInputHandle(handle C.CreatedTransactionInput__Handle) (*api.CreatedTransactionInput, bool) { + obj, ok := lookupHandle(C.Handle(handle)) + if ok { + if obj, isOK := (obj).(*api.CreatedTransactionInput); isOK { + return obj, true + } + } + return nil, false +} + +func registerCreateTransactionResponseHandle(obj *api.CreateTransactionResponse) C.CreateTransactionResponse__Handle { + return (C.CreateTransactionResponse__Handle)(registerHandle(obj)) +} + +func lookupCreateTransactionResponseHandle(handle C.CreateTransactionResponse__Handle) (*api.CreateTransactionResponse, bool) { + obj, ok := lookupHandle(C.Handle(handle)) + if ok { + if obj, isOK := (obj).(*api.CreateTransactionResponse); isOK { + return obj, true + } + } + return nil, false +} + +func registerBalanceResultHandle(obj *cli.BalanceResult) C.BalanceResult_Handle { + return (C.BalanceResult_Handle)(registerHandle(obj)) +} + +func lookupBalanceResultHandle(handle C.BalanceResult_Handle) (*cli.BalanceResult, bool) { + obj, ok := lookupHandle(C.Handle(handle)) + if ok { + if obj, isOK := (obj).(*cli.BalanceResult); isOK { + return obj, true + } + } + return nil, false +} + +func registerSpendResultHandle(obj *api.SpendResult) C.SpendResult_Handle { + return (C.SpendResult_Handle)(registerHandle(obj)) +} + +func lookupSpendResultHandle(handle C.SpendResult_Handle) (*api.SpendResult, bool) { + obj, ok := lookupHandle(C.Handle(handle)) + if ok { + if obj, isOK := (obj).(*api.SpendResult); isOK { + return obj, true + } + } + return nil, false +} + +func registerTransactionResultHandle(obj *webrpc.TxnResult) C.TransactionResult_Handle { + return (C.TransactionResult_Handle)(registerHandle(obj)) +} + +func lookupTransactionResultHandle(handle C.TransactionResult_Handle) (*webrpc.TxnResult, bool) { + obj, ok := lookupHandle(C.Handle(handle)) + if ok { + if obj, isOK := (obj).(*webrpc.TxnResult); isOK { + return obj, true + } + } + return nil, false +} + +func registerSortableTransactiontHandle(obj *coin.SortableTransactions) C.SortableTransactionResult_Handle { + return (C.SortableTransactionResult_Handle)(registerHandle(obj)) +} + +func lookupSortableTransactionHandle(handle C.SortableTransactionResult_Handle) (*coin.SortableTransactions, bool) { + obj, ok := lookupHandle(C.Handle(handle)) + if ok { + if obj, isOK := (obj).(*coin.SortableTransactions); isOK { + return obj, true + } + } + return nil, false +} + +func registerOutputsResultHandle(obj *webrpc.OutputsResult) C.OutputsResult_Handle { + return (C.OutputsResult_Handle)(registerHandle(obj)) +} + +func lookupOutputsResultHandle(handle C.OutputsResult_Handle) (*webrpc.OutputsResult, bool) { + obj, ok := lookupHandle(C.Handle(handle)) + if ok { + if obj, isOK := (obj).(*webrpc.OutputsResult); isOK { + return obj, true + } + } + return nil, false +} + +func registerStatusResultHandle(obj *webrpc.StatusResult) C.StatusResult_Handle { + return (C.StatusResult_Handle)(registerHandle(obj)) +} + +func lookupStatusResultHandle(handle C.StatusResult_Handle) (*webrpc.StatusResult, bool) { + obj, ok := lookupHandle(C.Handle(handle)) + if ok { + if obj, isOK := (obj).(*webrpc.StatusResult); isOK { + return obj, true + } + } + return nil, false +} + +func registerAddressUxOutHandle(obj *coin.AddressUxOuts) C.AddressUxOuts_Handle { + return (C.AddressUxOuts_Handle)(registerHandle(obj)) +} + +func lookupAddressUxOutHandle(handle C.AddressUxOuts_Handle) (*coin.AddressUxOuts, bool) { + obj, ok := lookupHandle(C.Handle(handle)) if ok { - if obj, isOK := (obj).(cli.PasswordReader); isOK { + if obj, isOK := (obj).(*coin.AddressUxOuts); isOK { + return obj, true + } + } + return nil, false +} + +func registerHashHandle(obj *hash.Hash) C.Hash_Handle { + return (C.Hash_Handle)(registerHandle(obj)) +} + +func lookupHashHandle(handle C.Hash_Handle) (*hash.Hash, bool) { + obj, ok := lookupHandle(C.Handle(handle)) + if ok { + if obj, isOK := (obj).(*hash.Hash); isOK { return obj, true } } @@ -113,3 +479,42 @@ func closeHandle(handle Handle) { func SKY_handle_close(handle C.Handle) { closeHandle(Handle(handle)) } + +//export SKY_handle_copy +func SKY_handle_copy(handle C.Handle, copy *C.Handle) uint32 { + obj, ok := lookupHandle(handle) + if ok { + *copy = registerHandle(obj) + return SKY_OK + } else { + return SKY_BAD_HANDLE + } +} + +func registerReadableUnspentOutputsSummaryHandle(obj *readable.UnspentOutputsSummary) C.ReadableUnspentOutputsSummary_Handle { + return (C.ReadableUnspentOutputsSummary_Handle)(registerHandle(obj)) +} + +func lookupReadableUnspentOutputsSummaryHandle(handle C.ReadableUnspentOutputsSummary_Handle) (*readable.UnspentOutputsSummary, bool) { + obj, ok := lookupHandle(C.Handle(handle)) + if ok { + if obj, isOK := (obj).(*readable.UnspentOutputsSummary); isOK { + return obj, true + } + } + return nil, false +} + +func registerBuildInfoHandle(obj *readable.BuildInfo) C.BuildInfo_Handle { + return (C.BuildInfo_Handle)(registerHandle(obj)) +} + +func lookupBuildInfoHandle(handle C.BuildInfo_Handle) (*readable.BuildInfo, bool) { + obj, ok := lookupHandle(C.Handle(handle)) + if ok { + if obj, isOK := (obj).(*readable.BuildInfo); isOK { + return obj, true + } + } + return nil, false +} diff --git a/lib/cgo/libsky_handle_helper.go b/lib/cgo/libsky_handle_helper.go new file mode 100644 index 0000000000..8bb40e9710 --- /dev/null +++ b/lib/cgo/libsky_handle_helper.go @@ -0,0 +1,383 @@ +package main + +/* + + #include + #include + + + #include "skytypes.h" +*/ +import "C" + +import ( + "encoding/json" + "path/filepath" + "sort" + "unsafe" + + api "github.com/skycoin/skycoin/src/api" + "github.com/skycoin/skycoin/src/daemon" + "github.com/skycoin/skycoin/src/readable" +) + +//export SKY_JsonEncode_Handle +func SKY_JsonEncode_Handle(handle C.Handle, json_string *C.GoString_) uint32 { + obj, ok := lookupHandle(handle) + if ok { + jsonBytes, err := json.Marshal(obj) + if err == nil { + copyString(string(jsonBytes), json_string) + return SKY_OK + } + } + return SKY_BAD_HANDLE +} + +//export SKY_Handle_Progress_GetCurrent +func SKY_Handle_Progress_GetCurrent(handle C.Handle, current *uint64) uint32 { + obj, ok := lookupHandle(C.Handle(handle)) + if ok { + if obj, isOK := (obj).(*daemon.BlockchainProgress); isOK { + *current = obj.Current + return SKY_OK + } + } + return SKY_BAD_HANDLE +} + +//export SKY_Handle_Block_GetHeadSeq +func SKY_Handle_Block_GetHeadSeq(handle C.Handle, seq *uint64) uint32 { + obj, ok := lookupHandle(C.Handle(handle)) + if ok { + if obj, isOK := (obj).(*readable.Block); isOK { + *seq = obj.Head.BkSeq + return SKY_OK + } + } + return SKY_BAD_HANDLE +} + +//export SKY_Handle_Block_GetHeadHash +func SKY_Handle_Block_GetHeadHash(handle C.Handle, hash *C.GoString_) uint32 { + obj, ok := lookupHandle(C.Handle(handle)) + if ok { + if obj, isOK := (obj).(*readable.Block); isOK { + copyString(obj.Head.Hash, hash) + return SKY_OK + } + } + return SKY_BAD_HANDLE +} + +//export SKY_Handle_Block_GetPreviousBlockHash +func SKY_Handle_Block_GetPreviousBlockHash(handle C.Handle, hash *C.GoString_) uint32 { + obj, ok := lookupHandle(C.Handle(handle)) + if ok { + if obj, isOK := (obj).(*readable.Block); isOK { + copyString(obj.Head.PreviousHash, hash) + return SKY_OK + } + } + return SKY_BAD_HANDLE +} + +//export SKY_Handle_Blocks_GetAt +func SKY_Handle_Blocks_GetAt(handle C.Handle, + index uint64, blockHandle *C.Handle) uint32 { + obj, ok := lookupHandle(C.Handle(handle)) + if ok { + if obj, isOK := (obj).(*readable.Blocks); isOK { + *blockHandle = registerHandle(&obj.Blocks[index]) + return SKY_OK + } + } + return SKY_BAD_HANDLE +} + +//export SKY_Handle_Blocks_GetCount +func SKY_Handle_Blocks_GetCount(handle C.Handle, + count *uint64) uint32 { + obj, ok := lookupHandle(C.Handle(handle)) + if ok { + if obj, isOK := (obj).(*readable.Blocks); isOK { + *count = uint64(len(obj.Blocks)) + return SKY_OK + } + } + return SKY_BAD_HANDLE +} + +//export SKY_Handle_Connections_GetCount +func SKY_Handle_Connections_GetCount(handle C.Handle, + count *uint64) uint32 { + obj, ok := lookupHandle(C.Handle(handle)) + if ok { + if obj, isOK := (obj).(*api.Connections); isOK { + *count = uint64(len(obj.Connections)) + return SKY_OK + } + } + return SKY_BAD_HANDLE +} + +//export SKY_Handle_Strings_GetCount +func SKY_Handle_Strings_GetCount(handle C.Strings__Handle, + count *uint32) uint32 { + obj, ok := lookupHandle(C.Handle(handle)) + if ok { + if obj, isOK := (obj).([]string); isOK { + *count = uint32(len(obj)) + return SKY_OK + } + } + return SKY_BAD_HANDLE +} + +//export SKY_Handle_Strings_Sort +func SKY_Handle_Strings_Sort(handle C.Strings__Handle) uint32 { + obj, ok := lookupHandle(C.Handle(handle)) + if ok { + if obj, isOK := (obj).([]string); isOK { + sort.Strings(obj) + return SKY_OK + } + } + return SKY_BAD_HANDLE +} + +//export SKY_Handle_Strings_GetAt +func SKY_Handle_Strings_GetAt(handle C.Strings__Handle, + index int, + str *C.GoString_) uint32 { + obj, ok := lookupHandle(C.Handle(handle)) + if ok { + if obj, isOK := (obj).([]string); isOK { + copyString(obj[index], str) + return SKY_OK + } + } + return SKY_BAD_HANDLE +} + +//export SKY_api_Handle_Client_GetWalletDir +func SKY_api_Handle_Client_GetWalletDir(handle C.Client__Handle, + walletDir *C.GoString_) uint32 { + client, ok := lookupClientHandle(handle) + if ok { + wf, err := client.WalletFolderName() + if err == nil { + copyString(wf.Address, walletDir) + return SKY_OK + } + } + return SKY_BAD_HANDLE +} + +//export SKY_api_Handle_Client_GetWalletFileName +func SKY_api_Handle_Client_GetWalletFileName(handle C.WalletResponse__Handle, + walletFileName *C.GoString_) uint32 { + w, ok := lookupWalletResponseHandle(handle) + if ok { + copyString(w.Meta.Filename, walletFileName) + return SKY_OK + } + return SKY_BAD_HANDLE +} + +//export SKY_api_Handle_Client_GetWalletLabel +func SKY_api_Handle_Client_GetWalletLabel(handle C.WalletResponse__Handle, + walletLabel *C.GoString_) uint32 { + w, ok := lookupWalletResponseHandle(handle) + if ok { + copyString(w.Meta.Label, walletLabel) + return SKY_OK + } + return SKY_BAD_HANDLE +} + +//export SKY_api_Handle_Client_GetWalletFullPath +func SKY_api_Handle_Client_GetWalletFullPath( + clientHandle C.Client__Handle, + walletHandle C.WalletResponse__Handle, + fullPath *C.GoString_) uint32 { + client, ok := lookupClientHandle(clientHandle) + if ok { + wf, err := client.WalletFolderName() + if err == nil { + w, okw := lookupWalletResponseHandle(walletHandle) + if okw { + walletPath := filepath.Join(wf.Address, w.Meta.Filename) + copyString(walletPath, fullPath) + return SKY_OK + } + } + } + return SKY_BAD_HANDLE +} + +//export SKY_api_Handle_GetWalletMeta +func SKY_api_Handle_GetWalletMeta(handle C.Wallet__Handle, + gomap *C.GoStringMap_) uint32 { + w, ok := lookupWalletHandle(handle) + if ok { + copyToStringMap(w.Meta, gomap) + return SKY_OK + } + return SKY_BAD_HANDLE +} + +//export SKY_api_Handle_GetWalletEntriesCount +func SKY_api_Handle_GetWalletEntriesCount(handle C.Wallet__Handle, + count *uint32) uint32 { + w, ok := lookupWalletHandle(handle) + if ok { + *count = uint32(len(w.Entries)) + return SKY_OK + } + return SKY_BAD_HANDLE +} + +//export SKY_api_Handle_Client_GetWalletResponseEntriesCount +func SKY_api_Handle_Client_GetWalletResponseEntriesCount( + handle C.WalletResponse__Handle, + count *uint32) uint32 { + w, ok := lookupWalletResponseHandle(handle) + if ok { + *count = uint32(len(w.Entries)) + return SKY_OK + } + return SKY_BAD_HANDLE +} + +//export SKY_api_Handle_WalletGetEntry +func SKY_api_Handle_WalletGetEntry(handle C.Wallet__Handle, + index uint32, + address *C.cipher__Address, + pubkey *C.cipher__PubKey) uint32 { + w, ok := lookupWalletHandle(handle) + if ok { + if index < uint32(len(w.Entries)) { + *address = *(*C.cipher__Address)(unsafe.Pointer(&w.Entries[index].Address)) + *pubkey = *(*C.cipher__PubKey)(unsafe.Pointer(&w.Entries[index].Public)) + return SKY_OK + } + } + return SKY_BAD_HANDLE +} + +//export SKY_api_Handle_WalletResponseGetEntry +func SKY_api_Handle_WalletResponseGetEntry(handle C.WalletResponse__Handle, + index uint32, + address *C.GoString_, + pubkey *C.GoString_) uint32 { + w, ok := lookupWalletResponseHandle(handle) + if ok { + if index < uint32(len(w.Entries)) { + copyString(w.Entries[index].Address, address) + copyString(w.Entries[index].Public, pubkey) + return SKY_OK + } + } + return SKY_BAD_HANDLE +} + +//export SKY_api_Handle_WalletResponseIsEncrypted +func SKY_api_Handle_WalletResponseIsEncrypted( + handle C.WalletResponse__Handle, + isEncrypted *bool) uint32 { + w, ok := lookupWalletResponseHandle(handle) + if ok { + *isEncrypted = w.Meta.Encrypted + return SKY_OK + } + return SKY_BAD_HANDLE +} + +//export SKY_api_Handle_WalletResponseGetCryptoType +func SKY_api_Handle_WalletResponseGetCryptoType( + handle C.WalletResponse__Handle, + cryptoType *C.GoString_) uint32 { + w, ok := lookupWalletResponseHandle(handle) + if ok { + copyString(w.Meta.CryptoType, cryptoType) + return SKY_OK + } + return SKY_BAD_HANDLE +} + +//export SKY_api_Handle_WalletsResponseGetCount +func SKY_api_Handle_WalletsResponseGetCount( + handle C.Wallets__Handle, + count *uint32) uint32 { + w, ok := lookupWalletsHandle(handle) + if ok { + *count = uint32(len(w)) + return SKY_OK + } + return SKY_BAD_HANDLE +} + +//export SKY_api_Handle_WalletsResponseGetAt +func SKY_api_Handle_WalletsResponseGetAt( + walletsHandle C.Wallets__Handle, + index uint32, + walletHandle *C.WalletResponse__Handle) uint32 { + w, ok := lookupWalletsHandle(walletsHandle) + if ok { + if index < uint32(len(w)) { + *walletHandle = registerWalletResponseHandle(w[index]) + } + return SKY_OK + } + return SKY_BAD_HANDLE +} + +//export SKY_api_Handle_GetWalletFolderAddress +func SKY_api_Handle_GetWalletFolderAddress( + folderHandle C.Handle, + address *C.GoString_) uint32 { + obj, ok := lookupHandle(folderHandle) + if ok { + if obj, isOK := (obj).(*api.WalletFolder); isOK { + copyString(obj.Address, address) + return SKY_OK + } + } + return SKY_BAD_HANDLE +} + +//export SKY_api_Handle_GetWalletSeed +func SKY_api_Handle_GetWalletSeed(handle C.Wallet__Handle, + seed *C.GoString_) uint32 { + w, ok := lookupWalletHandle(handle) + if ok { + copyString(w.Meta["seed"], seed) + return SKY_OK + } + return SKY_BAD_HANDLE +} + +//export SKY_api_Handle_GetWalletLastSeed +func SKY_api_Handle_GetWalletLastSeed(handle C.Wallet__Handle, + lastSeed *C.GoString_) uint32 { + w, ok := lookupWalletHandle(handle) + if ok { + copyString(w.Meta["lastSeed"], lastSeed) + return SKY_OK + } + return SKY_BAD_HANDLE +} + +//export SKY_api_Handle_GetBuildInfoData +func SKY_api_Handle_GetBuildInfoData(handle C.BuildInfo_Handle, + version *C.GoString_, commit *C.GoString_, branch *C.GoString_) uint32 { + bi, ok := lookupBuildInfoHandle(handle) + if ok { + copyString(bi.Version, version) + copyString(bi.Commit, commit) + copyString(bi.Branch, branch) + return SKY_OK + } + return SKY_BAD_HANDLE +} diff --git a/lib/cgo/libsky_map.go b/lib/cgo/libsky_map.go new file mode 100644 index 0000000000..77bf5436d9 --- /dev/null +++ b/lib/cgo/libsky_map.go @@ -0,0 +1,52 @@ +package main + +/* + + #include + #include + + #include "skytypes.h" +*/ +import "C" + +//export SKY_map_Get +func SKY_map_Get(gomap *C.GoStringMap_, key string, value *C.GoString_) (____error_code uint32) { + obj, ok := lookupHandle(C.Handle(*gomap)) + ____error_code = SKY_ERROR + if ok { + if m, isMap := (obj).(map[string]string); isMap { + result, ok := m[key] + if ok { + copyString(result, value) + ____error_code = SKY_OK + } + } + } + return +} + +//export SKY_map_HasKey +func SKY_map_HasKey(gomap *C.GoStringMap_, key string) (found bool) { + obj, ok := lookupHandle(C.Handle(*gomap)) + found = false + if ok { + if m, isMap := (obj).(map[string]string); isMap { + _, found = m[key] + } + } + return +} + +//export SKY_map_Close +func SKY_map_Close(gomap *C.GoStringMap_) (____error_code uint32) { + obj, ok := lookupHandle(C.Handle(*gomap)) + ____error_code = SKY_ERROR + if ok { + if _, isMap := (obj).(map[string]string); isMap { + closeHandle(Handle(*gomap)) + ____error_code = SKY_OK + } + } + return + +} diff --git a/lib/cgo/libsky_mem.go b/lib/cgo/libsky_mem.go index 7fed1ecdb7..90d886d2da 100644 --- a/lib/cgo/libsky_mem.go +++ b/lib/cgo/libsky_mem.go @@ -5,6 +5,7 @@ import ( "unsafe" "github.com/skycoin/skycoin/src/cipher" + "github.com/skycoin/skycoin/src/util/http" ) /* @@ -30,7 +31,6 @@ const ( SizeofSHA256 = unsafe.Sizeof(C.cipher__SHA256{}) SizeofTransactionOutput = unsafe.Sizeof(C.coin__TransactionOutput{}) SizeofTransaction = unsafe.Sizeof(C.coin__Transaction{}) - SizeofWallet = unsafe.Sizeof(C.wallet__Wallet{}) SizeofEntry = unsafe.Sizeof(C.wallet__Entry{}) SizeofUxBalance = unsafe.Sizeof(C.wallet__UxBalance{}) ) @@ -43,8 +43,8 @@ func inplaceAddress(p *C.cipher__Address) *cipher.Address { return (*cipher.Address)(unsafe.Pointer(p)) } -func nop(p unsafe.Pointer) { - // Do nothing +func inplaceHttpHelperAddress(p *C.httphelper__Address) *httphelper.Address { + return (*httphelper.Address)(unsafe.Pointer(p)) } /** @@ -52,15 +52,14 @@ func nop(p unsafe.Pointer) { */ func copyString(src string, dest *C.GoString_) { - strAddr := (*C.GoString_)(unsafe.Pointer(&src)) srcLen := len(src) - dest.p = (*C.char)(C.memcpy( - C.malloc(C.size_t(srcLen+1)), - unsafe.Pointer(strAddr.p), - C.size_t(srcLen), - )) - C.eos(dest.p, C.int(srcLen)) - dest.n = C.GoInt_(srcLen) + dest.p = (*C.char)(C.malloc(C.size_t(srcLen + 1))) + strAddr := (*C.GoString_)(unsafe.Pointer(&src)) + result := C.memcpy(unsafe.Pointer(dest.p), unsafe.Pointer(strAddr.p), C.size_t(srcLen)) + if result != nil { + C.eos(dest.p, C.int(srcLen)) + dest.n = C.GoInt_(srcLen) + } } // Determine the memory address of a slice buffer and the @@ -84,7 +83,8 @@ func copyToBuffer(src reflect.Value, dest unsafe.Pointer, n uint) { return } srcAddr, elemSize := getBufferData(src) - nop(C.memcpy(dest, srcAddr, C.size_t(n)*elemSize)) + if C.memcpy(dest, srcAddr, C.size_t(n)*elemSize) != nil { + } } // Copy source slice/array/string onto instance of C.GSlice struct @@ -108,11 +108,43 @@ func copyToGoSlice(src reflect.Value, dest *C.GoSlice_) { if overflow { n = int(dest.cap) } - nop(C.memcpy(dest.data, srcAddr, C.size_t(n)*elemSize)) - // Do not modify slice metadata until memory is actually copied - if overflow { - dest.len = dest.cap - C.GoInt_(srcLen) - } else { - dest.len = C.GoInt_(srcLen) + result := C.memcpy(dest.data, srcAddr, C.size_t(n)*elemSize) + if result != nil { + // Do not modify slice metadata until memory is actually copied + if overflow { + dest.len = dest.cap - C.GoInt_(srcLen) + } else { + dest.len = C.GoInt_(srcLen) + } + } +} + +func copyToStringMap(gomap map[string]string, dest *C.GoStringMap_) { + *dest = (C.GoStringMap_)(registerHandle(gomap)) +} + +func splitCliArgs(args string) (result []string) { + prevSep := -1 + quoted := false + var i int + for i = 0; i < len(args); i++ { + if args[i] == '"' { + quoted = !quoted + if !quoted { + result = append(result, args[prevSep+1:i]) + } + prevSep = i + } else if !quoted && args[i] == ' ' { + if prevSep+1 < i { + result = append(result, args[prevSep+1:i]) + } + prevSep = i + } + } + if len(args) > 0 { + if prevSep+1 < i { + result = append(result, args[prevSep+1:i]) + } } + return } diff --git a/lib/cgo/libsky_time.go b/lib/cgo/libsky_time.go new file mode 100644 index 0000000000..256dd4e76b --- /dev/null +++ b/lib/cgo/libsky_time.go @@ -0,0 +1,14 @@ +package main + +import ( + "log" + "time" +) + +func parseTimeValue(strTime string) (time.Time, error) { + t, err := time.Parse(time.RFC3339, strTime) + if err != nil { + log.Printf("Time conversion error. Format=%s Value=\"%s\" Error: %s", time.RFC3339, strTime, err) + } + return t, err +} diff --git a/lib/cgo/libsky_types.go b/lib/cgo/libsky_types.go new file mode 100644 index 0000000000..20c43f143e --- /dev/null +++ b/lib/cgo/libsky_types.go @@ -0,0 +1,23 @@ +package main + +import ( + cipher "github.com/skycoin/skycoin/src/cipher" +) + +/* + + #include + #include + + #include "skytypes.h" +*/ +import "C" + +func toAddresserArray(addrs []cipher.Address) []cipher.Addresser { + // TODO : Support for arrays of interface objects in cgogen + var __addrs = make([]cipher.Addresser, len(addrs)) + for _, addr := range addrs { + __addrs = append(__addrs, addr) + } + return __addrs +} diff --git a/lib/cgo/tests/check_cipher.address.c b/lib/cgo/tests/check_cipher.address.c index dcfc80cb93..7af917351c 100644 --- a/lib/cgo/tests/check_cipher.address.c +++ b/lib/cgo/tests/check_cipher.address.c @@ -1,7 +1,6 @@ #include #include - #include #include @@ -73,7 +72,6 @@ Test(cipher_address, TestDecodeBase58Address) { errorcode == SKY_ErrInvalidBase58Char, "trailing zeroes suffix are invalid" ); - } Test(cipher_address, TestAddressFromBytes){ @@ -117,13 +115,13 @@ Test(cipher_address, TestAddressVerify){ cipher__SecKey seckey2; cipher__Address addr; - SKY_cipher_GenerateKeyPair(&pubkey,&seckey); - SKY_cipher_AddressFromPubKey(&pubkey,&addr); + SKY_cipher_GenerateKeyPair(&pubkey, &seckey); + SKY_cipher_AddressFromPubKey(&pubkey, &addr); // Valid pubkey+address cr_assert( SKY_cipher_Address_Verify(&addr,&pubkey) == SKY_OK ,"Valid pubkey + address"); - SKY_cipher_GenerateKeyPair(&pubkey,&seckey2); + SKY_cipher_GenerateKeyPair(&pubkey, &seckey2); // // Invalid pubkey cr_assert( SKY_cipher_Address_Verify(&addr,&pubkey) == SKY_ErrAddressInvalidPubKey," Invalid pubkey"); @@ -152,31 +150,51 @@ Test(cipher_address,TestAddressString){ cr_assert(eq(type(cipher__Address), addr, addr2)); } -Test(cipher_address, TestAddressBulk){ +Test(cipher_address, TestAddressBulk) { unsigned char buff[50]; - GoSlice slice = { buff, 0, 50 }; + GoSlice slice = {buff, 0, 50}; - for (int i = 0; i < 1024; ++i) - { - randBytes(&slice,32); + for (int i = 0; i < 1024; ++i) { + randBytes(&slice, 32); cipher__PubKey pubkey; cipher__SecKey seckey; - SKY_cipher_GenerateDeterministicKeyPair( slice,&pubkey,&seckey); + SKY_cipher_GenerateDeterministicKeyPair(slice, &pubkey, &seckey); cipher__Address addr; - SKY_cipher_AddressFromPubKey(&pubkey,&addr); + SKY_cipher_AddressFromPubKey(&pubkey, &addr); unsigned int err; - err = SKY_cipher_Address_Verify(&addr,&pubkey); + err = SKY_cipher_Address_Verify(&addr, &pubkey); cr_assert(err == SKY_OK); GoString strAddr; SKY_cipher_Address_String(&addr, (GoString_ *)&strAddr); registerMemCleanup((void *) strAddr.p); cipher__Address addr2; - err = SKY_cipher_DecodeBase58Address(strAddr,&addr2); + err = SKY_cipher_DecodeBase58Address(strAddr, &addr2); cr_assert(err == SKY_OK); - cr_assert(eq(type(cipher__Address),addr,addr2)); + cr_assert(eq(type(cipher__Address), addr, addr2)); } } +Test(cipher_address, TestAddressNull) { + cipher__Address a; + memset(&a, 0, sizeof(cipher__Address)); + GoUint32 result; + GoUint8 isNull; + result = SKY_cipher_Address_Null(&a, &isNull); + cr_assert(result == SKY_OK, "SKY_cipher_Address_Null"); + cr_assert(isNull == 1); + + cipher__PubKey p; + cipher__SecKey s; + + result = SKY_cipher_GenerateKeyPair(&p, &s); + cr_assert(result == SKY_OK, "SKY_cipher_GenerateKeyPair failed"); + + result = SKY_cipher_AddressFromPubKey(&p, &a); + cr_assert(result == SKY_OK, "SKY_cipher_AddressFromPubKey failed"); + result = SKY_cipher_Address_Null(&a, &isNull); + cr_assert(result == SKY_OK, "SKY_cipher_Address_Null"); + cr_assert(isNull == 0); +} diff --git a/lib/cgo/tests/check_cipher.crypto.c b/lib/cgo/tests/check_cipher.crypto.c index b8282c0a4d..00e48dac42 100644 --- a/lib/cgo/tests/check_cipher.crypto.c +++ b/lib/cgo/tests/check_cipher.crypto.c @@ -1,5 +1,3 @@ - -#include #include #include @@ -11,13 +9,10 @@ #include "skystring.h" #include "skytest.h" -#if __APPLE__ - #include "TargetConditionals.h" -#endif - TestSuite(cipher_crypto, .init = setup, .fini = teardown); -Test(cipher_crypto, TestNewPubKey) { +Test(cipher_crypto, TestNewPubKey) +{ unsigned char buff[101]; GoSlice slice; cipher__PubKey pk, pk2; @@ -59,13 +54,14 @@ Test(cipher_crypto, TestNewPubKey) { cr_assert(eq(u8[33], pk, pk2)); } -Test(cipher_crypto, TestPubKeyFromHex) { +Test(cipher_crypto, TestPubKeyFromHex) +{ cipher__PubKey p, p1; cipher__SecKey sk; GoString s; unsigned char buff[51]; char sbuff[101]; - GoSlice slice = { (void *)buff, 0, 51 }; + GoSlice slice = {(void *)buff, 0, 51}; unsigned int errorcode; // Invalid hex @@ -97,37 +93,40 @@ Test(cipher_crypto, TestPubKeyFromHex) { cr_assert(eq(u8[33], p, p1)); } -Test(cipher_crypto, TestPubKeyHex) { +Test(cipher_crypto, TestPubKeyHex) +{ cipher__PubKey p, p2; cipher__SecKey sk; GoString s3, s4; unsigned char buff[50]; - GoSlice slice = { buff, 0, 50}; + GoSlice slice = {buff, 0, 50}; unsigned int errorcode; SKY_cipher_GenerateKeyPair(&p, &sk); - SKY_cipher_PubKey_Hex(&p, (GoString_ *) &s3); - registerMemCleanup((void *) s3.p); + SKY_cipher_PubKey_Hex(&p, (GoString_ *)&s3); + registerMemCleanup((void *)s3.p); errorcode = SKY_cipher_PubKeyFromHex(s3, &p2); cr_assert(errorcode == SKY_OK); cr_assert(eq(u8[33], p, p2)); SKY_cipher_PubKey_Hex(&p2, (GoString_ *)&s4); - registerMemCleanup((void *) s4.p); + registerMemCleanup((void *)s4.p); // TODO: Translate into cr_assert(eq(type(GoString), s3, s4)); cr_assert(s3.n == s4.n); - cr_assert(eq(str, ((char *) s3.p), ((char *) s4.p))); + cr_assert(eq(str, ((char *)s3.p), ((char *)s4.p))); } -Test(cipher_crypto, TestPubKeyVerify) { +Test(cipher_crypto, TestPubKeyVerify) +{ cipher__PubKey p; unsigned char buff[50]; - GoSlice slice = { buff, 0, 50 }; + GoSlice slice = {buff, 0, 50}; unsigned int errorcode; bool failed = false; int i = 0; - for (; i < 10; i++) { + for (; i < 10; i++) + { randBytes(&slice, 33); memcpy((void *) &p, slice.data, 33); failed = failed || (errorcode = SKY_cipher_PubKey_Verify(&p)); @@ -135,20 +134,21 @@ Test(cipher_crypto, TestPubKeyVerify) { cr_assert(failed); } -Test(cipher_crypto, TestPubKeyVerifyNil) { +Test(cipher_crypto, TestPubKeyVerifyNil) +{ cipher__PubKey p = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0 - }; + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0}; unsigned int errorcode; errorcode = SKY_cipher_PubKey_Verify(&p); cr_assert(errorcode == SKY_ErrInvalidPubKey); } -Test(cipher_crypto, TestPubKeyVerifyDefault1) { +Test(cipher_crypto, TestPubKeyVerifyDefault1) +{ cipher__PubKey p; cipher__SecKey s; @@ -157,12 +157,14 @@ Test(cipher_crypto, TestPubKeyVerifyDefault1) { cr_assert(errorcode == SKY_OK); } -Test(cipher_crypto, TestPubKeyVerifyDefault2) { +Test(cipher_crypto, TestPubKeyVerifyDefault2) +{ cipher__PubKey p; cipher__SecKey s; int i; - for (i = 0; i < 1024; ++i) { + for (i = 0; i < 1024; ++i) + { SKY_cipher_GenerateKeyPair(&p, &s); unsigned int errorcode = SKY_cipher_PubKey_Verify(&p); cr_assert(errorcode == SKY_OK); @@ -315,7 +317,7 @@ Test(cipher_crypto, TestSecKeyHex) { randBytes(&b, 32); SKY_cipher_NewSecKey(b, &sk); SKY_cipher_SecKey_Hex(&sk, (GoString_ *)&str); - registerMemCleanup((void *) str.p); + registerMemCleanup((void *)str.p); // Copy early to ensure memory is released strncpy((char *) h.p, str.p, str.n); @@ -468,7 +470,7 @@ Test(cipher_crypto, TestSigHex) { unsigned char buff[66]; GoSlice b = {buff, 0, 66}; char strBuff[150], - strBuff2[150]; + strBuff2[150]; GoString str = {NULL, 0}, str2 = {NULL, 0}; cipher__Sig s, s2; @@ -491,7 +493,7 @@ Test(cipher_crypto, TestSigHex) { } // FIXME: Split in multiple test cases so as to catch panic at the right place -Test(cipher_crypto, TestChkSig) { +Test(cipher_crypto, TestVerifyAddressSignedHash) { cipher__PubKey pk, pk2; cipher__SecKey sk, sk2; cipher__Address addr, addr2; @@ -513,20 +515,20 @@ Test(cipher_crypto, TestChkSig) { randBytes(&b, 256); SKY_cipher_SumSHA256(b, &h); SKY_cipher_SignHash(&h, &sk, &sig); - errorcode = SKY_cipher_ChkSig(&addr, &h, &sig); + errorcode = SKY_cipher_VerifyAddressSignedHash(&addr, &sig, &h); cr_assert(errorcode == SKY_OK); // Empty sig should be invalid memset(&sig, 0, sizeof(sig)); - errorcode = SKY_cipher_ChkSig(&addr, &h, &sig); - cr_assert(errorcode == SKY_ErrInvalidSigForPubKey); + errorcode = SKY_cipher_VerifyAddressSignedHash(&addr, &sig, &h); + cr_assert(errorcode == SKY_ErrInvalidSigPubKeyRecovery); // Random sigs should not pass int i; for (i = 0; i < 100; i++) { randBytes(&b, 65); SKY_cipher_NewSig(b, &sig); - errorcode = SKY_cipher_ChkSig(&addr, &h, &sig); + errorcode = SKY_cipher_VerifyAddressSignedHash(&addr, &sig, &h); cr_assert(errorcode != SKY_OK); // One of many error codes } @@ -534,11 +536,11 @@ Test(cipher_crypto, TestChkSig) { randBytes(&b, 256); SKY_cipher_SumSHA256(b, &h2); SKY_cipher_SignHash(&h2, &sk, &sig2); - errorcode = SKY_cipher_ChkSig(&addr, &h2, &sig2); + errorcode = SKY_cipher_VerifyAddressSignedHash(&addr, &sig2, &h2); cr_assert(errorcode == SKY_OK); - errorcode = SKY_cipher_ChkSig(&addr, &h, &sig2); + errorcode = SKY_cipher_VerifyAddressSignedHash(&addr, &sig2, &h); cr_assert(errorcode == SKY_ErrInvalidAddressForSig); - errorcode = SKY_cipher_ChkSig(&addr, &h2, &sig); + errorcode = SKY_cipher_VerifyAddressSignedHash(&addr, &sig, &h2); cr_assert(errorcode != SKY_OK); // One of many error codes // Different secret keys should not create same sig @@ -547,9 +549,9 @@ Test(cipher_crypto, TestChkSig) { memset(&h, 0, sizeof(h)); SKY_cipher_SignHash(&h, &sk, &sig); SKY_cipher_SignHash(&h, &sk2, &sig2); - errorcode = SKY_cipher_ChkSig(&addr, &h, &sig); + errorcode = SKY_cipher_VerifyAddressSignedHash(&addr, &sig, &h); cr_assert(errorcode == SKY_OK); - errorcode = SKY_cipher_ChkSig(&addr2, &h, &sig2); + errorcode = SKY_cipher_VerifyAddressSignedHash(&addr2, &sig2, &h); cr_assert(errorcode == SKY_OK); cr_assert(not(eq(u8[65], sig, sig2))); @@ -557,20 +559,20 @@ Test(cipher_crypto, TestChkSig) { SKY_cipher_SumSHA256(b, &h); SKY_cipher_SignHash(&h, &sk, &sig); SKY_cipher_SignHash(&h, &sk2, &sig2); - errorcode = SKY_cipher_ChkSig(&addr, &h, &sig); + errorcode = SKY_cipher_VerifyAddressSignedHash(&addr, &sig, &h); cr_assert(errorcode == SKY_OK); - errorcode = SKY_cipher_ChkSig(&addr2, &h, &sig2); + errorcode = SKY_cipher_VerifyAddressSignedHash(&addr2, &sig2, &h); cr_assert(errorcode == SKY_OK); cr_assert(not(eq(u8[65], sig, sig2))); // Bad address should be invalid - errorcode = SKY_cipher_ChkSig(&addr, &h, &sig2); + errorcode = SKY_cipher_VerifyAddressSignedHash(&addr, &sig2, &h); cr_assert(errorcode == SKY_ErrInvalidAddressForSig); - errorcode = SKY_cipher_ChkSig(&addr2, &h, &sig); + errorcode = SKY_cipher_VerifyAddressSignedHash(&addr2, &sig, &h); cr_assert(errorcode == SKY_ErrInvalidAddressForSig); } -Test(cipher_crypto, TestSignHash) { +Test(cipher_crypto, TestSignHash) { cipher__PubKey pk, pk2; cipher__SecKey sk; cipher__Address addr; @@ -589,7 +591,7 @@ Test(cipher_crypto, TestSignHash) { cr_assert(errorcode == SKY_OK); memset((void *) &sig2, 0, 65); cr_assert(not(eq(u8[65], sig2, sig))); - errorcode = SKY_cipher_ChkSig(&addr, &h, &sig); + errorcode = SKY_cipher_VerifyAddressSignedHash(&addr, &sig, &h); cr_assert(errorcode == SKY_OK); errorcode = SKY_cipher_PubKeyFromSig(&sig, &h, &pk2); @@ -649,10 +651,10 @@ Test(cipher_crypto, TestPubKeyFromSig) { memset(&sig, 0, sizeof(sig)); errorcode = SKY_cipher_PubKeyFromSig(&sig, &h, &pk2); - cr_assert(errorcode == SKY_ErrInvalidSigForPubKey); + cr_assert(errorcode == SKY_ErrInvalidSigPubKeyRecovery); } -Test(cipher_crypto, TestVerifySignature) { +Test(cipher_crypto, TestVerifyPubKeySignedHash) { cipher__PubKey pk, pk2; cipher__SecKey sk, sk2; cipher__SHA256 h, h2; @@ -667,25 +669,61 @@ Test(cipher_crypto, TestVerifySignature) { randBytes(&b, 256); SKY_cipher_SumSHA256(b, &h2); SKY_cipher_SignHash(&h, &sk, &sig); - errorcode = SKY_cipher_VerifySignature(&pk, &sig, &h); + errorcode = SKY_cipher_VerifyPubKeySignedHash(&pk, &sig, &h); cr_assert(errorcode == SKY_OK); memset(&sig2, 0, sizeof(sig2)); - errorcode = SKY_cipher_VerifySignature(&pk, &sig2, &h); - cr_assert(errorcode == SKY_ErrInvalidSigForPubKey); + errorcode = SKY_cipher_VerifyPubKeySignedHash(&pk, &sig2, &h); + cr_assert(errorcode == SKY_ErrInvalidSigPubKeyRecovery); - errorcode = SKY_cipher_VerifySignature(&pk, &sig, &h2); + errorcode = SKY_cipher_VerifyPubKeySignedHash(&pk, &sig, &h2); cr_assert(errorcode == SKY_ErrPubKeyRecoverMismatch); SKY_cipher_GenerateKeyPair(&pk2, &sk2); - errorcode = SKY_cipher_VerifySignature(&pk2, &sig, &h); + errorcode = SKY_cipher_VerifyPubKeySignedHash(&pk2, &sig, &h); cr_assert(errorcode == SKY_ErrPubKeyRecoverMismatch); memset(&pk2, 0, sizeof(pk2)); - errorcode = SKY_cipher_VerifySignature(&pk2, &sig, &h); + errorcode = SKY_cipher_VerifyPubKeySignedHash(&pk2, &sig, &h); cr_assert(errorcode == SKY_ErrPubKeyRecoverMismatch); } +Test(cipher_crypto, TestVerifySignedHash) { + cipher__SHA256 h; + cipher__Sig sig, badSig1, badSig2; + GoString hS, sigS, badSig1S, badSig2S; + int error; + + hS.p = "127e9b0d6b71cecd0363b366413f0f19fcd924ae033513498e7486570ff2a1c8"; + hS.n = strlen(hS.p); + error = SKY_cipher_SHA256FromHex(hS, &h); + cr_assert(error == SKY_OK); + + sigS.p = "63c035b0c95d0c5744fc1c0bdf38af02cef2d2f65a8f923732ab44e436f8a491216d9ab5ff795e3144f4daee37077b8b9db54d2ba3a3df8d4992f06bb21f724401"; + sigS.n = strlen(sigS.p); + error = SKY_cipher_SigFromHex(sigS, &sig); + cr_assert(error == SKY_OK); + + badSig1S.p = "71f2c01516fe696328e79bcf464eb0db374b63d494f7a307d1e77114f18581d7a81eed5275a9e04a336292dd2fd16977d9bef2a54ea3161d0876603d00c53bc9dd"; + badSig1S.n = strlen(badSig1S.p); + error = SKY_cipher_SigFromHex(badSig1S, &badSig1); + cr_assert(error == SKY_OK); + + badSig2S.p = "63c035b0c95d0c5744fc1c0bdf39af02cef2d2f65a8f923732ab44e436f8a491216d9ab5ff795e3144f4daee37077b8b9db54d2ba3a3df8d4992f06bb21f724401"; + badSig2S.n = strlen(badSig2S.p); + error = SKY_cipher_SigFromHex(badSig2S, &badSig2); + cr_assert(error == SKY_OK); + + error = SKY_cipher_VerifySignedHash(&sig, &h); + cr_assert(error == SKY_OK); + + error = SKY_cipher_VerifySignedHash(&badSig1, &h); + cr_assert(error == SKY_ErrInvalidHashForSig); + + error = SKY_cipher_VerifySignedHash(&badSig2, &h); + cr_assert(error == SKY_ErrInvalidSigPubKeyRecovery); +} + Test(cipher_crypto, TestGenerateKeyPair) { cipher__PubKey pk; cipher__SecKey sk; @@ -749,7 +787,6 @@ Test(cipher_crypto, TestSecKeyHashTest) { errorcode = SKY_cipher_CheckSecKeyHash(&sk, &h); cr_assert(errorcode == SKY_OK); - memset(&sk, 0, sizeof(sk)); errorcode = SKY_cipher_CheckSecKeyHash(&sk, &h); cr_assert(errorcode == SKY_ErrInvalidSecKyVerification); diff --git a/lib/cgo/tests/check_cipher.encrypt.scrypt_chacha20poly1305.c b/lib/cgo/tests/check_cipher.encrypt.scrypt_chacha20poly1305.c new file mode 100644 index 0000000000..0a9265539f --- /dev/null +++ b/lib/cgo/tests/check_cipher.encrypt.scrypt_chacha20poly1305.c @@ -0,0 +1,207 @@ +#include +#include +#include + +#include +#include + +#include "libskycoin.h" +#include "skyerrors.h" +#include "skystring.h" +#include "skytest.h" +#include "base64.h" + +#define PLAINTEXT "plaintext" +#define PASSWORD "password" +#define PASSWORD2 "pwd" +#define WRONG_PASSWORD "wrong password" +#define ENCRYPTED "dQB7Im4iOjUyNDI4OCwiciI6OCwicCI6MSwia2V5TGVuIjozMiwic2FsdCI6ImpiejUrSFNjTFFLWkI5T0tYblNNRmt2WDBPY3JxVGZ0ZFpDNm9KUFpaeHc9Iiwibm9uY2UiOiJLTlhOQmRQa1ZUWHZYNHdoIn3PQFmOot0ETxTuv//skTG7Q57UVamGCgG5" +#define BUFFER_SIZE 1024 +#define SCRYPTCHACHA20METALENGTHSIZE 2 + +TestSuite(cipher_encrypt_scrypt_chacha20poly1305, .init = setup, .fini = teardown); + +void parseJsonMetaData(char *metadata, int *n, int *r, int *p, int *keyLen) +{ + *n = *r = *p = *keyLen = 0; + int length = strlen(metadata); + int openingQuote = -1; + const char *keys[] = {"n", "r", "p", "keyLen"}; + int keysCount = 4; + int keyIndex = -1; + int startNumber = -1; + for (int i = 0; i < length; i++) + { + if (metadata[i] == '\"') + { + startNumber = -1; + if (openingQuote >= 0) + { + keyIndex = -1; + metadata[i] = 0; + for (int k = 0; k < keysCount; k++) + { + if (strcmp(metadata + openingQuote + 1, keys[k]) == 0) + { + keyIndex = k; + } + } + openingQuote = -1; + } + else + { + openingQuote = i; + } + } + else if (metadata[i] >= '0' && metadata[i] <= '9') + { + if (startNumber < 0) + startNumber = i; + } + else if (metadata[i] == ',') + { + if (startNumber >= 0) + { + metadata[i] = 0; + int number = atoi(metadata + startNumber); + startNumber = -1; + if (keyIndex == 0) + *n = number; + else if (keyIndex == 1) + *r = number; + else if (keyIndex == 2) + *p = number; + else if (keyIndex == 3) + *keyLen = number; + } + } + else + { + startNumber = -1; + } + } +} + +Test(cipher_encrypt_scrypt_chacha20poly1305, TestScryptChacha20poly1305Encrypt) +{ + GoSlice result; + GoSlice nullData; + GoSlice nullPassword; + char str[BUFFER_SIZE]; + GoSlice text; + GoSlice password; + GoSlice password2; + GoSlice wrong_password; + GoSlice encrypted; + + memset(&text, 0, sizeof(GoSlice)); + memset(&password, 0, sizeof(GoSlice)); + memset(&password2, 0, sizeof(GoSlice)); + memset(&wrong_password, 0, sizeof(GoSlice)); + memset(&encrypted, 0, sizeof(GoSlice)); + memset(&nullData, 0, sizeof(GoSlice)); + memset(&nullPassword, 0, sizeof(GoSlice)); + memset(str, 0, BUFFER_SIZE); + + text.data = PLAINTEXT; + text.len = strlen(PLAINTEXT); + text.cap = text.len; + password.data = PASSWORD; + password.len = strlen(PASSWORD); + password.cap = password.len; + password2.data = PASSWORD2; + password2.len = strlen(PASSWORD2); + password2.cap = password2.len; + wrong_password.data = WRONG_PASSWORD; + wrong_password.len = strlen(WRONG_PASSWORD); + wrong_password.cap = wrong_password.len; + encrypted.data = ENCRYPTED; + encrypted.len = strlen(ENCRYPTED); + encrypted.cap = encrypted.len; + + GoUint32 errcode; + unsigned int metalength; + encrypt__ScryptChacha20poly1305 encrypt = {1, 8, 1, 32}; + for (int i = 1; i <= 20; i++) + { + memset(&result, 0, sizeof(GoSlice)); + encrypt.N = 1 << i; + errcode = SKY_encrypt_ScryptChacha20poly1305_Encrypt( + &encrypt, text, password, (GoSlice_ *)&result); + cr_assert(errcode == SKY_OK, "SKY_encrypt_ScryptChacha20poly1305_Encrypt failed"); + registerMemCleanup((void *)result.data); + cr_assert(result.len > SCRYPTCHACHA20METALENGTHSIZE, "SKY_encrypt_ScryptChacha20poly1305_Encrypt failed, result data length too short"); + int decode_len = b64_decode((const unsigned char *)result.data, + result.len, (unsigned char *) str); + cr_assert(decode_len >= SCRYPTCHACHA20METALENGTHSIZE, "base64_decode_string failed"); + cr_assert(decode_len < BUFFER_SIZE, "base64_decode_string failed, buffer overflow"); + metalength = (unsigned int)str[0]; + for (int m = 1; m < SCRYPTCHACHA20METALENGTHSIZE; m++) + { + if (str[m] > 0) + { + metalength += (((unsigned int)str[m]) << (m * 8)); + } + } + cr_assert(metalength + SCRYPTCHACHA20METALENGTHSIZE < decode_len, "SKY_encrypt_ScryptChacha20poly1305_Encrypt failed. Metadata length greater than result lentgh."); + char *meta = str + SCRYPTCHACHA20METALENGTHSIZE; + meta[metalength] = 0; + int n, r, p, keyLen; + parseJsonMetaData(meta, &n, &r, &p, &keyLen); + + cr_assert(n == encrypt.N, "SKY_encrypt_ScryptChacha20poly1305_Encrypt failed. Metadata value N incorrect."); + cr_assert(r == encrypt.R, "SKY_encrypt_ScryptChacha20poly1305_Encrypt failed. Metadata value R incorrect."); + cr_assert(p == encrypt.P, "SKY_encrypt_ScryptChacha20poly1305_Encrypt failed. Metadata value P incorrect."); + cr_assert(keyLen == encrypt.KeyLen, "SKY_encrypt_ScryptChacha20poly1305_Encrypt failed. Metadata value KeyLen incorrect."); + } +} + +Test(cipher_encrypt_scrypt_chacha20poly1305, TestScryptChacha20poly1305Decrypt) +{ + GoSlice result; + GoSlice nullData; + GoSlice nullPassword; + GoSlice text; + GoSlice password; + GoSlice password2; + GoSlice wrong_password; + GoSlice encrypted; + + memset(&text, 0, sizeof(GoSlice)); + memset(&password, 0, sizeof(GoSlice)); + memset(&password2, 0, sizeof(GoSlice)); + memset(&wrong_password, 0, sizeof(GoSlice)); + memset(&encrypted, 0, sizeof(GoSlice)); + memset(&nullData, 0, sizeof(GoSlice)); + memset(&nullPassword, 0, sizeof(GoSlice)); + memset(&result, 0, sizeof(coin__UxArray)); + + text.data = PLAINTEXT; + text.len = strlen(PLAINTEXT); + text.cap = text.len; + password.data = PASSWORD; + password.len = strlen(PASSWORD); + password.cap = password.len; + password2.data = PASSWORD2; + password2.len = strlen(PASSWORD2); + password2.cap = password2.len; + wrong_password.data = WRONG_PASSWORD; + wrong_password.len = strlen(WRONG_PASSWORD); + wrong_password.cap = wrong_password.len; + encrypted.data = ENCRYPTED; + encrypted.len = strlen(ENCRYPTED); + encrypted.cap = encrypted.len; + + GoUint32 errcode; + encrypt__ScryptChacha20poly1305 encrypt = {0, 0, 0, 0}; + errcode = SKY_encrypt_ScryptChacha20poly1305_Decrypt(&encrypt, encrypted, password2, (GoSlice_ *)&result); + cr_assert(errcode == SKY_OK, "SKY_encrypt_ScryptChacha20poly1305_Decrypt failed"); + registerMemCleanup((void *)result.data); + cr_assert(eq(type(GoSlice), text, result)); + result.cap = result.len = 0; + errcode = SKY_encrypt_ScryptChacha20poly1305_Decrypt(&encrypt, encrypted, wrong_password, (GoSlice_ *)&result); + cr_assert(errcode == SKY_ERROR, "SKY_encrypt_ScryptChacha20poly1305_Decrypt decrypted with wrong password."); + result.cap = result.len = 0; + errcode = SKY_encrypt_ScryptChacha20poly1305_Decrypt(&encrypt, encrypted, nullPassword, (GoSlice_ *)&result); + cr_assert(errcode == SKY_ERROR, "SKY_encrypt_ScryptChacha20poly1305_Decrypt decrypted with null password."); +} diff --git a/lib/cgo/tests/check_cipher.hash.c b/lib/cgo/tests/check_cipher.hash.c index 41749c5483..cb0cb14741 100644 --- a/lib/cgo/tests/check_cipher.hash.c +++ b/lib/cgo/tests/check_cipher.hash.c @@ -1,141 +1,146 @@ #include #include - #include #include - #include "libskycoin.h" #include "skyerrors.h" #include "skystring.h" #include "skytest.h" -TestSuite(cipher_hash, .init = setup, .fini = teardown); -void freshSumRipemd160(GoSlice bytes, cipher__Ripemd160 *rp160){ + +TestSuite(cipher_hash, .init = setup, .fini = teardown); + +void freshSumRipemd160(GoSlice bytes, cipher__Ripemd160 *rp160) +{ SKY_cipher_HashRipemd160(bytes, rp160); } -void freshSumSHA256(GoSlice bytes, cipher__SHA256 *sha256){ - +void freshSumSHA256(GoSlice bytes, cipher__SHA256 *sha256) +{ SKY_cipher_SumSHA256(bytes, sha256); } -Test(cipher,TestHashRipemd160){ +Test(cipher, TestHashRipemd160) +{ cipher__Ripemd160 tmp; cipher__Ripemd160 r; cipher__Ripemd160 r2; unsigned char buff[257]; - GoSlice slice = { buff, 0, 257 }; + GoSlice slice = {buff, 0, 257}; - randBytes(&slice,128); - SKY_cipher_HashRipemd160(slice,&tmp); - randBytes(&slice,160); - SKY_cipher_HashRipemd160(slice,&r); - cr_assert(not(eq(u8[sizeof(cipher__Ripemd160)],tmp,r))); + randBytes(&slice, 128); + SKY_cipher_HashRipemd160(slice, &tmp); + randBytes(&slice, 160); + SKY_cipher_HashRipemd160(slice, &r); + cr_assert(not(eq(u8[sizeof(cipher__Ripemd160)], tmp, r))); unsigned char buff1[257]; - GoSlice b = { buff1, 0, 257 }; - randBytes(&b,256); - SKY_cipher_HashRipemd160(b,&r2); - cr_assert(not(eq(u8[sizeof(cipher__Ripemd160)],r2,tmp))); - freshSumRipemd160(b,&tmp); - cr_assert(eq(u8[20],tmp,r2)); + GoSlice b = {buff1, 0, 257}; + randBytes(&b, 256); + SKY_cipher_HashRipemd160(b, &r2); + cr_assert(not(eq(u8[sizeof(cipher__Ripemd160)], r2, tmp))); + freshSumRipemd160(b, &tmp); + cr_assert(eq(u8[20], tmp, r2)); } -Test(cipher_hash,TestRipemd160Set){ +Test(cipher_hash, TestRipemd160Set) +{ cipher__Ripemd160 h; unsigned char buff[101]; - GoSlice slice = { buff, 0, 101 }; + GoSlice slice = {buff, 0, 101}; int error; memset(h, 0, sizeof(cipher__Ripemd160)); - randBytes(&slice,21); + randBytes(&slice, 21); - error = SKY_cipher_Ripemd160_Set(&h,slice); - cr_assert( error == SKY_ErrInvalidLengthRipemd160); + error = SKY_cipher_Ripemd160_Set(&h, slice); + cr_assert(error == SKY_ErrInvalidLengthRipemd160); - randBytes(&slice,100); - error = SKY_cipher_Ripemd160_Set(&h,slice); + randBytes(&slice, 100); + error = SKY_cipher_Ripemd160_Set(&h, slice); cr_assert(error == SKY_ErrInvalidLengthRipemd160); - randBytes(&slice,19); - error = SKY_cipher_Ripemd160_Set(&h,slice); + randBytes(&slice, 19); + error = SKY_cipher_Ripemd160_Set(&h, slice); cr_assert(error == SKY_ErrInvalidLengthRipemd160); - randBytes(&slice,0); - error = SKY_cipher_Ripemd160_Set(&h,slice); + randBytes(&slice, 0); + error = SKY_cipher_Ripemd160_Set(&h, slice); cr_assert(error == SKY_ErrInvalidLengthRipemd160); - randBytes(&slice,20); - error = SKY_cipher_Ripemd160_Set(&h,slice); + randBytes(&slice, 20); + error = SKY_cipher_Ripemd160_Set(&h, slice); cr_assert(error == SKY_OK); cr_assert(eq(u8[20], h, buff)); } -Test(cipher_hash,TestSHA256Set){ +Test(cipher_hash, TestSHA256Set) +{ cipher__SHA256 h; unsigned char buff[101]; - GoSlice slice = { buff, 0, 101 }; + GoSlice slice = {buff, 0, 101}; int error; - randBytes(&slice,33); - error=SKY_cipher_SHA256_Set(&h,slice); + randBytes(&slice, 33); + error = SKY_cipher_SHA256_Set(&h, slice); cr_assert(error == SKY_ErrInvalidLengthSHA256); - randBytes(&slice,100); - error=SKY_cipher_SHA256_Set(&h,slice); + randBytes(&slice, 100); + error = SKY_cipher_SHA256_Set(&h, slice); cr_assert(error == SKY_ErrInvalidLengthSHA256); - randBytes(&slice,31); - error=SKY_cipher_SHA256_Set(&h,slice); + randBytes(&slice, 31); + error = SKY_cipher_SHA256_Set(&h, slice); cr_assert(error == SKY_ErrInvalidLengthSHA256); - randBytes(&slice,0); - error=SKY_cipher_SHA256_Set(&h,slice); + randBytes(&slice, 0); + error = SKY_cipher_SHA256_Set(&h, slice); cr_assert(error == SKY_ErrInvalidLengthSHA256); - randBytes(&slice,32); - error=SKY_cipher_SHA256_Set(&h,slice); + randBytes(&slice, 32); + error = SKY_cipher_SHA256_Set(&h, slice); cr_assert(error == SKY_OK); cr_assert(eq(u8[32], h, slice.data)); } -Test(cipher_hash,TestSHA256Hex){ +Test(cipher_hash, TestSHA256Hex) +{ cipher__SHA256 h; unsigned char buff[101]; - GoSlice slice = { buff, 0, 101 }; + GoSlice slice = {buff, 0, 101}; int error; memset(&h, 0, sizeof(h)); - randBytes(&slice,32); - SKY_cipher_SHA256_Set(&h,slice); + randBytes(&slice, 32); + SKY_cipher_SHA256_Set(&h, slice); GoString s; SKY_cipher_SHA256_Hex(&h, (GoString_ *)&s); - registerMemCleanup((void*) s.p); + registerMemCleanup((void *)s.p); cipher__SHA256 h2; - error = SKY_cipher_SHA256FromHex(s, &h2 ); + error = SKY_cipher_SHA256FromHex(s, &h2); cr_assert(error == SKY_OK); - cr_assert(eq(u8[32],h,h2)); + cr_assert(eq(u8[32], h, h2)); GoString s2; - SKY_cipher_SHA256_Hex(&h2, (GoString_ *) &s2); - registerMemCleanup((void*) s2.p); - cr_assert(eq(type(GoString),s,s2)); + SKY_cipher_SHA256_Hex(&h2, (GoString_ *)&s2); + registerMemCleanup((void *)s2.p); + cr_assert(eq(type(GoString), s, s2)); } -Test(cipher_hash,TestSHA256KnownValue){ - +Test(cipher_hash, TestSHA256KnownValue) +{ - typedef struct + typedef struct { char *input; char *output; @@ -144,13 +149,16 @@ Test(cipher_hash,TestSHA256KnownValue){ tmpstruct vals[3]; vals[0].input = "skycoin"; - vals[0].output = "5a42c0643bdb465d90bf673b99c14f5fa02db71513249d904573d2b8b63d353d"; + vals[0].output = + "5a42c0643bdb465d90bf673b99c14f5fa02db71513249d904573d2b8b63d353d"; vals[1].input = "hello world"; - vals[1].output = "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"; + vals[1].output = + "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"; vals[2].input = "hello world asd awd awd awdapodawpokawpod "; - vals[2].output = "99d71f95cafe05ea2dddebc35b6083bd5af0e44850c9dc5139b4476c99950be4"; + vals[2].output = + "99d71f95cafe05ea2dddebc35b6083bd5af0e44850c9dc5139b4476c99950be4"; for (int i = 0; i < 3; ++i) { @@ -159,145 +167,147 @@ Test(cipher_hash,TestSHA256KnownValue){ slice_input.data = vals[i].input; slice_input.len = strlen(vals[i].input); - slice_input.cap = strlen(vals[i].input)+1; + slice_input.cap = strlen(vals[i].input) + 1; cipher__SHA256 sha; - SKY_cipher_SumSHA256(slice_input,&sha); + SKY_cipher_SumSHA256(slice_input, &sha); GoString_ tmp_output; - SKY_cipher_SHA256_Hex(&sha,&tmp_output); - registerMemCleanup((void*) tmp_output.p); + SKY_cipher_SHA256_Hex(&sha, &tmp_output); + registerMemCleanup((void *)tmp_output.p); - cr_assert(strcmp(tmp_output.p,vals[i].output)== SKY_OK); + cr_assert(strcmp(tmp_output.p, vals[i].output) == SKY_OK); } } -Test(cipher_hash,TestSumSHA256){ +Test(cipher_hash, TestSumSHA256) +{ - unsigned char bbuff[257], - cbuff[257]; - GoSlice b = { bbuff, 0, 257 }; + unsigned char bbuff[257], cbuff[257]; + GoSlice b = {bbuff, 0, 257}; cipher__SHA256 h1; - randBytes(&b,256); - SKY_cipher_SumSHA256(b,&h1); + randBytes(&b, 256); + SKY_cipher_SumSHA256(b, &h1); cipher__SHA256 tmp; - cr_assert(not(eq(u8[32],h1,tmp))); - GoSlice c = { cbuff, 0, 257 }; - randBytes(&c,256); + cr_assert(not(eq(u8[32], h1, tmp))); + GoSlice c = {cbuff, 0, 257}; + randBytes(&c, 256); cipher__SHA256 h2; - SKY_cipher_SumSHA256(c,&h2); - cr_assert(not(eq(u8[32],h2,tmp))); + SKY_cipher_SumSHA256(c, &h2); + cr_assert(not(eq(u8[32], h2, tmp))); cipher__SHA256 tmp_h2; - freshSumSHA256(c,&tmp_h2); - cr_assert(eq(u8[32],h2,tmp_h2)); + freshSumSHA256(c, &tmp_h2); + cr_assert(eq(u8[32], h2, tmp_h2)); } -Test(cipher_hash,TestSHA256FromHex){ +Test(cipher_hash, TestSHA256FromHex) +{ unsigned int error; cipher__SHA256 tmp; // Invalid hex hash - GoString tmp_string = {"cawcd",5}; - error = SKY_cipher_SHA256FromHex(tmp_string,&tmp); + GoString tmp_string = {"cawcd", 5}; + error = SKY_cipher_SHA256FromHex(tmp_string, &tmp); cr_assert(error == SKY_ERROR); // Truncated hex hash cipher__SHA256 h; unsigned char buff[130]; char sbuff[300]; - GoSlice slice = { buff,0,130 }; - randBytes(&slice,128); - SKY_cipher_SumSHA256(slice,&h); + GoSlice slice = {buff, 0, 130}; + randBytes(&slice, 128); + SKY_cipher_SumSHA256(slice, &h); bytesnhex(h,sbuff,sizeof(h) >> 1); - GoString s1 = { sbuff, strlen(sbuff) }; - error = SKY_cipher_SHA256FromHex(s1,&h); + GoString s1 = {sbuff, strlen(sbuff)}; + error = SKY_cipher_SHA256FromHex(s1, &h); cr_assert(error == SKY_ErrInvalidHexLength); // Valid hex hash GoString_ s2 = {NULL, 0}; - SKY_cipher_SHA256_Hex(&h, &s2 ); + SKY_cipher_SHA256_Hex(&h, &s2); registerMemCleanup((void *) s2.p); cipher__SHA256 h2; - error = SKY_cipher_SHA256FromHex((*((GoString *) &s2)),&h2); + error = SKY_cipher_SHA256FromHex((*((GoString *)&s2)), &h2); cr_assert(error == SKY_OK); - cr_assert(eq(u8[32],h,h2)); + cr_assert(eq(u8[32], h, h2)); } - -Test(cipher_hash,TestDoubleSHA256){ +Test(cipher_hash, TestDoubleSHA256) +{ unsigned char bbuff[130]; - GoSlice b = { bbuff, 0, 130 }; - randBytes(&b,128); + GoSlice b = {bbuff, 0, 130}; + randBytes(&b, 128); cipher__SHA256 h; cipher__SHA256 tmp; - SKY_cipher_DoubleSHA256(b,&h); - cr_assert(not(eq(u8[32],tmp,h))); - freshSumSHA256(b,&tmp); - cr_assert(not(eq(u8[32],tmp,h))); + SKY_cipher_DoubleSHA256(b, &h); + cr_assert(not(eq(u8[32], tmp, h))); + freshSumSHA256(b, &tmp); + cr_assert(not(eq(u8[32], tmp, h))); } -Test(cipher_hash,TestAddSHA256){ +Test(cipher_hash, TestAddSHA256) +{ unsigned char bbuff[130]; - GoSlice b = { bbuff, 0, 130 }; - randBytes(&b,128); + GoSlice b = {bbuff, 0, 130}; + randBytes(&b, 128); cipher__SHA256 h; - SKY_cipher_SumSHA256(b,&h); + SKY_cipher_SumSHA256(b, &h); unsigned char cbuff[130]; - GoSlice c = { cbuff, 0, 130 }; - randBytes(&c,64); + GoSlice c = {cbuff, 0, 130}; + randBytes(&c, 64); cipher__SHA256 i; - SKY_cipher_SumSHA256(c,&i); + SKY_cipher_SumSHA256(c, &i); cipher__SHA256 add; cipher__SHA256 tmp; - SKY_cipher_AddSHA256(&h,&i,&add); + SKY_cipher_AddSHA256(&h, &i, &add); - cr_assert(not(eq(u8[32],add,tmp))); - cr_assert(not(eq(u8[32],add,h))); - cr_assert(not(eq(u8[32],add,i))); + cr_assert(not(eq(u8[32], add, tmp))); + cr_assert(not(eq(u8[32], add, h))); + cr_assert(not(eq(u8[32], add, i))); } -Test(cipher_hash,TestXorSHA256){ +Test(cipher_hash, TestXorSHA256) +{ - unsigned char bbuff[129], - cbuff[129]; - GoSlice b = { bbuff, 0, 129 } ; - GoSlice c = { cbuff, 0, 129 }; + unsigned char bbuff[129], cbuff[129]; + GoSlice b = {bbuff, 0, 129}; + GoSlice c = {cbuff, 0, 129}; cipher__SHA256 h, i; - randBytes(&b,128); - SKY_cipher_SumSHA256(b,&h); - randBytes(&c,128); - SKY_cipher_SumSHA256(c,&i); + randBytes(&b, 128); + SKY_cipher_SumSHA256(b, &h); + randBytes(&c, 128); + SKY_cipher_SumSHA256(c, &i); cipher__SHA256 tmp_xor1; cipher__SHA256 tmp_xor2; cipher__SHA256 tmp; - SKY_cipher_SHA256_Xor(&h,&i,&tmp_xor1); - SKY_cipher_SHA256_Xor(&i,&h,&tmp_xor2); - - cr_assert(not(eq(u8[32],tmp_xor1,h))); - cr_assert(not(eq(u8[32],tmp_xor1,i))); - cr_assert(not(eq(u8[32],tmp_xor1,tmp))); - cr_assert(eq(u8[32],tmp_xor1,tmp_xor2)); + SKY_cipher_SHA256_Xor(&h, &i, &tmp_xor1); + SKY_cipher_SHA256_Xor(&i, &h, &tmp_xor2); + cr_assert(not(eq(u8[32], tmp_xor1, h))); + cr_assert(not(eq(u8[32], tmp_xor1, i))); + cr_assert(not(eq(u8[32], tmp_xor1, tmp))); + cr_assert(eq(u8[32], tmp_xor1, tmp_xor2)); } -Test(cipher_hash,TestMerkle){ +Test(cipher_hash, TestMerkle) +{ unsigned char buff[129]; cipher__SHA256 hashlist[5]; - GoSlice b = { buff, 0, 129 }, - hashes = { hashlist, 0, 5 }; + GoSlice b = {buff, 0, 129}, hashes = {hashlist, 0, 5}; cipher__SHA256 h, zero, out, out1, out2, out3, out4; int i; memset(zero, 0, sizeof(zero)); - for (i = 0; i < 5; i++) { + for (i = 0; i < 5; i++) + { randBytes(&b, 128); SKY_cipher_SumSHA256(b, &hashlist[i]); } @@ -309,36 +319,52 @@ Test(cipher_hash,TestMerkle){ // 2 hashes should be Addcipher__SHA256 of them hashes.len = 2; - SKY_cipher_AddSHA256(&hashlist[0], &hashlist[1], &out); + SKY_cipher_AddSHA256(&hashlist[0], &hashlist[1], &out); SKY_cipher_Merkle(&hashes, &h); cr_assert(eq(u8[32], out, h)); // 3 hashes should be Add(Add()) hashes.len = 3; - SKY_cipher_AddSHA256(&hashlist[0], &hashlist[1], &out1); - SKY_cipher_AddSHA256(&hashlist[2], &zero, &out2); - SKY_cipher_AddSHA256(&out1, &out2, &out); + SKY_cipher_AddSHA256(&hashlist[0], &hashlist[1], &out1); + SKY_cipher_AddSHA256(&hashlist[2], &zero, &out2); + SKY_cipher_AddSHA256(&out1, &out2, &out); SKY_cipher_Merkle(&hashes, &h); cr_assert(eq(u8[32], out, h)); // 4 hashes should be Add(Add()) hashes.len = 4; - SKY_cipher_AddSHA256(&hashlist[0], &hashlist[1], &out1); - SKY_cipher_AddSHA256(&hashlist[2], &hashlist[3], &out2); - SKY_cipher_AddSHA256(&out1, &out2, &out); + SKY_cipher_AddSHA256(&hashlist[0], &hashlist[1], &out1); + SKY_cipher_AddSHA256(&hashlist[2], &hashlist[3], &out2); + SKY_cipher_AddSHA256(&out1, &out2, &out); SKY_cipher_Merkle(&hashes, &h); cr_assert(eq(u8[32], out, h)); // 5 hashes hashes.len = 5; - SKY_cipher_AddSHA256(&hashlist[0], &hashlist[1], &out1); - SKY_cipher_AddSHA256(&hashlist[2], &hashlist[3], &out2); - SKY_cipher_AddSHA256(&out1, &out2, &out3); - SKY_cipher_AddSHA256(&hashlist[4], &zero, &out1); - SKY_cipher_AddSHA256(&zero, &zero, &out2); - SKY_cipher_AddSHA256(&out1, &out2, &out4); - SKY_cipher_AddSHA256(&out3, &out4, &out); + SKY_cipher_AddSHA256(&hashlist[0], &hashlist[1], &out1); + SKY_cipher_AddSHA256(&hashlist[2], &hashlist[3], &out2); + SKY_cipher_AddSHA256(&out1, &out2, &out3); + SKY_cipher_AddSHA256(&hashlist[4], &zero, &out1); + SKY_cipher_AddSHA256(&zero, &zero, &out2); + SKY_cipher_AddSHA256(&out1, &out2, &out4); + SKY_cipher_AddSHA256(&out3, &out4, &out); SKY_cipher_Merkle(&hashes, &h); cr_assert(eq(u8[32], out, h)); } + +Test(cipher_hash, TestSHA256Null) +{ + cipher__SHA256 x; + memset(&x, 0, sizeof(cipher__SHA256)); + GoUint32 result; + GoUint8 isNull; + cr_assert(SKY_cipher_SHA256_Null(&x, &isNull) == SKY_OK); + cr_assert(isNull); + char buff[130]; + GoSlice b = {buff, 0, 129}; + randBytes(&b, 128); + cr_assert(SKY_cipher_SumSHA256(b, &x) == SKY_OK); + cr_assert(SKY_cipher_SHA256_Null(&x, &isNull) == SKY_OK); + cr_assert(not(isNull)); +} diff --git a/lib/cgo/tests/check_coin.block.c b/lib/cgo/tests/check_coin.block.c new file mode 100644 index 0000000000..cc99d372b4 --- /dev/null +++ b/lib/cgo/tests/check_coin.block.c @@ -0,0 +1,309 @@ + +#include +#include + +#include +#include + +#include "libskycoin.h" +#include "skyerrors.h" +#include "skystring.h" +#include "skytest.h" +#include "skytxn.h" +#include "skycriterion.h" +#include "time.h" + +TestSuite(coin_block, .init = setup, .fini = teardown); + +Transactions__Handle makeTestTransactions(){ + Transactions__Handle transactions; + Transaction__Handle transaction; + + int result = SKY_coin_Create_Transactions(&transactions); + cr_assert(result == SKY_OK, "SKY_coin_Create_Transactions failed"); + registerHandleClose( transactions ); + result = SKY_coin_Create_Transaction(&transaction); + cr_assert(result == SKY_OK, "SKY_coin_Create_Transaction failed"); + registerHandleClose( transaction ); + result = SKY_coin_Transactions_Add(transactions, transaction); + cr_assert(result == SKY_OK, "SKY_coin_Transactions_Add failed"); + return transactions; +} + +int makeNewBlock(cipher__SHA256* uxHash, Block__Handle* newBlock){ + int result; + cipher__SHA256 bodyhash; + BlockBody__Handle block; + Transactions__Handle transactions = makeTestTransactions(); + + result = SKY_coin_NewEmptyBlock(transactions, &block); + cr_assert(result == SKY_OK, "SKY_coin_NewEmptyBlock failed"); + registerHandleClose( block ); + coin__Block* pBlock; + result = SKY_coin_GetBlockObject(block, &pBlock); + cr_assert(result == SKY_OK, "SKY_coin_Get_Block_Object failed"); + + pBlock->Head.Version = 0x02; + pBlock->Head.Time = 100; + pBlock->Head.BkSeq = 0; + pBlock->Head.Fee = 10; + BlockBody__Handle body; + result = SKY_coin_GetBlockBody(block, &body); + cr_assert(result == SKY_OK, "SKY_coin_Get_Block_Body failed"); + result = SKY_coin_BlockBody_Hash(body, &bodyhash); + cr_assert(result == SKY_OK, "SKY_coin_BlockBody_Hash failed"); + FeeCalculator zf = {zeroFeeCalculator, NULL}; + result = SKY_coin_NewBlock(block, 100 + 200, uxHash, transactions, &zf, newBlock); + cr_assert(result == SKY_OK, "SKY_coin_NewBlock failed"); + registerHandleClose( *newBlock ); + return result; +} + +GoUint32_ fix121FeeCalculator(Transaction__Handle handle, GoUint64_ *pFee, void* context){ + *pFee = 121; + return SKY_OK; +} + +Test(coin_block, TestNewBlock) { + Block__Handle prevBlock = 0; + Block__Handle newBlock = 0; + coin__Block* pPrevBlock = NULL; + coin__Block* pNewBlock = NULL; + int result = 0; + + Transactions__Handle transactions = makeTestTransactions(); + result = SKY_coin_NewEmptyBlock(transactions, &prevBlock); + cr_assert(result == SKY_OK, "SKY_coin_NewEmptyBlock failed"); + registerHandleClose( prevBlock ); + coin__Block* pBlock; + result = SKY_coin_GetBlockObject(prevBlock, &pPrevBlock); + cr_assert(result == SKY_OK, "SKY_coin_GetBlockObject failed"); + + pPrevBlock->Head.Version = 0x02; + pPrevBlock->Head.Time = 100; + pPrevBlock->Head.BkSeq = 98; + + + GoSlice slice; + memset(&slice, 0, sizeof(GoSlice)); + cipher__SHA256 hash; + + result = SKY_cipher_RandByte( 128, (coin__UxArray*)&slice ); + cr_assert(result == SKY_OK, "SKY_cipher_RandByte failed"); + registerMemCleanup( slice.data ); + result = SKY_cipher_SumSHA256( slice, &hash ); + cr_assert(result == SKY_OK, "SKY_cipher_SumSHA256 failed"); + FeeCalculator zf = {zeroFeeCalculator, NULL}; + result = SKY_coin_NewBlock(prevBlock, 133, &hash, 0, &zf, &newBlock); + cr_assert(result != SKY_OK, "SKY_coin_NewBlock has to fail with no transactions"); + registerHandleClose( newBlock ); + + transactions = 0; + Transaction__Handle tx = 0; + result = SKY_coin_Create_Transactions(&transactions); + cr_assert(result == SKY_OK, "SKY_coin_Create_Transactions failed"); + registerHandleClose(transactions); + makeEmptyTransaction(&tx); + registerHandleClose(tx); + result = SKY_coin_Transactions_Add(transactions, tx); + cr_assert(result == SKY_OK, "SKY_coin_Transactions_Add failed"); + + GoUint64 fee = 121; + GoUint64 currentTime = 133; + + FeeCalculator f121 = {fix121FeeCalculator, NULL}; + result = SKY_coin_NewBlock(prevBlock, currentTime, &hash, transactions, &f121, &newBlock); + cr_assert(result == SKY_OK, "SKY_coin_NewBlock failed"); + registerHandleClose(newBlock); + result = SKY_coin_GetBlockObject(newBlock, &pNewBlock); + cr_assert(result == SKY_OK, "SKY_coin_GetBlockObject failed"); + coin__Transactions* pTransactions = NULL; + SKY_coin_GetTransactionsObject(transactions, &pTransactions); + cr_assert( eq( type(coin__Transactions), pNewBlock->Body.Transactions, *pTransactions ) ); + cr_assert( eq(pNewBlock->Head.Fee, fee * (GoUint64)( pTransactions->len ))); + cr_assert( eq(pNewBlock->Head.Time, currentTime)); + cr_assert( eq(pNewBlock->Head.BkSeq, pPrevBlock->Head.BkSeq + 1)); + cr_assert( eq( u8[sizeof(cipher__SHA256)], pNewBlock->Head.UxHash, hash) ); +} + + +Test(coin_block, TestBlockHashHeader){ + int result; + Block__Handle block = 0; + coin__Block* pBlock = NULL; + GoSlice slice; + memset(&slice, 0, sizeof(GoSlice)); + cipher__SHA256 hash; + + result = SKY_cipher_RandByte( 128, (coin__UxArray*)&slice ); + cr_assert(result == SKY_OK, "SKY_cipher_RandByte failed"); + registerMemCleanup( slice.data ); + result = SKY_cipher_SumSHA256( slice, &hash ); + cr_assert(result == SKY_OK, "SKY_cipher_SumSHA256 failed"); + result = makeNewBlock( &hash, &block ); + cr_assert(result == SKY_OK, "makeNewBlock failed"); + result = SKY_coin_GetBlockObject(block, &pBlock); + cr_assert(result == SKY_OK, "SKY_coin_GetBlockObject failed, block handle : %d", block); + + cipher__SHA256 hash1, hash2; + result = SKY_coin_Block_HashHeader(block, &hash1); + cr_assert(result == SKY_OK, "SKY_coin_Block_HashHeader failed"); + result = SKY_coin_BlockHeader_Hash(&pBlock->Head, &hash2); + cr_assert(result == SKY_OK, "SKY_coin_BlockHeader_Hash failed"); + cr_assert( eq( u8[sizeof(cipher__SHA256)],hash1, hash2) ); + memset(&hash2, 0, sizeof(cipher__SHA256)); + cr_assert( not( eq( u8[sizeof(cipher__SHA256)],hash1, hash2) ) ); +} + + +Test(coin_block, TestBlockHashBody){ + int result; + Block__Handle block = 0; + GoSlice slice; + memset(&slice, 0, sizeof(GoSlice)); + cipher__SHA256 hash; + + result = SKY_cipher_RandByte( 128, (coin__UxArray*)&slice ); + cr_assert(result == SKY_OK, "SKY_cipher_RandByte failed"); + registerMemCleanup( slice.data ); + result = SKY_cipher_SumSHA256( slice, &hash ); + cr_assert(result == SKY_OK, "SKY_cipher_SumSHA256 failed"); + result = makeNewBlock( &hash, &block ); + cr_assert(result == SKY_OK, "makeNewBlock failed"); + + cipher__SHA256 hash1, hash2; + result = SKY_coin_Block_HashBody(block, &hash1); + cr_assert(result == SKY_OK, "SKY_coin_BlockBody_Hash failed"); + BlockBody__Handle blockBody = 0; + result = SKY_coin_GetBlockBody(block, &blockBody); + cr_assert(result == SKY_OK, "SKY_coin_GetBlockBody failed"); + result = SKY_coin_BlockBody_Hash(blockBody, &hash2); + cr_assert(result == SKY_OK, "SKY_coin_BlockBody_Hash failed"); + cr_assert( eq( u8[sizeof(cipher__SHA256)], hash1, hash2) ); +} + +Test(coin_block, TestNewGenesisBlock){ + cipher__PubKey pubkey; + cipher__SecKey seckey; + cipher__Address address; + GoUint64 genTime = 1000; + GoUint64 genCoins = 1000 * 1000 * 1000; + GoUint64 genCoinHours = 1000 * 1000; + Block__Handle block = 0; + coin__Block* pBlock = NULL; + + int result = makeKeysAndAddress(&pubkey, &seckey, &address); + cr_assert(result == SKY_OK, "makeKeysAndAddress failed"); + result = SKY_coin_NewGenesisBlock(&address, genCoins, genTime, &block); + cr_assert(result == SKY_OK, "SKY_coin_NewGenesisBlock failed"); + result = SKY_coin_GetBlockObject(block, &pBlock); + cr_assert(result == SKY_OK, "SKY_coin_GetBlockObject failed"); + + cipher__SHA256 nullHash; + memset(&nullHash, 0, sizeof(cipher__SHA256)); + cr_assert( eq( u8[sizeof(cipher__SHA256)], nullHash, pBlock->Head.PrevHash) ); + cr_assert( genTime == pBlock->Head.Time ); + cr_assert( 0 == pBlock->Head.BkSeq ); + cr_assert( 0 == pBlock->Head.Version ); + cr_assert( 0 == pBlock->Head.Fee ); + cr_assert( eq( u8[sizeof(cipher__SHA256)], nullHash, pBlock->Head.UxHash) ); + + cr_assert( 1 == pBlock->Body.Transactions.len ); + coin__Transaction* ptransaction = (coin__Transaction*)pBlock->Body.Transactions.data; + cr_assert( 0 == ptransaction->In.len); + cr_assert( 0 == ptransaction->Sigs.len); + cr_assert( 1 == ptransaction->Out.len); + + coin__TransactionOutput* poutput = (coin__TransactionOutput*)ptransaction->Out.data; + cr_assert( eq( type(cipher__Address), address, poutput->Address ) ); + cr_assert( genCoins == poutput->Coins ); + cr_assert( genCoins == poutput->Hours ); +} + +typedef struct { + int index; + int failure; +} testcase_unspent; + +Test(coin_block, TestCreateUnspent){ + cipher__PubKey pubkey; + cipher__SecKey seckey; + cipher__Address address; + int result = makeKeysAndAddress(&pubkey, &seckey, &address); + + cipher__SHA256 hash; + coin__Transaction* ptx; + Transaction__Handle handle; + ptx = makeEmptyTransaction(&handle); + result = SKY_coin_Transaction_PushOutput(handle, &address, 11000000, 255); + cr_assert(result == SKY_OK, "SKY_coin_Transaction_PushOutput failed"); + coin__BlockHeader bh; + memset(&bh, 0, sizeof(coin__BlockHeader)); + bh.Time = time(0); + bh.BkSeq = 1; + + testcase_unspent t[] = { + {0, 0}, {10, 1}, + }; + coin__UxOut ux; + int tests_count = sizeof(t) / sizeof(testcase_unspent); + for( int i = 0; i < tests_count; i++){ + memset(&ux, 0, sizeof(coin__UxOut)); + result = SKY_coin_CreateUnspent( &bh, handle, t[i].index, &ux ); + if( t[i].failure ){ + cr_assert( result == SKY_ERROR, "SKY_coin_CreateUnspent should have failed" ); + continue; + } else { + cr_assert( result == SKY_OK, "SKY_coin_CreateUnspent failed" ); + } + cr_assert( bh.Time == ux.Head.Time ); + cr_assert( bh.BkSeq == ux.Head.BkSeq ); + result = SKY_coin_Transaction_Hash( handle, &hash ); + cr_assert( result == SKY_OK, "SKY_coin_Transaction_Hash failed" ); + cr_assert( eq( u8[sizeof(cipher__SHA256)], hash, ux.Body.SrcTransaction) ); + cr_assert( t[i].index < ptx->Out.len); + coin__TransactionOutput* poutput = (coin__TransactionOutput*)ptx->Out.data; + cr_assert( eq( type(cipher__Address), ux.Body.Address, poutput->Address ) ); + cr_assert( ux.Body.Coins == poutput->Coins ); + cr_assert( ux.Body.Hours == poutput->Hours ); + } +} + +Test(coin_block, TestCreateUnspents){ + cipher__PubKey pubkey; + cipher__SecKey seckey; + cipher__Address address; + int result = makeKeysAndAddress(&pubkey, &seckey, &address); + + cipher__SHA256 hash; + coin__Transaction* ptx; + Transaction__Handle handle; + ptx = makeEmptyTransaction(&handle); + result = SKY_coin_Transaction_PushOutput(handle, &address, 11000000, 255); + cr_assert(result == SKY_OK, "SKY_coin_Transaction_PushOutput failed"); + coin__BlockHeader bh; + memset(&bh, 0, sizeof(coin__BlockHeader)); + bh.Time = time(0); + bh.BkSeq = 1; + + coin__UxArray uxs = {NULL, 0, 0}; + result = SKY_coin_CreateUnspents(&bh, handle, &uxs); + cr_assert( result == SKY_OK, "SKY_coin_CreateUnspents failed" ); + registerMemCleanup( uxs.data ); + cr_assert( uxs.len == 1 ); + cr_assert( uxs.len == ptx->Out.len ); + coin__UxOut* pout = (coin__UxOut*)uxs.data; + coin__TransactionOutput* ptxout = (coin__TransactionOutput*)ptx->Out.data; + for(int i = 0; i < uxs.len; i++){ + cr_assert( bh.Time == pout->Head.Time ); + cr_assert( bh.BkSeq == pout->Head.BkSeq ); + result = SKY_coin_Transaction_Hash( handle, &hash ); + cr_assert( result == SKY_OK, "SKY_coin_Transaction_Hash failed" ); + cr_assert( eq( u8[sizeof(cipher__SHA256)], hash, pout->Body.SrcTransaction) ); + cr_assert( eq( type(cipher__Address), pout->Body.Address, ptxout->Address ) ); + cr_assert( pout->Body.Coins == ptxout->Coins ); + cr_assert( pout->Body.Hours == ptxout->Hours ); + pout++; + ptxout++; + } +} diff --git a/lib/cgo/tests/check_coin.coin.c b/lib/cgo/tests/check_coin.coin.c new file mode 100644 index 0000000000..2fbc746680 --- /dev/null +++ b/lib/cgo/tests/check_coin.coin.c @@ -0,0 +1,88 @@ + +#include +#include + +#include +#include + +#include "libskycoin.h" +#include "skyerrors.h" +#include "skystring.h" +#include "skytest.h" +#include "skytxn.h" +#include "time.h" + +TestSuite(coin_coin, .init = setup, .fini = teardown); + +Test(coin_coin, TestAddress1){ + char* address_hex = "02fa939957e9fc52140e180264e621c2576a1bfe781f88792fb315ca3d1786afb8"; + char address[128]; + int result; + int length = hexnstr(address_hex, (unsigned char *) address, 128); + cr_assert(length > 0, "Error decoding hex string"); + GoSlice slice = { address, length, 128 }; + cipher__PubKey pubkey; + result = SKY_cipher_NewPubKey(slice, &pubkey); + cr_assert( result == SKY_OK, "SKY_cipher_NewPubKey failed" ); + cipher__Address c_address; + result = SKY_cipher_AddressFromPubKey( &pubkey, &c_address ); + cr_assert( result == SKY_OK, "SKY_cipher_AddressFromPubKey failed" ); +} + +Test(coin_coin, TestAddress2){ + char* address_hex = "5a42c0643bdb465d90bf673b99c14f5fa02db71513249d904573d2b8b63d353d"; + char address[128]; + int result; + int length = hexnstr(address_hex, (unsigned char *) address, 128); + cr_assert(length > 0, "Error decoding hex string"); + GoSlice slice = { address, length, 128 }; + cipher__PubKey pubkey; + cipher__SecKey seckey; + result = SKY_cipher_NewSecKey(slice, &seckey); + cr_assert( result == SKY_OK, "SKY_cipher_NewSecKey failed" ); + result = SKY_cipher_PubKeyFromSecKey(&seckey, &pubkey); + cr_assert( result == SKY_OK, "SKY_cipher_PubKeyFromSecKey failed" ); + cipher__Address c_address; + result = SKY_cipher_AddressFromPubKey( &pubkey, &c_address ); + cr_assert( result == SKY_OK, "SKY_cipher_AddressFromPubKey failed" ); +} + +Test(coin_coin, TestCrypto1){ + cipher__PubKey pubkey; + cipher__SecKey seckey; + int result; + for(int i = 0; i < 10; i ++){ + result = SKY_cipher_GenerateKeyPair( &pubkey, &seckey ); + cr_assert( result == SKY_OK, "SKY_cipher_GenerateKeyPair failed" ); + result = SKY_cipher_CheckSecKey( &seckey ); + cr_assert( result == SKY_OK, "CRYPTOGRAPHIC INTEGRITY CHECK FAILED" ); + } +} + +Test(coin_coin, TestCrypto2){ + char* address_hex = "5a42c0643bdb465d90bf673b99c14f5fa02db71513249d904573d2b8b63d353d"; + char address[128]; + int result; + int length = hexnstr(address_hex, (unsigned char *) address, 128); + cr_assert(length == 32, "Error decoding hex string"); + + GoSlice slice = { address, length, 128 }; + cipher__PubKey pubkey; + cipher__SecKey seckey; + result = SKY_cipher_NewSecKey(slice, &seckey); + cr_assert( result == SKY_OK, "SKY_cipher_NewSecKey failed" ); + result = SKY_cipher_PubKeyFromSecKey(&seckey, &pubkey); + cr_assert( result == SKY_OK, "SKY_cipher_PubKeyFromSecKey failed" ); + cipher__Address c_address; + result = SKY_cipher_AddressFromPubKey( &pubkey, &c_address ); + cr_assert( result == SKY_OK, "SKY_cipher_AddressFromPubKey failed" ); + + char* text = "test message"; + int len = strlen(text); + GoSlice textslice = {text, len, len}; + cipher__SHA256 hash; + result = SKY_cipher_SumSHA256(textslice, &hash); + cr_assert( result == SKY_OK, "SKY_cipher_SumSHA256 failed" ); + result = SKY_cipher_CheckSecKeyHash( &seckey, &hash ); + cr_assert( result == SKY_OK, "SKY_cipher_CheckSecKeyHash failed" ); +} diff --git a/lib/cgo/tests/check_coin.math.c b/lib/cgo/tests/check_coin.math.c new file mode 100644 index 0000000000..16e6b20d91 --- /dev/null +++ b/lib/cgo/tests/check_coin.math.c @@ -0,0 +1,56 @@ +#include +#include + +#include +#include + +#include "libskycoin.h" +#include "skyerrors.h" +#include "skystring.h" +#include "skytest.h" + +TestSuite(coin_math, .init = setup, .fini = teardown); + +Test(coin_math, TestAddUint64){ + int result; + GoUint64 r; + result = SKY_coin_AddUint64(10, 11, &r); + cr_assert( result == SKY_OK ); + cr_assert(r == 21); + GoUint64 maxUint64 = 0xFFFFFFFFFFFFFFFF; + GoUint64 one = 1; + result = SKY_coin_AddUint64(maxUint64, one, &r); + cr_assert( result == SKY_ErrUint64AddOverflow ); +} + +typedef struct{ + GoUint64 a; + GoInt64 b; + int failure; +} math_tests; + +Test(coin_math, TestUint64ToInt64){ + int result; + GoInt64 r; + GoUint64 maxUint64 = 0xFFFFFFFFFFFFFFFF; + GoInt64 maxInt64 = 0x7FFFFFFFFFFFFFFF; + + math_tests tests[] = { + {0, 0, 0}, + {1, 1, 0}, + {maxInt64, maxInt64, 0}, + {maxUint64, 0, 1}, + //This is reset to zero in C, and it doesn't fail + //{maxUint64 + 1, 0, 1}, + }; + int tests_count = sizeof(tests) / sizeof(math_tests); + for(int i = 0; i < tests_count; i++){ + result = SKY_coin_Uint64ToInt64(tests[i].a, &r); + if( tests[i].failure ){ + cr_assert(result == SKY_ErrUint64OverflowsInt64, "Failed test # %d", i + 1); + } else { + cr_assert(result == SKY_OK, "Failed test # %d", i + 1); + cr_assert( tests[i].b == r ); + } + } +} diff --git a/lib/cgo/tests/check_coin.outputs.c b/lib/cgo/tests/check_coin.outputs.c new file mode 100644 index 0000000000..63174cd92d --- /dev/null +++ b/lib/cgo/tests/check_coin.outputs.c @@ -0,0 +1,834 @@ +#include +#include + +#include +#include + +#include "libskycoin.h" +#include "skyerrors.h" +#include "skystring.h" +#include "skytest.h" +#include "skycriterion.h" +#include "skytxn.h" + +TestSuite(coin_outputs, .init = setup, .fini = teardown); + +Test(coin_outputs, TestUxBodyHash) +{ + int result; + coin__UxBody uxbody; + result = makeUxBody(&uxbody); + cr_assert(result == SKY_OK, "makeUxBody failed"); + cipher__SHA256 hash, nullHash; + result = SKY_coin_UxBody_Hash(&uxbody, &hash); + cr_assert(result == SKY_OK, "SKY_coin_UxBody_Hash failed"); + memset(&nullHash, 0, sizeof(cipher__SHA256)); + cr_assert(not(eq(u8[sizeof(cipher__SHA256)], nullHash, hash))); +} + +Test(coin_outputs, TestUxOutHash) +{ + int result; + coin__UxBody uxbody; + result = makeUxBody(&uxbody); + cr_assert(result == SKY_OK, "makeUxBody failed"); + + coin__UxOut uxout; + memset(&uxout, 0, sizeof(coin__UxOut)); + memcpy(&uxout.Body, &uxbody, sizeof(coin__UxBody)); + + cipher__SHA256 hashBody, hashOut; + result = SKY_coin_UxBody_Hash(&uxbody, &hashBody); + cr_assert(result == SKY_OK, "SKY_coin_UxBody_Hash failed"); + result = SKY_coin_UxOut_Hash(&uxout, &hashOut); + cr_assert(result == SKY_OK, "SKY_coin_UxOut_Hash failed"); + cr_assert(eq(u8[sizeof(cipher__SHA256)], hashBody, hashOut)); + + //Head should not affect hash + uxout.Head.Time = 0; + uxout.Head.BkSeq = 1; + result = SKY_coin_UxOut_Hash(&uxout, &hashOut); + cr_assert(result == SKY_OK, "SKY_coin_UxOut_Hash failed"); + cr_assert(eq(u8[sizeof(cipher__SHA256)], hashBody, hashOut)); +} + +Test(coin_outputs, TestUxOutSnapshotHash) +{ + int result; + coin__UxOut uxout, uxout2; + result = makeUxOut(&uxout); + cr_assert(result == SKY_OK, "makeUxOut failed"); + cipher__SHA256 hash1, hash2; + result = SKY_coin_UxOut_SnapshotHash(&uxout, &hash1); + cr_assert(result == SKY_OK, "SKY_coin_UxOut_SnapshotHash failed"); + + memcpy(&uxout2, &uxout, sizeof(coin__UxOut)); + uxout2.Head.Time = 20; + result = SKY_coin_UxOut_SnapshotHash(&uxout2, &hash2); + cr_assert(result == SKY_OK, "SKY_coin_UxOut_SnapshotHash failed"); + cr_assert(not(eq(u8[sizeof(cipher__SHA256)], hash1, hash2)), "Snapshot hash must be different"); + + memcpy(&uxout2, &uxout, sizeof(coin__UxOut)); + uxout2.Head.BkSeq = 4; + result = SKY_coin_UxOut_SnapshotHash(&uxout2, &hash2); + cr_assert(result == SKY_OK, "SKY_coin_UxOut_SnapshotHash failed"); + cr_assert(not(eq(u8[sizeof(cipher__SHA256)], hash1, hash2)), "Snapshot hash must be different"); + + memcpy(&uxout2, &uxout, sizeof(coin__UxOut)); + makeRandHash(&uxout2.Body.SrcTransaction); + result = SKY_coin_UxOut_SnapshotHash(&uxout2, &hash2); + cr_assert(result == SKY_OK, "SKY_coin_UxOut_SnapshotHash failed"); + cr_assert(not(eq(u8[sizeof(cipher__SHA256)], hash1, hash2)), "Snapshot hash must be different"); + + memcpy(&uxout2, &uxout, sizeof(coin__UxOut)); + makeAddress(&uxout2.Body.Address); + result = SKY_coin_UxOut_SnapshotHash(&uxout2, &hash2); + cr_assert(result == SKY_OK, "SKY_coin_UxOut_SnapshotHash failed"); + cr_assert(not(eq(u8[sizeof(cipher__SHA256)], hash1, hash2)), "Snapshot hash must be different"); + + memcpy(&uxout2, &uxout, sizeof(coin__UxOut)); + uxout2.Body.Coins = uxout.Body.Coins * 2; + result = SKY_coin_UxOut_SnapshotHash(&uxout2, &hash2); + cr_assert(result == SKY_OK, "SKY_coin_UxOut_SnapshotHash failed"); + cr_assert(not(eq(u8[sizeof(cipher__SHA256)], hash1, hash2)), "Snapshot hash must be different"); + + memcpy(&uxout2, &uxout, sizeof(coin__UxOut)); + uxout2.Body.Hours = uxout.Body.Hours * 2; + result = SKY_coin_UxOut_SnapshotHash(&uxout2, &hash2); + cr_assert(result == SKY_OK, "SKY_coin_UxOut_SnapshotHash failed"); + cr_assert(not(eq(u8[sizeof(cipher__SHA256)], hash1, hash2)), "Snapshot hash must be different"); +} + +Test(coin_outputs, TestUxOutCoinHours) +{ + GoUint64 _genCoins = 1000000000; + GoUint64 _genCoinHours = 1000 * 1000; + + int result; + coin__UxOut ux; + result = makeUxOut(&ux); + cr_assert(result == SKY_OK, "makeUxOut failed"); + + GoUint64 now, hours; + + //Less than an hour passed + now = ux.Head.Time + 100; + result = SKY_coin_UxOut_CoinHours(&ux, now, &hours); + cr_assert(result == SKY_OK, "SKY_coin_UxOut_CoinHours failed"); + cr_assert(hours == ux.Body.Hours); + + //An hour passed + now = ux.Head.Time + 3600; + result = SKY_coin_UxOut_CoinHours(&ux, now, &hours); + cr_assert(result == SKY_OK, "SKY_coin_UxOut_CoinHours failed"); + cr_assert(hours == ux.Body.Hours + ux.Body.Coins / 1000000); + + //6 hours passed + now = ux.Head.Time + 3600 * 6; + result = SKY_coin_UxOut_CoinHours(&ux, now, &hours); + cr_assert(result == SKY_OK, "SKY_coin_UxOut_CoinHours failed"); + cr_assert(hours == ux.Body.Hours + (ux.Body.Coins / 1000000) * 6); + + //Time is backwards (treated as no hours passed) + now = ux.Head.Time / 2; + result = SKY_coin_UxOut_CoinHours(&ux, now, &hours); + cr_assert(result == SKY_OK, "SKY_coin_UxOut_CoinHours failed"); + cr_assert(hours == ux.Body.Hours); + + //1 hour has passed, output has 1.5 coins, should gain 1 coinhour + ux.Body.Coins = 1500000; + now = ux.Head.Time + 3600; + result = SKY_coin_UxOut_CoinHours(&ux, now, &hours); + cr_assert(result == SKY_OK, "SKY_coin_UxOut_CoinHours failed"); + cr_assert(hours == ux.Body.Hours + 1); + + //2 hours have passed, output has 1.5 coins, should gain 3 coin hours + ux.Body.Coins = 1500000; + now = ux.Head.Time + 3600 * 2; + result = SKY_coin_UxOut_CoinHours(&ux, now, &hours); + cr_assert(result == SKY_OK, "SKY_coin_UxOut_CoinHours failed"); + cr_assert(hours == ux.Body.Hours + 3); + + //1 second has passed, output has 3600 coins, should gain 1 coin hour + ux.Body.Coins = 3600000000; + now = ux.Head.Time + 1; + result = SKY_coin_UxOut_CoinHours(&ux, now, &hours); + cr_assert(result == SKY_OK, "SKY_coin_UxOut_CoinHours failed"); + cr_assert(hours == ux.Body.Hours + 1); + + //1000000 hours minus 1 second have passed, output has 1 droplet, should gain 0 coin hour + ux.Body.Coins = 1; + now = ux.Head.Time + (GoUint64)(1000000) * (GoUint64)(3600) - 1; + result = SKY_coin_UxOut_CoinHours(&ux, now, &hours); + cr_assert(result == SKY_OK, "SKY_coin_UxOut_CoinHours failed"); + cr_assert(hours == ux.Body.Hours); + + //1000000 hours have passed, output has 1 droplet, should gain 1 coin hour + ux.Body.Coins = 1; + now = ux.Head.Time + (GoUint64)(1000000) * (GoUint64)(3600); + result = SKY_coin_UxOut_CoinHours(&ux, now, &hours); + cr_assert(result == SKY_OK, "SKY_coin_UxOut_CoinHours failed"); + cr_assert(hours == ux.Body.Hours + 1); + + // No hours passed, using initial coin hours + ux.Body.Coins = _genCoins; + ux.Body.Hours = _genCoinHours; + now = ux.Head.Time; + result = SKY_coin_UxOut_CoinHours(&ux, now, &hours); + cr_assert(result == SKY_OK, "SKY_coin_UxOut_CoinHours failed"); + cr_assert(hours == ux.Body.Hours); + + // One hour passed, using initial coin hours + now = ux.Head.Time + 3600; + result = SKY_coin_UxOut_CoinHours(&ux, now, &hours); + cr_assert(result == SKY_OK, "SKY_coin_UxOut_CoinHours failed"); + cr_assert(hours == ux.Body.Hours + _genCoins / 1000000); + + // No hours passed and no hours to begin with0 + ux.Body.Hours = 0; + now = ux.Head.Time; + result = SKY_coin_UxOut_CoinHours(&ux, now, &hours); + cr_assert(result == SKY_OK, "SKY_coin_UxOut_CoinHours failed"); + cr_assert(hours == 0); + + // Centuries have passed, time-based calculation overflows uint64 + // when calculating the whole coin seconds + ux.Body.Coins = 2000000; + now = 0xFFFFFFFFFFFFFFFF; + result = SKY_coin_UxOut_CoinHours(&ux, now, &hours); + cr_assert(result == SKY_ERROR, "SKY_coin_UxOut_CoinHours should fail"); + + // Centuries have passed, time-based calculation overflows uint64 + // when calculating the droplet seconds + ux.Body.Coins = 1500000; + now = 0xFFFFFFFFFFFFFFFF; + result = SKY_coin_UxOut_CoinHours(&ux, now, &hours); + cr_assert(result == SKY_ERROR, "SKY_coin_UxOut_CoinHours should fail"); + + // Output would overflow if given more hours, has reached its limit + ux.Body.Coins = 3600000000; + now = 0xFFFFFFFFFFFFFFFE; + result = SKY_coin_UxOut_CoinHours(&ux, now, &hours); + cr_assert(result == SKY_ERROR, "SKY_coin_UxOut_CoinHours should fail"); +} + +Test(coin_outputs, TestUxArrayCoins) +{ + coin__UxArray uxs; + int result = makeUxArray(&uxs, 4); + cr_assert(result == SKY_OK, "makeUxArray failed"); + GoUint64 coins; + result = SKY_coin_UxArray_Coins(&uxs, &coins); + cr_assert(result == SKY_OK, "SKY_coin_UxArray_Coins failed"); + cr_assert(coins == 4000000); + coin__UxOut *p = (coin__UxOut *)uxs.data; + p += 2; + p->Body.Coins = 0xFFFFFFFFFFFFFFFF - 1000000; + result = SKY_coin_UxArray_Coins(&uxs, &coins); + cr_assert(result == SKY_ERROR, "SKY_coin_UxArray_Coins should fail with overflow"); +} + +Test(coin_outputs, TestUxArrayCoinHours) +{ + coin__UxArray uxs; + int result = makeUxArray(&uxs, 4); + cr_assert(result == SKY_OK, "makeUxArray failed"); + coin__UxOut *p = (coin__UxOut *)uxs.data; + GoUint64 n; + + result = SKY_coin_UxArray_CoinHours(&uxs, p->Head.Time, &n); + cr_assert(result == SKY_OK, "SKY_coin_UxOut_CoinHours failed"); + cr_assert(n == 400); + + result = SKY_coin_UxArray_CoinHours(&uxs, p->Head.Time + 3600, &n); + cr_assert(result == SKY_OK, "SKY_coin_UxOut_CoinHours failed"); + cr_assert(n == 404); + + result = SKY_coin_UxArray_CoinHours(&uxs, p->Head.Time + 3600 + 4600, &n); + cr_assert(result == SKY_OK, "SKY_coin_UxOut_CoinHours failed"); + cr_assert(n == 408); + + p[2].Body.Hours = 0xFFFFFFFFFFFFFFFF - 100; + result = SKY_coin_UxArray_CoinHours(&uxs, p->Head.Time, &n); + cr_assert(result == SKY_ERROR, "SKY_coin_UxOut_CoinHours should have fail with overflow"); + + result = SKY_coin_UxArray_CoinHours(&uxs, p->Head.Time * (GoUint64)1000000000000, &n); + cr_assert(result == SKY_ErrAddEarnedCoinHoursAdditionOverflow, "SKY_coin_UxOut_CoinHours should have fail with overflow"); +} + +Test(coin_outputs, TestUxArrayHashArray) +{ + coin__UxArray uxs; + int result = makeUxArray(&uxs, 4); + cr_assert(result == SKY_OK, "makeUxArray failed"); + coin__UxOut *p = (coin__UxOut *)uxs.data; + + GoSlice_ hashes = {NULL, 0, 0}; + result = SKY_coin_UxArray_Hashes(&uxs, &hashes); + cr_assert(result == SKY_OK, "SKY_coin_UxArray_Hashes failed"); + registerMemCleanup(hashes.data); + cr_assert(hashes.len == uxs.len); + coin__UxOut *pux = (coin__UxOut *)uxs.data; + cipher__SHA256 *ph = (cipher__SHA256 *)hashes.data; + cipher__SHA256 hash; + for (int i = 0; i < hashes.len; i++) + { + result = SKY_coin_UxOut_Hash(pux, &hash); + cr_assert(result == SKY_OK, "SKY_coin_UxOut_Hash failed"); + cr_assert(eq(u8[sizeof(cipher__SHA256)], hash, *ph)); + pux++; + ph++; + } +} + +Test(coin_outputs, TestUxArrayHasDupes) +{ + coin__UxArray uxs; + int result = makeUxArray(&uxs, 4); + cr_assert(result == SKY_OK, "makeUxArray failed"); + GoUint8 hasDupes; + result = SKY_coin_UxArray_HasDupes(&uxs, &hasDupes); + cr_assert(result == SKY_OK, "SKY_coin_UxArray_HasDupes failed"); + cr_assert(hasDupes == 0); + coin__UxOut *p = (coin__UxOut *)uxs.data; + p++; + memcpy(uxs.data, p, sizeof(coin__UxOut)); + result = SKY_coin_UxArray_HasDupes(&uxs, &hasDupes); + cr_assert(result == SKY_OK, "SKY_coin_UxArray_HasDupes failed"); + cr_assert(hasDupes != 0); +} + +Test(coin_outputs, TestUxArraySub) +{ + + int result, equal; + coin__UxArray uxa, uxb, uxc, uxd; + coin__UxArray t1, t2, t3, t4; + + int arraySize = sizeof(coin__UxArray); + memset(&uxa, 0, arraySize); + memset(&uxb, 0, arraySize); + memset(&uxc, 0, arraySize); + memset(&uxd, 0, arraySize); + memset(&t1, 0, arraySize); + memset(&t2, 0, arraySize); + memset(&t3, 0, arraySize); + memset(&t4, 0, arraySize); + + result = makeUxArray(&uxa, 4); + cr_assert(result == SKY_OK, "makeUxArray failed"); + result = makeUxArray(&uxb, 4); + cr_assert(result == SKY_OK, "makeUxArray failed"); + + int elems_size = sizeof(coin__UxOut); + cutSlice(&uxa, 0, 1, elems_size, &t1); + cr_assert(result == SKY_OK, "cutSlice failed"); + result = concatSlices(&t1, &uxb, elems_size, &t2); + cr_assert(result == SKY_OK, "concatSlices failed"); + result = cutSlice(&uxa, 1, 2, elems_size, &t3); + cr_assert(result == SKY_OK, "cutSlice failed"); + result = concatSlices(&t2, &t3, elems_size, &uxc); + cr_assert(result == SKY_OK, "concatSlices failed"); + // //TODO: Fix comparision + memset(&uxd, 0, arraySize); + result = SKY_coin_UxArray_Sub(&uxc, &uxa, &uxd); + cr_assert(result == SKY_OK, "SKY_coin_UxArray_Sub failed"); + cr_assert(eq(type(coin__UxArray), uxd, uxb)); + + memset(&uxd, 0, arraySize); + result = SKY_coin_UxArray_Sub(&uxc, &uxb, &uxd); + cr_assert(result == SKY_OK, "SKY_coin_UxArray_Sub failed"); + cr_assert(uxd.len == 2, "uxd length must be 2 and it is: %s", uxd.len); + cutSlice(&uxa, 0, 2, elems_size, &t1); + cr_assert(eq(type(coin__UxArray), uxd, t1)); + + // No intersection + memset(&t1, 0, arraySize); + memset(&t2, 0, arraySize); + result = SKY_coin_UxArray_Sub(&uxa, &uxb, &t1); + cr_assert(result == SKY_OK, "SKY_coin_UxArray_Sub failed"); + result = SKY_coin_UxArray_Sub(&uxb, &uxa, &t2); + cr_assert(result == SKY_OK, "SKY_coin_UxArray_Sub failed"); + cr_assert(eq(type(coin__UxArray), uxa, t1)); + cr_assert(eq(type(coin__UxArray), uxb, t2)); +} + +int isUxArraySorted(coin__UxArray *uxa) +{ + int n = uxa->len; + coin__UxOut *prev = uxa->data; + coin__UxOut *current = prev; + current++; + cipher__SHA256 hash1, hash2; + cipher__SHA256 *prevHash = NULL; + cipher__SHA256 *currentHash = NULL; + + int result; + for (int i = 1; i < n; i++) + { + if (prevHash == NULL) + { + result = SKY_coin_UxOut_Hash(prev, &hash1); + cr_assert(result == SKY_OK, "SKY_coin_UxOut_Hash failed"); + prevHash = &hash1; + } + if (currentHash == NULL) + currentHash = &hash2; + result = SKY_coin_UxOut_Hash(current, currentHash); + cr_assert(result == SKY_OK, "SKY_coin_UxOut_Hash failed"); + if (memcmp(prevHash, currentHash, sizeof(cipher__SHA256)) > 0) + return 0; //Array is not sorted + if (i % 2 != 0) + { + prevHash = &hash2; + currentHash = &hash1; + } + else + { + prevHash = &hash1; + currentHash = &hash2; + } + prev++; + current++; + } + return 1; +} + +Test(coin_outputs, TestUxArraySorting) +{ + + int result; + coin__UxArray uxa; + result = makeUxArray(&uxa, 4); + cr_assert(result == SKY_OK, "makeUxArray failed"); + int isSorted = isUxArraySorted(&uxa); + if (isSorted) + { //If already sorted then break the order + coin__UxOut temp; + coin__UxOut *p = uxa.data; + memcpy(&temp, p, sizeof(coin__UxOut)); + memcpy(p, p + 1, sizeof(coin__UxOut)); + memcpy(p + 1, &temp, sizeof(coin__UxOut)); + } + isSorted = isUxArraySorted(&uxa); + cr_assert(isSorted == 0); + result = SKY_coin_UxArray_Sort(&uxa); + cr_assert(result == SKY_OK, "SKY_coin_UxArray_Sort failed"); + isSorted = isUxArraySorted(&uxa); + cr_assert(isSorted == 1); +} + +Test(coin_outputs, TestUxArrayLen) +{ + int result; + coin__UxArray uxa; + result = makeUxArray(&uxa, 4); + cr_assert(result == SKY_OK, "makeUxArray failed"); + GoInt len; + result = SKY_coin_UxArray_Len(&uxa, &len); + cr_assert(result == SKY_OK, "SKY_coin_UxArray_Len failed"); + cr_assert(len == uxa.len); + cr_assert(len == 4); +} + +Test(coin_outputs, TestUxArrayLess) +{ + int result; + coin__UxArray uxa; + result = makeUxArray(&uxa, 2); + cr_assert(result == SKY_OK, "makeUxArray failed"); + cipher__SHA256 hashes[2]; + coin__UxOut *p = uxa.data; + result = SKY_coin_UxOut_Hash(p, &hashes[0]); + cr_assert(result == SKY_OK, "SKY_coin_UxOut_Hash failed"); + p++; + result = SKY_coin_UxOut_Hash(p, &hashes[1]); + cr_assert(result == SKY_OK, "SKY_coin_UxOut_Hash failed"); + GoUint8 lessResult1, lessResult2; + int memcmpResult; + result = SKY_coin_UxArray_Less(&uxa, 0, 1, &lessResult1); + cr_assert(result == SKY_OK, "SKY_coin_UxArray_Less failed"); + result = SKY_coin_UxArray_Less(&uxa, 1, 0, &lessResult2); + cr_assert(result == SKY_OK, "SKY_coin_UxArray_Less failed"); + memcmpResult = memcmp(&hashes[0], &hashes[1], sizeof(cipher__SHA256)); + int r; + r = (lessResult1 == 1) == (memcmpResult < 0); + cr_assert(r != 0); + r = (lessResult2 == 1) == (memcmpResult > 0); + cr_assert(r != 0); +} + +Test(coin_outputs, TestUxArraySwap) +{ + int result; + coin__UxArray uxa; + result = makeUxArray(&uxa, 2); + cr_assert(result == SKY_OK, "makeUxArray failed"); + coin__UxOut uxx, uxy; + coin__UxOut *p = uxa.data; + memcpy(&uxx, p, sizeof(coin__UxOut)); + memcpy(&uxy, p + 1, sizeof(coin__UxOut)); + + result = SKY_coin_UxArray_Swap(&uxa, 0, 1); + cr_assert(result == SKY_OK, "SKY_coin_UxArray_Swap failed"); + cr_assert(eq(type(coin__UxOut), uxy, *p)); + cr_assert(eq(type(coin__UxOut), uxx, *(p + 1))); + + result = SKY_coin_UxArray_Swap(&uxa, 0, 1); + cr_assert(result == SKY_OK, "SKY_coin_UxArray_Swap failed"); + cr_assert(eq(type(coin__UxOut), uxy, *(p + 1))); + cr_assert(eq(type(coin__UxOut), uxx, *p)); + + result = SKY_coin_UxArray_Swap(&uxa, 1, 0); + cr_assert(result == SKY_OK, "SKY_coin_UxArray_Swap failed"); + cr_assert(eq(type(coin__UxOut), uxy, *p)); + cr_assert(eq(type(coin__UxOut), uxx, *(p + 1))); +} + +Test(coin_outputs, TestAddressUxOutsKeys) +{ + int result; + int test_count = 3; + coin__UxOut uxs[test_count]; + for (int i = 0; i < 3; i++) + { + makeUxOut(&uxs[i]); + } + + coin__UxArray uxa = {uxs, test_count, test_count}; + AddressUxOuts_Handle uxOutsHandle; + result = SKY_coin_NewAddressUxOuts(&uxa, &uxOutsHandle); + cr_assert(result == SKY_OK, "SKY_coin_NewAddressUxOuts failed"); + GoSlice_ keys = {NULL, 0, 0}; + result = SKY_coin_AddressUxOuts_Keys(uxOutsHandle, &keys); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOuts_Keys failed"); + registerMemCleanup(keys.data); + cr_assert(keys.len == test_count); + cipher__Address *pKey = keys.data; + for (int i = 0; i < test_count; i++) + { + //Check if every key matches uxout + int found = 0; + for (int j = 0; j < test_count; j++) + { + if (memcmp(pKey, &uxs[j].Body.Address, sizeof(cipher__Address)) == 0) + { + found = 1; + } + } + cr_assert(found == 1, "Invalid key received from SKY_coin_AddressUxOuts_Keys"); + found = 0; + if (i < test_count - 1) + { + cipher__Address *pKey2 = pKey; + for (int j = i + 1; j < test_count; j++) + { + pKey2++; + if (memcmp(pKey, pKey2, sizeof(cipher__Address)) == 0) + { + found = 1; + } + } + } + cr_assert(found == 0, "Duplicate keys received from SKY_coin_AddressUxOuts_Keys"); + pKey++; + } +} + +Test(coin_outputs, TestAddressUxOutsSub) +{ + int result; + coin__UxArray uxa, empty; + makeUxArray(&uxa, 4); + coin__UxOut *pData = uxa.data; + memset(&empty, 0, sizeof(coin__UxArray)); + AddressUxOuts_Handle h1, h2, h3; + result = SKY_coin_NewAddressUxOuts(&empty, &h1); + cr_assert(result == SKY_OK, "SKY_coin_NewAddressUxOuts failed"); + registerHandleClose(h1); + result = SKY_coin_NewAddressUxOuts(&empty, &h2); + cr_assert(result == SKY_OK, "SKY_coin_NewAddressUxOuts failed"); + registerHandleClose(h2); + memcpy(&(pData + 1)->Body.Address, &pData->Body.Address, sizeof(cipher__Address)); + + coin__UxArray ux2 = {pData, 2, 2}; + result = SKY_coin_AddressUxOuts_Set(h1, &pData->Body.Address, &ux2); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOut_Set failed"); + coin__UxArray ux3 = {pData + 2, 1, 1}; + result = SKY_coin_AddressUxOuts_Set(h1, &(pData + 2)->Body.Address, &ux3); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOut_Set failed"); + coin__UxArray ux4 = {pData + 3, 1, 1}; + result = SKY_coin_AddressUxOuts_Set(h1, &(pData + 3)->Body.Address, &ux4); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOut_Set failed"); + + coin__UxArray ux5 = {pData, 1, 1}; + result = SKY_coin_AddressUxOuts_Set(h2, &pData->Body.Address, &ux5); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOut_Set failed"); + coin__UxArray ux6 = {pData + 2, 1, 1}; + result = SKY_coin_AddressUxOuts_Set(h2, &(pData + 2)->Body.Address, &ux6); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOut_Set failed"); + + result = SKY_coin_AddressUxOuts_Sub(h1, h2, &h3); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOut_Sub failed"); + registerHandleClose(h3); + + GoInt length; + result = SKY_coin_AddressUxOuts_Length(h3, &length); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOuts_Length failed"); + // One address should have been removed, because no elements + cr_assert(length == 2, "Invalid length %d", length); + GoUint8_ hasKey; + result = SKY_coin_AddressUxOuts_HasKey(h3, &(pData + 2)->Body.Address, &hasKey); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOuts_HasKey failed"); + cr_assert(hasKey == 0); + + memset(&ux3, 0, sizeof(coin__UxArray)); + result = SKY_coin_AddressUxOuts_Get(h3, &(pData + 3)->Body.Address, &ux3); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOuts_Get failed"); + registerMemCleanup(ux3.data); + cr_assert(ux3.len == 1); + coin__UxOut *pData2 = ux3.data; + cr_assert(eq(type(coin__UxOut), *pData2, *(pData + 3))); + + memset(&ux2, 0, sizeof(coin__UxArray)); + result = SKY_coin_AddressUxOuts_Get(h3, &pData->Body.Address, &ux2); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOuts_Get failed"); + registerMemCleanup(ux2.data); + cr_assert(ux2.len == 1); + pData2 = ux2.data; + cr_assert(eq(type(coin__UxOut), *pData2, *(pData + 1))); + + // Originals should be unmodified + result = SKY_coin_AddressUxOuts_Length(h1, &length); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOuts_Length failed"); + cr_assert(length == 3, "Invalid length %d", length); + result = SKY_coin_AddressUxOuts_GetOutputLength(h1, &pData->Body.Address, &length); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOuts_Length failed"); + cr_assert(length == 2, "Invalid length %d", length); + result = SKY_coin_AddressUxOuts_GetOutputLength(h1, &(pData + 2)->Body.Address, &length); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOuts_Length failed"); + cr_assert(length == 1, "Invalid length %d", length); + result = SKY_coin_AddressUxOuts_GetOutputLength(h1, &(pData + 3)->Body.Address, &length); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOuts_Length failed"); + cr_assert(length == 1, "Invalid length %d", length); + + result = SKY_coin_AddressUxOuts_Length(h2, &length); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOuts_Length failed"); + cr_assert(length == 2, "Invalid length %d", length); + result = SKY_coin_AddressUxOuts_GetOutputLength(h2, &pData->Body.Address, &length); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOuts_Length failed"); + cr_assert(length == 1, "Invalid length %d", length); + result = SKY_coin_AddressUxOuts_GetOutputLength(h2, &(pData + 2)->Body.Address, &length); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOuts_Length failed"); + cr_assert(length == 1, "Invalid length %d", length); +} + +Test(coin_outputs, TestAddressUxOutsAdd) +{ + int result; + coin__UxArray uxa, empty; + makeUxArray(&uxa, 4); + coin__UxOut *pData = uxa.data; + memset(&empty, 0, sizeof(coin__UxArray)); + AddressUxOuts_Handle h1, h2, h3; + result = SKY_coin_NewAddressUxOuts(&empty, &h1); + cr_assert(result == SKY_OK, "SKY_coin_NewAddressUxOuts failed"); + registerHandleClose(h1); + result = SKY_coin_NewAddressUxOuts(&empty, &h2); + cr_assert(result == SKY_OK, "SKY_coin_NewAddressUxOuts failed"); + registerHandleClose(h2); + memcpy(&(pData + 1)->Body.Address, &pData->Body.Address, sizeof(cipher__Address)); + + coin__UxArray ux2 = {pData, 1, 1}; + result = SKY_coin_AddressUxOuts_Set(h1, &pData->Body.Address, &ux2); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOut_Set failed"); + coin__UxArray ux3 = {pData + 2, 1, 1}; + result = SKY_coin_AddressUxOuts_Set(h1, &(pData + 2)->Body.Address, &ux3); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOut_Set failed"); + coin__UxArray ux4 = {pData + 3, 1, 1}; + result = SKY_coin_AddressUxOuts_Set(h1, &(pData + 3)->Body.Address, &ux4); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOut_Set failed"); + + coin__UxArray ux5 = {pData + 1, 1, 1}; + result = SKY_coin_AddressUxOuts_Set(h2, &pData->Body.Address, &ux5); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOut_Set failed"); + coin__UxArray ux6 = {pData + 2, 1, 1}; + result = SKY_coin_AddressUxOuts_Set(h2, &(pData + 2)->Body.Address, &ux6); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOut_Set failed"); + + result = SKY_coin_AddressUxOuts_Add(h1, h2, &h3); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOuts_Add failed"); + registerHandleClose(h3); + + GoInt length; + result = SKY_coin_AddressUxOuts_Length(h3, &length); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOuts_Length failed"); + // One address should have been removed, because no elements + cr_assert(length == 3, "Invalid length %d", length); + + result = SKY_coin_AddressUxOuts_GetOutputLength(h3, &pData->Body.Address, &length); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOuts_Length failed"); + cr_assert(length == 2, "Invalid length %d", length); + + memset(&ux2, 0, sizeof(coin__UxArray)); + result = SKY_coin_AddressUxOuts_Get(h3, &pData->Body.Address, &ux2); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOuts_Get failed"); + registerMemCleanup(ux2.data); + cr_assert(ux2.len == 2); + coin__UxOut *pData2 = ux2.data; + cr_assert(eq(type(coin__UxOut), *pData2, *pData)); + cr_assert(eq(type(coin__UxOut), *(pData2 + 1), *(pData + 1))); + + memset(&ux2, 0, sizeof(coin__UxArray)); + result = SKY_coin_AddressUxOuts_Get(h3, &(pData + 2)->Body.Address, &ux2); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOuts_Get failed"); + registerMemCleanup(ux2.data); + cr_assert(ux2.len == 1); + pData2 = ux2.data; + cr_assert(eq(type(coin__UxOut), *pData2, *(pData + 2))); + + memset(&ux2, 0, sizeof(coin__UxArray)); + result = SKY_coin_AddressUxOuts_Get(h3, &(pData + 3)->Body.Address, &ux2); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOuts_Get failed"); + registerMemCleanup(ux2.data); + cr_assert(ux2.len == 1); + pData2 = ux2.data; + cr_assert(eq(type(coin__UxOut), *pData2, *(pData + 3))); + + memset(&ux2, 0, sizeof(coin__UxArray)); + result = SKY_coin_AddressUxOuts_Get(h3, &(pData + 1)->Body.Address, &ux2); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOuts_Get failed"); + registerMemCleanup(ux2.data); + cr_assert(ux2.len == 2); + pData2 = ux2.data; + cr_assert(eq(type(coin__UxOut), *pData2, *pData)); + cr_assert(eq(type(coin__UxOut), *(pData2 + 1), *(pData + 1))); + + // Originals should be unmodified + result = SKY_coin_AddressUxOuts_Length(h1, &length); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOuts_Length failed"); + cr_assert(length == 3, "Invalid length %d", length); + result = SKY_coin_AddressUxOuts_GetOutputLength(h1, &pData->Body.Address, &length); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOuts_Length failed"); + cr_assert(length == 1, "Invalid length %d", length); + result = SKY_coin_AddressUxOuts_GetOutputLength(h1, &(pData + 2)->Body.Address, &length); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOuts_Length failed"); + cr_assert(length == 1, "Invalid length %d", length); + result = SKY_coin_AddressUxOuts_GetOutputLength(h1, &(pData + 3)->Body.Address, &length); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOuts_Length failed"); + cr_assert(length == 1, "Invalid length %d", length); + result = SKY_coin_AddressUxOuts_Length(h2, &length); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOuts_Length failed"); + cr_assert(length == 2, "Invalid length %d", length); + result = SKY_coin_AddressUxOuts_GetOutputLength(h2, &pData->Body.Address, &length); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOuts_Length failed"); + cr_assert(length == 1, "Invalid length %d", length); + result = SKY_coin_AddressUxOuts_GetOutputLength(h2, &(pData + 2)->Body.Address, &length); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOuts_Length failed"); + cr_assert(length == 1, "Invalid length %d", length); +} + +Test(coin_outputs, TestAddressUxOutsFlatten) +{ + int result; + coin__UxArray uxa, emptyArray; + makeUxArray(&uxa, 3); + coin__UxOut *pData = uxa.data; + memcpy(&(pData + 2)->Body.Address, &(pData + 1)->Body.Address, sizeof(cipher__Address)); + memset(&emptyArray, 0, sizeof(coin__UxArray)); + AddressUxOuts_Handle h; + result = SKY_coin_NewAddressUxOuts(&emptyArray, &h); + cr_assert(result == SKY_OK, "SKY_coin_NewAddressUxOuts failed"); + registerHandleClose(h); + cipher__Address emptyAddr; + makeAddress(&emptyAddr); + coin__UxArray ux1 = {pData, 1, 1}; + coin__UxArray ux2 = {pData + 1, 2, 2}; + result = SKY_coin_AddressUxOuts_Set(h, &emptyAddr, &emptyArray); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOut_Set failed"); + result = SKY_coin_AddressUxOuts_Set(h, &pData->Body.Address, &ux1); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOut_Set failed"); + result = SKY_coin_AddressUxOuts_Set(h, &(pData + 1)->Body.Address, &ux2); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOut_Set failed"); + + coin__UxArray flatArray; + memset(&flatArray, 0, sizeof(coin__UxArray)); + result = SKY_coin_AddressUxOuts_Flatten(h, &flatArray); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOuts_Flatten failed"); + registerMemCleanup(flatArray.data); + cr_assert(flatArray.len == 3); + // emptyAddr should not be in the array + coin__UxOut *pData2 = flatArray.data; + for (int i = 0; i < flatArray.len; pData2++, i++) + { + int cmp = memcmp(&emptyAddr, &pData2->Body.Address, sizeof(cipher__Address)); + cr_assert(cmp != 0); + } + pData2 = flatArray.data; + int cmp = memcmp(&pData->Body.Address, &pData2->Body.Address, sizeof(cipher__Address)); + if (cmp == 0) + { + cr_assert(eq(type(coin__UxOut), *pData2, *pData)); + cr_assert(eq(type(coin__UxOut), *(pData2 + 1), *(pData + 1))); + cr_assert(eq(type(coin__UxOut), *(pData2 + 2), *(pData + 2))); + cr_assert(eq(type(cipher__Address), pData2->Body.Address, pData->Body.Address)); + cr_assert(eq(type(cipher__Address), (pData2 + 1)->Body.Address, (pData + 1)->Body.Address)); + cr_assert(eq(type(cipher__Address), (pData2 + 2)->Body.Address, (pData + 2)->Body.Address)); + } + else + { + cr_assert(eq(type(coin__UxOut), *pData2, *(pData + 1))); + cr_assert(eq(type(coin__UxOut), *(pData2 + 1), *(pData + 2))); + cr_assert(eq(type(coin__UxOut), *(pData2 + 2), *(pData))); + cr_assert(eq(type(cipher__Address), pData2->Body.Address, (pData + 1)->Body.Address)); + cr_assert(eq(type(cipher__Address), (pData2 + 1)->Body.Address, (pData + 2)->Body.Address)); + cr_assert(eq(type(cipher__Address), (pData2 + 2)->Body.Address, (pData)->Body.Address)); + } +} + +Test(coin_outputs, TestNewAddressUxOuts) +{ + int result; + coin__UxArray uxa, ux2; + makeUxArray(&uxa, 6); + coin__UxOut *pData = uxa.data; + memcpy(&(pData + 1)->Body.Address, &(pData)->Body.Address, sizeof(cipher__Address)); + memcpy(&(pData + 3)->Body.Address, &(pData + 2)->Body.Address, sizeof(cipher__Address)); + memcpy(&(pData + 4)->Body.Address, &(pData + 2)->Body.Address, sizeof(cipher__Address)); + AddressUxOuts_Handle h; + result = SKY_coin_NewAddressUxOuts(&uxa, &h); + cr_assert(result == SKY_OK, "SKY_coin_NewAddressUxOuts failed"); + registerHandleClose(h); + + GoInt length; + result = SKY_coin_AddressUxOuts_Length(h, &length); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOuts_Length failed"); + cr_assert(length == 3); + + memset(&ux2, 0, sizeof(coin__UxArray)); + result = SKY_coin_AddressUxOuts_Get(h, &(pData)->Body.Address, &ux2); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOuts_Get failed"); + registerMemCleanup(ux2.data); + cr_assert(ux2.len == 2); + coin__UxOut *pData2 = ux2.data; + cr_assert(eq(type(coin__UxOut), *(pData2), *(pData))); + cr_assert(eq(type(coin__UxOut), *(pData2 + 1), *(pData + 1))); + + memset(&ux2, 0, sizeof(coin__UxArray)); + result = SKY_coin_AddressUxOuts_Get(h, &(pData + 3)->Body.Address, &ux2); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOuts_Get failed"); + registerMemCleanup(ux2.data); + cr_assert(ux2.len == 3); + pData2 = ux2.data; + cr_assert(eq(type(coin__UxOut), *(pData2), *(pData + 2))); + cr_assert(eq(type(coin__UxOut), *(pData2 + 1), *(pData + 3))); + cr_assert(eq(type(coin__UxOut), *(pData2 + 2), *(pData + 4))); + + memset(&ux2, 0, sizeof(coin__UxArray)); + result = SKY_coin_AddressUxOuts_Get(h, &(pData + 5)->Body.Address, &ux2); + cr_assert(result == SKY_OK, "SKY_coin_AddressUxOuts_Get failed"); + registerMemCleanup(ux2.data); + cr_assert(ux2.len == 1); + pData2 = ux2.data; + cr_assert(eq(type(coin__UxOut), *(pData2), *(pData + 5))); +} diff --git a/lib/cgo/tests/check_coin.transactions.c b/lib/cgo/tests/check_coin.transactions.c new file mode 100644 index 0000000000..98995b3ffb --- /dev/null +++ b/lib/cgo/tests/check_coin.transactions.c @@ -0,0 +1,1079 @@ + +#include +#include +#include +#include +#include + +#include "libskycoin.h" +#include "skyerrors.h" +#include "skystring.h" +#include "skytest.h" +#include "skycriterion.h" +#include "skytxn.h" + +TestSuite(coin_transaction, .init = setup, .fini = teardown); + +GoUint64 MaxUint64 = 0xFFFFFFFFFFFFFFFF; +GoUint64 Million = 1000000; + +Test(coin_transaction, TestTransactionVerify) +{ + int result; + coin__Transaction *ptx; + Transaction__Handle handle; + // Mismatch header hash + ptx = makeTransaction(&handle); + memset(ptx->InnerHash, 0, sizeof(cipher__SHA256)); + result = SKY_coin_Transaction_Verify(handle); + cr_assert(result == SKY_ERROR); + + // No inputs + ptx = makeTransaction(&handle); + result = SKY_coin_Transaction_ResetInputs(handle, 0); + cr_assert(result == SKY_OK); + result = SKY_coin_Transaction_UpdateHeader(handle); + cr_assert(result == SKY_OK); + result = SKY_coin_Transaction_Verify(handle); + cr_assert(result == SKY_ERROR); + + // No outputs + ptx = makeTransaction(&handle); + result = SKY_coin_Transaction_ResetOutputs(handle, 0); + cr_assert(result == SKY_OK); + result = SKY_coin_Transaction_UpdateHeader(handle); + cr_assert(result == SKY_OK); + result = SKY_coin_Transaction_Verify(handle); + cr_assert(result == SKY_ERROR); + + // Invalid number of Sigs + ptx = makeTransaction(&handle); + result = SKY_coin_Transaction_ResetSignatures(handle, 0); + cr_assert(result == SKY_OK); + result = SKY_coin_Transaction_UpdateHeader(handle); + cr_assert(result == SKY_OK); + result = SKY_coin_Transaction_Verify(handle); + cr_assert(result == SKY_ERROR); + result = SKY_coin_Transaction_ResetSignatures(handle, 20); + cr_assert(result == SKY_OK); + result = SKY_coin_Transaction_UpdateHeader(handle); + cr_assert(result == SKY_OK); + result = SKY_coin_Transaction_Verify(handle); + cr_assert(result == SKY_ERROR); + + int MaxUint16 = 0xFFFF; + // Too many sigs & inputs + ptx = makeTransaction(&handle); + result = SKY_coin_Transaction_ResetSignatures(handle, MaxUint16); + cr_assert(result == SKY_OK); + result = SKY_coin_Transaction_ResetInputs(handle, MaxUint16); + cr_assert(result == SKY_OK); + result = SKY_coin_Transaction_UpdateHeader(handle); + cr_assert(result == SKY_OK); + result = SKY_coin_Transaction_Verify(handle); + cr_assert(result == SKY_ERROR); + + // Duplicate inputs + coin__UxOut ux; + cipher__SecKey seckey; + cipher__SHA256 sha256; + makeUxOutWithSecret(&ux, &seckey); + ptx = makeTransactionFromUxOut(&ux, &seckey, &handle); + memcpy(&sha256, ptx->In.data, sizeof(cipher__SHA256)); + GoUint16 r; + result = SKY_coin_Transaction_PushInput(handle, &sha256, &r); + result = SKY_coin_Transaction_ResetSignatures(handle, 0); + cr_assert(result == SKY_OK); + GoSlice seckeys; + seckeys.data = malloc(sizeof(cipher__SecKey) * 2); + cr_assert(seckeys.data != NULL); + registerMemCleanup(seckeys.data); + seckeys.len = seckeys.cap = 2; + memcpy(seckeys.data, &seckey, sizeof(cipher__SecKey)); + memcpy(((cipher__SecKey *)seckeys.data) + 1, &seckey, sizeof(cipher__SecKey)); + result = SKY_coin_Transaction_SignInputs(handle, seckeys); + result = SKY_coin_Transaction_UpdateHeader(handle); + cr_assert(result == SKY_OK); + result = SKY_coin_Transaction_Verify(handle); + cr_assert(result == SKY_ERROR); + + // Duplicate outputs + ptx = makeTransaction(&handle); + coin__TransactionOutput *pOutput = ptx->Out.data; + cipher__Address addr; + memcpy(&addr, &pOutput->Address, sizeof(cipher__Address)); + result = SKY_coin_Transaction_PushOutput(handle, &addr, pOutput->Coins, + pOutput->Hours); + cr_assert(result == SKY_OK); + result = SKY_coin_Transaction_UpdateHeader(handle); + cr_assert(result == SKY_OK); + result = SKY_coin_Transaction_Verify(handle); + cr_assert(result == SKY_ERROR); + + // Invalid signature, empty + ptx = makeTransaction(&handle); + memset(ptx->Sigs.data, 0, sizeof(cipher__Sig)); + result = SKY_coin_Transaction_Verify(handle); + cr_assert(result == SKY_ErrInvalidSigPubKeyRecovery); + + // Output coins are 0 + ptx = makeTransaction(&handle); + pOutput = ptx->Out.data; + pOutput->Coins = 0; + result = SKY_coin_Transaction_UpdateHeader(handle); + cr_assert(result == SKY_OK); + result = SKY_coin_Transaction_Verify(handle); + cr_assert(result == SKY_ERROR); + + GoUint64 MaxUint64 = 0xFFFFFFFFFFFFFFFF; + // Output coin overflow + ptx = makeTransaction(&handle); + pOutput = ptx->Out.data; + pOutput->Coins = MaxUint64 - 3000000; + result = SKY_coin_Transaction_UpdateHeader(handle); + cr_assert(result == SKY_OK); + result = SKY_coin_Transaction_Verify(handle); + cr_assert(result == SKY_ERROR); + + // Output coins are not multiples of 1e6 (valid, decimal restriction is not + // enforced here) + ptx = makeTransaction(&handle); + pOutput = ptx->Out.data; + pOutput->Coins += 10; + result = SKY_coin_Transaction_UpdateHeader(handle); + cr_assert(result == SKY_OK); + result = SKY_coin_Transaction_ResetSignatures(handle, 0); + cr_assert(result == SKY_OK); + cipher__PubKey pubkey; + result = SKY_cipher_GenerateKeyPair(&pubkey, &seckey); + cr_assert(result == SKY_OK); + seckeys.data = &seckey; + seckeys.len = 1; + seckeys.cap = 1; + result = SKY_coin_Transaction_SignInputs(handle, seckeys); + cr_assert(result == SKY_OK); + cr_assert(pOutput->Coins % 1000000 != 0); + result = SKY_coin_Transaction_Verify(handle); + cr_assert(result == SKY_OK); + + // Valid + ptx = makeTransaction(&handle); + pOutput = ptx->Out.data; + pOutput->Coins = 10000000; + pOutput++; + pOutput->Coins = 1000000; + result = SKY_coin_Transaction_UpdateHeader(handle); + cr_assert(result == SKY_OK); + result = SKY_coin_Transaction_Verify(handle); + cr_assert(result == SKY_OK); +} + +Test(coin_transaction, TestTransactionPushInput, SKY_ABORT) +{ + int result; + Transaction__Handle handle; + coin__Transaction *ptx; + coin__UxOut ux; + ptx = makeEmptyTransaction(&handle); + makeUxOut(&ux); + cipher__SHA256 hash; + result = SKY_coin_UxOut_Hash(&ux, &hash); + cr_assert(result == SKY_OK); + GoUint16 r; + result = SKY_coin_Transaction_PushInput(handle, &hash, &r); + cr_assert(result == SKY_OK); + cr_assert(r == 0); + cr_assert(ptx->In.len == 1); + cipher__SHA256 *pIn = ptx->In.data; + cr_assert(eq(u8[sizeof(cipher__SHA256)], hash, *pIn)); + + GoUint16 MaxUint16 = 0xFFFF; + int len = ptx->In.len; + void *data = malloc(len * sizeof(cipher__SHA256)); + cr_assert(data != NULL); + registerMemCleanup(data); + memcpy(data, ptx->In.data, len * sizeof(cipher__SHA256)); + result = SKY_coin_Transaction_ResetInputs(handle, MaxUint16 + len); + cr_assert(result == SKY_OK); + memcpy(ptx->In.data, data, len * sizeof(cipher__Sig)); + freeRegisteredMemCleanup(data); + makeUxOut(&ux); + result = SKY_coin_UxOut_Hash(&ux, &hash); + cr_assert(result == SKY_OK); + result = SKY_coin_Transaction_PushInput(handle, &hash, &r); + cr_assert(result == SKY_ERROR); +} + +Test(coin_transaction, TestTransactionPushOutput) +{ + int result; + Transaction__Handle handle; + coin__Transaction *ptx; + ptx = makeEmptyTransaction(&handle); + + cipher__Address addr; + makeAddress(&addr); + result = SKY_coin_Transaction_PushOutput(handle, &addr, 100, 150); + cr_assert(result == SKY_OK); + cr_assert(ptx->Out.len == 1); + coin__TransactionOutput *pOutput = ptx->Out.data; + coin__TransactionOutput output; + memcpy(&output.Address, &addr, sizeof(cipher__Address)); + output.Coins = 100; + output.Hours = 150; + cr_assert(eq(type(coin__TransactionOutput), output, *pOutput)); + for (int i = 1; i < 20; i++) + { + makeAddress(&addr); + result = SKY_coin_Transaction_PushOutput(handle, &addr, i * 100, i * 50); + cr_assert(result == SKY_OK); + cr_assert(ptx->Out.len == i + 1); + pOutput = ptx->Out.data; + pOutput += i; + memcpy(&output.Address, &addr, sizeof(cipher__Address)); + output.Coins = i * 100; + output.Hours = i * 50; + cr_assert(eq(type(coin__TransactionOutput), output, *pOutput)); + } +} + +Test(coin_transaction, TestTransactionHash) +{ + int result; + Transaction__Handle handle; + coin__Transaction *ptx; + ptx = makeEmptyTransaction(&handle); + + cipher__SHA256 nullHash, hash1, hash2; + memset(&nullHash, 0, sizeof(cipher__SHA256)); + result = SKY_coin_Transaction_Hash(handle, &hash1); + cr_assert(result == SKY_OK); + cr_assert(not(eq(u8[sizeof(cipher__SHA256)], nullHash, hash1))); + result = SKY_coin_Transaction_HashInner(handle, &hash2); + cr_assert(result == SKY_OK); + cr_assert(not(eq(u8[sizeof(cipher__SHA256)], hash2, hash1))); +} + +Test(coin_transaction, TestTransactionUpdateHeader) +{ + int result; + Transaction__Handle handle; + coin__Transaction *ptx; + ptx = makeTransaction(&handle); + cipher__SHA256 hash, nullHash, hashInner; + memcpy(&hash, &ptx->InnerHash, sizeof(cipher__SHA256)); + memset(&ptx->InnerHash, 0, sizeof(cipher__SHA256)); + memset(&nullHash, 0, sizeof(cipher__SHA256)); + result = SKY_coin_Transaction_UpdateHeader(handle); + cr_assert(not(eq(u8[sizeof(cipher__SHA256)], ptx->InnerHash, nullHash))); + cr_assert(eq(u8[sizeof(cipher__SHA256)], hash, ptx->InnerHash)); + result = SKY_coin_Transaction_HashInner(handle, &hashInner); + cr_assert(result == SKY_OK); + cr_assert(eq(u8[sizeof(cipher__SHA256)], hashInner, ptx->InnerHash)); +} + +Test(coin_transaction, TestTransactionsSize) +{ + int result; + Transactions__Handle txns; + result = makeTransactions(10, &txns); + cr_assert(result == SKY_OK); + GoInt size = 0; + for (size_t i = 0; i < 10; i++) { + Transaction__Handle handle; + result = SKY_coin_Transactions_GetAt(txns, i, &handle); + registerHandleClose(handle); + cr_assert(result == SKY_OK); + GoSlice p1 = {NULL, 0, 0}; + result = SKY_coin_Transaction_Serialize(handle, (GoSlice_ *) &p1); + cr_assert(result == SKY_OK, "SKY_coin_Transaction_Serialize"); + size += p1.len; + cr_assert(result == SKY_OK, "SKY_coin_Transaction_Size"); + } + GoInt sizeTransactions; + result = SKY_coin_Transactions_Size(txns, &sizeTransactions); + cr_assert(size != 0); + cr_assert(sizeTransactions == size); +} + +Test(coin_transactions, TestTransactionVerifyInput, SKY_ABORT) +{ + int result; + Transaction__Handle handle; + coin__Transaction *ptx; + ptx = makeTransaction(&handle); + result = SKY_coin_Transaction_VerifyInput(handle, NULL); + cr_assert(result == SKY_ERROR); + coin__UxArray ux; + memset(&ux, 0, sizeof(coin__UxArray)); + result = SKY_coin_Transaction_VerifyInput(handle, &ux); + cr_assert(result == SKY_ERROR); + memset(&ux, 0, sizeof(coin__UxArray)); + ux.data = malloc(3 * sizeof(coin__UxOut)); + cr_assert(ux.data != NULL); + registerMemCleanup(ux.data); + ux.len = 3; + ux.cap = 3; + memset(ux.data, 0, 3 * sizeof(coin__UxOut)); + result = SKY_coin_Transaction_VerifyInput(handle, &ux); + cr_assert(result == SKY_ERROR); + + coin__UxOut uxOut; + cipher__SecKey seckey; + cipher__Sig sig; + cipher__SHA256 hash; + + result = makeUxOutWithSecret(&uxOut, &seckey); + cr_assert(result == SKY_OK); + ptx = makeTransactionFromUxOut(&uxOut, &seckey, &handle); + cr_assert(result == SKY_OK); + result = SKY_coin_Transaction_ResetSignatures(handle, 0); + cr_assert(result == SKY_OK); + ux.data = &uxOut; + ux.len = 1; + ux.cap = 1; + result = SKY_coin_Transaction_VerifyInput(handle, &ux); + cr_assert(result == SKY_ERROR); + + memset(&sig, 0, sizeof(cipher__Sig)); + result = makeUxOutWithSecret(&uxOut, &seckey); + cr_assert(result == SKY_OK); + ptx = makeTransactionFromUxOut(&uxOut, &seckey, &handle); + cr_assert(result == SKY_OK); + result = SKY_coin_Transaction_ResetSignatures(handle, 1); + cr_assert(result == SKY_OK); + memcpy(ptx->Sigs.data, &sig, sizeof(cipher__Sig)); + ux.data = &uxOut; + ux.len = 1; + ux.cap = 1; + result = SKY_coin_Transaction_VerifyInput(handle, &ux); + cr_assert(result == SKY_ERROR); + + // Invalid Tx Inner Hash + result = makeUxOutWithSecret(&uxOut, &seckey); + cr_assert(result == SKY_OK); + ptx = makeTransactionFromUxOut(&uxOut, &seckey, &handle); + cr_assert(result == SKY_OK); + memset(ptx->InnerHash, 0, sizeof(cipher__SHA256)); + ux.data = &uxOut; + ux.len = 1; + ux.cap = 1; + result = SKY_coin_Transaction_VerifyInput(handle, &ux); + cr_assert(result == SKY_ERROR); + + // Ux hash mismatch + result = makeUxOutWithSecret(&uxOut, &seckey); + cr_assert(result == SKY_OK); + ptx = makeTransactionFromUxOut(&uxOut, &seckey, &handle); + cr_assert(result == SKY_OK); + memset(&uxOut, 0, sizeof(coin__UxOut)); + ux.data = &uxOut; + ux.len = 1; + ux.cap = 1; + result = SKY_coin_Transaction_VerifyInput(handle, &ux); + cr_assert(result == SKY_ERROR); + + // Invalid signature + result = makeUxOutWithSecret(&uxOut, &seckey); + cr_assert(result == SKY_OK); + ptx = makeTransactionFromUxOut(&uxOut, &seckey, &handle); + cr_assert(result == SKY_OK); + result = SKY_coin_Transaction_ResetSignatures(handle, 1); + cr_assert(result == SKY_OK); + memset(ptx->Sigs.data, 0, sizeof(cipher__Sig)); + ux.data = &uxOut; + ux.len = 1; + ux.cap = 1; + result = SKY_coin_Transaction_VerifyInput(handle, &ux); + cr_assert(result == SKY_ERROR); + + // Valid + result = makeUxOutWithSecret(&uxOut, &seckey); + cr_assert(result == SKY_OK); + ptx = makeTransactionFromUxOut(&uxOut, &seckey, &handle); + cr_assert(result == SKY_OK); + ux.data = &uxOut; + ux.len = 1; + ux.cap = 1; + result = SKY_coin_Transaction_VerifyInput(handle, &ux); + cr_assert(result == SKY_OK); +} + +Test(coin_transactions, TestTransactionSignInputs, SKY_ABORT) +{ + int result; + coin__Transaction *ptx; + Transaction__Handle handle; + coin__UxOut ux, ux2; + cipher__SecKey seckey, seckey2; + cipher__SHA256 hash, hash2; + cipher__Address addr, addr2; + cipher__PubKey pubkey; + GoUint16 r; + GoSlice keys; + + // Error if txns already signed + ptx = makeEmptyTransaction(&handle); + result = SKY_coin_Transaction_ResetSignatures(handle, 1); + cr_assert(result == SKY_OK); + + memset(&seckey, 0, sizeof(cipher__SecKey)); + keys.data = &seckey; + keys.len = 1; + keys.cap = 1; + result = SKY_coin_Transaction_SignInputs(handle, keys); + cr_assert(result == SKY_ERROR); + + // Panics if not enough keys + ptx = makeEmptyTransaction(&handle); + memset(&seckey, 0, sizeof(cipher__SecKey)); + memset(&seckey2, 0, sizeof(cipher__SecKey)); + result = makeUxOutWithSecret(&ux, &seckey); + cr_assert(result == SKY_OK); + result = SKY_coin_UxOut_Hash(&ux, &hash); + cr_assert(result == SKY_OK); + result = SKY_coin_Transaction_PushInput(handle, &hash, &r); + cr_assert(result == SKY_OK); + result = makeUxOutWithSecret(&ux2, &seckey2); + cr_assert(result == SKY_OK); + result = SKY_coin_UxOut_Hash(&ux2, &hash2); + cr_assert(result == SKY_OK); + result = SKY_coin_Transaction_PushInput(handle, &hash2, &r); + cr_assert(result == SKY_OK); + makeAddress(&addr); + result = SKY_coin_Transaction_PushOutput(handle, &addr, 40, 80); + cr_assert(result == SKY_OK); + cr_assert(ptx->Sigs.len == 0); + keys.data = &seckey; + keys.len = 1; + keys.cap = 1; + result = SKY_coin_Transaction_SignInputs(handle, keys); + cr_assert(result == SKY_ERROR); + cr_assert(ptx->Sigs.len == 0); + + // Valid signing + result = SKY_coin_Transaction_HashInner(handle, &hash); + cr_assert(result == SKY_OK); + keys.data = malloc(2 * sizeof(cipher__SecKey)); + cr_assert(keys.data != NULL); + registerMemCleanup(keys.data); + keys.len = keys.cap = 2; + memcpy(keys.data, &seckey, sizeof(cipher__SecKey)); + memcpy(((cipher__SecKey *)keys.data) + 1, &seckey2, sizeof(cipher__SecKey)); + result = SKY_coin_Transaction_SignInputs(handle, keys); + cr_assert(result == SKY_OK); + cr_assert(ptx->Sigs.len == 2); + result = SKY_coin_Transaction_HashInner(handle, &hash2); + cr_assert(result == SKY_OK); + cr_assert(eq(u8[sizeof(cipher__SHA256)], hash, hash2)); + + result = SKY_cipher_PubKeyFromSecKey(&seckey, &pubkey); + cr_assert(result == SKY_OK); + result = SKY_cipher_AddressFromPubKey(&pubkey, &addr); + cr_assert(result == SKY_OK); + result = SKY_cipher_PubKeyFromSecKey(&seckey2, &pubkey); + cr_assert(result == SKY_OK); + result = SKY_cipher_AddressFromPubKey(&pubkey, &addr2); + cr_assert(result == SKY_OK); + + cipher__SHA256 addHash, addHash2; + result = + SKY_cipher_AddSHA256(&hash, (cipher__SHA256 *)ptx->In.data, &addHash); + cr_assert(result == SKY_OK); + result = SKY_cipher_AddSHA256(&hash, ((cipher__SHA256 *)ptx->In.data) + 1, + &addHash2); + cr_assert(result == SKY_OK); + result = SKY_cipher_VerifyAddressSignedHash(&addr, &addHash, (cipher__Sig *)ptx->Sigs.data); + cr_assert(result == SKY_OK); + result = + SKY_cipher_VerifyAddressSignedHash(&addr2, &addHash2, ((cipher__Sig *)ptx->Sigs.data) + 1); + cr_assert(result == SKY_OK); + result = SKY_cipher_VerifyAddressSignedHash(&addr, &hash, ((cipher__Sig *)ptx->Sigs.data) + 1); + cr_assert(result == SKY_ERROR); + result = SKY_cipher_VerifyAddressSignedHash(&addr2, &hash, (cipher__Sig *)ptx->Sigs.data); + cr_assert(result == SKY_ERROR); +} + +Test(coin_transactions, TestTransactionHashInner) +{ + int result; + Transaction__Handle handle1 = 0, handle2 = 0; + coin__Transaction *ptx = NULL; + coin__Transaction *ptx2 = NULL; + ptx = makeTransaction(&handle1); + cipher__SHA256 hash, nullHash; + result = SKY_coin_Transaction_HashInner(handle1, &hash); + cr_assert(result == SKY_OK); + memset(&nullHash, 0, sizeof(cipher__SHA256)); + cr_assert(not(eq(u8[sizeof(cipher__SHA256)], nullHash, hash))); + + // If tx.In is changed, hash should change + ptx2 = copyTransaction(handle1, &handle2); + cr_assert(eq(type(coin__Transaction), *ptx, *ptx2)); + cr_assert(ptx != ptx2); + cr_assert(ptx2->In.len > 0); + coin__UxOut uxOut; + makeUxOut(&uxOut); + cipher__SHA256 *phash = ptx2->In.data; + result = SKY_coin_UxOut_Hash(&uxOut, phash); + cr_assert(result == SKY_OK); + cr_assert(not(eq(type(coin__Transaction), *ptx, *ptx2))); + cipher__SHA256 hash1, hash2; + result = SKY_coin_Transaction_HashInner(handle1, &hash1); + cr_assert(result == SKY_OK); + result = SKY_coin_Transaction_HashInner(handle2, &hash2); + cr_assert(result == SKY_OK); + cr_assert(not(eq(u8[sizeof(cipher__SHA256)], hash1, hash2))); + + // If tx.Out is changed, hash should change + handle2 = 0; + ptx2 = copyTransaction(handle1, &handle2); + cr_assert(ptx != ptx2); + cr_assert(eq(type(coin__Transaction), *ptx, *ptx2)); + coin__TransactionOutput *output = ptx2->Out.data; + cipher__Address addr; + makeAddress(&addr); + memcpy(&output->Address, &addr, sizeof(cipher__Address)); + cr_assert(not(eq(type(coin__Transaction), *ptx, *ptx2))); + cr_assert(eq(type(cipher__Address), addr, output->Address)); + result = SKY_coin_Transaction_HashInner(handle1, &hash1); + cr_assert(result == SKY_OK); + result = SKY_coin_Transaction_HashInner(handle2, &hash2); + cr_assert(result == SKY_OK); + cr_assert(not(eq(u8[sizeof(cipher__SHA256)], hash1, hash2))); + + // If tx.Head is changed, hash should not change + ptx2 = copyTransaction(handle1, &handle2); + int len = ptx2->Sigs.len; + cipher__Sig *newSigs = malloc((len + 1) * sizeof(cipher__Sig)); + cr_assert(newSigs != NULL); + registerMemCleanup(newSigs); + memcpy(newSigs, ptx2->Sigs.data, len * sizeof(cipher__Sig)); + result = SKY_coin_Transaction_ResetSignatures(handle2, len + 1); + cr_assert(result == SKY_OK); + memcpy(ptx2->Sigs.data, newSigs, len * sizeof(cipher__Sig)); + newSigs += len; + memset(newSigs, 0, sizeof(cipher__Sig)); + result = SKY_coin_Transaction_HashInner(handle1, &hash1); + cr_assert(result == SKY_OK); + result = SKY_coin_Transaction_HashInner(handle2, &hash2); + cr_assert(result == SKY_OK); + cr_assert(eq(u8[sizeof(cipher__SHA256)], hash1, hash2)); +} + +Test(coin_transactions, TestTransactionSerialization) +{ + int result; + coin__Transaction *ptx; + Transaction__Handle handle; + ptx = makeTransaction(&handle); + GoSlice_ data; + memset(&data, 0, sizeof(GoSlice_)); + result = SKY_coin_Transaction_Serialize(handle, &data); + cr_assert(result == SKY_OK); + registerMemCleanup(data.data); + coin__Transaction *ptx2; + Transaction__Handle handle2; + GoSlice d = {data.data, data.len, data.cap}; + result = SKY_coin_TransactionDeserialize(d, &handle2); + cr_assert(result == SKY_OK); + result = SKY_coin_GetTransactionObject(handle2, &ptx2); + cr_assert(result == SKY_OK); + cr_assert(eq(type(coin__Transaction), *ptx, *ptx2)); +} + +Test(coin_transactions, TestTransactionOutputHours) +{ + coin__Transaction *ptx; + Transaction__Handle handle; + ptx = makeEmptyTransaction(&handle); + cipher__Address addr; + makeAddress(&addr); + int result; + result = SKY_coin_Transaction_PushOutput(handle, &addr, 1000000, 100); + cr_assert(result == SKY_OK); + makeAddress(&addr); + result = SKY_coin_Transaction_PushOutput(handle, &addr, 1000000, 200); + cr_assert(result == SKY_OK); + makeAddress(&addr); + result = SKY_coin_Transaction_PushOutput(handle, &addr, 1000000, 500); + cr_assert(result == SKY_OK); + makeAddress(&addr); + result = SKY_coin_Transaction_PushOutput(handle, &addr, 1000000, 0); + cr_assert(result == SKY_OK); + GoUint64 hours; + result = SKY_coin_Transaction_OutputHours(handle, &hours); + cr_assert(result == SKY_OK); + cr_assert(hours == 800); + makeAddress(&addr); + result = SKY_coin_Transaction_PushOutput(handle, &addr, 1000000, + 0xFFFFFFFFFFFFFFFF - 700); + result = SKY_coin_Transaction_OutputHours(handle, &hours); + cr_assert(result == SKY_ERROR); +} + +Test(coin_transactions, TestTransactionsHashes) +{ + int result; + GoSlice_ hashes = {NULL, 0, 0}; + Transactions__Handle hTxns; + result = makeTransactions(4, &hTxns); + cr_assert(result == SKY_OK); + + result = SKY_coin_Transactions_Hashes(hTxns, &hashes); + cr_assert(result == SKY_OK, "SKY_coin_Transactions_Hashes failed"); + registerMemCleanup(hashes.data); + cr_assert(hashes.len == 4); + cipher__SHA256 *ph = hashes.data; + cipher__SHA256 hash; + for (int i = 0; i < 4; i++) + { + Transaction__Handle handle; + result = SKY_coin_Transactions_GetAt(hTxns, i, &handle); + cr_assert(result == SKY_OK); + result = SKY_coin_Transaction_Hash(handle, &hash); + cr_assert(result == SKY_OK, "SKY_coin_Transaction_Hash failed"); + cr_assert(eq(u8[sizeof(cipher__SHA256)], *ph, hash)); + ph++; + } +} + +Test(coin_transactions, TestTransactionsTruncateBytesTo) +{ + int result; + Transactions__Handle h1, h2; + result = makeTransactions(10, &h1); + cr_assert(result == SKY_OK); + GoInt length; + result = SKY_coin_Transactions_Length(h1, &length); + cr_assert(result == SKY_OK); + int trunc = 0; + GoInt size; + for (int i = 0; i < length / 2; i++) + { + Transaction__Handle handle; + result = SKY_coin_Transactions_GetAt(h1, i, &handle); + registerHandleClose(handle); + cr_assert(result == SKY_OK); + result = SKY_coin_Transaction_Size(handle, &size); + trunc += size; + cr_assert(result == SKY_OK, "SKY_coin_Transaction_Size failed"); + } + result = SKY_coin_Transactions_TruncateBytesTo(h1, trunc, &h2); + cr_assert(result == SKY_OK, "SKY_coin_Transactions_TruncateBytesTo failed"); + registerHandleClose(h2); + + GoInt length2; + result = SKY_coin_Transactions_Length(h2, &length2); + cr_assert(result == SKY_OK); + cr_assert(length2 == length / 2); + result = SKY_coin_Transactions_Size(h2, &size); + cr_assert(result == SKY_OK, "SKY_coin_Transactions_Size failed"); + cr_assert(trunc == size); + + trunc++; + result = SKY_coin_Transactions_TruncateBytesTo(h1, trunc, &h2); + cr_assert(result == SKY_OK, "SKY_coin_Transactions_TruncateBytesTo failed"); + registerHandleClose(h2); + + // Stepping into next boundary has same cutoff, must exceed + result = SKY_coin_Transactions_Length(h2, &length2); + cr_assert(result == SKY_OK); + cr_assert(length2 == length / 2); + result = SKY_coin_Transactions_Size(h2, &size); + cr_assert(result == SKY_OK, "SKY_coin_Transactions_Size failed"); + cr_assert(trunc - 1 == size); +} + +typedef struct +{ + GoUint64 coins; + GoUint64 hours; +} test_ux; + +typedef struct +{ + test_ux *inUxs; + test_ux *outUxs; + int sizeIn; + int sizeOut; + GoUint64 headTime; + int failure; +} test_case; + +int makeTestCaseArrays(test_ux *elems, int size, coin__UxArray *pArray) +{ + if (size <= 0) + { + pArray->len = 0; + pArray->cap = 0; + pArray->data = NULL; + return SKY_OK; + } + int elems_size = sizeof(coin__UxOut); + void *data; + data = malloc(size * elems_size); + if (data == NULL) + return SKY_ERROR; + registerMemCleanup(data); + memset(data, 0, size * elems_size); + pArray->data = data; + pArray->len = size; + pArray->cap = size; + coin__UxOut *p = data; + for (int i = 0; i < size; i++) + { + p->Body.Coins = elems[i].coins; + p->Body.Hours = elems[i].hours; + p++; + } + return SKY_OK; +} + +Test(coin_transactions, TestVerifyTransactionCoinsSpending) +{ + + // Input coins overflow + test_ux in1[] = {{MaxUint64 - Million + 1, 10}, {Million, 0}}; + + // Output coins overflow + test_ux in2[] = {{10 * Million, 10}}; + test_ux out2[] = {{MaxUint64 - 10 * Million + 1, 0}, {20 * Million, 1}}; + + // Insufficient coins + test_ux in3[] = {{10 * Million, 10}, {15 * Million, 10}}; + test_ux out3[] = {{20 * Million, 1}, {10 * Million, 1}}; + + // Destroyed coins + test_ux in4[] = {{10 * Million, 10}, {15 * Million, 10}}; + test_ux out4[] = {{5 * Million, 1}, {10 * Million, 1}}; + + // Valid + test_ux in5[] = {{10 * Million, 10}, {15 * Million, 10}}; + test_ux out5[] = {{10 * Million, 11}, {10 * Million, 1}, {5 * Million, 0}}; + + test_case tests[] = { + {in1, NULL, 2, 0, 0, 1}, // Input coins overflow + {in2, out2, 1, 2, 0, 1}, // Output coins overflow + {in3, out3, 2, 2, 0, 1}, // Destroyed coins + {in4, out4, 1, 1, Million, + 1}, // Invalid (coin hours overflow when adding earned hours, which is + // treated as 0, and now enough coin hours) + {in5, out5, 2, 3, 0, 0} // Valid + }; + + coin__UxArray inArray; + coin__UxArray outArray; + int result; + int count = sizeof(tests) / sizeof(tests[0]); + for (int i = 0; i < count; i++) + { + result = makeTestCaseArrays(tests[i].inUxs, tests[i].sizeIn, &inArray); + cr_assert(result == SKY_OK); + result = makeTestCaseArrays(tests[i].outUxs, tests[i].sizeOut, &outArray); + cr_assert(result == SKY_OK); + result = SKY_coin_VerifyTransactionCoinsSpending(&inArray, &outArray); + if (tests[i].failure) + cr_assert(result == SKY_ERROR, "VerifyTransactionCoinsSpending succeeded %d", + i + 1); + else + cr_assert(result == SKY_OK, "VerifyTransactionCoinsSpending failed %d", + i + 1); + } +} + +Test(coin_transactions, TestVerifyTransactionHoursSpending) +{ + GoUint64 MaxUint64 = 0xFFFFFFFFFFFFFFFF; + GoUint64 Million = 1000000; + + // Input hours overflow + test_ux in1[] = {{3 * Million, MaxUint64 - Million + 1}, {Million, Million}}; + + // Insufficient coin hours + test_ux in2[] = {{10 * Million, 10}, {15 * Million, 10}}; + + test_ux out2[] = {{15 * Million, 10}, {10 * Million, 11}}; + + // coin hours time calculation overflow + test_ux in3[] = {{10 * Million, 10}, {15 * Million, 10}}; + + test_ux out3[] = {{10 * Million, 11}, {10 * Million, 1}, {5 * Million, 0}}; + + // Invalid (coin hours overflow when adding earned hours, which is treated as + // 0, and now enough coin hours) + test_ux in4[] = {{10 * Million, MaxUint64}}; + + test_ux out4[] = {{10 * Million, 1}}; + + // Valid (coin hours overflow when adding earned hours, which is treated as 0, + // but not sending any hours) + test_ux in5[] = {{10 * Million, MaxUint64}}; + + test_ux out5[] = {{10 * Million, 0}}; + + // Valid (base inputs have insufficient coin hours, but have sufficient after + // adjusting coinhours by headTime) + test_ux in6[] = {{10 * Million, 10}, {15 * Million, 10}}; + + test_ux out6[] = {{15 * Million, 10}, {10 * Million, 11}}; + + // valid + test_ux in7[] = {{10 * Million, 10}, {15 * Million, 10}}; + + test_ux out7[] = {{10 * Million, 11}, {10 * Million, 1}, {5 * Million, 0}}; + + test_case tests[] = { + {in1, NULL, 2, 0, 0, 1}, // Input hours overflow + {in2, out2, 2, 2, 0, 1}, // Insufficient coin hours + {in3, out3, 2, 3, MaxUint64, 1}, // coin hours time calculation overflow + {in4, out4, 1, 1, Million, + 1}, // Invalid (coin hours overflow when adding earned hours, which is + // treated as 0, and now enough coin hours) + {in5, out5, 1, 1, 0, + 0}, // Valid (coin hours overflow when adding earned hours, which is + // treated as 0, but not sending any hours) + {in6, out6, 2, 2, 1492707255, + 0}, // Valid (base inputs have insufficient coin hours, but have + // sufficient after adjusting coinhours by headTime) + {in7, out7, 2, 3, 0, 0}, // Valid + }; + coin__UxArray inArray; + coin__UxArray outArray; + int result; + int count = sizeof(tests) / sizeof(tests[0]); + for (int i = 0; i < count; i++) + { + result = makeTestCaseArrays(tests[i].inUxs, tests[i].sizeIn, &inArray); + cr_assert(result == SKY_OK); + result = makeTestCaseArrays(tests[i].outUxs, tests[i].sizeOut, &outArray); + cr_assert(result == SKY_OK); + result = SKY_coin_VerifyTransactionHoursSpending(tests[i].headTime, + &inArray, &outArray); + if (tests[i].failure) + cr_assert(result == SKY_ERROR, + "SKY_coin_VerifyTransactionHoursSpending succeeded %d", i + 1); + else + cr_assert(result == SKY_OK, + "SKY_coin_VerifyTransactionHoursSpending failed %d", i + 1); + } +} + +GoUint32_ fix1FeeCalculator(Transaction__Handle handle, GoUint64_ *pFee, void *context) +{ + *pFee = 1; + return SKY_OK; +} + +GoUint32_ badFeeCalculator(Transaction__Handle handle, GoUint64_ *pFee, void *context) +{ + return SKY_ERROR; +} + +GoUint32_ overflowFeeCalculator(Transaction__Handle handle, GoUint64_ *pFee, void *context) +{ + *pFee = 0xFFFFFFFFFFFFFFFF; + return SKY_OK; +} + +Test(coin_transactions, TestTransactionsFees) +{ + GoUint64 fee; + int result; + Transactions__Handle transactionsHandle = 0; + Transaction__Handle transactionHandle = 0; + + // Nil txns + makeTransactions(0, &transactionsHandle); + FeeCalculator f1 = {fix1FeeCalculator, NULL}; + result = SKY_coin_Transactions_Fees(transactionsHandle, &f1, &fee); + cr_assert(result == SKY_OK); + cr_assert(fee == 0); + + makeEmptyTransaction(&transactionHandle); + result = SKY_coin_Transactions_Add(transactionsHandle, transactionHandle); + cr_assert(result == SKY_OK); + makeEmptyTransaction(&transactionHandle); + result = SKY_coin_Transactions_Add(transactionsHandle, transactionHandle); + cr_assert(result == SKY_OK); + // 2 transactions, calc() always returns 1 + result = SKY_coin_Transactions_Fees(transactionsHandle, &f1, &fee); + cr_assert(result == SKY_OK); + cr_assert(fee == 2); + + // calc error + FeeCalculator badFee = {badFeeCalculator, NULL}; + result = SKY_coin_Transactions_Fees(transactionsHandle, &badFee, &fee); + cr_assert(result == SKY_ERROR); + + // summing of calculated fees overflows + FeeCalculator overflow = {overflowFeeCalculator, NULL}; + result = SKY_coin_Transactions_Fees(transactionsHandle, &overflow, &fee); + cr_assert(result == SKY_ERROR); +} + +GoUint32_ feeCalculator1(Transaction__Handle handle, GoUint64_ *pFee, void *context) +{ + coin__Transaction *pTx; + int result = SKY_coin_GetTransactionObject(handle, &pTx); + if (result == SKY_OK) + { + coin__TransactionOutput *pOutput = pTx->Out.data; + *pFee = 100 * Million - pOutput->Hours; + } + return result; +} + +GoUint32_ feeCalculator2(Transaction__Handle handle, GoUint64_ *pFee, void *context) +{ + *pFee = 100 * Million; + return SKY_OK; +} + +void assertTransactionsHandleEqual(Transaction__Handle h1, Transaction__Handle h2, + char *testName) +{ + coin__Transaction *pTx1; + coin__Transaction *pTx2; + int result; + result = SKY_coin_GetTransactionObject(h1, &pTx1); + cr_assert(result == SKY_OK); + result = SKY_coin_GetTransactionObject(h2, &pTx2); + cr_assert(result == SKY_OK); + cr_assert(eq(type(coin__Transaction), *pTx1, *pTx2), "Failed SortTransactions test \"%s\"", testName); +} + +void testTransactionSorting(Transactions__Handle hTrans, + int *original_indexes, int original_indexes_count, + int *expected_indexes, int expected_indexes_count, FeeCalculator *feeCalc, + char *testName) +{ + + int result; + Transactions__Handle transactionsHandle, sortedTxnsHandle; + Transaction__Handle handle; + makeTransactions(0, &transactionsHandle); + for (int i = 0; i < original_indexes_count; i++) + { + result = SKY_coin_Transactions_GetAt(hTrans, original_indexes[i], &handle); + cr_assert(result == SKY_OK); + registerHandleClose(handle); + result = SKY_coin_Transactions_Add(transactionsHandle, handle); + cr_assert(result == SKY_OK); + } + result = SKY_coin_SortTransactions(transactionsHandle, feeCalc, &sortedTxnsHandle); + cr_assert(result == SKY_OK, "SKY_coin_SortTransactions"); + registerHandleClose(sortedTxnsHandle); + Transaction__Handle h1, h2; + for (int i = 0; i < expected_indexes_count; i++) + { + int expected_index = expected_indexes[i]; + result = SKY_coin_Transactions_GetAt(sortedTxnsHandle, i, &h1); + cr_assert(result == SKY_OK); + registerHandleClose(h1); + result = SKY_coin_Transactions_GetAt(hTrans, expected_index, &h2); + cr_assert(result == SKY_OK); + registerHandleClose(h2); + assertTransactionsHandleEqual(h1, h2, testName); + } +} + +GoUint32_ feeCalculator3(Transaction__Handle handle, GoUint64_ * pFee, void *context) +{ + cipher__SHA256 *thirdHash = (cipher__SHA256 *) context; + cipher__SHA256 hash; + + int result = SKY_coin_Transaction_Hash(handle, &hash); + if (result == SKY_OK && (memcmp(&hash, thirdHash, sizeof(cipher__SHA256)) == 0)) + { + *pFee = MaxUint64 / 2; + } + else + { + coin__Transaction *pTx; + result = SKY_coin_GetTransactionObject(handle, &pTx); + if (result == SKY_OK) + { + coin__TransactionOutput *pOutput = pTx->Out.data; + *pFee = 100 * Million - pOutput->Hours; + } + } + return result; +} + +GoUint32_ feeCalculator4(Transaction__Handle handle, GoUint64_ * pFee, void *context) +{ + cipher__SHA256 hash; + cipher__SHA256 *thirdHash = (cipher__SHA256 *) context; + + int result = SKY_coin_Transaction_Hash(handle, &hash); + if (result == SKY_OK && (memcmp(&hash, thirdHash, sizeof(cipher__SHA256)) == 0)) + { + *pFee = 0; + result = SKY_ERROR; + } + else + { + coin__Transaction *pTx; + result = SKY_coin_GetTransactionObject(handle, &pTx); + if (result == SKY_OK) + { + coin__TransactionOutput *pOutput = pTx->Out.data; + *pFee = 100 * Million - pOutput->Hours; + } + } + return result; +} + +Test(coin_transactions, TestSortTransactions) +{ + int n = 6; + int i; + int result; + + Transactions__Handle transactionsHandle = 0; + Transactions__Handle transactionsHandle2 = 0; + Transactions__Handle hashSortedTxnsHandle = 0; + Transactions__Handle sortedTxnsHandle = 0; + Transaction__Handle transactionHandle = 0; + cipher__Address addr; + makeTransactions(0, &transactionsHandle); + cipher__SHA256 thirdHash; + for (i = 0; i < 6; i++) + { + makeEmptyTransaction(&transactionHandle); + makeAddress(&addr); + result = SKY_coin_Transaction_PushOutput(transactionHandle, &addr, 1000000, i * 1000); + cr_assert(result == SKY_OK); + result = SKY_coin_Transaction_UpdateHeader(transactionHandle); + cr_assert(result == SKY_OK); + result = SKY_coin_Transactions_Add(transactionsHandle, transactionHandle); + cr_assert(result == SKY_OK); + if (i == 2) + { + result = SKY_coin_Transaction_Hash(transactionHandle, &thirdHash); + cr_assert(result == SKY_OK); + } + } + sortTransactions(transactionsHandle, &hashSortedTxnsHandle); + + int index1[] = {0, 1}; + int expec1[] = {0, 1}; + FeeCalculator fc1 = {feeCalculator1, NULL}; + testTransactionSorting(transactionsHandle, index1, 2, expec1, 2, &fc1, "Already sorted"); + int index2[] = {1, 0}; + int expec2[] = {0, 1}; + testTransactionSorting(transactionsHandle, index2, 2, expec2, 2, &fc1, "reverse sorted"); + FeeCalculator fc2 = {feeCalculator2, NULL}; + testTransactionSorting(hashSortedTxnsHandle, index2, 2, expec2, 2, &fc2, "hash tiebreaker"); + + int index3[] = {1, 2, 0}; + int expec3[] = {2, 0, 1}; + FeeCalculator f3 = {feeCalculator3, &thirdHash}; + testTransactionSorting(transactionsHandle, index3, 3, expec3, 3, &f3, "invalid fee multiplication is capped"); + + int index4[] = {1, 2, 0}; + int expec4[] = {0, 1}; + FeeCalculator f4 = {feeCalculator4, &thirdHash}; + testTransactionSorting(transactionsHandle, index4, 3, expec4, 2, &f4, "failed fee calc is filtered"); +} diff --git a/lib/cgo/tests/cipher.testsuite.c b/lib/cgo/tests/cipher.testsuite.c index 297b08178b..16bf882695 100644 --- a/lib/cgo/tests/cipher.testsuite.c +++ b/lib/cgo/tests/cipher.testsuite.c @@ -430,22 +430,26 @@ void ValidateSeedData(SeedTestData* seedData, InputTestData* inputData) { cr_assert(eq(type(cipher__Address), addr1, addr2), "%d-th SKY_cipher_AddressFromPubKey and SKY_cipher_AddressFromSecKey must generate same addresses", i); - // TODO : Translate once secp256k1 be part of libskycoin + //----------------------------------------------- + // secp256k1 not exported in the libc API + //----------------------------------------------- /* - validSec := secp256k1.VerifySeckey(s[:]) - if validSec != 1 { - return errors.New("secp256k1.VerifySeckey failed") - } - - validPub := secp256k1.VerifyPubkey(p[:]) - if validPub != 1 { - return errors.New("secp256k1.VerifyPubkey failed") - } - */ + GoInt validSec; + char bufferSecKey[101]; + strnhex((unsigned char *)s, bufferSecKey, sizeof(cipher__SecKey)); + GoSlice slseckey = { bufferSecKey,sizeof(cipher__SecKey),65 }; + SKY_secp256k1_VerifySeckey(slseckey,&validSec); + cr_assert(validSec ==1 ,"SKY_secp256k1_VerifySeckey failed"); + + GoInt validPub; + GoSlice slpubkey = { &p,sizeof(cipher__PubKey), sizeof(cipher__PubKey) }; + SKY_secp256k1_VerifyPubkey(slpubkey,&validPub); + cr_assert(validPub ==1 ,"SKY_secp256k1_VerifyPubkey failed"); // FIXME: without cond : 'not give a valid preprocessing token' bool cond = (!(inputData == NULL && expected->Signatures.len != 0)); cr_assert(cond, "%d seed data contains signatures but input data was not provided", i); + */ if (inputData != NULL) { cr_assert(expected->Signatures.len == inputData->Hashes.len, @@ -460,11 +464,11 @@ void ValidateSeedData(SeedTestData* seedData, InputTestData* inputData) { mem_actual.size = mem_expect.size = sizeof(cipher__Sig); cr_assert(ne(mem, mem_actual, mem_expect), "%d-th provided signature for %d-th data set must not be null", j, i); - GoUint32 err = SKY_cipher_VerifySignature(&p, sig, h); + GoUint32 err = SKY_cipher_VerifyPubKeySignedHash(&p, sig, h); cr_assert(err == SKY_OK, - "SKY_cipher_VerifySignature failed: error=%d dataset=%d hashidx=%d", err, i, j); - err = SKY_cipher_ChkSig(&addr1, h, sig); - cr_assert(err == SKY_OK, "SKY_cipher_ChkSig failed: error=%d dataset=%d hashidx=%d", err, i, j); + "SKY_cipher_VerifyPubKeySignedHash failed: error=%d dataset=%d hashidx=%d", err, i, j); + err = SKY_cipher_VerifyAddressSignedHash(&addr1, sig, h); + cr_assert(err == SKY_OK, "SKY_cipher_VerifyAddressSignedHash failed: error=%d dataset=%d hashidx=%d", err, i, j); err = SKY_cipher_VerifySignedHash(sig, h); cr_assert(err == SKY_OK, "SKY_cipher_VerifySignedHash failed: error=%d dataset=%d hashidx=%d", err, i, j); diff --git a/lib/cgo/tests/libsky_criterion.c b/lib/cgo/tests/libsky_criterion.c deleted file mode 100644 index 80e7bcf071..0000000000 --- a/lib/cgo/tests/libsky_criterion.c +++ /dev/null @@ -1,104 +0,0 @@ - -#include -#include "skycriterion.h" -#include "skystring.h" - -int cr_user_cipher__Address_eq(cipher__Address *addr1, cipher__Address *addr2){ - if(addr1->Version != addr2->Version) - return 0; - for (int i = 0; i < sizeof(cipher__Ripemd160); ++i) { - if(addr1->Key[i] != addr2->Key[i]) - return 0; - } - return 1; -} - -char *cr_user_cipher__Address_tostr(cipher__Address *addr1) -{ - char *out; - - cr_asprintf(&out, "(cipher__Address) { .Key = %s, .Version = %llu }", addr1->Key, (unsigned long long) addr1->Version); - return out; -} - -int cr_user_cipher__Address_noteq(cipher__Address *addr1, cipher__Address *addr2){ - if(addr1->Version != addr2->Version) - return 0; - for (int i = 0; i < sizeof(cipher__Ripemd160); ++i) { - if(addr1->Key[i] != addr2->Key[i]) - return 0; - } - return 1; -} - -int cr_user_GoString_eq(GoString *string1, GoString *string2){ - return (string1->n == string2->n) && - (strcmp( (char *) string1->p, (char *) string2->p) == 0); -} - -char *cr_user_GoString_tostr(GoString *string) -{ - char *out; - cr_asprintf(&out, "(GoString) { .Data = %s, .Length = %llu }", - string->p, (unsigned long long) string->n); - return out; -} - -int cr_user_GoString__eq(GoString_ *string1, GoString_ *string2){ - return cr_user_GoString_eq((GoString *) &string1, (GoString *) &string2); -} - -char *cr_user_GoString__tostr(GoString_ *string) { - return cr_user_GoString_tostr((GoString *)string); -} - -int cr_user_cipher__SecKey_eq(cipher__SecKey *seckey1, cipher__SecKey *seckey2){ - return memcmp((void *)seckey1,(void *)seckey2, sizeof(cipher__SecKey)) == 0; -} - -char *cr_user_cipher__SecKey_tostr(cipher__SecKey *seckey1) -{ - char *out; - char hexdump[101]; - - strnhex((unsigned char *)seckey1, hexdump, sizeof(cipher__SecKey)); - cr_asprintf(&out, "(cipher__SecKey) { %s }", hexdump); - return out; -} - - -int cr_user_cipher__Ripemd160_noteq(cipher__Ripemd160 *rp1, cipher__Ripemd160 *rp2){ - return memcmp((void *)rp1,(void *)rp2, sizeof(cipher__Ripemd160)) != 0; -} - -int cr_user_cipher__Ripemd160_eq(cipher__Ripemd160 *rp1, cipher__Ripemd160 *rp2){ - return memcmp((void *)rp1,(void *)rp2, sizeof(cipher__Ripemd160)) == 0; -} - -char *cr_user_cipher__Ripemd160_tostr(cipher__Ripemd160 *rp1) -{ - char *out; - char hexdump[101]; - - strnhex((unsigned char *)rp1, hexdump, sizeof(cipher__Ripemd160)); - cr_asprintf(&out, "(cipher__Ripemd160) { %s }", hexdump ); - return out; -} - -int cr_user_cipher__SHA256_noteq(cipher__SHA256 *sh1, cipher__SHA256 *sh2){ - return memcmp((void *)sh1,(void *)sh1, sizeof(cipher__SHA256)) != 0; -} - -int cr_user_cipher__SHA256_eq(cipher__SHA256 *sh1, cipher__SHA256 *sh2){ - return memcmp((void *)sh1,(void *)sh1, sizeof(cipher__SHA256)) == 0; -} - -char *cr_user_cipher__SHA256_tostr(cipher__SHA256 *sh1) { - char *out; - char hexdump[101]; - - strnhex((unsigned char *)sh1, hexdump, sizeof(cipher__SHA256)); - cr_asprintf(&out, "(cipher__SHA256) { %s }", hexdump); - return out; -} - diff --git a/lib/cgo/tests/libsky_string.c b/lib/cgo/tests/libsky_string.c deleted file mode 100644 index 06e0f62b13..0000000000 --- a/lib/cgo/tests/libsky_string.c +++ /dev/null @@ -1,41 +0,0 @@ - -#include "skystring.h" - -#define ALPHANUM "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" -#define ALPHANUM_LEN 62 -#define SIZE_ALL -1 - -void randBytes(GoSlice *bytes, size_t n) { - size_t i = 0; - unsigned char *ptr = (unsigned char *) bytes->data; - for (; i < n; ++i, ++ptr) { - *ptr = ALPHANUM[rand() % ALPHANUM_LEN]; - } - bytes->len = (GoInt) n; -} - -void bytesnhex(unsigned char* buf, char *str, int n){ - unsigned char * pin = buf; - const char * hex = "0123456789ABCDEF"; - char * pout = str; - for(; n; --n){ - *pout++ = hex[(*pin>>4)&0xF]; - *pout++ = hex[(*pin++)&0xF]; - } - *pout = 0; -} - -void strnhex(unsigned char* buf, char *str, int n){ - unsigned char * pin = buf; - const char * hex = "0123456789ABCDEF"; - char * pout = str; - for(; *pin && n; --n){ - *pout++ = hex[(*pin>>4)&0xF]; - *pout++ = hex[(*pin++)&0xF]; - } - *pout = 0; -} - -void strhex(unsigned char* buf, char *str){ - strnhex(buf, str, SIZE_ALL); -} diff --git a/lib/cgo/tests/libsky_testutil.c b/lib/cgo/tests/libsky_testutil.c deleted file mode 100644 index b38a03be4a..0000000000 --- a/lib/cgo/tests/libsky_testutil.c +++ /dev/null @@ -1,121 +0,0 @@ - -#include -#include -#include -#include -#include - -#include "json.h" -#include "skytest.h" -#include "skytypes.h" - -int MEMPOOLIDX = 0; -void *MEMPOOL[1024 * 256]; - -int JSONPOOLIDX = 0; -json_value* JSON_POOL[128]; - -void* registerMemCleanup(void* p) { - int i; - for (i = 0; i < MEMPOOLIDX; i++) { - if(MEMPOOL[i] == NULL){ - MEMPOOL[i] = p; - return p; - } - } - MEMPOOL[MEMPOOLIDX++] = p; - return p; -} - -int registerJsonFree(void *p){ - int i; - for (i = 0; i < JSONPOOLIDX; i++) { - if(JSON_POOL[i] == NULL){ - JSON_POOL[i] = p; - return i; - } - } - JSON_POOL[JSONPOOLIDX++] = p; - return JSONPOOLIDX-1; -} - -void cleanupMem() { - int i; - void **ptr; - - for (i = MEMPOOLIDX, ptr = MEMPOOL; i; --i) { - if( *ptr ) { - free(*ptr); - *ptr = NULL; - } - ptr++; - } - MEMPOOLIDX = 0; - for (i = JSONPOOLIDX, ptr = (void*)JSON_POOL; i; --i) { - if( *ptr ) { - json_value_free(*ptr); - *ptr = NULL; - } - ptr++; - } - JSONPOOLIDX = 0; -} - -json_value* loadJsonFile(const char* filename){ - FILE *fp; - struct stat filestatus; - int file_size; - char* file_contents; - json_char* json; - json_value* value; - - if ( stat(filename, &filestatus) != 0) { - return NULL; - } - file_size = filestatus.st_size; - file_contents = (char*)malloc(filestatus.st_size); - if ( file_contents == NULL) { - return NULL; - } - fp = fopen(filename, "rt"); - if (fp == NULL) { - free(file_contents); - return NULL; - } - if ( fread(file_contents, file_size, 1, fp) != 1 ) { - fclose(fp); - free(file_contents); - return NULL; - } - fclose(fp); - - json = (json_char*)file_contents; - value = json_parse(json, file_size); - free(file_contents); - return value; -} - -void setup(void) { - srand ((unsigned int) time (NULL)); -} - -void teardown(void) { - cleanupMem(); -} - -// TODO: Move to libsky_io.c -void fprintbuff(FILE *f, void *buff, size_t n) { - unsigned char *ptr = (unsigned char *) buff; - fprintf(f, "[ "); - for (; n; --n, ptr++) { - fprintf(f, "%02d ", *ptr); - } - fprintf(f, "]"); -} - - -void toGoString(GoString_ *s, GoString *r){ -GoString * tmp = r; - - *tmp = (*(GoString *) s); -} diff --git a/lib/cgo/tests/testutils/base64.c b/lib/cgo/tests/testutils/base64.c index fa9585b4f4..3cb4a5e5b3 100644 --- a/lib/cgo/tests/testutils/base64.c +++ b/lib/cgo/tests/testutils/base64.c @@ -14,174 +14,180 @@ Thank you for inspiration: //Base64 char table - used internally for encoding unsigned char b64_chr[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; -unsigned int b64_int(unsigned int ch) { - // ASCII to base64_int - // 65-90 Upper Case >> 0-25 - // 97-122 Lower Case >> 26-51 - // 48-57 Numbers >> 52-61 - // 43 Plus (+) >> 62 - // 47 Slash (/) >> 63 - // 61 Equal (=) >> 64~ - if (ch==43) - return 62; - if (ch==47) - return 63; - if (ch==61) - return 64; - if ((ch>47) && (ch<58)) - return ch + 4; - if ((ch>64) && (ch<91)) - return ch - 'A'; - if ((ch>96) && (ch<123)) - return (ch - 'a') + 26; - return 0; +int b64_int(unsigned int ch) { + // ASCII to base64_int + // 65-90 Upper Case >> 0-25 + // 97-122 Lower Case >> 26-51 + // 48-57 Numbers >> 52-61 + // 43 Plus (+) >> 62 + // 47 Slash (/) >> 63 + // 61 Equal (=) >> 64~ + if (ch==43) + return 62; + if (ch==47) + return 63; + if (ch==61) + return 64; + if ((ch>47) && (ch<58)) + return ch + 4; + if ((ch>64) && (ch<91)) + return ch - 'A'; + if ((ch>96) && (ch<123)) + return (ch - 'a') + 26; + return -1; //Invalid character, invalid base64 string } unsigned int b64e_size(unsigned int in_size) { - // size equals 4*floor((1/3)*(in_size+2)); - int i, j = 0; - for (i=0;i>2 ]; - out[k+1] = b64_chr[ ((s[0]&0x03)<<4)+((s[1]&0xF0)>>4) ]; - out[k+2] = b64_chr[ ((s[1]&0x0F)<<2)+((s[2]&0xC0)>>6) ]; - out[k+3] = b64_chr[ s[2]&0x3F ]; - j=0; k+=4; - } - } - - if (j) { - if (j==1) - s[1] = 0; - out[k+0] = b64_chr[ (s[0]&255)>>2 ]; - out[k+1] = b64_chr[ ((s[0]&0x03)<<4)+((s[1]&0xF0)>>4) ]; - if (j==2) - out[k+2] = b64_chr[ ((s[1]&0x0F)<<2) ]; - else - out[k+2] = '='; - out[k+3] = '='; - k+=4; - } - - out[k] = '\0'; - - return k; +unsigned int b64_encode(const unsigned char * in, unsigned int in_len, unsigned char* out) { + unsigned int i=0, j=0, k=0, s[3]; + + for (i=0;i>2 ]; + out[k+1] = b64_chr[ ((s[0]&0x03)<<4)+((s[1]&0xF0)>>4) ]; + out[k+2] = b64_chr[ ((s[1]&0x0F)<<2)+((s[2]&0xC0)>>6) ]; + out[k+3] = b64_chr[ s[2]&0x3F ]; + j=0; k+=4; + } + } + + if (j) { + if (j==1) + s[1] = 0; + out[k+0] = b64_chr[ (s[0]&255)>>2 ]; + out[k+1] = b64_chr[ ((s[0]&0x03)<<4)+((s[1]&0xF0)>>4) ]; + if (j==2) + out[k+2] = b64_chr[ ((s[1]&0x0F)<<2) ]; + else + out[k+2] = '='; + out[k+3] = '='; + k+=4; + } + + out[k] = '\0'; + + return k; } -unsigned int b64_decode(const unsigned char* in, unsigned int in_len, unsigned char* out) { - - unsigned int i=0, j=0, k=0, s[4]; - for (i=0;i>4); - if (s[2]!=64) { - out[k+1] = ((s[1]&0x0F)<<4)+((s[2]&0x3C)>>2); - if ((s[3]!=64)) { - out[k+2] = ((s[2]&0x03)<<6)+(s[3]); k+=3; - } else { - k+=2; - } - } else { - k+=1; - } - j=0; - } - } - - return k; +int b64_decode(const unsigned char* in, unsigned int in_len, unsigned char* out) { + + unsigned int i=0, j=0, k=0, s[4]; + for (i=0;i>4); + if (s[2]!=64) { + out[k+1] = ((s[1]&0x0F)<<4)+((s[2]&0x3C)>>2); + if ((s[3]!=64)) { + out[k+2] = ((s[2]&0x03)<<6)+(s[3]); k+=3; + } else { + k+=2; + } + } else { + k+=1; + } + j=0; + } + } + + return k; } unsigned int b64_encodef(char *InFile, char *OutFile) { - FILE *pInFile = fopen(InFile,"rb"); - FILE *pOutFile = fopen(OutFile,"wb"); - if ( (pInFile==NULL) || (pOutFile==NULL) ) - return 0; - - unsigned int i=0, j=0, c=0, s[3]; - - while(c!=EOF) { - c=fgetc(pInFile); - if (c==EOF) - break; - s[j++]=c; - if (j==3) { - fputc(b64_chr[ (s[0]&255)>>2 ],pOutFile); - fputc(b64_chr[ ((s[0]&0x03)<<4)+((s[1]&0xF0)>>4) ],pOutFile); - fputc(b64_chr[ ((s[1]&0x0F)<<2)+((s[2]&0xC0)>>6) ],pOutFile); - fputc(b64_chr[ s[2]&0x3F ],pOutFile); - j=0; i+=4; - } - } - - if (j) { - if (j==1) - s[1] = 0; - fputc(b64_chr[ (s[0]&255)>>2 ],pOutFile); - fputc(b64_chr[ ((s[0]&0x03)<<4)+((s[1]&0xF0)>>4) ],pOutFile); - if (j==2) - fputc(b64_chr[ ((s[1]&0x0F)<<2) ],pOutFile); - else - fputc('=',pOutFile); - fputc('=',pOutFile); - i+=4; - } - - fclose(pInFile); - fclose(pOutFile); - - return i; + FILE *pInFile = fopen(InFile,"rb"); + FILE *pOutFile = fopen(OutFile,"wb"); + if ( (pInFile==NULL) || (pOutFile==NULL) ) + return 0; + + unsigned int i=0, j=0, c=0, s[3]; + + while(c!=EOF) { + c=fgetc(pInFile); + if (c==EOF) + break; + s[j++]=c; + if (j==3) { + fputc(b64_chr[ (s[0]&255)>>2 ],pOutFile); + fputc(b64_chr[ ((s[0]&0x03)<<4)+((s[1]&0xF0)>>4) ],pOutFile); + fputc(b64_chr[ ((s[1]&0x0F)<<2)+((s[2]&0xC0)>>6) ],pOutFile); + fputc(b64_chr[ s[2]&0x3F ],pOutFile); + j=0; i+=4; + } + } + + if (j) { + if (j==1) + s[1] = 0; + fputc(b64_chr[ (s[0]&255)>>2 ],pOutFile); + fputc(b64_chr[ ((s[0]&0x03)<<4)+((s[1]&0xF0)>>4) ],pOutFile); + if (j==2) + fputc(b64_chr[ ((s[1]&0x0F)<<2) ],pOutFile); + else + fputc('=',pOutFile); + fputc('=',pOutFile); + i+=4; + } + + fclose(pInFile); + fclose(pOutFile); + + return i; } -unsigned int b64_decodef(char *InFile, char *OutFile) { - - FILE *pInFile = fopen(InFile,"rb"); - FILE *pOutFile = fopen(OutFile,"wb"); - if ( (pInFile==NULL) || (pOutFile==NULL) ) - return 0; - - unsigned int c=0, j=0, k=0, s[4]; - - while(c!=EOF) { - c=fgetc(pInFile); - if (c==EOF) - break; - s[j++]=b64_int(c); - if (j==4) { - fputc(((s[0]&255)<<2)+((s[1]&0x30)>>4),pOutFile); - if (s[2]!=64) { - fputc(((s[1]&0x0F)<<4)+((s[2]&0x3C)>>2),pOutFile); - if ((s[3]!=64)) { - fputc(((s[2]&0x03)<<6)+(s[3]),pOutFile); k+=3; - } else { - k+=2; - } - } else { - k+=1; - } - j=0; - } - } - - fclose(pInFile); - fclose(pOutFile); - - return k; +int b64_decodef(char *InFile, char *OutFile) { + + FILE *pInFile = fopen(InFile,"rb"); + FILE *pOutFile = fopen(OutFile,"wb"); + if ( (pInFile==NULL) || (pOutFile==NULL) ) + return 0; + + unsigned int c=0, j=0, k=0, s[4]; + int n; + + while(c!=EOF) { + c=fgetc(pInFile); + if (c==EOF) + break; + n = b64_int(c); + if( n < 0 ) + return -1; + s[j++] = (unsigned int)n; + if (j==4) { + fputc(((s[0]&255)<<2)+((s[1]&0x30)>>4),pOutFile); + if (s[2]!=64) { + fputc(((s[1]&0x0F)<<4)+((s[2]&0x3C)>>2),pOutFile); + if ((s[3]!=64)) { + fputc(((s[2]&0x03)<<6)+(s[3]),pOutFile); k+=3; + } else { + k+=2; + } + } else { + k+=1; + } + j=0; + } + } + + fclose(pInFile); + fclose(pOutFile); + + return (int)k; } - diff --git a/lib/cgo/tests/testutils/json.c b/lib/cgo/tests/testutils/json.c index bae4b35d73..a1fbe90d54 100644 --- a/lib/cgo/tests/testutils/json.c +++ b/lib/cgo/tests/testutils/json.c @@ -1,4 +1,3 @@ - /* vim: set et ts=3 sw=3 sts=3 ft=c: * * Copyright (C) 2012, 2013, 2014 James McLaughlin et al. All rights reserved. diff --git a/lib/cgo/tests/testutils/json_util.c b/lib/cgo/tests/testutils/json_util.c index f617cd149c..a0f2cf3dbf 100644 --- a/lib/cgo/tests/testutils/json_util.c +++ b/lib/cgo/tests/testutils/json_util.c @@ -3,135 +3,146 @@ #include json_value* json_get_string(json_value* value, const char* key){ - int length, x; - if (value == NULL) { - return NULL; - } - if (value->type != json_object) { - return NULL; - } - length = value->u.object.length; - for (x = 0; x < length; x++) { - if( strcmp( value->u.object.values[x].name, key) == 0){ - if( value->u.object.values[x].value->type == json_string){ - return value->u.object.values[x].value; - } - } - } - return NULL; + int length, x; + if (value == NULL) { + return NULL; + } + if (value->type != json_object) { + return NULL; + } + length = value->u.object.length; + for (x = 0; x < length; x++) { + if( strcmp( value->u.object.values[x].name, key) == 0){ + if( value->u.object.values[x].value->type == json_string){ + return value->u.object.values[x].value; + } + } + } + return NULL; } int json_set_string(json_value* value, const char* new_string_value){ - if( value->type == json_string){ - int length = strlen(new_string_value); - if( length > value->u.string.length ){ - value->u.string.ptr = malloc(length + 1); - } - strcpy( value->u.string.ptr, new_string_value ); - value->u.string.length = length; - } - return 0; + if( value->type == json_string){ + int length = strlen(new_string_value); + if( length > value->u.string.length ){ + value->u.string.ptr = malloc(length + 1); + } + strcpy( value->u.string.ptr, new_string_value ); + value->u.string.length = length; + } + return 0; } -int compareJsonValues(json_value* value1, json_value* value2); +int _compareJsonValues(json_value* value1, json_value* value2, const char* ignore); -int compareJsonObjects(json_value* value1, json_value* value2){ - int length1 = value1->u.object.length; - int length2 = value2->u.object.length; - if( length1 != length2 ) - return 0; - for (int x = 0; x < length1; x++) { - char* name = value1->u.object.values[x].name; - int found = 0; - for( int y = 0; y < length2; y++){ - if( strcmp( value2->u.object.values[y].name, name ) == 0){ - if( !compareJsonValues( value1->u.object.values[x].value, - value2->u.object.values[y].value ) ) - return 0; - found = 1; - break; - } - } - if( !found ) - return 0; - } - return 1; +int compareJsonObjects(json_value* value1, json_value* value2, + const char* ignore){ + int length1 = value1->u.object.length; + int length2 = value2->u.object.length; + /*if( length1 != length2 ) + return 0;*/ + for (int x = 0; x < length1; x++) { + char* name = value1->u.object.values[x].name; + if( ignore != NULL && strcmp( ignore, name ) == 0) + continue; + int found = 0; + for( int y = 0; y < length2; y++){ + if( strcmp( value2->u.object.values[y].name, name ) == 0){ + if( !_compareJsonValues( value1->u.object.values[x].value, + value2->u.object.values[y].value, ignore ) ) + return 0; + found = 1; + break; + } + } + if( !found ) + return 0; + } + return 1; } -int compareJsonArrays(json_value* value1, json_value* value2){ - int length1 = value1->u.array.length; - int length2 = value2->u.array.length; - if( length1 != length2 ) - return 0; - for (int x = 0; x < length1; x++) { - if( !compareJsonValues(value1->u.array.values[x], value2->u.array.values[x]) ) - return 0; - } - return 1; +int compareJsonArrays(json_value* value1, json_value* value2, const char* ignore){ + int length1 = value1->u.array.length; + int length2 = value2->u.array.length; + if( length1 != length2 ) + return 0; + for (int x = 0; x < length1; x++) { + if( !_compareJsonValues(value1->u.array.values[x], + value2->u.array.values[x], ignore) ) + return 0; + } + return 1; } -int compareJsonValues(json_value* value1, json_value* value2){ - if( value1 == NULL && value2 == NULL) - return 1; - if( value1 == NULL || value2 == NULL) - return 0; - if( value1->type != value2->type) - return 0; - switch (value1->type) { +int _compareJsonValues(json_value* value1, json_value* value2, const char* ignore){ + if( value1 == NULL && value2 == NULL) + return 1; + if( value1 == NULL || value2 == NULL) + return 0; + if( value1->type != value2->type) + return 0; + switch (value1->type) { case json_null: return value2->type == json_null; - case json_none: - return 1; - case json_object: - return compareJsonObjects(value1, value2); - case json_array: - return compareJsonArrays(value1, value2); - case json_integer: - return value1->u.integer == value2->u.integer; - case json_double: - return fabs(value1->u.dbl - value2->u.dbl) < 0.000001; - case json_string: - return strcmp(value1->u.string.ptr, value2->u.string.ptr) == 0; - case json_boolean: - return value1->u.boolean == value2->u.boolean; - } - return 1; + case json_none: + return 1; + case json_object: + return compareJsonObjects(value1, value2, ignore); + case json_array: + return compareJsonArrays(value1, value2, ignore); + case json_integer: + return value1->u.integer == value2->u.integer; + case json_double: + return fabs(value1->u.dbl - value2->u.dbl) < 0.000001; + case json_string: + return strcmp(value1->u.string.ptr, value2->u.string.ptr) == 0; + case json_boolean: + return value1->u.boolean == value2->u.boolean; + } + return 1; +} + +int compareJsonValues(json_value* value1, json_value* value2){ + return _compareJsonValues(value2, value1, NULL); +} + +int compareJsonValuesWithIgnoreList(json_value* value1, json_value* value2, const char* ignoreList){ + return _compareJsonValues(value2, value1, ignoreList); } json_value* get_json_value_not_strict(json_value* node, const char* path, - json_type type, int allow_null){ - int n; - const char* p = strchr(path, '/'); - if( p == NULL ) - n = strlen(path); - else - n = p - path; - if( n > 0 ) { - if( node->type == json_object){ - for (int x = 0; x < node->u.object.length; x++) { - json_object_entry * entry = &node->u.object.values[x]; - char* name = entry->name; - json_value* value = entry->value; - if( strncmp( path, name, n ) == 0){ - if( p == NULL){ - if( value->type == type || - (allow_null && value->type == json_null)) - return value; - }else - return get_json_value_not_strict( - value, p + 1, type, allow_null); - } - } - } else { - return NULL; - } - } - return NULL; + json_type type, int allow_null){ + int n; + const char* p = strchr(path, '/'); + if( p == NULL ) + n = strlen(path); + else + n = p - path; + if( n > 0 ) { + if( node->type == json_object){ + for (int x = 0; x < node->u.object.length; x++) { + json_object_entry * entry = &node->u.object.values[x]; + char* name = entry->name; + json_value* value = entry->value; + if( strncmp( path, name, n ) == 0){ + if( p == NULL){ + if( value->type == type || + (allow_null && value->type == json_null)) + return value; + }else + return get_json_value_not_strict( + value, p + 1, type, allow_null); + } + } + } else { + return NULL; + } + } + return NULL; } json_value* get_json_value(json_value* node, const char* path, - json_type type){ - return get_json_value_not_strict(node, path, type, 1); + json_type type){ + return get_json_value_not_strict(node, path, type, 1); } - diff --git a/lib/cgo/tests/testutils/libsky_criterion.c b/lib/cgo/tests/testutils/libsky_criterion.c new file mode 100644 index 0000000000..0e8fe4f0a4 --- /dev/null +++ b/lib/cgo/tests/testutils/libsky_criterion.c @@ -0,0 +1,303 @@ + +#include +#include "skycriterion.h" +#include "skystring.h" + +int equalSlices(GoSlice *slice1, GoSlice *slice2, int elem_size) +{ + if (slice1->len != slice2->len) + return 0; + return memcmp(slice1->data, slice2->data, slice1->len * elem_size) == 0; +} + +int equalTransactions(coin__Transactions *pTxs1, coin__Transactions *pTxs2) +{ + if (pTxs1->len != pTxs2->len) + return 0; + coin__Transaction *pTx1 = pTxs1->data; + coin__Transaction *pTx2 = pTxs2->data; + for (int i = 0; i < pTxs1->len; i++) + { + if (!cr_user_coin__Transaction_eq(pTx1, pTx2)) + return 0; + pTx1++; + pTx2++; + } + return 1; +} + +int cr_user_cipher__Address_eq(cipher__Address *addr1, cipher__Address *addr2) +{ + if (addr1->Version != addr2->Version) + return 0; + return memcmp((void *)addr1, (void *)addr2, sizeof(cipher__Address)) == 0; +} + +char *cr_user_cipher__Address_tostr(cipher__Address *addr1) +{ + char *out; + + cr_asprintf(&out, "(cipher__Address) { .Key = %s, .Version = %llu }", addr1->Key, (unsigned long long)addr1->Version); + return out; +} + +int cr_user_cipher__Address_noteq(cipher__Address *addr1, cipher__Address *addr2) +{ + if (addr1->Version == addr2->Version) + return 0; + return memcmp((void *)addr1, (void *)addr2, sizeof(cipher__Address)) != 0; +} + +int cr_user_GoString_eq(GoString *string1, GoString *string2) +{ + return (string1->n == string2->n) && + (strcmp((char *)string1->p, (char *)string2->p) == 0); +} + +char *cr_user_GoString_tostr(GoString *string) +{ + char *out; + cr_asprintf(&out, "(GoString) { .Data = %s, .Length = %llu }", + string->p, (unsigned long long)string->n); + return out; +} + +int cr_user_GoString__eq(GoString_ *string1, GoString_ *string2) +{ + return cr_user_GoString_eq((GoString *)string1, (GoString *)string2); +} + +char *cr_user_GoString__tostr(GoString_ *string) +{ + return cr_user_GoString_tostr((GoString *)string); +} + +int cr_user_cipher__SecKey_eq(cipher__SecKey *seckey1, cipher__SecKey *seckey2) +{ + return memcmp((void *)seckey1, (void *)seckey2, sizeof(cipher__SecKey)) == 0; +} + +char *cr_user_cipher__SecKey_tostr(cipher__SecKey *seckey1) +{ + char *out; + char hexdump[101]; + + strnhex((unsigned char *)seckey1, hexdump, sizeof(cipher__SecKey)); + cr_asprintf(&out, "(cipher__SecKey) { %s }", hexdump); + return out; +} + +int cr_user_cipher__Ripemd160_noteq(cipher__Ripemd160 *rp1, cipher__Ripemd160 *rp2) +{ + return memcmp((void *)rp1, (void *)rp2, sizeof(cipher__Ripemd160)) != 0; +} + +int cr_user_cipher__Ripemd160_eq(cipher__Ripemd160 *rp1, cipher__Ripemd160 *rp2) +{ + return memcmp((void *)rp1, (void *)rp2, sizeof(cipher__Ripemd160)) == 0; +} + +char *cr_user_cipher__Ripemd160_tostr(cipher__Ripemd160 *rp1) +{ + char *out; + char hexdump[101]; + + strnhex((unsigned char *)rp1, hexdump, sizeof(cipher__Ripemd160)); + cr_asprintf(&out, "(cipher__Ripemd160) { %s }", hexdump); + return out; +} + +int cr_user_cipher__SHA256_noteq(cipher__SHA256 *sh1, cipher__SHA256 *sh2) +{ + return memcmp((void *)sh1, (void *)sh1, sizeof(cipher__SHA256)) != 0; +} + +int cr_user_cipher__SHA256_eq(cipher__SHA256 *sh1, cipher__SHA256 *sh2) +{ + return memcmp((void *)sh1, (void *)sh1, sizeof(cipher__SHA256)) == 0; +} + +char *cr_user_cipher__SHA256_tostr(cipher__SHA256 *sh1) +{ + char *out; + char hexdump[101]; + + strnhex((unsigned char *)sh1, hexdump, sizeof(cipher__SHA256)); + cr_asprintf(&out, "(cipher__SHA256) { %s }", hexdump); + return out; +} + +int cr_user_GoSlice_eq(GoSlice *slice1, GoSlice *slice2) +{ + return (slice1->len == slice2->len) && + (memcmp(slice1->data, slice2->data, slice1->len) == 0); +} + +int cr_user_GoSlice_noteq(GoSlice *slice1, GoSlice *slice2) +{ + if( (slice1->data == NULL) || (slice2->data == NULL) ) return false; + return !(((slice1->len == slice2->len)) && + (memcmp(slice1->data, slice2->data, slice1->len) == 0)); +} + +char *cr_user_GoSlice_tostr(GoSlice *slice1) +{ + char *out; + cr_asprintf(&out, "(GoSlice) { .data %s, .len %lli, .cap %lli }", (char *)slice1->data, slice1->len, slice1->cap); + return out; +} + +int cr_user_GoSlice__eq(GoSlice_ *slice1, GoSlice_ *slice2) +{ + return ((slice1->len == slice2->len)) && (memcmp(slice1->data, slice2->data, slice1->len) == 0); +} + +char *cr_user_GoSlice__tostr(GoSlice_ *slice1) +{ + char *out; + cr_asprintf(&out, "(GoSlice_) { .data %s, .len %lli, .cap %lli }", (char *)slice1->data, slice1->len, slice1->cap); + return out; +} + +int cr_user_coin__Transactions_eq(coin__Transactions *x1, coin__Transactions *x2) +{ + return equalTransactions(x1, x2); +} + +int cr_user_coin__Transactions_noteq(coin__Transactions *x1, coin__Transactions *x2) +{ + return !equalTransactions(x1, x2); +} + +char *cr_user_coin__Transactions_tostr(coin__Transactions *x1) +{ + char *out; + cr_asprintf(&out, "(coin__Transactions) { .data %s, .len %lli, .cap %lli }", (char *)x1->data, x1->len, x1->cap); + return out; +} + +int cr_user_coin__BlockBody_eq(coin__BlockBody *b1, coin__BlockBody *b2) +{ + return equalTransactions(&b1->Transactions, &b2->Transactions); +} + +int cr_user_coin__BlockBody_noteq(coin__BlockBody *b1, coin__BlockBody *b2) +{ + return !equalTransactions(&b1->Transactions, &b2->Transactions); +} + +char *cr_user_coin__BlockBody_tostr(coin__BlockBody *b) +{ + char *out; + cr_asprintf(&out, "(coin__BlockBody) { .data %s, .len %lli, .cap %lli }", (char *)b->Transactions.data, b->Transactions.len, b->Transactions.cap); + return out; +} + +int cr_user_coin__UxOut_eq(coin__UxOut *x1, coin__UxOut *x2) +{ + return memcmp(x1, x2, sizeof(coin__UxOut)) == 0; +} + +int cr_user_coin__UxOut_noteq(coin__UxOut *x1, coin__UxOut *x2) +{ + return memcmp(x1, x2, sizeof(coin__UxOut)) != 0; +} + +char *cr_user_coin__UxOut_tostr(coin__UxOut *x1) +{ + char *out; + cr_asprintf(&out, "(coin__UxOut) { %s }", (char *)x1); + return out; +} + +int cr_user_coin__Transaction_eq(coin__Transaction *x1, coin__Transaction *x2) +{ + if (x1->Length != x2->Length || + x1->Type != x2->Type) + { + return 0; + } + if (!cr_user_cipher__SHA256_eq(&x1->InnerHash, &x2->InnerHash)) + return 0; + if (!equalSlices((GoSlice *)&x1->Sigs, (GoSlice *)&x2->Sigs, sizeof(cipher__Sig))) + return 0; + if (!equalSlices((GoSlice *)&x1->In, (GoSlice *)&x2->In, sizeof(cipher__SHA256))) + return 0; + if (!equalSlices((GoSlice *)&x1->Out, (GoSlice *)&x2->Out, sizeof(coin__TransactionOutput))) + return 0; + return 1; +} + +int cr_user_coin__Transaction_noteq(coin__Transaction *x1, coin__Transaction *x2) +{ + return !cr_user_coin__Transaction_eq(x1, x2); +} + +char *cr_user_coin__Transaction_tostr(coin__Transaction *x1) +{ + char *out; + cr_asprintf(&out, "(coin__Transaction) { Length : %i }", x1->Length); + return out; +} + +int cr_user_coin__TransactionOutput_eq(coin__TransactionOutput *x1, coin__TransactionOutput *x2) +{ + if (x1->Coins != x2->Coins || + x1->Hours != x2->Hours) + { + return 0; + } + + if (!cr_user_cipher__Address_eq(&x1->Address, &x2->Address)) + return 0; + return 1; +} + +int cr_user_coin__TransactionOutput_noteq(coin__TransactionOutput *x1, coin__TransactionOutput *x2) +{ + return !cr_user_coin__TransactionOutput_eq(x1, x2); +} + +char *cr_user_coin__TransactionOutput_tostr(coin__TransactionOutput *x1) +{ + char *out; + cr_asprintf(&out, "(coin__TransactionOutput) { Coins : %lli, Hours: %lli}", x1->Coins, x1->Hours); + return out; +} + +int cr_user_coin__UxArray_eq(coin__UxArray *slice1, coin__UxArray *slice2) +{ + return (memcmp(slice1->data, slice2->data, slice1->len) == 0) && ((slice1->len == slice2->len)); +} + +int cr_user_coin__UxArray_noteq(coin__UxArray *slice1, coin__UxArray *slice2) +{ + return (memcmp(slice1->data, slice2->data, slice1->len) != 0) && ((slice1->len != slice2->len)); +} + +char *cr_user_coin__UxArray_tostr(coin__UxArray *x1) +{ + char *out; + cr_asprintf(&out, "(coin__UxArray) { Length : %lli }", x1->len); + return out; +} + +int cr_user_Number_eq(Number *n1, Number *n2) +{ + return (equalSlices((GoSlice *)&n1->nat, (GoSlice *)&n2->nat, sizeof(GoInt)) && + ((GoInt)n1->neg == (GoInt)n2->neg)); +} + +int cr_user_Number_noteq(Number *n1, Number *n2) +{ + return (!(equalSlices((GoSlice *)&n1->nat, (GoSlice *)&n2->nat, sizeof(GoInt))) || + ((GoInt)n1->neg != (GoInt)n2->neg)); +} + +char *cr_user_Number_tostr(Number *n1) +{ + char *out; + cr_asprintf(&out, "(Number) { nat : [.data %s, .len %lli , cap %lli] , neg %lli }", + (char *)n1->nat.data, n1->nat.len, n1->nat.cap, (GoInt)n1->neg); + return out; +} diff --git a/lib/cgo/tests/testutils/libsky_string.c b/lib/cgo/tests/testutils/libsky_string.c new file mode 100644 index 0000000000..2a170f1995 --- /dev/null +++ b/lib/cgo/tests/testutils/libsky_string.c @@ -0,0 +1,126 @@ + +#include "skystring.h" + +#define ALPHANUM "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" +#define ALPHANUM_LEN 62 +#define SIZE_ALL -1 + +void randBytes(GoSlice *bytes, size_t n) { + size_t i = 0; + unsigned char *ptr = (unsigned char *) bytes->data; + for (; i < n; ++i, ++ptr) { + *ptr = ALPHANUM[rand() % ALPHANUM_LEN]; + } + bytes->len = (GoInt) n; +} + +void bytesnhex(unsigned char* buf, char *str, int n){ + unsigned char * pin = buf; + const char * hex = "0123456789ABCDEF"; + char * pout = str; + for(; n; --n){ + *pout++ = hex[(*pin>>4)&0xF]; + *pout++ = hex[(*pin++)&0xF]; + } + *pout = 0; +} + +void strnhex(unsigned char* buf, char *str, int n){ + unsigned char * pin = buf; + const char * hex = "0123456789ABCDEF"; + char * pout = str; + for(; n; --n){ + *pout++ = hex[(*pin>>4)&0xF]; + *pout++ = hex[(*pin++)&0xF]; + } + *pout = 0; +} + +void strnhexlower(unsigned char* buf, char *str, int n){ + unsigned char * pin = buf; + const char * hex = "0123456789abcdef"; + char * pout = str; + for(; n; --n){ + *pout++ = hex[(*pin>>4)&0xF]; + *pout++ = hex[(*pin++)&0xF]; + } + *pout = 0; +} + + +int hexnstr(const char* hex, unsigned char* str, int n){ + const char * pin = hex; + unsigned char * pout = str; + unsigned char c; + int odd = 0; + int size = 0; + for(; *pin && size < n; pin++){ + if(*pin >= '0' && *pin <= '9'){ + c = *pin - '0'; + } else if(*pin >= 'A' && *pin <= 'F'){ + c = 10 + (*pin - 'A'); + } else if(*pin >= 'a' && *pin <= 'f'){ + c = 10 + (*pin - 'a'); + } else { //Invalid hex string + return -1; + } + if(odd){ + *pout = (*pout << 4) | c; + pout++; + size++; + } else { + *pout = c; + } + odd = !odd; + } + if( odd ) + return -1; + if( size < n ) + *pout = 0; + return size; +} + +int cmpGoSlice_GoSlice(GoSlice *slice1, GoSlice_ *slice2){ + return ((slice1->len == slice2->len)) && (memcmp(slice1->data,slice2->data, sizeof(GoSlice_))==0 ); +} + +void bin2hex(unsigned char* buf, char *str, int n){ + unsigned char * pin = buf; + const char * hex = "0123456789ABCDEF"; + char * pout = str; + for(; n; --n){ + *pout++ = hex[(*pin>>4)&0xF]; + *pout++ = hex[(*pin++)&0xF]; + } + *pout = 0; +} + +int string_has_suffix(const char* str, const char* suffix){ + int string_len = strlen(str); + int suffix_len = strlen(suffix); + if(string_len >= suffix_len){ + char* p = (char*)str + (string_len - suffix_len); + return strcmp(p, suffix) == 0; + } + return 0; +} + +int string_has_prefix(const char* str, const char* prefix){ + int string_len = strlen(str); + int prefix_len = strlen(prefix); + if(string_len >= prefix_len){ + return strncmp(str, prefix, prefix_len) == 0; + } + return 0; +} + +extern int count_words(const char* str, int length){ + int words = 1; + char prevChar = 0; + for(int i = 0; i < length; i++){ + char c = str[i]; + if( c == ' ' && prevChar != ' ' ) words++; + prevChar = c; + } + return words; +} diff --git a/lib/cgo/tests/testutils/libsky_testutil.c b/lib/cgo/tests/testutils/libsky_testutil.c new file mode 100644 index 0000000000..437c088818 --- /dev/null +++ b/lib/cgo/tests/testutils/libsky_testutil.c @@ -0,0 +1,320 @@ + +#include +#include +#include +#include +#include +#include "json.h" +#include "skytypes.h" +#include "skytest.h" + +#define BUFFER_SIZE 1024 +#define stableWalletName "integration-test.wlt" +#define STRING_SIZE 128 +#define JSON_FILE_SIZE 4096 +#define JSON_BIG_FILE_SIZE 102400 +#define TEST_DATA_DIR "src/cli/integration/testdata/" +#define stableEncryptWalletName "integration-test-encrypted.wlt" + +//Define function SKY_handle_close to avoid including libskycoin.h +void SKY_handle_close(Handle p0); + +int MEMPOOLIDX = 0; +void *MEMPOOL[1024 * 256]; + +int JSONPOOLIDX = 0; +json_value* JSON_POOL[128]; + +int HANDLEPOOLIDX = 0; +Handle HANDLE_POOL[128]; + +typedef struct { + Client__Handle client; + WalletResponse__Handle wallet; +} wallet_register; + +int WALLETPOOLIDX = 0; +wallet_register WALLET_POOL[64]; + +int stdout_backup; +int pipefd[2]; + +void * registerMemCleanup(void *p) { + int i; + for (i = 0; i < MEMPOOLIDX; i++) { + if(MEMPOOL[i] == NULL){ + MEMPOOL[i] = p; + return p; + } + } + MEMPOOL[MEMPOOLIDX++] = p; + return p; +} + +void freeRegisteredMemCleanup(void *p){ + int i; + for (i = 0; i < MEMPOOLIDX; i++) { + if(MEMPOOL[i] == p){ + free(p); + MEMPOOL[i] = NULL; + break; + } + } +} + +int registerJsonFree(void *p){ + int i; + for (i = 0; i < JSONPOOLIDX; i++) { + if(JSON_POOL[i] == NULL){ + JSON_POOL[i] = p; + return i; + } + } + JSON_POOL[JSONPOOLIDX++] = p; + return JSONPOOLIDX-1; +} + +void freeRegisteredJson(void *p){ + int i; + for (i = 0; i < JSONPOOLIDX; i++) { + if(JSON_POOL[i] == p){ + JSON_POOL[i] = NULL; + json_value_free( (json_value*)p ); + break; + } + } +} + +int registerWalletClean(Client__Handle clientHandle, + WalletResponse__Handle walletHandle){ + int i; + for (i = 0; i < WALLETPOOLIDX; i++) { + if(WALLET_POOL[i].wallet == 0 && WALLET_POOL[i].client == 0){ + WALLET_POOL[i].wallet = walletHandle; + WALLET_POOL[i].client = clientHandle; + return i; + } + } + WALLET_POOL[WALLETPOOLIDX].wallet = walletHandle; + WALLET_POOL[WALLETPOOLIDX].client = clientHandle; + return WALLETPOOLIDX++; +} + +int registerHandleClose(Handle handle){ + int i; + for (i = 0; i < HANDLEPOOLIDX; i++) { + if(HANDLE_POOL[i] == 0){ + HANDLE_POOL[i] = handle; + return i; + } + } + HANDLE_POOL[HANDLEPOOLIDX++] = handle; + return HANDLEPOOLIDX - 1; +} + +void closeRegisteredHandle(Handle handle){ + int i; + for (i = 0; i < HANDLEPOOLIDX; i++) { + if(HANDLE_POOL[i] == handle){ + HANDLE_POOL[i] = 0; + SKY_handle_close(handle); + break; + } + } +} + +void cleanupWallet(Client__Handle client, WalletResponse__Handle wallet){ + int result; + GoString_ strWalletDir; + GoString_ strFileName; + memset(&strWalletDir, 0, sizeof(GoString_)); + memset(&strFileName, 0, sizeof(GoString_)); + + + result = SKY_api_Handle_Client_GetWalletDir(client, &strWalletDir); + if( result != SKY_OK ){ + return; + } + result = SKY_api_Handle_Client_GetWalletFileName(wallet, &strFileName); + if( result != SKY_OK ){ + free( (void*)strWalletDir.p ); + return; + } + char fullPath[128]; + if( strWalletDir.n + strFileName.n < 126){ + strcpy( fullPath, strWalletDir.p ); + if( fullPath[0] == 0 || fullPath[strlen(fullPath) - 1] != '/' ) + strcat(fullPath, "/"); + strcat( fullPath, strFileName.p ); + result = unlink( fullPath ); + if( strlen(fullPath) < 123 ){ + strcat( fullPath, ".bak" ); + result = unlink( fullPath ); + } + } + GoString str = { strFileName.p, strFileName.n }; + result = SKY_api_Client_UnloadWallet( client, str ); + GoString strFullPath = { fullPath, strlen(fullPath) }; + free( (void*)strWalletDir.p ); + free( (void*)strFileName.p ); +} + +void cleanRegisteredWallet( + Client__Handle client, + WalletResponse__Handle wallet){ + + int i; + for (i = 0; i < WALLETPOOLIDX; i++) { + if(WALLET_POOL[i].wallet == wallet && WALLET_POOL[i].client == client){ + WALLET_POOL[i].wallet = 0; + WALLET_POOL[i].client = 0; + cleanupWallet( client, wallet ); + return; + } + } +} + +void cleanupMem() { + int i; + + for (i = 0; i < WALLETPOOLIDX; i++) { + if(WALLET_POOL[i].client != 0 && WALLET_POOL[i].wallet != 0){ + cleanupWallet( WALLET_POOL[i].client, WALLET_POOL[i].wallet ); + } + } + + void **ptr; + for (i = MEMPOOLIDX, ptr = MEMPOOL; i; --i) { + if( *ptr ) + free(*ptr); + ptr++; + } + for (i = JSONPOOLIDX, ptr = (void*)JSON_POOL; i; --i) { + if( *ptr ) + json_value_free(*ptr); + ptr++; + } + for (i = 0; i < HANDLEPOOLIDX; i++) { + if( HANDLE_POOL[i] ) + SKY_handle_close(HANDLE_POOL[i]); + } +} + +json_value* loadJsonFile(const char* filename){ + FILE *fp; + struct stat filestatus; + int file_size; + char* file_contents; + json_char* json; + json_value* value; + + if ( stat(filename, &filestatus) != 0) { + return NULL; + } + file_size = filestatus.st_size; + file_contents = (char*)malloc(filestatus.st_size); + if ( file_contents == NULL) { + return NULL; + } + fp = fopen(filename, "rt"); + if (fp == NULL) { + free(file_contents); + return NULL; + } + if ( fread(file_contents, file_size, 1, fp) != 1 ) { + fclose(fp); + free(file_contents); + return NULL; + } + fclose(fp); + + json = (json_char*)file_contents; + value = json_parse(json, file_size); + free(file_contents); + return value; +} + + +void setup(void) { + srand ((unsigned int) time (NULL)); +} + +void teardown(void) { + cleanupMem(); +} + +// TODO: Move to libsky_io.c +void fprintbuff(FILE *f, void *buff, size_t n) { + unsigned char *ptr = (unsigned char *) buff; + fprintf(f, "[ "); + for (; n; --n, ptr++) { + fprintf(f, "%02d ", *ptr); + } + fprintf(f, "]"); +} + +int parseBoolean(const char* str, int length){ + int result = 0; + if(length == 1){ + result = str[0] == '1' || str[0] == 't' || str[0] == 'T'; + } else { + result = strncmp(str, "true", length) == 0 || + strncmp(str, "True", length) == 0 || + strncmp(str, "TRUE", length) == 0; + } + return result; +} + +int copySlice(GoSlice_* pdest, GoSlice_* psource, int elem_size){ + pdest->len = psource->len; + pdest->cap = psource->len; + int size = pdest->len * elem_size; + pdest->data = malloc(size); + if( pdest->data == NULL ) + return SKY_ERROR; + registerMemCleanup( pdest->data ); + memcpy(pdest->data, psource->data, size ); + return SKY_OK; +} + +int cutSlice(GoSlice_* slice, int start, int end, int elem_size, GoSlice_* result){ + int size = end - start; + if( size <= 0) + return SKY_ERROR; + void* data = malloc(size * elem_size); + if( data == NULL ) + return SKY_ERROR; + registerMemCleanup( data ); + result->data = data; + result->len = size; + result->cap = size; + char* p = slice->data; + p += (elem_size * start); + memcpy( data, p, elem_size * size ); + return SKY_OK; +} + +int concatSlices(GoSlice_* slice1, GoSlice_* slice2, int elem_size, GoSlice_* result){ + int size1 = slice1->len; + int size2 = slice2->len; + int size = size1 + size2; + if (size <= 0) + return SKY_ERROR; + void* data = malloc(size * elem_size); + if( data == NULL ) + return SKY_ERROR; + registerMemCleanup( data ); + result->data = data; + result->len = size; + result->cap = size; + char* p = data; + if(size1 > 0){ + memcpy( p, slice1->data, size1 * elem_size ); + p += (elem_size * size1); + } + if(size2 > 0){ + memcpy( p, slice2->data, size2 * elem_size ); + } + return SKY_OK; +} + diff --git a/lib/cgo/tests/testutils/transutils.c b/lib/cgo/tests/testutils/transutils.c new file mode 100644 index 0000000000..c94b3907f9 --- /dev/null +++ b/lib/cgo/tests/testutils/transutils.c @@ -0,0 +1,247 @@ + +#include +#include + +#include +#include + +#include "libskycoin.h" +#include "skyerrors.h" +#include "skystring.h" +#include "skytest.h" +#include "skytxn.h" + +GoUint32_ zeroFeeCalculator(Transaction__Handle handle, GoUint64_ *pFee, void* context){ + *pFee = 0; + return SKY_OK; +} + +int makeKeysAndAddress(cipher__PubKey* ppubkey, cipher__SecKey* pseckey, cipher__Address* paddress){ + int result; + result = SKY_cipher_GenerateKeyPair(ppubkey, pseckey); + cr_assert(result == SKY_OK, "SKY_cipher_GenerateKeyPair failed"); + result = SKY_cipher_AddressFromPubKey( ppubkey, paddress ); + cr_assert(result == SKY_OK, "SKY_cipher_AddressFromPubKey failed"); + return result; +} + +int makeUxBodyWithSecret(coin__UxBody* puxBody, cipher__SecKey* pseckey){ + cipher__PubKey pubkey; + cipher__Address address; + int result; + + memset( puxBody, 0, sizeof(coin__UxBody) ); + puxBody->Coins = 1000000; + puxBody->Hours = 100; + + result = SKY_cipher_GenerateKeyPair(&pubkey, pseckey); + cr_assert(result == SKY_OK, "SKY_cipher_GenerateKeyPair failed"); + + GoSlice slice; + memset(&slice, 0, sizeof(GoSlice)); + cipher__SHA256 hash; + + result = SKY_cipher_RandByte( 128, (coin__UxArray*)&slice ); + registerMemCleanup( slice.data ); + cr_assert(result == SKY_OK, "SKY_cipher_RandByte failed"); + result = SKY_cipher_SumSHA256( slice, &puxBody->SrcTransaction ); + cr_assert(result == SKY_OK, "SKY_cipher_SumSHA256 failed"); + + result = SKY_cipher_AddressFromPubKey( &pubkey, &puxBody->Address ); + cr_assert(result == SKY_OK, "SKY_cipher_AddressFromPubKey failed"); + return result; +} + +int makeUxOutWithSecret(coin__UxOut* puxOut, cipher__SecKey* pseckey){ + int result; + memset( puxOut, 0, sizeof(coin__UxOut) ); + result = makeUxBodyWithSecret(&puxOut->Body, pseckey); + puxOut->Head.Time = 100; + puxOut->Head.BkSeq = 2; + return result; +} + +int makeUxBody(coin__UxBody* puxBody){ + cipher__SecKey seckey; + return makeUxBodyWithSecret(puxBody, &seckey); +} + +int makeUxOut(coin__UxOut* puxOut){ + cipher__SecKey seckey; + return makeUxOutWithSecret(puxOut, &seckey); +} + +int makeAddress(cipher__Address* paddress){ + cipher__PubKey pubkey; + cipher__SecKey seckey; + cipher__Address address; + int result; + + result = SKY_cipher_GenerateKeyPair(&pubkey, &seckey); + cr_assert(result == SKY_OK, "SKY_cipher_GenerateKeyPair failed"); + + result = SKY_cipher_AddressFromPubKey( &pubkey, paddress ); + cr_assert(result == SKY_OK, "SKY_cipher_AddressFromPubKey failed"); + return result; +} + +coin__Transaction* makeTransactionFromUxOut(coin__UxOut* puxOut, cipher__SecKey* pseckey, Transaction__Handle* handle ){ + int result; + coin__Transaction* ptransaction = NULL; + result = SKY_coin_Create_Transaction(handle); + cr_assert(result == SKY_OK, "SKY_coin_Create_Transaction failed"); + registerHandleClose(*handle); + result = SKY_coin_GetTransactionObject( *handle, &ptransaction ); + cr_assert(result == SKY_OK, "SKY_coin_GetTransactionObject failed"); + cipher__SHA256 sha256; + result = SKY_coin_UxOut_Hash(puxOut, &sha256); + cr_assert(result == SKY_OK, "SKY_coin_UxOut_Hash failed"); + GoUint16 r; + result = SKY_coin_Transaction_PushInput(*handle, &sha256, &r); + cr_assert(result == SKY_OK, "SKY_coin_Transaction_PushInput failed"); + + cipher__Address address1, address2; + result = makeAddress(&address1); + cr_assert(result == SKY_OK, "makeAddress failed"); + result = makeAddress(&address2); + cr_assert(result == SKY_OK, "makeAddress failed"); + + result = SKY_coin_Transaction_PushOutput(*handle, &address1, 1000000, 50); + cr_assert(result == SKY_OK, "SKY_coin_Transaction_PushOutput failed"); + result = SKY_coin_Transaction_PushOutput(*handle, &address2, 5000000, 50); + cr_assert(result == SKY_OK, "SKY_coin_Transaction_PushOutput failed"); + + GoSlice secKeys = { pseckey, 1, 1 }; + result = SKY_coin_Transaction_SignInputs( *handle, secKeys ); + cr_assert(result == SKY_OK, "SKY_coin_Transaction_SignInputs failed"); + result = SKY_coin_Transaction_UpdateHeader( *handle ); + cr_assert(result == SKY_OK, "SKY_coin_Transaction_UpdateHeader failed"); + return ptransaction; +} + +coin__Transaction* makeTransaction(Transaction__Handle* handle){ + int result; + coin__UxOut uxOut; + cipher__SecKey seckey; + + coin__Transaction* ptransaction = NULL; + + result = makeUxOutWithSecret( &uxOut, &seckey ); + cr_assert(result == SKY_OK, "makeUxOutWithSecret failed"); + ptransaction = makeTransactionFromUxOut( &uxOut, &seckey, handle ); + cr_assert(result == SKY_OK, "makeTransactionFromUxOut failed"); + return ptransaction; +} + +coin__Transaction* makeEmptyTransaction(Transaction__Handle* handle){ + int result; + coin__Transaction* ptransaction = NULL; + result = SKY_coin_Create_Transaction(handle); + cr_assert(result == SKY_OK, "SKY_coin_Create_Transaction failed"); + registerHandleClose(*handle); + result = SKY_coin_GetTransactionObject( *handle, &ptransaction ); + cr_assert(result == SKY_OK, "SKY_coin_GetTransactionObject failed"); + return ptransaction; +} + + +int makeTransactions(int n, Transactions__Handle* handle){ + int result = SKY_coin_Create_Transactions(handle); + cr_assert(result == SKY_OK); + registerHandleClose(*handle); + for(int i = 0; i < n; i++){ + Transaction__Handle thandle; + makeTransaction(&thandle); + registerHandleClose(thandle); + result = SKY_coin_Transactions_Add(*handle, thandle); + cr_assert(result == SKY_OK); + } + return result; +} + +typedef struct{ + cipher__SHA256 hash; + Transaction__Handle handle; +} TransactionObjectHandle; + +int sortTransactions(Transactions__Handle txns_handle, Transactions__Handle* sorted_txns_handle){ + int result = SKY_coin_Create_Transactions(sorted_txns_handle); + cr_assert(result == SKY_OK); + registerHandleClose(*sorted_txns_handle); + GoInt n, i, j; + result = SKY_coin_Transactions_Length(txns_handle, &n); + cr_assert(result == SKY_OK); + TransactionObjectHandle* pTrans = malloc( n * sizeof(TransactionObjectHandle)); + cr_assert(pTrans != NULL); + registerMemCleanup(pTrans); + memset(pTrans, 0, n * sizeof(TransactionObjectHandle)); + int* indexes = malloc( n * sizeof(int) ); + cr_assert(indexes != NULL); + registerMemCleanup(indexes); + for( i = 0; i < n; i ++){ + indexes[i] = i; + result = SKY_coin_Transactions_GetAt(txns_handle, i, &pTrans[i].handle); + cr_assert(result == SKY_OK); + registerHandleClose(pTrans[i].handle); + result = SKY_coin_Transaction_Hash(pTrans[i].handle, &pTrans[i].hash); + cr_assert(result == SKY_OK); + } + + //Swap sort. + cipher__SHA256 hash1, hash2; + for(i = 0; i < n - 1; i++){ + for(j = i + 1; j < n; j++){ + int cmp = memcmp(&pTrans[indexes[i]].hash, &pTrans[indexes[j]].hash, sizeof(cipher__SHA256)); + if(cmp > 0){ + //Swap + int tmp = indexes[i]; + indexes[i] = indexes[j]; + indexes[j] = tmp; + } + } + } + for( i = 0; i < n; i ++){ + result = SKY_coin_Transactions_Add(*sorted_txns_handle, pTrans[indexes[i]].handle); + cr_assert(result == SKY_OK); + } + return result; +} + +coin__Transaction* copyTransaction(Transaction__Handle handle, Transaction__Handle* handle2){ + coin__Transaction* ptransaction = NULL; + int result = 0; + result = SKY_coin_Transaction_Copy(handle, handle2); + cr_assert(result == SKY_OK); + registerHandleClose(*handle2); + result = SKY_coin_GetTransactionObject( *handle2, &ptransaction ); + cr_assert(result == SKY_OK, "SKY_coin_GetTransactionObject failed"); + return ptransaction; +} + +void makeRandHash(cipher__SHA256* phash){ + GoSlice slice; + memset(&slice, 0, sizeof(GoSlice)); + + int result = SKY_cipher_RandByte( 128, (coin__UxArray*)&slice ); + cr_assert(result == SKY_OK, "SKY_cipher_RandByte failed"); + registerMemCleanup( slice.data ); + result = SKY_cipher_SumSHA256( slice, phash ); + cr_assert(result == SKY_OK, "SKY_cipher_SumSHA256 failed"); +} + +int makeUxArray(coin__UxArray* parray, int n){ + parray->data = malloc( sizeof(coin__UxOut) * n ); + if(!parray->data) + return SKY_ERROR; + registerMemCleanup( parray->data ); + parray->cap = parray->len = n; + coin__UxOut* p = (coin__UxOut*)parray->data; + int result = SKY_OK; + for(int i = 0; i < n; i++){ + result = makeUxOut(p); + if( result != SKY_OK ) + break; + p++; + } + return result; +} diff --git a/lib/cgo/testutil.testutil.go b/lib/cgo/testutil.testutil.go new file mode 100644 index 0000000000..ce8776849b --- /dev/null +++ b/lib/cgo/testutil.testutil.go @@ -0,0 +1,23 @@ +package main + +import ( + "unsafe" + + testutil "github.com/skycoin/skycoin/src/testutil" +) + +/* + + #include + #include + + #include "skytypes.h" +*/ +import "C" + +//export SKY_testutil_MakeAddress +func SKY_testutil_MakeAddress(_arg0 *C.cipher__Address) (____error_code uint32) { + __arg0 := testutil.MakeAddress() + *_arg0 = *(*C.cipher__Address)(unsafe.Pointer(&__arg0)) + return +} diff --git a/lib/cgo/util.apputil.apputil.go b/lib/cgo/util.apputil.apputil.go new file mode 100644 index 0000000000..4897d52da9 --- /dev/null +++ b/lib/cgo/util.apputil.apputil.go @@ -0,0 +1,30 @@ +package main + +import apputil "github.com/skycoin/skycoin/src/util/apputil" + +/* + + #include + #include + + #include "skytypes.h" +*/ +import "C" + +//export SKY_apputil_CatchInterruptPanic +func SKY_apputil_CatchInterruptPanic() (____error_code uint32) { + apputil.CatchInterruptPanic() + return +} + +//export SKY_apputil_CatchDebug +func SKY_apputil_CatchDebug() (____error_code uint32) { + apputil.CatchDebug() + return +} + +//export SKY_apputil_PrintProgramStatus +func SKY_apputil_PrintProgramStatus() (____error_code uint32) { + apputil.PrintProgramStatus() + return +} diff --git a/lib/cgo/util.cert.cert.go b/lib/cgo/util.cert.cert.go new file mode 100644 index 0000000000..23943a2f56 --- /dev/null +++ b/lib/cgo/util.cert.cert.go @@ -0,0 +1,30 @@ +package main + +import ( + "reflect" + + cert "github.com/skycoin/skycoin/src/util/certutil" +) + +/* + + #include + #include + + #include "skytypes.h" +*/ +import "C" + +//export SKY_certutil_NewTLSCertPair +func SKY_certutil_NewTLSCertPair(organization string, validUntil string, extraHosts []string, _cert *C.GoSlice_, _key *C.GoSlice_) (____error_code uint32) { + ____time_validUntil, ____return_err := parseTimeValue(validUntil) + if ____return_err == nil { + cert, key, ____return_err := cert.NewTLSCertPair(organization, ____time_validUntil, extraHosts) + if ____return_err == nil { + copyToGoSlice(reflect.ValueOf(cert), _cert) + copyToGoSlice(reflect.ValueOf(key), _key) + } + } + ____error_code = libErrorCode(____return_err) + return +} diff --git a/lib/cgo/util.droplet.droplet.go b/lib/cgo/util.droplet.droplet.go new file mode 100644 index 0000000000..4693a84531 --- /dev/null +++ b/lib/cgo/util.droplet.droplet.go @@ -0,0 +1,34 @@ +package main + +import droplet "github.com/skycoin/skycoin/src/util/droplet" + +/* + + #include + #include + + #include "skytypes.h" +*/ +import "C" + +//export SKY_droplet_FromString +func SKY_droplet_FromString(_b string, _arg1 *uint64) (____error_code uint32) { + b := _b + __arg1, ____return_err := droplet.FromString(b) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg1 = __arg1 + } + return +} + +//export SKY_droplet_ToString +func SKY_droplet_ToString(_n uint64, _arg1 *C.GoString_) (____error_code uint32) { + n := _n + __arg1, ____return_err := droplet.ToString(n) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + copyString(__arg1, _arg1) + } + return +} diff --git a/lib/cgo/util.fee.fee.go b/lib/cgo/util.fee.fee.go new file mode 100644 index 0000000000..527ffb2283 --- /dev/null +++ b/lib/cgo/util.fee.fee.go @@ -0,0 +1,74 @@ +package main + +import ( + "unsafe" + + coin "github.com/skycoin/skycoin/src/coin" + fee "github.com/skycoin/skycoin/src/util/fee" +) + +/* + + #include + #include + + #include "skytypes.h" +*/ +import "C" + +//export SKY_fee_VerifyTransactionFee +func SKY_fee_VerifyTransactionFee(_t C.Transaction__Handle, _fee uint64, _burnFactor uint64) (____error_code uint32) { + t, ok := lookupTransactionHandle(_t) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + ____return_err := fee.VerifyTransactionFee(t, _fee, _burnFactor) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + } + return +} + +//export SKY_fee_VerifyTransactionFeeForHours +func SKY_fee_VerifyTransactionFeeForHours(_hours, _fee uint64, _burnFactor uint64) (____error_code uint32) { + hours := _hours + ____return_err := fee.VerifyTransactionFeeForHours(hours, _fee, _burnFactor) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + } + return +} + +//export SKY_fee_RequiredFee +func SKY_fee_RequiredFee(_hours uint64, _burnFactor uint64, _arg1 *uint64) (____error_code uint32) { + hours := _hours + __arg1 := fee.RequiredFee(hours, _burnFactor) + *_arg1 = __arg1 + return +} + +//export SKY_fee_RemainingHours +func SKY_fee_RemainingHours(_hours uint64, _burnFactor uint64, _arg1 *uint64) (____error_code uint32) { + hours := _hours + __arg1 := fee.RemainingHours(hours, _burnFactor) + *_arg1 = __arg1 + return +} + +//export SKY_fee_TransactionFee +func SKY_fee_TransactionFee(_tx C.Transaction__Handle, _headTime uint64, _inUxs *C.coin__UxArray, _arg3 *uint64) (____error_code uint32) { + tx, ok := lookupTransactionHandle(_tx) + if !ok { + ____error_code = SKY_BAD_HANDLE + return + } + headTime := _headTime + inUxs := *(*coin.UxArray)(unsafe.Pointer(_inUxs)) + __arg3, ____return_err := fee.TransactionFee(tx, headTime, inUxs) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg3 = __arg3 + } + return +} diff --git a/lib/cgo/util.file.file.go b/lib/cgo/util.file.file.go new file mode 100644 index 0000000000..5b7d6c2958 --- /dev/null +++ b/lib/cgo/util.file.file.go @@ -0,0 +1,51 @@ +package main + +import file "github.com/skycoin/skycoin/src/util/file" + +/* + + #include + #include + + #include "skytypes.h" +*/ +import "C" + +//export SKY_file_InitDataDir +func SKY_file_InitDataDir(_dir string, _arg1 *C.GoString_) (____error_code uint32) { + dir := _dir + __arg1, ____return_err := file.InitDataDir(dir) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + copyString(__arg1, _arg1) + } + return +} + +//export SKY_file_UserHome +func SKY_file_UserHome(_arg0 *C.GoString_) (____error_code uint32) { + __arg0 := file.UserHome() + copyString(__arg0, _arg0) + return +} + +//export SKY_file_ResolveResourceDirectory +func SKY_file_ResolveResourceDirectory(_path string, _arg1 *C.GoString_) (____error_code uint32) { + path := _path + __arg1 := file.ResolveResourceDirectory(path) + copyString(__arg1, _arg1) + return +} + +//export SKY_file_DetermineResourcePath +func SKY_file_DetermineResourcePath(_staticDir string, _resourceDir string, _devDir string, _arg3 *C.GoString_) (____error_code uint32) { + staticDir := _staticDir + resourceDir := _resourceDir + devDir := _devDir + __arg3, ____return_err := file.DetermineResourcePath(staticDir, resourceDir, devDir) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + copyString(__arg3, _arg3) + } + return +} diff --git a/lib/cgo/util.http.json.go b/lib/cgo/util.http.json.go new file mode 100644 index 0000000000..d311d19659 --- /dev/null +++ b/lib/cgo/util.http.json.go @@ -0,0 +1,99 @@ +package main + +import ( + "reflect" + "unsafe" + + http "github.com/skycoin/skycoin/src/util/http" +) + +/* + + #include + #include + + #include "skytypes.h" +*/ +import "C" + +//export SKY_httphelper_Address_UnmarshalJSON +func SKY_httphelper_Address_UnmarshalJSON(_a *C.httphelper__Address, _b []byte) (____error_code uint32) { + a := inplaceHttpHelperAddress(_a) + b := *(*[]byte)(unsafe.Pointer(&_b)) + ____return_err := a.UnmarshalJSON(b) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + } + return +} + +//export SKY_httphelper_Address_MarshalJSON +func SKY_httphelper_Address_MarshalJSON(_a *C.httphelper__Address, _arg0 *C.GoSlice_) (____error_code uint32) { + a := *inplaceHttpHelperAddress(_a) + __arg0, ____return_err := a.MarshalJSON() + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + copyToGoSlice(reflect.ValueOf(__arg0), _arg0) + } + return +} + +//export SKY_httphelper_Coins_UnmarshalJSON +func SKY_httphelper_Coins_UnmarshalJSON(_c *C.httphelper__Coins, _b []byte) (____error_code uint32) { + c := (*http.Coins)(unsafe.Pointer(_c)) + b := *(*[]byte)(unsafe.Pointer(&_b)) + ____return_err := c.UnmarshalJSON(b) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + } + return +} + +//export SKY_httphelper_Coins_MarshalJSON +func SKY_httphelper_Coins_MarshalJSON(_c *C.httphelper__Coins, _arg0 *C.GoSlice_) (____error_code uint32) { + c := *(*http.Coins)(unsafe.Pointer(_c)) + __arg0, ____return_err := c.MarshalJSON() + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + copyToGoSlice(reflect.ValueOf(__arg0), _arg0) + } + return +} + +//export SKY_httphelper_Coins_Value +func SKY_httphelper_Coins_Value(_c *C.httphelper__Coins, _arg0 *uint64) (____error_code uint32) { + c := *(*http.Coins)(unsafe.Pointer(_c)) + __arg0 := c.Value() + *_arg0 = __arg0 + return +} + +//export SKY_httphelper_Hours_UnmarshalJSON +func SKY_httphelper_Hours_UnmarshalJSON(_h *C.httphelper__Hours, _b []byte) (____error_code uint32) { + h := (*http.Hours)(unsafe.Pointer(_h)) + b := *(*[]byte)(unsafe.Pointer(&_b)) + ____return_err := h.UnmarshalJSON(b) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + } + return +} + +//export SKY_httphelper_Hours_MarshalJSON +func SKY_httphelper_Hours_MarshalJSON(_h *C.httphelper__Hours, _arg0 *C.GoSlice_) (____error_code uint32) { + h := *(*http.Hours)(unsafe.Pointer(_h)) + __arg0, ____return_err := h.MarshalJSON() + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + copyToGoSlice(reflect.ValueOf(__arg0), _arg0) + } + return +} + +//export SKY_httphelper_Hours_Value +func SKY_httphelper_Hours_Value(_h *C.httphelper__Hours, _arg0 *uint64) (____error_code uint32) { + h := *(*http.Hours)(unsafe.Pointer(_h)) + __arg0 := h.Value() + *_arg0 = __arg0 + return +} diff --git a/lib/cgo/util.iputil.iputil.go b/lib/cgo/util.iputil.iputil.go new file mode 100644 index 0000000000..f3906de38d --- /dev/null +++ b/lib/cgo/util.iputil.iputil.go @@ -0,0 +1,42 @@ +package main + +import iputil "github.com/skycoin/skycoin/src/util/iputil" + +/* + + #include + #include + + #include "skytypes.h" +*/ +import "C" + +//export SKY_iputil_LocalhostIP +func SKY_iputil_LocalhostIP(_arg0 *C.GoString_) (____error_code uint32) { + __arg0, ____return_err := iputil.LocalhostIP() + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + copyString(__arg0, _arg0) + } + return +} + +//export SKY_iputil_IsLocalhost +func SKY_iputil_IsLocalhost(_addr string, _arg1 *bool) (____error_code uint32) { + addr := _addr + __arg1 := iputil.IsLocalhost(addr) + *_arg1 = __arg1 + return +} + +//export SKY_iputil_SplitAddr +func SKY_iputil_SplitAddr(_addr string, _arg1 *C.GoString_, _arg2 *uint16) (____error_code uint32) { + addr := _addr + __arg1, __arg2, ____return_err := iputil.SplitAddr(addr) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + copyString(__arg1, _arg1) + *_arg2 = __arg2 + } + return +} diff --git a/lib/cgo/util.logging.logging.go b/lib/cgo/util.logging.logging.go new file mode 100644 index 0000000000..b544364d68 --- /dev/null +++ b/lib/cgo/util.logging.logging.go @@ -0,0 +1,30 @@ +package main + +import logging "github.com/skycoin/skycoin/src/util/logging" + +/* + + #include + #include + + #include "skytypes.h" +*/ +import "C" + +//export SKY_logging_EnableColors +func SKY_logging_EnableColors() (____error_code uint32) { + logging.EnableColors() + return +} + +//export SKY_logging_DisableColors +func SKY_logging_DisableColors() (____error_code uint32) { + logging.DisableColors() + return +} + +//export SKY_logging_Disable +func SKY_logging_Disable() (____error_code uint32) { + logging.Disable() + return +} diff --git a/lib/cgo/wallet.balance.go b/lib/cgo/wallet.balance.go new file mode 100644 index 0000000000..c491ab4122 --- /dev/null +++ b/lib/cgo/wallet.balance.go @@ -0,0 +1,76 @@ +package main + +import ( + "unsafe" + + coin "github.com/skycoin/skycoin/src/coin" + wallet "github.com/skycoin/skycoin/src/wallet" +) + +/* + + #include + #include + + #include "skytypes.h" +*/ +import "C" + +//export SKY_wallet_NewBalance +func SKY_wallet_NewBalance(_coins, _hours uint64, _arg1 *C.wallet__Balance) (____error_code uint32) { + coins := _coins + hours := _hours + __arg1 := wallet.NewBalance(coins, hours) + *_arg1 = *(*C.wallet__Balance)(unsafe.Pointer(&__arg1)) + return +} + +//export SKY_wallet_NewBalanceFromUxOut +func SKY_wallet_NewBalanceFromUxOut(_headTime uint64, _ux *C.coin__UxOut, _arg2 *C.wallet__Balance) (____error_code uint32) { + headTime := _headTime + ux := (*coin.UxOut)(unsafe.Pointer(_ux)) + __arg2, ____return_err := wallet.NewBalanceFromUxOut(headTime, ux) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg2 = *(*C.wallet__Balance)(unsafe.Pointer(&__arg2)) + } + return +} + +//export SKY_wallet_Balance_Add +func SKY_wallet_Balance_Add(_bal *C.wallet__Balance, _other *C.wallet__Balance, _arg1 *C.wallet__Balance) (____error_code uint32) { + bal := *(*wallet.Balance)(unsafe.Pointer(_bal)) + other := *(*wallet.Balance)(unsafe.Pointer(_other)) + __arg1, ____return_err := bal.Add(other) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg1 = *(*C.wallet__Balance)(unsafe.Pointer(&__arg1)) + } + return +} + +//export SKY_wallet_Balance_Sub +func SKY_wallet_Balance_Sub(_bal *C.wallet__Balance, _other *C.wallet__Balance, _arg1 *C.wallet__Balance) (____error_code uint32) { + bal := *(*wallet.Balance)(unsafe.Pointer(_bal)) + other := *(*wallet.Balance)(unsafe.Pointer(_other)) + __arg1 := bal.Sub(other) + *_arg1 = *(*C.wallet__Balance)(unsafe.Pointer(&__arg1)) + return +} + +//export SKY_wallet_Balance_Equals +func SKY_wallet_Balance_Equals(_bal *C.wallet__Balance, _other *C.wallet__Balance, _arg1 *bool) (____error_code uint32) { + bal := *(*wallet.Balance)(unsafe.Pointer(_bal)) + other := *(*wallet.Balance)(unsafe.Pointer(_other)) + __arg1 := bal.Equals(other) + *_arg1 = __arg1 + return +} + +//export SKY_wallet_Balance_IsZero +func SKY_wallet_Balance_IsZero(_bal *C.wallet__Balance, _arg0 *bool) (____error_code uint32) { + bal := *(*wallet.Balance)(unsafe.Pointer(_bal)) + __arg0 := bal.IsZero() + *_arg0 = __arg0 + return +} diff --git a/lib/cgo/wallet.crypto.go b/lib/cgo/wallet.crypto.go new file mode 100644 index 0000000000..86a9bc1465 --- /dev/null +++ b/lib/cgo/wallet.crypto.go @@ -0,0 +1,25 @@ +package main + +import ( + wallet "github.com/skycoin/skycoin/src/wallet" +) + +/* + + #include + #include + + #include "skytypes.h" +*/ +import "C" + +//export SKY_wallet_CryptoTypeFromString +func SKY_wallet_CryptoTypeFromString(_s string, _arg1 *C.GoString_) (____error_code uint32) { + s := _s + __arg1, ____return_err := wallet.CryptoTypeFromString(s) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + copyString(string(__arg1), _arg1) + } + return +} diff --git a/lib/cgo/wallet.entry.go b/lib/cgo/wallet.entry.go new file mode 100644 index 0000000000..2d7c7807e3 --- /dev/null +++ b/lib/cgo/wallet.entry.go @@ -0,0 +1,36 @@ +package main + +import ( + "unsafe" + + wallet "github.com/skycoin/skycoin/src/wallet" +) + +/* + + #include + #include + + #include "skytypes.h" +*/ +import "C" + +//export SKY_wallet_Entry_Verify +func SKY_wallet_Entry_Verify(_we *C.wallet__Entry) (____error_code uint32) { + we := (*wallet.Entry)(unsafe.Pointer(_we)) + ____return_err := we.Verify() + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + } + return +} + +//export SKY_wallet_Entry_VerifyPublic +func SKY_wallet_Entry_VerifyPublic(_we *C.wallet__Entry) (____error_code uint32) { + we := (*wallet.Entry)(unsafe.Pointer(_we)) + ____return_err := we.VerifyPublic() + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + } + return +} diff --git a/lib/cgo/wallet.readable.go b/lib/cgo/wallet.readable.go new file mode 100644 index 0000000000..0747bda66c --- /dev/null +++ b/lib/cgo/wallet.readable.go @@ -0,0 +1,77 @@ +package main + +import ( + "unsafe" + + wallet "github.com/skycoin/skycoin/src/wallet" +) + +/* + + #include + #include + + #include "skytypes.h" +*/ +import "C" + +//export SKY_wallet_NewReadableEntry +func SKY_wallet_NewReadableEntry(_coinType string, _w *C.wallet__Entry, _arg1 *C.ReadableEntry__Handle) (____error_code uint32) { + coinType := wallet.CoinType(_coinType) + w := *(*wallet.Entry)(unsafe.Pointer(_w)) + __arg1 := wallet.NewReadableEntry(coinType, w) + *_arg1 = registerReadableEntryHandle(&__arg1) + return +} + +//export SKY_wallet_LoadReadableWallet +func SKY_wallet_LoadReadableWallet(_filename string, _arg1 *C.ReadableWallet__Handle) (____error_code uint32) { + filename := _filename + __arg1, ____return_err := wallet.LoadReadableWallet(filename) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + *_arg1 = registerReadableWalletHandle(__arg1) + } + return +} + +//export SKY_wallet_ReadableWallet_Save +func SKY_wallet_ReadableWallet_Save(_rw C.ReadableWallet__Handle, _filename string) (____error_code uint32) { + rw, okrw := lookupReadableWalletHandle(_rw) + if !okrw { + ____error_code = SKY_BAD_HANDLE + return + } + filename := _filename + ____return_err := rw.Save(filename) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + } + return +} + +//export SKY_wallet_ReadableWallet_Load +func SKY_wallet_ReadableWallet_Load(_rw C.ReadableWallet__Handle, _filename string) (____error_code uint32) { + rw, okrw := lookupReadableWalletHandle(_rw) + if !okrw { + ____error_code = SKY_BAD_HANDLE + return + } + filename := _filename + ____return_err := rw.Load(filename) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + } + return +} + +//export SKY_wallet_ReadableWallet_Erase +func SKY_wallet_ReadableWallet_Erase(_rw C.ReadableWallet__Handle) (____error_code uint32) { + rw, okrw := lookupReadableWalletHandle(_rw) + if !okrw { + ____error_code = SKY_BAD_HANDLE + return + } + rw.Erase() + return +} diff --git a/lib/cgo/wallet.wallet.go b/lib/cgo/wallet.wallet.go index 05f7441b2d..a69afb20de 100644 --- a/lib/cgo/wallet.wallet.go +++ b/lib/cgo/wallet.wallet.go @@ -10,6 +10,7 @@ import ( ) /* + #include #include @@ -17,6 +18,16 @@ import ( */ import "C" +//export SKY_wallet_NewError +func SKY_wallet_NewError(_err error) (____error_code uint32) { + err := _err + ____return_err := wallet.NewError(err) + ____error_code = libErrorCode(____return_err) + if ____return_err == nil { + } + return +} + //export SKY_wallet_NewWallet func SKY_wallet_NewWallet(_wltName string, _opts C.Options__Handle, _arg2 *C.Wallet__Handle) (____error_code uint32) { __opts, okopts := lookupOptionsHandle(_opts) @@ -106,62 +117,62 @@ func SKY_wallet_Wallet_Validate(_w C.Wallet__Handle) (____error_code uint32) { } //export SKY_wallet_Wallet_Type -func SKY_wallet_Wallet_Type(_w C.Wallet__Handle, _argSKY_OK *C.GoString_) (____error_code uint32) { +func SKY_wallet_Wallet_Type(_w C.Wallet__Handle, _arg0 *C.GoString_) (____error_code uint32) { w, okw := lookupWalletHandle(_w) if !okw { ____error_code = SKY_BAD_HANDLE return } - __argSKY_OK := w.Type() - copyString(__argSKY_OK, _argSKY_OK) + __arg0 := w.Type() + copyString(__arg0, _arg0) return } //export SKY_wallet_Wallet_Version -func SKY_wallet_Wallet_Version(_w C.Wallet__Handle, _argSKY_OK *C.GoString_) (____error_code uint32) { +func SKY_wallet_Wallet_Version(_w C.Wallet__Handle, _arg0 *C.GoString_) (____error_code uint32) { w, okw := lookupWalletHandle(_w) if !okw { ____error_code = SKY_BAD_HANDLE return } - __argSKY_OK := w.Version() - copyString(__argSKY_OK, _argSKY_OK) + __arg0 := w.Version() + copyString(__arg0, _arg0) return } //export SKY_wallet_Wallet_Filename -func SKY_wallet_Wallet_Filename(_w C.Wallet__Handle, _argSKY_OK *C.GoString_) (____error_code uint32) { +func SKY_wallet_Wallet_Filename(_w C.Wallet__Handle, _arg0 *C.GoString_) (____error_code uint32) { w, okw := lookupWalletHandle(_w) if !okw { ____error_code = SKY_BAD_HANDLE return } - __argSKY_OK := w.Filename() - copyString(__argSKY_OK, _argSKY_OK) + __arg0 := w.Filename() + copyString(__arg0, _arg0) return } //export SKY_wallet_Wallet_Label -func SKY_wallet_Wallet_Label(_w C.Wallet__Handle, _argSKY_OK *C.GoString_) (____error_code uint32) { +func SKY_wallet_Wallet_Label(_w C.Wallet__Handle, _arg0 *C.GoString_) (____error_code uint32) { w, okw := lookupWalletHandle(_w) if !okw { ____error_code = SKY_BAD_HANDLE return } - __argSKY_OK := w.Label() - copyString(__argSKY_OK, _argSKY_OK) + __arg0 := w.Label() + copyString(__arg0, _arg0) return } //export SKY_wallet_Wallet_IsEncrypted -func SKY_wallet_Wallet_IsEncrypted(_w C.Wallet__Handle, _argSKY_OK *bool) (____error_code uint32) { +func SKY_wallet_Wallet_IsEncrypted(_w C.Wallet__Handle, _arg0 *bool) (____error_code uint32) { w, okw := lookupWalletHandle(_w) if !okw { ____error_code = SKY_BAD_HANDLE return } - __argSKY_OK := w.IsEncrypted() - *_argSKY_OK = __argSKY_OK + __arg0 := w.IsEncrypted() + *_arg0 = __arg0 return } @@ -182,14 +193,14 @@ func SKY_wallet_Wallet_GenerateAddresses(_w C.Wallet__Handle, _num uint64, _arg1 } //export SKY_wallet_Wallet_GetAddresses -func SKY_wallet_Wallet_GetAddresses(_w C.Wallet__Handle, _argSKY_OK *C.GoSlice_) (____error_code uint32) { +func SKY_wallet_Wallet_GetAddresses(_w C.Wallet__Handle, _arg0 *C.GoSlice_) (____error_code uint32) { w, okw := lookupWalletHandle(_w) if !okw { ____error_code = SKY_BAD_HANDLE return } - __argSKY_OK := w.GetAddresses() - copyToGoSlice(reflect.ValueOf(__argSKY_OK), _argSKY_OK) + __arg0 := w.GetAddresses() + copyToGoSlice(reflect.ValueOf(__arg0), _arg0) return } diff --git a/peers.txt b/peers.txt index 9aab20f655..b8d1a70025 100644 --- a/peers.txt +++ b/peers.txt @@ -45,4 +45,5 @@ 49.135.7.8:6000 207.237.45.196:6000 45.63.21.232:6000 -101.167.169.158:6000 \ No newline at end of file +101.167.169.158:6000 +172.105.210.184:6000 diff --git a/run.sh b/run-client.sh similarity index 92% rename from run.sh rename to run-client.sh index c820623f63..c35155cadf 100755 --- a/run.sh +++ b/run-client.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash +# Runs skycoin in desktop client configuration + set -x DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" diff --git a/run-daemon.sh b/run-daemon.sh new file mode 100755 index 0000000000..4340b94058 --- /dev/null +++ b/run-daemon.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +# Runs skycoin in daemon mode configuration + +set -x + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +echo "skycoin binary dir:" "$DIR" +pushd "$DIR" >/dev/null + +COMMIT=$(git rev-parse HEAD) +BRANCH=$(git rev-parse --abbrev-ref HEAD) +GOLDFLAGS="-X main.Commit=${COMMIT} -X main.Branch=${BRANCH}" + +GORUNFLAGS=${GORUNFLAGS:-} + +go run -ldflags "${GOLDFLAGS}" $GORUNFLAGS cmd/skycoin/skycoin.go \ + -enable-gui=false \ + -launch-browser=false \ + -rpc-interface=false \ + -log-level=debug \ + $@ + +popd >/dev/null diff --git a/src/api/README.md b/src/api/README.md index 607b027570..8b80d10fa7 100644 --- a/src/api/README.md +++ b/src/api/README.md @@ -8,7 +8,78 @@ The API has two versions, `/api/v1` and `/api/v2`. Previously, there was no `/api/vx` prefix. Starting in application version v0.24.0, the existing endpoints from v0.23.0 are now prefixed with `/api/v1`. To retain the old endpoints, run the application -with `-enable-unversioned-api`. +with `-enable-unversioned-api`. This option will be removed in v0.26.0 +and the `/api/v1` prefix will be required for previously unversioned endpoints. + + + +- [API Version 1](#api-version-1) +- [API Version 2](#api-version-2) +- [API Sets](#api-sets) +- [Authentication](#authentication) +- [CSRF](#csrf) + - [Get current csrf token](#get-current-csrf-token) +- [General system checks](#general-system-checks) + - [Health check](#health-check) + - [Version info](#version-info) + - [Prometheus metrics](#prometheus-metrics) +- [Simple query APIs](#simple-query-apis) + - [Get balance of addresses](#get-balance-of-addresses) + - [Get unspent output set of address or hash](#get-unspent-output-set-of-address-or-hash) + - [Verify an address](#verify-an-address) +- [Wallet APIs](#wallet-apis) + - [Get wallet](#get-wallet) + - [Get unconfirmed transactions of a wallet](#get-unconfirmed-transactions-of-a-wallet) + - [Get wallets](#get-wallets) + - [Get wallet folder name](#get-wallet-folder-name) + - [Generate wallet seed](#generate-wallet-seed) + - [Create a wallet from seed](#create-a-wallet-from-seed) + - [Generate new address in wallet](#generate-new-address-in-wallet) + - [Updates wallet label](#updates-wallet-label) + - [Get wallet balance](#get-wallet-balance) + - [Spend coins from wallet](#spend-coins-from-wallet) + - [Create transaction](#create-transaction) + - [Unload wallet](#unload-wallet) + - [Encrypt wallet](#encrypt-wallet) + - [Decrypt wallet](#decrypt-wallet) + - [Get wallet seed](#get-wallet-seed) + - [Recover encrypted wallet by seed](#recover-encrypted-wallet-by-seed) +- [Transaction APIs](#transaction-apis) + - [Get unconfirmed transactions](#get-unconfirmed-transactions) + - [Get transaction info by id](#get-transaction-info-by-id) + - [Get raw transaction by id](#get-raw-transaction-by-id) + - [Inject raw transaction](#inject-raw-transaction) + - [Get transactions for addresses](#get-transactions-for-addresses) + - [Resend unconfirmed transactions](#resend-unconfirmed-transactions) + - [Verify encoded transaction](#verify-encoded-transaction) +- [Block APIs](#block-apis) + - [Get blockchain metadata](#get-blockchain-metadata) + - [Get blockchain progress](#get-blockchain-progress) + - [Get block by hash or seq](#get-block-by-hash-or-seq) + - [Get blocks in specific range](#get-blocks-in-specific-range) + - [Get last N blocks](#get-last-n-blocks) +- [Explorer APIs](#explorer-apis) + - [Get address affected transactions](#get-address-affected-transactions) +- [Uxout APIs](#uxout-apis) + - [Get uxout](#get-uxout) + - [Get historical unspent outputs for an address](#get-historical-unspent-outputs-for-an-address) +- [Coin supply related information](#coin-supply-related-information) + - [Coin supply](#coin-supply) + - [Richlist show top N addresses by uxouts](#richlist-show-top-n-addresses-by-uxouts) + - [Count unique addresses](#count-unique-addresses) +- [Network status](#network-status) + - [Get information for a specific connection](#get-information-for-a-specific-connection) + - [Get a list of all connections](#get-a-list-of-all-connections) + - [Get a list of all default connections](#get-a-list-of-all-default-connections) + - [Get a list of all trusted connections](#get-a-list-of-all-trusted-connections) + - [Get a list of all connections discovered through peer exchange](#get-a-list-of-all-connections-discovered-through-peer-exchange) + - [Disconnect a peer](#disconnect-a-peer) +- [Migrating from the unversioned API](#migrating-from-the-unversioned-api) +- [Migrating from the JSONRPC API](#migrating-from-the-jsonrpc-api) +- [Migrating from /api/v1/spend](#migrating-from-apiv1spend) +- [Migration from /api/v1/explorer/address](#migration-from-apiv1exploreraddress) + + ## API Version 1 @@ -66,11 +137,12 @@ These API sets are: * `READ` - All query-related endpoints, they do not modify the state of the program * `STATUS` - A subset of `READ`, these endpoints report the application, network or blockchain status -* `TXN` - Enables `/api/v1/injectTransaction` without enabling wallet endpoints +* `TXN` - Enables `/api/v1/injectTransaction` and `/api/v1/resendUnconfirmedTxns` without enabling wallet endpoints * `WALLET` - These endpoints operate on local wallet files -* `INSECURE_WALLET_SEED` - This is the `/api/v1/wallet/seed` endpoint, used to decrypt and return the seed from an encrypted wallet. It is only intended for use by the desktop client. -* `DEPRECATED_WALLET_SPEND` - This is the `/api/v1/wallet/spend` method which is deprecated and will be removed * `PROMETHEUS` - This is the `/api/v2/metrics` method exposing in Prometheus text format the default metrics for Skycoin node application +* `NET_CTRL` - The `/api/v1/network/connection/disconnect` method, intended for network administration endpoints +* `INSECURE_WALLET_SEED` - This is the `/api/v1/wallet/seed` endpoint, used to decrypt and return the seed from an encrypted wallet. It is only intended for use by the desktop client. +* `DEPRECATED_WALLET_SPEND` - This is the `/api/v1/wallet/spend` method which is deprecated and will be removed in v0.26.0 ## Authentication @@ -79,68 +151,6 @@ The username and password should be provided in an `Authorization: Basic` header Authentication can only be enabled when using HTTPS with `-web-interface-https`, unless `-web-interface-plaintext-auth` is enabled. - - -- [CSRF](#csrf) - - [Get current csrf token](#get-current-csrf-token) -- [General system checks](#general-system-checks) - - [Health check](#health-check) - - [Version info](#version-info) - - [Prometheus metrics](#prometheus-metrics) -- [Simple query APIs](#simple-query-apis) - - [Get balance of addresses](#get-balance-of-addresses) - - [Get unspent output set of address or hash](#get-unspent-output-set-of-address-or-hash) - - [Verify an address](#verify-an-address) -- [Wallet APIs](#wallet-apis) - - [Get wallet](#get-wallet) - - [Get unconfirmed transactions of a wallet](#get-unconfirmed-transactions-of-a-wallet) - - [Get wallets](#get-wallets) - - [Get wallet folder name](#get-wallet-folder-name) - - [Generate wallet seed](#generate-wallet-seed) - - [Create a wallet from seed](#create-a-wallet-from-seed) - - [Generate new address in wallet](#generate-new-address-in-wallet) - - [Updates wallet label](#updates-wallet-label) - - [Get wallet balance](#get-wallet-balance) - - [Spend coins from wallet](#spend-coins-from-wallet) - - [Create transaction](#create-transaction) - - [Unload wallet](#unload-wallet) - - [Encrypt wallet](#encrypt-wallet) - - [Decrypt wallet](#decrypt-wallet) - - [Get wallet seed](#get-wallet-seed) - - [Recover encrypted wallet by seed](#recover-encrypted-wallet-by-seed) -- [Transaction APIs](#transaction-apis) - - [Get unconfirmed transactions](#get-unconfirmed-transactions) - - [Get transaction info by id](#get-transaction-info-by-id) - - [Get raw transaction by id](#get-raw-transaction-by-id) - - [Inject raw transaction](#inject-raw-transaction) - - [Get transactions that are addresses related](#get-transactions-that-are-addresses-related) - - [Resend unconfirmed transactions](#resend-unconfirmed-transactions) - - [Verify encoded transaction](#verify-encoded-transaction) -- [Block APIs](#block-apis) - - [Get blockchain metadata](#get-blockchain-metadata) - - [Get blockchain progress](#get-blockchain-progress) - - [Get block by hash or seq](#get-block-by-hash-or-seq) - - [Get blocks in specific range](#get-blocks-in-specific-range) - - [Get last N blocks](#get-last-n-blocks) -- [Explorer APIs](#explorer-apis) - - [Get address affected transactions](#get-address-affected-transactions) -- [Uxout APIs](#uxout-apis) - - [Get uxout](#get-uxout) - - [Get historical unspent outputs for an address](#get-historical-unspent-outputs-for-an-address) -- [Coin supply related information](#coin-supply-related-information) - - [Coin supply](#coin-supply) - - [Richlist show top N addresses by uxouts](#richlist-show-top-n-addresses-by-uxouts) - - [Count unique addresses](#count-unique-addresses) -- [Network status](#network-status) - - [Get information for a specific connection](#get-information-for-a-specific-connection) - - [Get a list of all connections](#get-a-list-of-all-connections) - - [Get a list of all default connections](#get-a-list-of-all-default-connections) - - [Get a list of all trusted connections](#get-a-list-of-all-trusted-connections) - - [Get a list of all connections discovered through peer exchange](#get-a-list-of-all-connections-discovered-through-peer-exchange) -- [Migrating from the JSONRPC API](#migrating-from-the-jsonrpc-api) - - - ## CSRF All `POST`, `PUT` and `DELETE` requests require a CSRF token, obtained with a `GET /api/v1/csrf` call. @@ -215,14 +225,19 @@ Response: "commit": "8798b5ee43c7ce43b9b75d57a1a6cd2c1295cd1e", "branch": "develop" }, + "coin": "skycoin", + "user_agent": "skycoin:0.25.0-rc1", "open_connections": 8, + "outgoing_connections": 5, + "incoming_connections": 3, "uptime": "6m30.629057248s", "csrf_enabled": true, "csp_enabled": true, "wallet_api_enabled": true, "gui_enabled": true, "unversioned_api_enabled": false, - "json_rpc_enabled": false + "json_rpc_enabled": false, + "coinhour_burn_factor": 2 } ``` @@ -379,11 +394,14 @@ API sets: `READ` ``` URI: /api/v1/balance -Method: GET +Method: GET, POST Args: addrs: comma-separated list of addresses. must contain at least one address ``` +Returns the cumulative and individual balances of one or more addresses. +The `POST` method can be used if many addresses need to be queried. + Example: ```sh @@ -443,7 +461,7 @@ API sets: `READ` ``` URI: /api/v1/outputs -Method: GET +Method: GET, POST Args: addrs: address list, joined with "," hashes: hash list, joined with "," @@ -457,6 +475,8 @@ and `"incoming_outputs"` are outputs that will be created by an unconfirmed tran The current head block header is returned as `"head"`. +The `POST` method can be used if many addresses or hashes need to be queried. + Example: ```sh @@ -1515,9 +1535,9 @@ API sets: `INSECURE_WALLET_SEED` URI: /api/v2/wallet/recover Method: POST Args: - id: wallet id - seed: wallet seed - password: [optional] password to encrypt the recovered wallet with + id: wallet id + seed: wallet seed + password: [optional] password to encrypt the recovered wallet with ``` Recovers an encrypted wallet by providing the wallet seed. @@ -1534,28 +1554,28 @@ Result: ```json { - "data": { - "meta": { - "coin": "skycoin", - "filename": "2017_11_25_e5fb.wlt", - "label": "test", - "type": "deterministic", - "version": "0.2", - "crypto_type": "", - "timestamp": 1511640884, - "encrypted": false - }, - "entries": [ - { - "address": "2HTnQe3ZupkG6k8S81brNC3JycGV2Em71F2", - "public_key": "0316ff74a8004adf9c71fa99808ee34c3505ee73c5cf82aa301d17817da3ca33b1" - }, - { - "address": "SMnCGfpt7zVXm8BkRSFMLeMRA6LUu3Ewne", - "public_key": "02539528248a1a2c4f0b73233491103ca83b40249dac3ae9eee9a10b9f9debd9a3" - } - ] - } + "data": { + "meta": { + "coin": "skycoin", + "filename": "2017_11_25_e5fb.wlt", + "label": "test", + "type": "deterministic", + "version": "0.2", + "crypto_type": "", + "timestamp": 1511640884, + "encrypted": false + }, + "entries": [ + { + "address": "2HTnQe3ZupkG6k8S81brNC3JycGV2Em71F2", + "public_key": "0316ff74a8004adf9c71fa99808ee34c3505ee73c5cf82aa301d17817da3ca33b1" + }, + { + "address": "SMnCGfpt7zVXm8BkRSFMLeMRA6LUu3Ewne", + "public_key": "02539528248a1a2c4f0b73233491103ca83b40249dac3ae9eee9a10b9f9debd9a3" + } + ] + } } ``` @@ -1841,9 +1861,9 @@ Method: POST Content-Type: application/json Body: {"rawtx": "hex-encoded serialized transaction string"} Errors: - 400 - Bad input - 500 - Other - 503 - Network unavailable (transaction failed to broadcast) + 400 - Bad input + 500 - Other + 503 - Network unavailable (transaction failed to broadcast) ``` Broadcasts a hex-encoded, serialized transaction to the network. @@ -1882,13 +1902,13 @@ Result: "3615fc23cc12a5cb9190878a2151d1cf54129ff0cd90e5fc4f4e7debebad6868" ``` -### Get transactions that are addresses related +### Get transactions for addresses API sets: `READ` ``` URI: /api/v1/transactions -Method: GET +Method: GET, POST Args: addrs: Comma seperated addresses [optional, returns all transactions if no address is provided] confirmed: Whether the transactions should be confirmed [optional, must be 0 or 1; if not provided, returns all] @@ -1901,18 +1921,24 @@ If the transaction is confirmed, the calculated hours are the hours the transact If the transaction is unconfirmed, the calculated hours are based upon the current system time, and are approximately equal to the hours the output would have if it become confirmed immediately. -To get address related confirmed transactions: +The `"time"` field at the top level of each object in the response array indicates either the confirmed timestamp of a confirmed +transaction or the last received timestamp of an unconfirmed transaction. + +The `POST` method can be used if many addresses need to be queried. + +To get confirmed transactions for one or more addresses: ```sh curl http://127.0.0.1:6420/api/v1/transactions?addrs=7cpQ7t3PZZXvjTst8G7Uvs7XH4LeM8fBPD,6dkVxyKFbFKg9Vdg6HPg1UANLByYRqkrdY&confirmed=1 ``` -To get address related unconfirmed transactions: +To get unconfirmed transactions for one or more addresses: + ```sh curl http://127.0.0.1:6420/api/v1/transactions?addrs=7cpQ7t3PZZXvjTst8G7Uvs7XH4LeM8fBPD,6dkVxyKFbFKg9Vdg6HPg1UANLByYRqkrdY&confirmed=0 ``` -To get all addresses related transactions: +To get both confirmed and unconfirmed transactions for one or more addresses: ```sh curl http://127.0.0.1:6420/api/v1/transactions?addrs=7cpQ7t3PZZXvjTst8G7Uvs7XH4LeM8fBPD,6dkVxyKFbFKg9Vdg6HPg1UANLByYRqkrdY @@ -2218,17 +2244,17 @@ Result: ### Resend unconfirmed transactions -API sets: `READ` +API sets: `TXN` ``` URI: /api/v1/resendUnconfirmedTxns -Method: GET +Method: POST ``` Example: ```sh -curl http://127.0.0.1:6420/api/v1/resendUnconfirmedTxns +curl -X POST 'http://127.0.0.1:6420/api/v1/resendUnconfirmedTxns' ``` Result: @@ -3213,6 +3239,8 @@ Args: address ``` +**Deprecated** Use `/api/v1/transactions?verbose=1&addrs=` instead. + Example: ```sh @@ -3548,6 +3576,12 @@ Args: addr: ip:port address of a known connection ``` +Connection `"state"` value can be `"pending"`, `"connected"` or `"introduced"`. + +* The `"pending"` state is prior to connection establishment. +* The `"connected"` state is after connection establishment, but before the introduction handshake has completed. +* The `"introduced"` state is after the introduction handshake has completed. + Example: ```sh @@ -3562,11 +3596,14 @@ Result: "address": "176.9.84.75:6000", "last_sent": 1520675817, "last_received": 1520675817, + "connected_at": 1520675700, "outgoing": false, - "introduced": true, + "state": "introduced", "mirror": 719118746, "height": 181, - "listen_port": 6000 + "listen_port": 6000, + "user_agent": "skycoin:0.25.0", + "is_trusted_peer": true } ``` @@ -3577,8 +3614,19 @@ API sets: `STATUS`, `READ` ``` URI: /api/v1/network/connections Method: GET +Args: + states: [optional] comma-separated list of connection states ("pending", "connected" or "introduced"). Defaults to "connected,introduced" + direction: [optional] "outgoing" or "incoming". If not provided, both are included. ``` +Connection `"state"` value can be `"pending"`, `"connected"` or `"introduced"`. + +* The `"pending"` state is prior to connection establishment. +* The `"connected"` state is after connection establishment, but before the introduction handshake has completed. +* The `"introduced"` state is after the introduction handshake has completed. + +By default, both incoming and outgoing connections in the `"connected"` or `"introduced"` state are returned. + Example: ```sh @@ -3595,33 +3643,42 @@ Result: "address": "139.162.161.41:20002", "last_sent": 1520675750, "last_received": 1520675750, + "connected_at": 1520675500, "outgoing": false, - "introduced": true, + "state": "introduced", "mirror": 1338939619, + "listen_port": 20002, "height": 180, - "listen_port": 20002 + "user_agent": "skycoin:0.25.0", + "is_trusted_peer": true }, { "id": 109548, "address": "176.9.84.75:6000", "last_sent": 1520675751, "last_received": 1520675751, - "outgoing": false, - "introduced": true, - "mirror": 719118746, - "height": 182, - "listen_port": 6000 + "connected_at": 1520675751, + "state": "connected", + "outgoing": true, + "mirror": 0, + "listen_port": 6000, + "height": 0, + "user_agent": "", + "is_trusted_peer": false }, { "id": 99115, "address": "185.120.34.60:6000", "last_sent": 1520675754, "last_received": 1520675754, + "connected_at": 1520673013, "outgoing": false, - "introduced": true, + "state": "introduced", "mirror": 1931713869, + "listen_port": 6000, "height": 180, - "listen_port": 6000 + "user_agent": "", + "is_trusted_peer": false } ] } @@ -3637,6 +3694,8 @@ URI: /api/v1/network/defaultConnections Method: GET ``` +Returns addresses in the default hardcoded list of peers. + Example: ```sh @@ -3666,6 +3725,9 @@ URI: /api/v1/network/connections/trust Method: GET ``` +Returns addresses marked as trusted in the peerlist. +This is typically equal to the list of addresses in the default hardcoded list of peers. + Example: ```sh @@ -3695,6 +3757,8 @@ URI: /api/v1/network/connections/exchange Method: GET ``` +Returns addresses from the peerlist that are known to have an open port. + Example: ```sh @@ -3731,9 +3795,49 @@ Result: ] ``` +### Disconnect a peer + +API sets: `NET_CTRL` + +``` +URI: /api/v1/network/connection/disconnect +Method: POST +Args: + id: ID of the connection + +Returns 404 if the connection is not found. +``` + +Disconnects a peer by ID. + +Example: + +```sh +curl -X POST 'http://127.0.0.1:6420/api/v1/network/connection/disconnect?id=999' +``` + +Result: + +```json +{} +``` + +## Migrating from the unversioned API + +The unversioned API are the API endpoints without an `/api` prefix. +These endpoints are all prefixed with `/api/v1` now. + +`-enable-unversioned-api` was added as an option to assist migration to `/api/v1` +but this option will be removed in v0.26.0. + +To migrate from the unversioned API, add `/api/v1` to all endpoints that you call +that do not have an `/api` prefix already. + +For example, `/block` would become `/api/v1/block`. + ## Migrating from the JSONRPC API -The JSONRPC-2.0 RPC API will be removed as of version `0.26.0`. +The JSONRPC-2.0 RPC API will be removed in v0.26.0. Anyone still using this can follow this guide to migrate to the REST API: * `get_status` is replaced by `/api/v1/blockchain/metadata` and `/api/v1/health` @@ -3742,3 +3846,157 @@ Anyone still using this can follow this guide to migrate to the REST API: * `get_outputs` is replaced by `/api/v1/outputs` * `inject_transaction` is replaced by `/api/v1/injectTransaction` * `get_transaction` is replaced by `/api/v1/transaction` + +## Migrating from /api/v1/spend + +The `POST /api/v1/spend` endpoint is deprecated and will be removed in v0.26.0. + +To migrate from it, use [`POST /api/v1/wallet/transaction`](#create-transaction) followed by [`POST /api/v1/injectTransaction`](#inject-raw-transaction). +Do not create another transaction before injecting the created transaction, otherwise you might create two conflicting transactions. + +`POST /api/v1/wallet/transaction` has more options for creating the transaction than the `/api/v1/spend` endpoint. +To replicate the same behavior as `/api/v1/spend`, use the following request body template: + +```json +{ + "hours_selection": { + "type": "auto", + "mode": "share", + "share_factor": "0.5", + }, + "wallet": { + "id": "$wallet_id", + "password": "$password" + }, + "to": [{ + "address": "$dst", + "coins": "$coins" + }] +} +``` + +You must use a string for `"coins"` instead of an integer measured in "droplets" (the smallest unit of currency in Skycoin, 1/1000000 of a skycoin). +For example, if you sent 1 Skycoin with `/api/v1/spend` you would have specified the `coins` field as `1000000`. +Now, you would specify it as `"1"`. + +Some examples: + +* 123.456 coins: before `123456000`, now `"123.456"` +* 0.1 coins: before `100000`, now `"0.1"` +* 1 coin: before `1000000`, now `"1"` + +Extra zeros on the `"coins"` string are ok, for example `"1"` is the same as `"1.0"` or `"1.000000"`. + +Only provide `"password"` if the wallet is encrypted. Note that decryption can take a few seconds, and this can impact +throughput. + +The request header `Content-Type` must be `application/json`. + +The response to `POST /api/v1/wallet/transaction` will include a verbose decoded transaction with details +and the hex-encoded binary transaction in the `"encoded_transaction"` field. +Use the value of `"encoded_transaction"` as the `"rawtx"` value in the request to `/api/v1/injectTransaction`. + +## Migration from /api/v1/explorer/address + +The `GET /api/v1/explorer/address` endpoint is deprecated and will be removed in v0.26.0. + +To migrate from it, use [`GET /api/v1/transactions?verbose=1`](#get-transactions-for-addresses). + +`/api/v1/explorer/address` accepted a single `address` query parameter. `/api/v1/transactions` uses an `addrs` query parameter and +accepts multiple addresses at once. + +The response data is the same but the structure is slightly different. Compare the follow two example responses: + +`/api/v1/explorer/address?address=WzPDgdfL1NzSbX96tscUNXUqtCRLjaBugC`: + +```json +[ + { + "status": { + "confirmed": true, + "unconfirmed": false, + "height": 38076, + "block_seq": 15493 + }, + "timestamp": 1518878675, + "length": 183, + "type": 0, + "txid": "6d8e2f8b436a2f38d604b3aa1196ef2176779c5e11e33fbdd09f993fe659c39f", + "inner_hash": "8da7c64dcedeeb6aa1e0d21fb84a0028dcd68e6801f1a3cc0224fdd50682046f", + "fee": 126249, + "sigs": [ + "c60e43980497daad59b4c72a2eac053b1584f960c57a5e6ac8337118dccfcee4045da3f60d9be674867862a13fdd87af90f4b85cbf39913bde13674e0a039b7800" + ], + "inputs": [ + { + "uxid": "349b06e5707f633fd2d8f048b687b40462d875d968b246831434fb5ab5dcac38", + "owner": "WzPDgdfL1NzSbX96tscUNXUqtCRLjaBugC", + "coins": "125.000000", + "hours": 34596, + "calculated_hours": 178174 + } + ], + "outputs": [ + { + "uxid": "5b4a79c7de2e9099e083bbc8096619ae76ba6fbe34875c61bbe2d3bfa6b18b99", + "dst": "2NfNKsaGJEndpSajJ6TsKJfsdDjW2gFsjXg", + "coins": "125.000000", + "hours": 51925 + } + ] + } +] +``` + +`/api/v1/transactions?verbose=1&addrs=WzPDgdfL1NzSbX96tscUNXUqtCRLjaBugC`: + +```json +[ + { + "status": { + "confirmed": true, + "unconfirmed": false, + "height": 57564, + "block_seq": 7498 + }, + "time": 1514743602, + "txn": { + "timestamp": 1514743602, + "length": 220, + "type": 0, + "txid": "df5bcef198fe6e96d496c30482730f895cabc1d55b338afe5633b0c2889d02f9", + "inner_hash": "4677ff9b9b56485495a45693cc09f8496199929fccb52091d32f2d3cf2ee8a41", + "fee": 69193, + "sigs": [ + "8e1f6f621a11f737ac2031be975d4b2fc17bf9f17a0da0a2fe219ee018011ab506e2ad0367be302a8d859cc355c552313389cd0aa9fa98dc7d2085a52f11ef5a00" + ], + "inputs": [ + { + "uxid": "2374201ff29f1c024ccfc6c53160e741d06720562853ad3613c121acd8389031", + "owner": "2GgFvqoyk9RjwVzj8tqfcXVXB4orBwoc9qv", + "coins": "162768.000000", + "hours": 485, + "calculated_hours": 138385 + } + ], + "outputs": [ + { + "uxid": "63f299fc85fe6fc34d392718eee55909837c7231b6ffd93e5a9a844c4375b313", + "dst": "2GgFvqoyk9RjwVzj8tqfcXVXB4orBwoc9qv", + "coins": "162643.000000", + "hours": 34596 + }, + { + "uxid": "349b06e5707f633fd2d8f048b687b40462d875d968b246831434fb5ab5dcac38", + "dst": "WzPDgdfL1NzSbX96tscUNXUqtCRLjaBugC", + "coins": "125.000000", + "hours": 34596 + } + ] + } + } +] +``` + +The transaction data is wrapped in a `"txn"` field. A `"time"` field is present at the top level. This `"time"` field +is either the confirmation timestamp of a confirmed transaction or the last received time of an unconfirmed transaction. diff --git a/src/api/address.go b/src/api/address.go index 1efa0c26fb..adb28bbfeb 100644 --- a/src/api/address.go +++ b/src/api/address.go @@ -27,7 +27,7 @@ func addressVerifyHandler(w http.ResponseWriter, r *http.Request) { return } - if r.Header.Get("Content-Type") != "application/json" { + if r.Header.Get("Content-Type") != ContentTypeJSON { resp := NewHTTPErrorResponse(http.StatusUnsupportedMediaType, "") writeHTTPResponse(w, resp) return diff --git a/src/api/address_test.go b/src/api/address_test.go index 05f7a7c6e5..7950088399 100644 --- a/src/api/address_test.go +++ b/src/api/address_test.go @@ -1,10 +1,10 @@ package api import ( - "bytes" "encoding/json" "net/http" "net/http/httptest" + "strings" "testing" "github.com/stretchr/testify/require" @@ -36,7 +36,7 @@ func TestVerifyAddress(t *testing.T) { { name: "415 - Unsupported Media Type", method: http.MethodPost, - contentType: "application/x-www-form-urlencoded", + contentType: ContentTypeForm, status: http.StatusUnsupportedMediaType, httpResponse: NewHTTPErrorResponse(http.StatusUnsupportedMediaType, ""), }, @@ -44,7 +44,7 @@ func TestVerifyAddress(t *testing.T) { { name: "400 - EOF", method: http.MethodPost, - contentType: "application/json", + contentType: ContentTypeJSON, status: http.StatusBadRequest, httpResponse: NewHTTPErrorResponse(http.StatusBadRequest, "EOF"), }, @@ -52,7 +52,7 @@ func TestVerifyAddress(t *testing.T) { { name: "400 - Missing address", method: http.MethodPost, - contentType: "application/json", + contentType: ContentTypeJSON, status: http.StatusBadRequest, httpBody: "{}", httpResponse: NewHTTPErrorResponse(http.StatusBadRequest, "address is required"), @@ -101,12 +101,12 @@ func TestVerifyAddress(t *testing.T) { endpoint := "/api/v2/address/verify" gateway := &MockGatewayer{} - req, err := http.NewRequest(tc.method, endpoint, bytes.NewBufferString(tc.httpBody)) + req, err := http.NewRequest(tc.method, endpoint, strings.NewReader(tc.httpBody)) require.NoError(t, err) contentType := tc.contentType if contentType == "" { - contentType = "application/json" + contentType = ContentTypeJSON } req.Header.Set("Content-Type", contentType) diff --git a/src/api/blockchain.go b/src/api/blockchain.go index 80f6d2db41..8155127b10 100644 --- a/src/api/blockchain.go +++ b/src/api/blockchain.go @@ -197,7 +197,7 @@ func blockHandler(gateway Gatewayer) http.HandlerFunc { // or an explicit list of sequences. // If using start and end, the block sequences include both the start and end point. // Explicit sequences cannot be combined with start and end. -// Method: GET +// Method: GET, POST // URI: /api/v1/blocks // Args: // start [int] @@ -206,7 +206,7 @@ func blockHandler(gateway Gatewayer) http.HandlerFunc { // verbose [bool] func blocksHandler(gateway Gatewayer) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { + if r.Method != http.MethodGet && r.Method != http.MethodPost { wh.Error405(w) return } diff --git a/src/api/blockchain_test.go b/src/api/blockchain_test.go index afc0bc0327..54c8c4aa9b 100644 --- a/src/api/blockchain_test.go +++ b/src/api/blockchain_test.go @@ -1,6 +1,7 @@ package api import ( + "io" "net/http" "net/http/httptest" "net/url" @@ -553,7 +554,7 @@ func TestGetBlock(t *testing.T) { req, err := http.NewRequest(tc.method, endpoint, nil) require.NoError(t, err) - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + req.Header.Add("Content-Type", ContentTypeForm) csrfStore := &CSRFStore{ Enabled: true, @@ -622,7 +623,7 @@ func TestGetBlocks(t *testing.T) { }{ { name: "405", - method: http.MethodPost, + method: http.MethodDelete, status: http.StatusMethodNotAllowed, err: "405 Method Not Allowed", }, @@ -915,6 +916,32 @@ func TestGetBlocks(t *testing.T) { }, }, }, + + { + name: "200 seqs POST", + method: http.MethodPost, + status: http.StatusOK, + body: &httpBody{ + Seqs: "1,2,3", + }, + seqs: []uint64{1, 2, 3}, + gatewayGetBlocksResult: []coin.SignedBlock{{}}, + response: &readable.Blocks{ + Blocks: []readable.Block{ + readable.Block{ + Head: readable.BlockHeader{ + Hash: "7b8ec8dd836b564f0c85ad088fc744de820345204e154bc1503e04e9d6fdd9f1", + PreviousHash: "0000000000000000000000000000000000000000000000000000000000000000", + BodyHash: "0000000000000000000000000000000000000000000000000000000000000000", + UxHash: "0000000000000000000000000000000000000000000000000000000000000000", + }, + Body: readable.BlockBody{ + Transactions: []readable.Transaction{}, + }, + }, + }, + }, + }, } for _, tc := range tt { @@ -944,13 +971,23 @@ func TestGetBlocks(t *testing.T) { v.Add("seqs", tc.body.Seqs) } } + + var reqBody io.Reader if len(v) > 0 { - endpoint += "?" + v.Encode() + if tc.method == http.MethodPost { + reqBody = strings.NewReader(v.Encode()) + } else { + endpoint += "?" + v.Encode() + } } - req, err := http.NewRequest(tc.method, endpoint, nil) + req, err := http.NewRequest(tc.method, endpoint, reqBody) require.NoError(t, err) + if tc.method == http.MethodPost { + req.Header.Set("Content-Type", ContentTypeForm) + } + csrfStore := &CSRFStore{ Enabled: true, } diff --git a/src/api/client.go b/src/api/client.go index c61646d58e..1d668f8875 100644 --- a/src/api/client.go +++ b/src/api/client.go @@ -15,6 +15,7 @@ import ( "time" "github.com/skycoin/skycoin/src/coin" + "github.com/skycoin/skycoin/src/daemon" "github.com/skycoin/skycoin/src/readable" ) @@ -22,6 +23,11 @@ const ( dialTimeout = 60 * time.Second httpClientTimeout = 120 * time.Second tlsHandshakeTimeout = 60 * time.Second + + // ContentTypeJSON json content type header + ContentTypeJSON = "application/json" + // ContentTypeForm form data content type header + ContentTypeForm = "application/x-www-form-urlencoded" ) // ClientError is used for non-200 API responses @@ -136,9 +142,9 @@ func (c *Client) get(endpoint string) (*http.Response, error) { return c.HTTPClient.Do(req) } -// PostForm makes a POST request to an endpoint with body of "application/x-www-form-urlencoded" formated data. +// PostForm makes a POST request to an endpoint with body of ContentTypeForm formated data. func (c *Client) PostForm(endpoint string, body io.Reader, obj interface{}) error { - return c.post(endpoint, "application/x-www-form-urlencoded", body, obj) + return c.Post(endpoint, ContentTypeForm, body, obj) } // PostJSON makes a POST request to an endpoint with body of json data. @@ -148,11 +154,11 @@ func (c *Client) PostJSON(endpoint string, reqObj, respObj interface{}) error { return err } - return c.post(endpoint, "application/json", bytes.NewReader(body), respObj) + return c.Post(endpoint, ContentTypeJSON, bytes.NewReader(body), respObj) } -// post makes a POST request to an endpoint. -func (c *Client) post(endpoint string, contentType string, body io.Reader, obj interface{}) error { +// Post makes a POST request to an endpoint. +func (c *Client) Post(endpoint string, contentType string, body io.Reader, obj interface{}) error { csrf, err := c.CSRF() if err != nil { return err @@ -226,8 +232,8 @@ func (c *Client) PostJSONV2(endpoint string, reqObj, respObj interface{}) (bool, req.Header.Set(CSRFHeaderName, csrf) } - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Accept", "application/json") + req.Header.Set("Content-Type", ContentTypeJSON) + req.Header.Set("Accept", ContentTypeJSON) resp, err := c.HTTPClient.Do(req) if err != nil { @@ -333,27 +339,28 @@ func (c *Client) Outputs() (*readable.UnspentOutputsSummary, error) { return &o, nil } -// OutputsForAddresses makes a request to GET /api/v1/outputs?addrs=xxx +// OutputsForAddresses makes a request to POST /api/v1/outputs?addrs=xxx func (c *Client) OutputsForAddresses(addrs []string) (*readable.UnspentOutputsSummary, error) { v := url.Values{} v.Add("addrs", strings.Join(addrs, ",")) - endpoint := "/api/v1/outputs?" + v.Encode() + + endpoint := "/api/v1/outputs" var o readable.UnspentOutputsSummary - if err := c.Get(endpoint, &o); err != nil { + if err := c.PostForm(endpoint, strings.NewReader(v.Encode()), &o); err != nil { return nil, err } return &o, nil } -// OutputsForHashes makes a request to GET /api/v1/outputs?hashes=zzz +// OutputsForHashes makes a request to POST /api/v1/outputs?hashes=zzz func (c *Client) OutputsForHashes(hashes []string) (*readable.UnspentOutputsSummary, error) { v := url.Values{} v.Add("hashes", strings.Join(hashes, ",")) - endpoint := "/api/v1/outputs?" + v.Encode() + endpoint := "/api/v1/outputs" var o readable.UnspentOutputsSummary - if err := c.Get(endpoint, &o); err != nil { + if err := c.PostForm(endpoint, strings.NewReader(v.Encode()), &o); err != nil { return nil, err } return &o, nil @@ -422,7 +429,7 @@ func (c *Client) BlockBySeqVerbose(seq uint64) (*readable.BlockVerbose, error) { return &b, nil } -// Blocks makes a request to GET /api/v1/blocks?seqs= +// Blocks makes a request to POST /api/v1/blocks?seqs= func (c *Client) Blocks(seqs []uint64) (*readable.Blocks, error) { sSeqs := make([]string, len(seqs)) for i, x := range seqs { @@ -431,16 +438,16 @@ func (c *Client) Blocks(seqs []uint64) (*readable.Blocks, error) { v := url.Values{} v.Add("seqs", strings.Join(sSeqs, ",")) - endpoint := "/api/v1/blocks?" + v.Encode() + endpoint := "/api/v1/blocks" var b readable.Blocks - if err := c.Get(endpoint, &b); err != nil { + if err := c.PostForm(endpoint, strings.NewReader(v.Encode()), &b); err != nil { return nil, err } return &b, nil } -// BlocksVerbose makes a request to GET /api/v1/blocks?verbose=1&start=&end= +// BlocksVerbose makes a request to POST /api/v1/blocks?verbose=1&seqs= func (c *Client) BlocksVerbose(seqs []uint64) (*readable.BlocksVerbose, error) { sSeqs := make([]string, len(seqs)) for i, x := range seqs { @@ -450,10 +457,10 @@ func (c *Client) BlocksVerbose(seqs []uint64) (*readable.BlocksVerbose, error) { v := url.Values{} v.Add("seqs", strings.Join(sSeqs, ",")) v.Add("verbose", "1") - endpoint := "/api/v1/blocks?" + v.Encode() + endpoint := "/api/v1/blocks" var b readable.BlocksVerbose - if err := c.Get(endpoint, &b); err != nil { + if err := c.PostForm(endpoint, strings.NewReader(v.Encode()), &b); err != nil { return nil, err } return &b, nil @@ -533,14 +540,14 @@ func (c *Client) BlockchainProgress() (*readable.BlockchainProgress, error) { return &b, nil } -// Balance makes a request to GET /api/v1/balance?addrs=xxx +// Balance makes a request to POST /api/v1/balance?addrs=xxx func (c *Client) Balance(addrs []string) (*BalanceResponse, error) { v := url.Values{} v.Add("addrs", strings.Join(addrs, ",")) - endpoint := "/api/v1/balance?" + v.Encode() + endpoint := "/api/v1/balance" var b BalanceResponse - if err := c.Get(endpoint, &b); err != nil { + if err := c.PostForm(endpoint, strings.NewReader(v.Encode()), &b); err != nil { return nil, err } return &b, nil @@ -819,17 +826,40 @@ func (c *Client) NetworkConnection(addr string) (*readable.Connection, error) { return &dc, nil } -// NetworkConnections makes a request to GET /api/v1/network/connections -func (c *Client) NetworkConnections() (*Connections, error) { +// NetworkConnectionsFilter filters for network connections +type NetworkConnectionsFilter struct { + States []daemon.ConnectionState // "pending", "connected" and "introduced" + Direction string // "incoming" or "outgoing" +} + +// NetworkConnections makes a request to GET /api/v1/network/connections. +// Connections can be filtered by state and direction. By default, "connected" and "introduced" connections +// of both directions are returned. +func (c *Client) NetworkConnections(filters *NetworkConnectionsFilter) (*Connections, error) { + v := url.Values{} + if filters != nil { + if len(filters.States) != 0 { + states := make([]string, len(filters.States)) + for i, s := range filters.States { + states[i] = string(s) + } + v.Add("states", strings.Join(states, ",")) + } + if filters.Direction != "" { + v.Add("direction", filters.Direction) + } + } + endpoint := "/api/v1/network/connections?" + v.Encode() + var dc Connections - if err := c.Get("/api/v1/network/connections", &dc); err != nil { + if err := c.Get(endpoint, &dc); err != nil { return nil, err } return &dc, nil } -// NetworkDefaultConnections makes a request to GET /api/v1/network/defaultConnections -func (c *Client) NetworkDefaultConnections() ([]string, error) { +// NetworkDefaultPeers makes a request to GET /api/v1/network/defaultConnections +func (c *Client) NetworkDefaultPeers() ([]string, error) { var dc []string if err := c.Get("/api/v1/network/defaultConnections", &dc); err != nil { return nil, err @@ -837,8 +867,8 @@ func (c *Client) NetworkDefaultConnections() ([]string, error) { return dc, nil } -// NetworkTrustedConnections makes a request to GET /api/v1/network/connections/trust -func (c *Client) NetworkTrustedConnections() ([]string, error) { +// NetworkTrustedPeers makes a request to GET /api/v1/network/connections/trust +func (c *Client) NetworkTrustedPeers() ([]string, error) { var dc []string if err := c.Get("/api/v1/network/connections/trust", &dc); err != nil { return nil, err @@ -846,8 +876,8 @@ func (c *Client) NetworkTrustedConnections() ([]string, error) { return dc, nil } -// NetworkExchangeableConnections makes a request to GET /api/v1/network/connections/exchange -func (c *Client) NetworkExchangeableConnections() ([]string, error) { +// NetworkExchangedPeers makes a request to GET /api/v1/network/connections/exchange +func (c *Client) NetworkExchangedPeers() ([]string, error) { var dc []string if err := c.Get("/api/v1/network/connections/exchange", &dc); err != nil { return nil, err @@ -914,86 +944,86 @@ func (c *Client) TransactionEncoded(txid string) (*TransactionEncodedResponse, e return &r, nil } -// Transactions makes a request to GET /api/v1/transactions +// Transactions makes a request to POST /api/v1/transactions func (c *Client) Transactions(addrs []string) ([]readable.TransactionWithStatus, error) { v := url.Values{} v.Add("addrs", strings.Join(addrs, ",")) - endpoint := "/api/v1/transactions?" + v.Encode() + endpoint := "/api/v1/transactions" var r []readable.TransactionWithStatus - if err := c.Get(endpoint, &r); err != nil { + if err := c.PostForm(endpoint, strings.NewReader(v.Encode()), &r); err != nil { return nil, err } return r, nil } -// ConfirmedTransactions makes a request to GET /api/v1/transactions?confirmed=true +// ConfirmedTransactions makes a request to POST /api/v1/transactions?confirmed=true func (c *Client) ConfirmedTransactions(addrs []string) ([]readable.TransactionWithStatus, error) { v := url.Values{} v.Add("addrs", strings.Join(addrs, ",")) v.Add("confirmed", "true") - endpoint := "/api/v1/transactions?" + v.Encode() + endpoint := "/api/v1/transactions" var r []readable.TransactionWithStatus - if err := c.Get(endpoint, &r); err != nil { + if err := c.PostForm(endpoint, strings.NewReader(v.Encode()), &r); err != nil { return nil, err } return r, nil } -// UnconfirmedTransactions makes a request to GET /api/v1/transactions?confirmed=false +// UnconfirmedTransactions makes a request to POST /api/v1/transactions?confirmed=false func (c *Client) UnconfirmedTransactions(addrs []string) ([]readable.TransactionWithStatus, error) { v := url.Values{} v.Add("addrs", strings.Join(addrs, ",")) v.Add("confirmed", "false") - endpoint := "/api/v1/transactions?" + v.Encode() + endpoint := "/api/v1/transactions" var r []readable.TransactionWithStatus - if err := c.Get(endpoint, &r); err != nil { + if err := c.PostForm(endpoint, strings.NewReader(v.Encode()), &r); err != nil { return nil, err } return r, nil } -// TransactionsVerbose makes a request to GET /api/v1/transactions?verbose=1 +// TransactionsVerbose makes a request to POST /api/v1/transactions?verbose=1 func (c *Client) TransactionsVerbose(addrs []string) ([]readable.TransactionWithStatusVerbose, error) { v := url.Values{} v.Add("addrs", strings.Join(addrs, ",")) v.Add("verbose", "1") - endpoint := "/api/v1/transactions?" + v.Encode() + endpoint := "/api/v1/transactions" var r []readable.TransactionWithStatusVerbose - if err := c.Get(endpoint, &r); err != nil { + if err := c.PostForm(endpoint, strings.NewReader(v.Encode()), &r); err != nil { return nil, err } return r, nil } -// ConfirmedTransactionsVerbose makes a request to GET /api/v1/transactions?confirmed=true&verbose=1 +// ConfirmedTransactionsVerbose makes a request to POST /api/v1/transactions?confirmed=true&verbose=1 func (c *Client) ConfirmedTransactionsVerbose(addrs []string) ([]readable.TransactionWithStatusVerbose, error) { v := url.Values{} v.Add("addrs", strings.Join(addrs, ",")) v.Add("confirmed", "true") v.Add("verbose", "1") - endpoint := "/api/v1/transactions?" + v.Encode() + endpoint := "/api/v1/transactions" var r []readable.TransactionWithStatusVerbose - if err := c.Get(endpoint, &r); err != nil { + if err := c.PostForm(endpoint, strings.NewReader(v.Encode()), &r); err != nil { return nil, err } return r, nil } -// UnconfirmedTransactionsVerbose makes a request to GET /api/v1/transactions?confirmed=false&verbose=1 +// UnconfirmedTransactionsVerbose makes a request to POST /api/v1/transactions?confirmed=false&verbose=1 func (c *Client) UnconfirmedTransactionsVerbose(addrs []string) ([]readable.TransactionWithStatusVerbose, error) { v := url.Values{} v.Add("addrs", strings.Join(addrs, ",")) v.Add("confirmed", "false") v.Add("verbose", "1") - endpoint := "/api/v1/transactions?" + v.Encode() + endpoint := "/api/v1/transactions" var r []readable.TransactionWithStatusVerbose - if err := c.Get(endpoint, &r); err != nil { + if err := c.PostForm(endpoint, strings.NewReader(v.Encode()), &r); err != nil { return nil, err } return r, nil @@ -1022,10 +1052,11 @@ func (c *Client) InjectEncodedTransaction(rawTxn string) (string, error) { return txid, nil } -// ResendUnconfirmedTransactions makes a request to GET /api/v1/resendUnconfirmedTxns +// ResendUnconfirmedTransactions makes a request to POST /api/v1/resendUnconfirmedTxns func (c *Client) ResendUnconfirmedTransactions() (*ResendResult, error) { + endpoint := "/api/v1/resendUnconfirmedTxns" var r ResendResult - if err := c.Get("/api/v1/resendUnconfirmedTxns", &r); err != nil { + if err := c.PostForm(endpoint, strings.NewReader(""), &r); err != nil { return nil, err } return &r, nil @@ -1186,3 +1217,12 @@ func (c *Client) RecoverWallet(id, seed, password string) (*WalletResponse, erro return nil, err } + +// Disconnect disconnect a connections by ID +func (c *Client) Disconnect(id uint64) error { + v := url.Values{} + v.Add("id", fmt.Sprint(id)) + + var obj struct{} + return c.PostForm("/api/v1/network/connection/disconnect", strings.NewReader(v.Encode()), &obj) +} diff --git a/src/api/csrf_test.go b/src/api/csrf_test.go index 7e68ef6e63..d02cd744af 100644 --- a/src/api/csrf_test.go +++ b/src/api/csrf_test.go @@ -1,7 +1,6 @@ package api import ( - "bytes" "encoding/json" "fmt" "net/http" @@ -109,9 +108,9 @@ func TestCSRF(t *testing.T) { v.Add("id", "fooid") v.Add("label", "foolabel") - req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBufferString(v.Encode())) + req, err := http.NewRequest(http.MethodPost, endpoint, strings.NewReader(v.Encode())) require.NoError(t, err) - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + req.Header.Add("Content-Type", ContentTypeForm) if csrfToken != "" { req.Header.Set("X-CSRF-Token", csrfToken) diff --git a/src/api/explorer.go b/src/api/explorer.go index 33085fe4a5..3fd70e9885 100644 --- a/src/api/explorer.go +++ b/src/api/explorer.go @@ -7,10 +7,10 @@ import ( "github.com/skycoin/skycoin/src/cipher" "github.com/skycoin/skycoin/src/coin" + "github.com/skycoin/skycoin/src/params" "github.com/skycoin/skycoin/src/readable" "github.com/skycoin/skycoin/src/util/droplet" wh "github.com/skycoin/skycoin/src/util/http" - "github.com/skycoin/skycoin/src/visor" ) // CoinSupply records the coin supply info @@ -57,7 +57,7 @@ func coinSupplyHandler(gateway Gatewayer) http.HandlerFunc { return } - unlockedAddrs := visor.GetUnlockedDistributionAddresses() + unlockedAddrs := params.GetUnlockedDistributionAddresses() // Search map of unlocked addresses // used to filter unspents unlockedAddrSet := newStringSet(unlockedAddrs) @@ -78,8 +78,8 @@ func coinSupplyHandler(gateway Gatewayer) http.HandlerFunc { } // "total supply" is the number of coins unlocked. - // Each distribution address was allocated visor.DistributionAddressInitialBalance coins. - totalSupply := uint64(len(unlockedAddrs)) * visor.DistributionAddressInitialBalance + // Each distribution address was allocated params.DistributionAddressInitialBalance coins. + totalSupply := uint64(len(unlockedAddrs)) * params.DistributionAddressInitialBalance totalSupply *= droplet.Multiplier // "current supply" is the number of coins distributed from the unlocked pool @@ -99,7 +99,7 @@ func coinSupplyHandler(gateway Gatewayer) http.HandlerFunc { return } - maxSupplyStr, err := droplet.ToString(visor.MaxCoinSupply * droplet.Multiplier) + maxSupplyStr, err := droplet.ToString(params.MaxCoinSupply * droplet.Multiplier) if err != nil { err = fmt.Errorf("Failed to convert coins to string: %v", err) wh.Error500(w, err.Error()) @@ -107,7 +107,7 @@ func coinSupplyHandler(gateway Gatewayer) http.HandlerFunc { } // locked distribution addresses - lockedAddrs := visor.GetLockedDistributionAddresses() + lockedAddrs := params.GetLockedDistributionAddresses() lockedAddrSet := newStringSet(lockedAddrs) // get total coins hours which excludes locked distribution addresses @@ -149,7 +149,7 @@ func coinSupplyHandler(gateway Gatewayer) http.HandlerFunc { CurrentCoinHourSupply: strconv.FormatUint(currentCoinHours, 10), TotalCoinHourSupply: strconv.FormatUint(totalCoinHours, 10), UnlockedAddresses: unlockedAddrs, - LockedAddresses: visor.GetLockedDistributionAddresses(), + LockedAddresses: params.GetLockedDistributionAddresses(), } wh.SendJSONOr500(logger, w, cs) @@ -163,6 +163,8 @@ func coinSupplyHandler(gateway Gatewayer) http.HandlerFunc { // address [string] func transactionsForAddressHandler(gateway Gatewayer) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { + logger.Critical().Warning("Call to deprecated /api/v1/explorer/address endpoint") + if r.Method != http.MethodGet { wh.Error405(w) return diff --git a/src/api/explorer_test.go b/src/api/explorer_test.go index 88f35764ed..100c2398a1 100644 --- a/src/api/explorer_test.go +++ b/src/api/explorer_test.go @@ -15,6 +15,7 @@ import ( "github.com/skycoin/skycoin/src/cipher" "github.com/skycoin/skycoin/src/coin" + "github.com/skycoin/skycoin/src/params" "github.com/skycoin/skycoin/src/readable" "github.com/skycoin/skycoin/src/testutil" "github.com/skycoin/skycoin/src/util/droplet" @@ -22,7 +23,7 @@ import ( ) func makeSuccessCoinSupplyResult(t *testing.T, allUnspents readable.UnspentOutputsSummary) *CoinSupply { - unlockedAddrs := visor.GetUnlockedDistributionAddresses() + unlockedAddrs := params.GetUnlockedDistributionAddresses() var unlockedSupply uint64 // check confirmed unspents only // Search map of unlocked addresses @@ -37,8 +38,8 @@ func makeSuccessCoinSupplyResult(t *testing.T, allUnspents readable.UnspentOutpu } } // "total supply" is the number of coins unlocked. - // Each distribution address was allocated visor.DistributionAddressInitialBalance coins. - totalSupply := uint64(len(unlockedAddrs)) * visor.DistributionAddressInitialBalance + // Each distribution address was allocated params.DistributionAddressInitialBalance coins. + totalSupply := uint64(len(unlockedAddrs)) * params.DistributionAddressInitialBalance totalSupply *= droplet.Multiplier // "current supply" is the number of coins distribution from the unlocked pool @@ -50,11 +51,11 @@ func makeSuccessCoinSupplyResult(t *testing.T, allUnspents readable.UnspentOutpu totalSupplyStr, err := droplet.ToString(totalSupply) require.NoError(t, err) - maxSupplyStr, err := droplet.ToString(visor.MaxCoinSupply * droplet.Multiplier) + maxSupplyStr, err := droplet.ToString(params.MaxCoinSupply * droplet.Multiplier) require.NoError(t, err) // locked distribution addresses - lockedAddrs := visor.GetLockedDistributionAddresses() + lockedAddrs := params.GetLockedDistributionAddresses() lockedAddrSet := newStringSet(lockedAddrs) // get total coins hours which excludes locked distribution addresses @@ -84,7 +85,7 @@ func makeSuccessCoinSupplyResult(t *testing.T, allUnspents readable.UnspentOutpu CurrentCoinHourSupply: strconv.FormatUint(currentCoinHours, 10), TotalCoinHourSupply: strconv.FormatUint(totalCoinHours, 10), UnlockedAddresses: unlockedAddrs, - LockedAddresses: visor.GetLockedDistributionAddresses(), + LockedAddresses: params.GetLockedDistributionAddresses(), } return &cs } @@ -247,7 +248,7 @@ func TestGetTransactionsForAddress(t *testing.T) { } func TestCoinSupply(t *testing.T) { - unlockedAddrs := visor.GetUnlockedDistributionAddresses() + unlockedAddrs := params.GetUnlockedDistributionAddresses() successGatewayGetUnspentOutputsResult := readable.UnspentOutputsSummary{ HeadOutputs: readable.UnspentOutputs{ readable.UnspentOutput{ diff --git a/src/api/gateway.go b/src/api/gateway.go index 5bb56dd8c9..8c5f0f72f8 100644 --- a/src/api/gateway.go +++ b/src/api/gateway.go @@ -44,7 +44,8 @@ type Gatewayer interface { GetBlockchainMetadata() (*visor.BlockchainMetadata, error) GetBlockchainProgress() (*daemon.BlockchainProgress, error) GetConnection(addr string) (*daemon.Connection, error) - GetOutgoingConnections() ([]daemon.Connection, error) + GetConnections(f func(c daemon.Connection) bool) ([]daemon.Connection, error) + Disconnect(id uint64) error GetDefaultConnections() []string GetTrustConnections() []string GetExchgConnection() []string diff --git a/src/api/health.go b/src/api/health.go index f08559971e..e5731d3c6d 100644 --- a/src/api/health.go +++ b/src/api/health.go @@ -5,6 +5,7 @@ import ( "net/http" "time" + "github.com/skycoin/skycoin/src/params" "github.com/skycoin/skycoin/src/readable" wh "github.com/skycoin/skycoin/src/util/http" ) @@ -19,7 +20,11 @@ type BlockchainMetadata struct { type HealthResponse struct { BlockchainMetadata BlockchainMetadata `json:"blockchain"` Version readable.BuildInfo `json:"version"` + CoinName string `json:"coin"` + DaemonUserAgent string `json:"user_agent"` OpenConnections int `json:"open_connections"` + OutgoingConnections int `json:"outgoing_connections"` + IncomingConnections int `json:"incoming_connections"` Uptime wh.Duration `json:"uptime"` CSRFEnabled bool `json:"csrf_enabled"` CSPEnabled bool `json:"csp_enabled"` @@ -27,6 +32,7 @@ type HealthResponse struct { GUIEnabled bool `json:"gui_enabled"` UnversionedAPIEnabled bool `json:"unversioned_api_enabled"` JSON20RPCEnabled bool `json:"json_rpc_enabled"` + BurnFactor uint64 `json:"coinhour_burn_factor"` } // healthHandler returns node health data @@ -51,13 +57,23 @@ func healthHandler(c muxConfig, csrfStore *CSRFStore, gateway Gatewayer) http.Ha _, walletAPIEnabled := c.enabledAPISets[EndpointsWallet] + userAgent, err := c.health.DaemonUserAgent.Build() + if err != nil { + wh.Error500(w, err.Error()) + return + } + wh.SendJSONOr500(logger, w, HealthResponse{ BlockchainMetadata: BlockchainMetadata{ BlockchainMetadata: readable.NewBlockchainMetadata(health.BlockchainMetadata), TimeSinceLastBlock: wh.FromDuration(timeSinceLastBlock), }, - Version: c.buildInfo, - OpenConnections: health.OpenConnections, + Version: c.health.BuildInfo, + CoinName: c.health.CoinName, + DaemonUserAgent: userAgent, + OpenConnections: health.OutgoingConnections + health.IncomingConnections, + OutgoingConnections: health.OutgoingConnections, + IncomingConnections: health.IncomingConnections, Uptime: wh.FromDuration(health.Uptime), CSRFEnabled: csrfStore.Enabled, CSPEnabled: !c.disableCSP, @@ -65,6 +81,7 @@ func healthHandler(c muxConfig, csrfStore *CSRFStore, gateway Gatewayer) http.Ha GUIEnabled: c.enableGUI, JSON20RPCEnabled: c.enableJSON20RPC, WalletAPIEnabled: walletAPIEnabled, + BurnFactor: params.UserBurnFactor, }) } } diff --git a/src/api/health_test.go b/src/api/health_test.go index 7590d0a0ae..681d15d77a 100644 --- a/src/api/health_test.go +++ b/src/api/health_test.go @@ -16,6 +16,7 @@ import ( "github.com/skycoin/skycoin/src/coin" "github.com/skycoin/skycoin/src/daemon" "github.com/skycoin/skycoin/src/readable" + "github.com/skycoin/skycoin/src/util/useragent" "github.com/skycoin/skycoin/src/visor" ) @@ -113,12 +114,20 @@ func TestHealthHandler(t *testing.T) { Commit: "abcdef", Branch: "develop", } - tc.cfg.buildInfo = buildInfo + tc.cfg.health.BuildInfo = buildInfo + + tc.cfg.health.CoinName = "skycoin" + tc.cfg.health.DaemonUserAgent = useragent.Data{ + Coin: "skycoin", + Version: "0.25.0", + Remark: "test", + } health := &daemon.Health{ - BlockchainMetadata: metadata, - OpenConnections: 3, - Uptime: time.Second * 4, + BlockchainMetadata: metadata, + OutgoingConnections: 3, + IncomingConnections: 2, + Uptime: time.Second * 4, } gateway := &MockGatewayer{} @@ -157,7 +166,11 @@ func TestHealthHandler(t *testing.T) { require.Equal(t, buildInfo.Branch, r.Version.Branch) require.Equal(t, health.Uptime, r.Uptime.Duration) - require.Equal(t, health.OpenConnections, r.OpenConnections) + require.Equal(t, health.OutgoingConnections, r.OutgoingConnections) + require.Equal(t, health.IncomingConnections, r.IncomingConnections) + require.Equal(t, health.OutgoingConnections+health.IncomingConnections, r.OpenConnections) + require.Equal(t, "skycoin", r.CoinName) + require.Equal(t, "skycoin:0.25.0(test)", r.DaemonUserAgent) require.Equal(t, unconfirmed, r.BlockchainMetadata.Unconfirmed) require.Equal(t, unspents, r.BlockchainMetadata.Unspents) @@ -177,6 +190,8 @@ func TestHealthHandler(t *testing.T) { require.Equal(t, tc.cfg.enableGUI, r.GUIEnabled) require.Equal(t, tc.cfg.enableJSON20RPC, r.JSON20RPCEnabled) require.Equal(t, tc.walletAPIEnabled, r.WalletAPIEnabled) + + require.Equal(t, uint64(0x2), r.BurnFactor) }) } } diff --git a/src/api/http.go b/src/api/http.go index 82aa2cb244..2f4090ffb1 100644 --- a/src/api/http.go +++ b/src/api/http.go @@ -25,6 +25,7 @@ import ( "github.com/skycoin/skycoin/src/util/file" wh "github.com/skycoin/skycoin/src/util/http" "github.com/skycoin/skycoin/src/util/logging" + "github.com/skycoin/skycoin/src/util/useragent" ) var ( @@ -57,6 +58,8 @@ const ( EndpointsDeprecatedWalletSpend = "DEPRECATED_WALLET_SPEND" // EndpointsPrometheus endpoints for Go application metrics EndpointsPrometheus = "PROMETHEUS" + // EndpointsNetCtrl endpoints for managing network connections + EndpointsNetCtrl = "NET_CTRL" ) // Server exposes an HTTP API @@ -77,13 +80,20 @@ type Config struct { ReadTimeout time.Duration WriteTimeout time.Duration IdleTimeout time.Duration - BuildInfo readable.BuildInfo + Health HealthConfig HostWhitelist []string EnabledAPISets map[string]struct{} Username string Password string } +// HealthConfig configuration data exposed in /health +type HealthConfig struct { + BuildInfo readable.BuildInfo + CoinName string + DaemonUserAgent useragent.Data +} + type muxConfig struct { host string appLoc string @@ -91,11 +101,11 @@ type muxConfig struct { enableJSON20RPC bool enableUnversionedAPI bool disableCSP bool - buildInfo readable.BuildInfo enabledAPISets map[string]struct{} hostWhitelist []string username string password string + health HealthConfig } // HTTPResponse represents the http response struct @@ -131,7 +141,7 @@ func writeHTTPResponse(w http.ResponseWriter, resp HTTPResponse) { return } - w.Header().Add("Content-Type", "application/json") + w.Header().Add("Content-Type", ContentTypeJSON) if resp.Error == nil { w.WriteHeader(http.StatusOK) @@ -195,7 +205,7 @@ func create(host string, c Config, gateway Gatewayer) (*Server, error) { enableJSON20RPC: c.EnableJSON20RPC, enableUnversionedAPI: c.EnableUnversionedAPI, disableCSP: c.DisableCSP, - buildInfo: c.BuildInfo, + health: c.Health, enabledAPISets: c.EnabledAPISets, hostWhitelist: c.HostWhitelist, username: c.Username, @@ -208,6 +218,7 @@ func create(host string, c Config, gateway Gatewayer) (*Server, error) { ReadTimeout: c.ReadTimeout, WriteTimeout: c.WriteTimeout, IdleTimeout: c.IdleTimeout, + // MaxHeaderBytes: http.DefaultMaxHeaderBytes, // adjust this to allow longer GET queries } return &Server{ @@ -427,7 +438,7 @@ func newServerMux(c muxConfig, gateway Gatewayer, csrfStore *CSRFStore, rpc *web csrfHandlerV1("/csrf", getCSRFToken(csrfStore)) // csrf is always available, regardless of the API set // Status endpoints - webHandlerV1("/version", versionHandler(c.buildInfo)) // version is always available, regardless of the API set + webHandlerV1("/version", versionHandler(c.health.BuildInfo)) // version is always available, regardless of the API set webHandlerV1("/health", forAPISet(healthHandler(c, csrfStore, gateway), []string{EndpointsRead, EndpointsStatus})) // Wallet endpoints @@ -463,13 +474,16 @@ func newServerMux(c muxConfig, gateway Gatewayer, csrfStore *CSRFStore, rpc *web webHandlerV1("/network/connections/trust", forAPISet(trustConnectionsHandler(gateway), []string{EndpointsRead, EndpointsStatus})) webHandlerV1("/network/connections/exchange", forAPISet(exchgConnectionsHandler(gateway), []string{EndpointsRead, EndpointsStatus})) + // Network admin endpoints + webHandlerV1("/network/connection/disconnect", forAPISet(disconnectHandler(gateway), []string{EndpointsNetCtrl})) + // Transaction related endpoints webHandlerV1("/pendingTxs", forAPISet(pendingTxnsHandler(gateway), []string{EndpointsRead})) webHandlerV1("/transaction", forAPISet(transactionHandler(gateway), []string{EndpointsRead})) webHandlerV2("/transaction/verify", forAPISet(verifyTxnHandler(gateway), []string{EndpointsRead})) webHandlerV1("/transactions", forAPISet(transactionsHandler(gateway), []string{EndpointsRead})) webHandlerV1("/injectTransaction", forAPISet(injectTransactionHandler(gateway), []string{EndpointsTransaction, EndpointsWallet})) - webHandlerV1("/resendUnconfirmedTxns", forAPISet(resendUnconfirmedTxnsHandler(gateway), []string{EndpointsRead})) + webHandlerV1("/resendUnconfirmedTxns", forAPISet(resendUnconfirmedTxnsHandler(gateway), []string{EndpointsTransaction})) webHandlerV1("/rawtx", forAPISet(rawTxnHandler(gateway), []string{EndpointsRead})) // Unspent output related endpoints diff --git a/src/api/http_test.go b/src/api/http_test.go index 357b4cc41b..2179913a56 100644 --- a/src/api/http_test.go +++ b/src/api/http_test.go @@ -21,6 +21,8 @@ var allAPISetsEnabled = map[string]struct{}{ EndpointsWallet: struct{}{}, EndpointsInsecureWalletSeed: struct{}{}, EndpointsDeprecatedWalletSpend: struct{}{}, + EndpointsPrometheus: struct{}{}, + EndpointsNetCtrl: struct{}{}, } func defaultMuxConfig() muxConfig { @@ -47,6 +49,7 @@ var endpoints = []string{ "/last_blocks", "/version", "/network/connection", + "/network/connection/disconnect", "/network/connections", "/network/connections/exchange", "/network/connections/trust", diff --git a/src/api/integration/integration_test.go b/src/api/integration/integration_test.go index 1fbdb9727b..0fd819501a 100644 --- a/src/api/integration/integration_test.go +++ b/src/api/integration/integration_test.go @@ -27,10 +27,13 @@ import ( "github.com/skycoin/skycoin/src/api" "github.com/skycoin/skycoin/src/cipher" "github.com/skycoin/skycoin/src/coin" + "github.com/skycoin/skycoin/src/daemon" + "github.com/skycoin/skycoin/src/params" "github.com/skycoin/skycoin/src/readable" "github.com/skycoin/skycoin/src/testutil" "github.com/skycoin/skycoin/src/util/droplet" "github.com/skycoin/skycoin/src/util/fee" + "github.com/skycoin/skycoin/src/util/useragent" "github.com/skycoin/skycoin/src/visor" "github.com/skycoin/skycoin/src/wallet" ) @@ -428,7 +431,7 @@ func TestStableVerifyTransaction(t *testing.T) { txn: badSignatureTxn, golden: "verify-transaction-invalid-bad-sig.golden", errCode: http.StatusUnprocessableEntity, - errMsg: "Transaction violates hard constraint: Invalid sig: invalid for hash", + errMsg: "Transaction violates hard constraint: Signature not valid for hash", }, } @@ -1804,7 +1807,7 @@ func TestStableNetworkConnections(t *testing.T) { } c := newClient() - connections, err := c.NetworkConnections() + connections, err := c.NetworkConnections(nil) require.NoError(t, err) require.Empty(t, connections.Connections) @@ -1819,7 +1822,7 @@ func TestLiveNetworkConnections(t *testing.T) { } c := newClient() - connections, err := c.NetworkConnections() + connections, err := c.NetworkConnections(nil) require.NoError(t, err) if liveDisableNetworking(t) { @@ -1829,18 +1832,68 @@ func TestLiveNetworkConnections(t *testing.T) { require.NotEmpty(t, connections.Connections) + checked := false + for _, cc := range connections.Connections { connection, err := c.NetworkConnection(cc.Addr) + + // The connection may have disconnected by now + if err != nil { + assertResponseError(t, err, http.StatusNotFound, "404 Not Found") + continue + } + require.NoError(t, err) require.NotEmpty(t, cc.Addr) require.Equal(t, cc.Addr, connection.Addr) - require.Equal(t, cc.ID, connection.ID) + require.Equal(t, cc.GnetID, connection.GnetID) require.Equal(t, cc.ListenPort, connection.ListenPort) require.Equal(t, cc.Mirror, connection.Mirror) - require.Equal(t, cc.Introduced, connection.Introduced) + + switch cc.State { + case daemon.ConnectionStateIntroduced: + // If the connection was introduced it should stay introduced + require.Equal(t, daemon.ConnectionStateIntroduced, connection.State) + case daemon.ConnectionStateConnected: + // If the connection was connected it should stay connected or have become introduced + require.NotEqual(t, daemon.ConnectionStatePending, connection.State) + } + + // The GnetID should be 0 if pending, otherwise it should not be 0 + if cc.State == daemon.ConnectionStatePending { + require.Equal(t, uint64(0), cc.GnetID) + } else { + require.NotEmpty(t, uint64(0), cc.GnetID) + } + require.Equal(t, cc.Outgoing, connection.Outgoing) require.True(t, cc.LastReceived <= connection.LastReceived) require.True(t, cc.LastSent <= connection.LastSent) + require.Equal(t, cc.ConnectedAt, connection.ConnectedAt) + + checked = true + } + + // This could unfortunately occur if a connection disappeared in between the two calls, + // which will require a test re-run. + require.True(t, checked, "Was not able to find any connection by address, despite finding connections when querying all") + + connections, err = c.NetworkConnections(&api.NetworkConnectionsFilter{ + States: []daemon.ConnectionState{daemon.ConnectionStatePending}, + }) + require.NoError(t, err) + + for _, cc := range connections.Connections { + require.Equal(t, daemon.ConnectionStatePending, cc.State) + } + + connections, err = c.NetworkConnections(&api.NetworkConnectionsFilter{ + Direction: "incoming", + }) + require.NoError(t, err) + + for _, cc := range connections.Connections { + require.False(t, cc.Outgoing) } } @@ -1850,13 +1903,13 @@ func TestNetworkDefaultConnections(t *testing.T) { } c := newClient() - connections, err := c.NetworkDefaultConnections() + connections, err := c.NetworkDefaultPeers() require.NoError(t, err) require.NotEmpty(t, connections) sort.Strings(connections) var expected []string - checkGoldenFile(t, "network-default-connections.golden", TestData{connections, &expected}) + checkGoldenFile(t, "network-default-peers.golden", TestData{connections, &expected}) } func TestNetworkTrustedConnections(t *testing.T) { @@ -1865,13 +1918,13 @@ func TestNetworkTrustedConnections(t *testing.T) { } c := newClient() - connections, err := c.NetworkTrustedConnections() + connections, err := c.NetworkTrustedPeers() require.NoError(t, err) require.NotEmpty(t, connections) sort.Strings(connections) var expected []string - checkGoldenFile(t, "network-trusted-connections.golden", TestData{connections, &expected}) + checkGoldenFile(t, "network-trusted-peers.golden", TestData{connections, &expected}) } func TestStableNetworkExchangeableConnections(t *testing.T) { @@ -1880,11 +1933,11 @@ func TestStableNetworkExchangeableConnections(t *testing.T) { } c := newClient() - connections, err := c.NetworkExchangeableConnections() + connections, err := c.NetworkExchangedPeers() require.NoError(t, err) var expected []string - checkGoldenFile(t, "network-exchangeable-connections.golden", TestData{connections, &expected}) + checkGoldenFile(t, "network-exchanged-peers.golden", TestData{connections, &expected}) } func TestLiveNetworkExchangeableConnections(t *testing.T) { @@ -1893,7 +1946,7 @@ func TestLiveNetworkExchangeableConnections(t *testing.T) { } c := newClient() - _, err := c.NetworkExchangeableConnections() + _, err := c.NetworkExchangedPeers() require.NoError(t, err) } @@ -2902,9 +2955,11 @@ func TestStableResendUnconfirmedTransactions(t *testing.T) { return } c := newClient() - res, err := c.ResendUnconfirmedTransactions() - require.NoError(t, err) - require.True(t, len(res.Txids) == 0) + _, err := c.ResendUnconfirmedTransactions() + respErr, ok := err.(api.ClientError) + require.True(t, ok) + require.Equal(t, fmt.Sprintf("503 Service Unavailable - %s", daemon.ErrNetworkingDisabled), respErr.Message) + require.Equal(t, http.StatusServiceUnavailable, respErr.StatusCode) } func TestLiveResendUnconfirmedTransactions(t *testing.T) { @@ -3808,7 +3863,7 @@ func TestLiveWalletCreateTransactionSpecific(t *testing.T) { w, totalCoins, totalHours, password := prepareAndCheckWallet(t, c, 2e6, 20) - remainingHours := fee.RemainingHours(totalHours) + remainingHours := fee.RemainingHours(totalHours, params.UserBurnFactor) require.True(t, remainingHours > 1) addresses := make([]string, len(w.Entries)) @@ -4564,7 +4619,7 @@ func TestLiveWalletCreateTransactionRandom(t *testing.T) { return } - remainingHours := fee.RemainingHours(totalHours) + remainingHours := fee.RemainingHours(totalHours, params.UserBurnFactor) require.True(t, remainingHours > 1) assertTxnOutputCount := func(t *testing.T, changeAddress string, nOutputs int, result *api.CreateTransactionResponse) { @@ -4595,13 +4650,13 @@ func TestLiveWalletCreateTransactionRandom(t *testing.T) { tLog(t, "totalCoins", totalCoins) tLog(t, "totalHours", totalHours) - spendableHours := fee.RemainingHours(totalHours) + spendableHours := fee.RemainingHours(totalHours, params.UserBurnFactor) tLog(t, "spendableHours", spendableHours) coins := rand.Intn(int(totalCoins)) + 1 - coins -= coins % int(visor.MaxDropletDivisor()) + coins -= coins % int(params.MaxDropletDivisor()) if coins == 0 { - coins = int(visor.MaxDropletDivisor()) + coins = int(params.MaxDropletDivisor()) } hours := rand.Intn(int(spendableHours + 1)) nOutputs := rand.Intn(maxOutputs) + 1 @@ -4636,9 +4691,9 @@ func TestLiveWalletCreateTransactionRandom(t *testing.T) { remainingHours = 0 } else { receiverCoins := rand.Intn(remainingCoins) + 1 - receiverCoins -= receiverCoins % int(visor.MaxDropletDivisor()) + receiverCoins -= receiverCoins % int(params.MaxDropletDivisor()) if receiverCoins == 0 { - receiverCoins = int(visor.MaxDropletDivisor()) + receiverCoins = int(params.MaxDropletDivisor()) } var err error @@ -5719,7 +5774,7 @@ func TestDisableWalletAPI(t *testing.T) { name: "create transaction", method: http.MethodPost, endpoint: "/api/v1/wallet/transaction", - contentType: "application/json", + contentType: api.ContentTypeJSON, json: func() interface{} { return api.CreateTransactionRequest{ HoursSelection: api.HoursSelection{ @@ -5753,7 +5808,7 @@ func TestDisableWalletAPI(t *testing.T) { err = c.Get(tc.endpoint, nil) case http.MethodPost: switch tc.contentType { - case "application/json": + case api.ContentTypeJSON: err = c.PostJSON(tc.endpoint, tc.json(), nil) default: err = c.PostForm(tc.endpoint, tc.body(), nil) @@ -5788,6 +5843,11 @@ func checkHealthResponse(t *testing.T, r *api.HealthResponse) { require.NotEmpty(t, r.BlockchainMetadata.Head.Time) require.NotEmpty(t, r.Version.Version) require.True(t, r.Uptime.Duration > time.Duration(0)) + require.NotEmpty(t, r.CoinName) + require.NotEmpty(t, r.DaemonUserAgent) + + _, err := useragent.Parse(r.DaemonUserAgent) + require.NoError(t, err) } func TestStableHealth(t *testing.T) { @@ -5803,6 +5863,8 @@ func TestStableHealth(t *testing.T) { checkHealthResponse(t, r) require.Equal(t, 0, r.OpenConnections) + require.Equal(t, 0, r.IncomingConnections) + require.Equal(t, 0, r.OutgoingConnections) require.True(t, r.BlockchainMetadata.TimeSinceLastBlock.Duration > time.Duration(0)) @@ -5810,6 +5872,13 @@ func TestStableHealth(t *testing.T) { require.NotEmpty(t, r.Version.Commit) require.NotEmpty(t, r.Version.Branch) + coinName := os.Getenv("COIN") + require.Equal(t, coinName, r.CoinName) + require.Equal(t, fmt.Sprintf("%s:%s", coinName, r.Version.Version), r.DaemonUserAgent) + + _, err = useragent.Parse(r.DaemonUserAgent) + require.NoError(t, err) + require.Equal(t, useCSRF(t), r.CSRFEnabled) require.True(t, r.CSPEnabled) require.True(t, r.WalletAPIEnabled) @@ -5832,10 +5901,14 @@ func TestLiveHealth(t *testing.T) { if liveDisableNetworking(t) { require.Equal(t, 0, r.OpenConnections) + require.Equal(t, 0, r.OutgoingConnections) + require.Equal(t, 0, r.IncomingConnections) } else { require.NotEqual(t, 0, r.OpenConnections) } + require.Equal(t, r.OutgoingConnections+r.IncomingConnections, r.OpenConnections) + // The TimeSinceLastBlock can be any value, including negative values, due to clock skew // The live node is not necessarily run with the commit and branch ldflags, so don't check them } diff --git a/src/api/integration/testdata/network-default-connections.golden b/src/api/integration/testdata/network-default-peers.golden similarity index 100% rename from src/api/integration/testdata/network-default-connections.golden rename to src/api/integration/testdata/network-default-peers.golden diff --git a/src/api/integration/testdata/network-exchangeable-connections.golden b/src/api/integration/testdata/network-exchanged-peers.golden similarity index 100% rename from src/api/integration/testdata/network-exchangeable-connections.golden rename to src/api/integration/testdata/network-exchanged-peers.golden diff --git a/src/api/integration/testdata/network-trusted-connections.golden b/src/api/integration/testdata/network-trusted-peers.golden similarity index 100% rename from src/api/integration/testdata/network-trusted-connections.golden rename to src/api/integration/testdata/network-trusted-peers.golden diff --git a/src/api/mock_gatewayer_test.go b/src/api/mock_gatewayer_test.go index 7dcdd90eb1..6208d587cf 100644 --- a/src/api/mock_gatewayer_test.go +++ b/src/api/mock_gatewayer_test.go @@ -93,6 +93,20 @@ func (_m *MockGatewayer) DecryptWallet(wltID string, password []byte) (*wallet.W return r0, r1 } +// Disconnect provides a mock function with given fields: id +func (_m *MockGatewayer) Disconnect(id uint64) error { + ret := _m.Called(id) + + var r0 error + if rf, ok := ret.Get(0).(func(uint64) error); ok { + r0 = rf(id) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // EncryptWallet provides a mock function with given fields: wltID, password func (_m *MockGatewayer) EncryptWallet(wltID string, password []byte) (*wallet.Wallet, error) { ret := _m.Called(wltID, password) @@ -394,6 +408,29 @@ func (_m *MockGatewayer) GetConnection(addr string) (*daemon.Connection, error) return r0, r1 } +// GetConnections provides a mock function with given fields: f +func (_m *MockGatewayer) GetConnections(f func(daemon.Connection) bool) ([]daemon.Connection, error) { + ret := _m.Called(f) + + var r0 []daemon.Connection + if rf, ok := ret.Get(0).(func(func(daemon.Connection) bool) []daemon.Connection); ok { + r0 = rf(f) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]daemon.Connection) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(func(daemon.Connection) bool) error); ok { + r1 = rf(f) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetDefaultConnections provides a mock function with given fields: func (_m *MockGatewayer) GetDefaultConnections() []string { ret := _m.Called() @@ -504,29 +541,6 @@ func (_m *MockGatewayer) GetLastBlocksVerbose(num uint64) ([]coin.SignedBlock, [ return r0, r1, r2 } -// GetOutgoingConnections provides a mock function with given fields: -func (_m *MockGatewayer) GetOutgoingConnections() ([]daemon.Connection, error) { - ret := _m.Called() - - var r0 []daemon.Connection - if rf, ok := ret.Get(0).(func() []daemon.Connection); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]daemon.Connection) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func() error); ok { - r1 = rf() - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // GetRichlist provides a mock function with given fields: includeDistribution func (_m *MockGatewayer) GetRichlist(includeDistribution bool) (visor.Richlist, error) { ret := _m.Called(includeDistribution) diff --git a/src/api/network.go b/src/api/network.go index 768e329daa..3912c3c7f1 100644 --- a/src/api/network.go +++ b/src/api/network.go @@ -3,8 +3,11 @@ package api // APIs for network-related information import ( + "fmt" "net/http" "sort" + "strconv" + "strings" "github.com/skycoin/skycoin/src/daemon" "github.com/skycoin/skycoin/src/readable" @@ -64,6 +67,9 @@ func NewConnections(dconns []daemon.Connection) Connections { // connectionsHandler returns all outgoing connections // URI: /api/v1/network/connections // Method: GET +// Args: +// states: [optional] comma-separated list of connection states ("pending", "connected" or "introduced"). Defaults to "connected,introduced" +// direction: [optional] "outgoing" or "incoming". If not provided, both are included. func connectionsHandler(gateway Gatewayer) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { @@ -71,13 +77,62 @@ func connectionsHandler(gateway Gatewayer) http.HandlerFunc { return } - dcnxs, err := gateway.GetOutgoingConnections() + formStates := r.FormValue("states") + statesMap := make(map[daemon.ConnectionState]struct{}, 3) + if formStates != "" { + states := strings.Split(formStates, ",") + for _, s := range states { + switch daemon.ConnectionState(s) { + case daemon.ConnectionStatePending, + daemon.ConnectionStateConnected, + daemon.ConnectionStateIntroduced: + statesMap[daemon.ConnectionState(s)] = struct{}{} + default: + wh.Error400(w, fmt.Sprintf("Invalid state in states. Valid states are %q, %q or %q", daemon.ConnectionStatePending, daemon.ConnectionStateConnected, daemon.ConnectionStateIntroduced)) + return + } + } + } + + // "connected" and "introduced" are the defaults, if not specified + if len(statesMap) == 0 { + statesMap[daemon.ConnectionStateConnected] = struct{}{} + statesMap[daemon.ConnectionStateIntroduced] = struct{}{} + } + + direction := r.FormValue("direction") + switch direction { + case "incoming", "outgoing", "": + default: + wh.Error400(w, "Invalid direction. Valid directions are \"outgoing\" or \"incoming\"") + return + } + + conns, err := gateway.GetConnections(func(c daemon.Connection) bool { + switch direction { + case "outgoing": + if !c.Outgoing { + return false + } + case "incoming": + if c.Outgoing { + return false + } + } + + if _, ok := statesMap[c.State]; !ok { + return false + } + + return true + }) + if err != nil { wh.Error500(w, err.Error()) return } - wh.SendJSONOr500(logger, w, NewConnections(dcnxs)) + wh.SendJSONOr500(logger, w, NewConnections(conns)) } } @@ -133,3 +188,41 @@ func exchgConnectionsHandler(gateway Gatewayer) http.HandlerFunc { wh.SendJSONOr500(logger, w, conns) } } + +// disconnectHandler disconnects a connection by ID or address +// URI: /api/v1/network/connection/disconnect +// Method: POST +// Args: +// id: ID of the connection +func disconnectHandler(gateway Gatewayer) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + wh.Error405(w) + return + } + + formID := r.FormValue("id") + if formID == "" { + wh.Error400(w, "id is required") + return + } + + id, err := strconv.ParseUint(formID, 10, 64) + if err != nil || id == 0 { // gnet IDs are non-zero + wh.Error400(w, "invalid id") + return + } + + if err := gateway.Disconnect(uint64(id)); err != nil { + switch err { + case daemon.ErrConnectionNotExist: + wh.Error404(w, "") + default: + wh.Error500(w, err.Error()) + } + return + } + + wh.SendJSONOr500(logger, w, struct{}{}) + } +} diff --git a/src/api/network_test.go b/src/api/network_test.go index eb8224a17a..5d14778e3c 100644 --- a/src/api/network_test.go +++ b/src/api/network_test.go @@ -8,11 +8,15 @@ import ( "net/url" "strings" "testing" + "time" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/skycoin/skycoin/src/daemon" + "github.com/skycoin/skycoin/src/daemon/pex" "github.com/skycoin/skycoin/src/readable" + "github.com/skycoin/skycoin/src/util/useragent" ) func TestConnection(t *testing.T) { @@ -48,26 +52,38 @@ func TestConnection(t *testing.T) { err: "", addr: "addr", gatewayGetConnectionResult: &daemon.Connection{ - ID: 1, - Addr: "127.0.0.1", - LastSent: 99999, - LastReceived: 1111111, - Outgoing: true, - Introduced: true, - Mirror: 9876, - ListenPort: 9877, - Height: 1234, + Addr: "127.0.0.1:6061", + Gnet: daemon.GnetConnectionDetails{ + ID: 1, + LastSent: time.Unix(99999, 0), + LastReceived: time.Unix(1111111, 0), + }, + ConnectionDetails: daemon.ConnectionDetails{ + Outgoing: true, + ConnectedAt: time.Unix(222222, 0), + State: daemon.ConnectionStateIntroduced, + Mirror: 6789, + ListenPort: 9877, + Height: 1234, + UserAgent: useragent.MustParse("skycoin:0.25.1(foo)"), + }, + Pex: pex.Peer{ + Trusted: false, + }, }, result: &readable.Connection{ - ID: 1, - Addr: "127.0.0.1", - LastSent: 99999, - LastReceived: 1111111, - Outgoing: true, - Introduced: true, - Mirror: 9876, - ListenPort: 9877, - Height: 1234, + Addr: "127.0.0.1:6061", + GnetID: 1, + LastSent: 99999, + LastReceived: 1111111, + ConnectedAt: 222222, + Outgoing: true, + State: daemon.ConnectionStateIntroduced, + Mirror: 6789, + ListenPort: 9877, + Height: 1234, + UserAgent: useragent.MustParse("skycoin:0.25.1(foo)"), + IsTrustedPeer: false, }, }, @@ -125,10 +141,84 @@ func TestConnection(t *testing.T) { } func TestConnections(t *testing.T) { + intrOut := daemon.Connection{ + Addr: "127.0.0.1:6061", + Gnet: daemon.GnetConnectionDetails{ + ID: 1, + LastSent: time.Unix(99999, 0), + LastReceived: time.Unix(1111111, 0), + }, + ConnectionDetails: daemon.ConnectionDetails{ + Outgoing: true, + State: daemon.ConnectionStateIntroduced, + ConnectedAt: time.Unix(222222, 0), + Mirror: 9876, + ListenPort: 9877, + Height: 1234, + UserAgent: useragent.MustParse("skycoin:0.25.1(foo)"), + }, + Pex: pex.Peer{ + Trusted: true, + }, + } + + intrIn := daemon.Connection{ + Addr: "127.0.0.2:6062", + Gnet: daemon.GnetConnectionDetails{ + ID: 2, + LastSent: time.Unix(99999, 0), + LastReceived: time.Unix(1111111, 0), + }, + ConnectionDetails: daemon.ConnectionDetails{ + Outgoing: false, + State: daemon.ConnectionStateIntroduced, + ConnectedAt: time.Unix(222222, 0), + Mirror: 9877, + ListenPort: 9879, + Height: 1234, + UserAgent: useragent.MustParse("skycoin:0.25.1(foo)"), + }, + } + + readIntrOut := readable.Connection{ + Addr: "127.0.0.1:6061", + GnetID: 1, + LastSent: 99999, + LastReceived: 1111111, + ConnectedAt: 222222, + Outgoing: true, + State: daemon.ConnectionStateIntroduced, + Mirror: 9876, + ListenPort: 9877, + Height: 1234, + UserAgent: useragent.MustParse("skycoin:0.25.1(foo)"), + IsTrustedPeer: true, + } + + readIntrIn := readable.Connection{ + Addr: "127.0.0.2:6062", + GnetID: 2, + LastSent: 99999, + LastReceived: 1111111, + ConnectedAt: 222222, + Outgoing: false, + State: daemon.ConnectionStateIntroduced, + Mirror: 9877, + ListenPort: 9879, + Height: 1234, + UserAgent: useragent.MustParse("skycoin:0.25.1(foo)"), + IsTrustedPeer: false, + } + + conns := []daemon.Connection{intrOut, intrIn} + readConns := []readable.Connection{readIntrOut, readIntrIn} + tt := []struct { name string method string status int + states string + direction string err string gatewayGetSolicitedConnectionsResult []daemon.Connection gatewayGetSolicitedConnectionsError error @@ -140,54 +230,119 @@ func TestConnections(t *testing.T) { status: http.StatusMethodNotAllowed, err: "405 Method Not Allowed", }, + { - name: "200", - method: http.MethodGet, - status: http.StatusOK, - err: "", - gatewayGetSolicitedConnectionsResult: []daemon.Connection{ - { - ID: 1, - Addr: "127.0.0.1", - LastSent: 99999, - LastReceived: 1111111, - Outgoing: true, - Introduced: true, - Mirror: 9876, - ListenPort: 9877, - Height: 1234, - }, + name: "200 defaults", + method: http.MethodGet, + status: http.StatusOK, + err: "", + gatewayGetSolicitedConnectionsResult: conns, + result: Connections{ + Connections: readConns, }, + }, + + { + name: "200 incoming", + method: http.MethodGet, + status: http.StatusOK, + direction: "incoming", + err: "", + gatewayGetSolicitedConnectionsResult: conns, result: Connections{ - Connections: []readable.Connection{ - { - ID: 1, - Addr: "127.0.0.1", - LastSent: 99999, - LastReceived: 1111111, - Outgoing: true, - Introduced: true, - Mirror: 9876, - ListenPort: 9877, - Height: 1234, - }, - }, + Connections: readConns, + }, + }, + + { + name: "200 outgoing", + method: http.MethodGet, + status: http.StatusOK, + direction: "outgoing", + err: "", + gatewayGetSolicitedConnectionsResult: conns, + result: Connections{ + Connections: readConns, + }, + }, + + { + name: "200 pending,connected", + method: http.MethodGet, + status: http.StatusOK, + states: "pending,connected", + err: "", + gatewayGetSolicitedConnectionsResult: conns, + result: Connections{ + Connections: readConns, + }, + }, + + { + name: "200 pending,connected outgoing", + method: http.MethodGet, + status: http.StatusOK, + states: "pending,connected", + direction: "outgoing", + err: "", + gatewayGetSolicitedConnectionsResult: conns, + result: Connections{ + Connections: readConns, }, }, { - name: "500 - GetOutgoingConnections failed", + name: "200 pending,introduced,connected", + method: http.MethodGet, + status: http.StatusOK, + states: "pending,introduced,connected", + err: "", + gatewayGetSolicitedConnectionsResult: conns, + result: Connections{ + Connections: readConns, + }, + }, + + { + name: "400 - bad state", + method: http.MethodGet, + status: http.StatusBadRequest, + states: "pending,foo", + err: "400 Bad Request - Invalid state in states. Valid states are \"pending\", \"connected\" or \"introduced\"", + }, + + { + name: "400 - bad direction", + method: http.MethodGet, + status: http.StatusBadRequest, + direction: "foo", + err: "400 Bad Request - Invalid direction. Valid directions are \"outgoing\" or \"incoming\"", + }, + + { + name: "500 - GetConnections failed", method: http.MethodGet, status: http.StatusInternalServerError, - err: "500 Internal Server Error - GetOutgoingConnections failed", - gatewayGetSolicitedConnectionsError: errors.New("GetOutgoingConnections failed"), + err: "500 Internal Server Error - GetConnections failed", + gatewayGetSolicitedConnectionsError: errors.New("GetConnections failed"), }, } + for _, tc := range tt { t.Run(tc.name, func(t *testing.T) { endpoint := "/api/v1/network/connections" gateway := &MockGatewayer{} - gateway.On("GetOutgoingConnections").Return(tc.gatewayGetSolicitedConnectionsResult, tc.gatewayGetSolicitedConnectionsError) + gateway.On("GetConnections", mock.Anything).Return(tc.gatewayGetSolicitedConnectionsResult, tc.gatewayGetSolicitedConnectionsError) + + v := url.Values{} + if tc.states != "" { + v.Add("states", tc.states) + } + if tc.direction != "" { + v.Add("direction", tc.direction) + } + + endpoint += "?" + v.Encode() req, err := http.NewRequest(tc.method, endpoint, nil) require.NoError(t, err) @@ -370,3 +525,107 @@ func TestGetExchgConnection(t *testing.T) { }) } } + +func TestDisconnect(t *testing.T) { + tt := []struct { + name string + method string + status int + err string + disconnectErr error + id string + gnetID uint64 + }{ + { + name: "405", + method: http.MethodGet, + status: http.StatusMethodNotAllowed, + err: "405 Method Not Allowed", + }, + + { + name: "400 missing ID", + method: http.MethodPost, + status: http.StatusBadRequest, + err: "400 Bad Request - id is required", + }, + + { + name: "400 invalid ID 0", + method: http.MethodPost, + status: http.StatusBadRequest, + err: "400 Bad Request - invalid id", + id: "0", + }, + + { + name: "400 invalid ID negative", + method: http.MethodPost, + status: http.StatusBadRequest, + err: "400 Bad Request - invalid id", + id: "-100", + }, + + { + name: "404 Disconnect connection not found", + method: http.MethodPost, + status: http.StatusNotFound, + err: "404 Not Found", + disconnectErr: daemon.ErrConnectionNotExist, + id: "100", + gnetID: 100, + }, + + { + name: "500 Disconnect error", + method: http.MethodPost, + status: http.StatusInternalServerError, + err: "500 Internal Server Error - foo", + disconnectErr: errors.New("foo"), + id: "100", + gnetID: 100, + }, + + { + name: "200", + method: http.MethodPost, + status: http.StatusOK, + id: "100", + gnetID: 100, + }, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + gateway := &MockGatewayer{} + gateway.On("Disconnect", tc.gnetID).Return(tc.disconnectErr) + + endpoint := "/api/v1/network/connection/disconnect" + v := url.Values{} + if tc.id != "" { + v.Add("id", tc.id) + } + + req, err := http.NewRequest(tc.method, endpoint, strings.NewReader(v.Encode())) + require.NoError(t, err) + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + + rr := httptest.NewRecorder() + handler := newServerMux(defaultMuxConfig(), gateway, &CSRFStore{}, nil) + handler.ServeHTTP(rr, req) + + status := rr.Code + require.Equal(t, tc.status, status, "got `%v` want `%v`", status, tc.status) + + if status != http.StatusOK { + require.Equal(t, tc.err, strings.TrimSpace(rr.Body.String()), "got `%v`| %d, want `%v`", + strings.TrimSpace(rr.Body.String()), status, tc.err) + } else { + var obj struct{} + err = json.Unmarshal(rr.Body.Bytes(), &obj) + require.NoError(t, err) + } + }) + } + +} diff --git a/src/api/outputs.go b/src/api/outputs.go index d0ab95ac5e..93b81c10f0 100644 --- a/src/api/outputs.go +++ b/src/api/outputs.go @@ -12,7 +12,7 @@ import ( // outputsHandler returns UxOuts filtered by a set of addresses or a set of hashes // URI: /api/v1/outputs -// Method: GET +// Method: GET, POST // Args: // addrs: comma-separated list of addresses // hashes: comma-separated list of uxout hashes @@ -21,7 +21,7 @@ import ( // Both filters cannot be specified. func outputsHandler(gateway Gatewayer) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { + if r.Method != http.MethodGet && r.Method != http.MethodPost { wh.Error405(w) return } diff --git a/src/api/outputs_test.go b/src/api/outputs_test.go index c9ddacf9b3..94f8735358 100644 --- a/src/api/outputs_test.go +++ b/src/api/outputs_test.go @@ -3,6 +3,7 @@ package api import ( "encoding/json" "errors" + "io" "net/http" "net/http/httptest" "net/url" @@ -38,7 +39,7 @@ func TestGetOutputsHandler(t *testing.T) { }{ { name: "405", - method: http.MethodPost, + method: http.MethodDelete, status: http.StatusMethodNotAllowed, err: "405 Method Not Allowed", }, @@ -88,6 +89,25 @@ func TestGetOutputsHandler(t *testing.T) { IncomingOutputs: readable.UnspentOutputs{}, }, }, + { + name: "200 - OK POST", + method: http.MethodPost, + status: http.StatusOK, + getUnspentOutputsResponse: &visor.UnspentOutputsSummary{ + HeadBlock: &coin.SignedBlock{}, + }, + httpResponse: &readable.UnspentOutputsSummary{ + Head: readable.BlockHeader{ + Hash: "7b8ec8dd836b564f0c85ad088fc744de820345204e154bc1503e04e9d6fdd9f1", + PreviousHash: "0000000000000000000000000000000000000000000000000000000000000000", + BodyHash: "0000000000000000000000000000000000000000000000000000000000000000", + UxHash: "0000000000000000000000000000000000000000000000000000000000000000", + }, + HeadOutputs: readable.UnspentOutputs{}, + OutgoingOutputs: readable.UnspentOutputs{}, + IncomingOutputs: readable.UnspentOutputs{}, + }, + }, } for _, tc := range tt { @@ -106,13 +126,22 @@ func TestGetOutputsHandler(t *testing.T) { } } + var reqBody io.Reader if len(v) > 0 { - endpoint += "?" + v.Encode() + if tc.method == http.MethodPost { + reqBody = strings.NewReader(v.Encode()) + } else { + endpoint += "?" + v.Encode() + } } - req, err := http.NewRequest(tc.method, endpoint, nil) + req, err := http.NewRequest(tc.method, endpoint, reqBody) require.NoError(t, err) + if tc.method == http.MethodPost { + req.Header.Set("Content-Type", ContentTypeForm) + } + rr := httptest.NewRecorder() handler := newServerMux(defaultMuxConfig(), gateway, &CSRFStore{}, nil) handler.ServeHTTP(rr, req) diff --git a/src/api/spend.go b/src/api/spend.go index 8e790cea08..4cff7e64c5 100644 --- a/src/api/spend.go +++ b/src/api/spend.go @@ -12,10 +12,10 @@ import ( "github.com/skycoin/skycoin/src/cipher" "github.com/skycoin/skycoin/src/coin" + "github.com/skycoin/skycoin/src/params" "github.com/skycoin/skycoin/src/util/droplet" "github.com/skycoin/skycoin/src/util/fee" wh "github.com/skycoin/skycoin/src/util/http" - "github.com/skycoin/skycoin/src/visor" "github.com/skycoin/skycoin/src/visor/blockdb" "github.com/skycoin/skycoin/src/wallet" ) @@ -369,7 +369,7 @@ func (r createTransactionRequest) Validate() error { return fmt.Errorf("to[%d].coins must not be zero", i) } - if to.Coins.Value()%visor.MaxDropletDivisor() != 0 { + if to.Coins.Value()%params.MaxDropletDivisor() != 0 { return fmt.Errorf("to[%d].coins has too many decimal places", i) } } @@ -476,7 +476,7 @@ func createTransactionHandler(gateway Gatewayer) http.HandlerFunc { return } - if r.Header.Get("Content-Type") != "application/json" { + if r.Header.Get("Content-Type") != ContentTypeJSON { wh.Error415(w) return } diff --git a/src/api/spend_test.go b/src/api/spend_test.go index e18a71da62..d9d623c338 100644 --- a/src/api/spend_test.go +++ b/src/api/spend_test.go @@ -131,7 +131,7 @@ func TestCreateTransaction(t *testing.T) { name: "415", method: http.MethodPost, status: http.StatusUnsupportedMediaType, - contentType: "application/x-www-form-urlencoded", + contentType: ContentTypeForm, err: "415 Unsupported Media Type", }, @@ -862,7 +862,7 @@ func TestCreateTransaction(t *testing.T) { contentType := tc.contentType if contentType == "" { - contentType = "application/json" + contentType = ContentTypeJSON } req.Header.Add("Content-Type", contentType) diff --git a/src/api/transaction.go b/src/api/transaction.go index 41e1653b2d..63cb48f5f6 100644 --- a/src/api/transaction.go +++ b/src/api/transaction.go @@ -13,7 +13,6 @@ import ( "github.com/skycoin/skycoin/src/cipher" "github.com/skycoin/skycoin/src/coin" "github.com/skycoin/skycoin/src/daemon" - "github.com/skycoin/skycoin/src/daemon/gnet" "github.com/skycoin/skycoin/src/readable" wh "github.com/skycoin/skycoin/src/util/http" "github.com/skycoin/skycoin/src/visor" @@ -247,7 +246,7 @@ func NewTransactionsWithStatusVerbose(txns []visor.Transaction, inputs [][]visor } // Returns transactions that match the filters. -// Method: GET +// Method: GET, POST // URI: /api/v1/transactions // Args: // addrs: Comma separated addresses [optional, returns all transactions if no address provided] @@ -255,7 +254,7 @@ func NewTransactionsWithStatusVerbose(txns []visor.Transaction, inputs [][]visor // verbose: [bool] include verbose transaction input data func transactionsHandler(gateway Gatewayer) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { + if r.Method != http.MethodGet && r.Method != http.MethodPost { wh.Error405(w) return } @@ -379,10 +378,9 @@ func injectTransactionHandler(gateway Gatewayer) http.HandlerFunc { } if err := gateway.InjectBroadcastTransaction(txn); err != nil { - switch err { - case daemon.ErrOutgoingConnectionsDisabled, gnet.ErrPoolEmpty, gnet.ErrNoReachableConnections: + if daemon.IsBroadcastFailure(err) { wh.Error503(w, err.Error()) - default: + } else { wh.Error500(w, err.Error()) } return @@ -409,18 +407,27 @@ func NewResendResult(hashes []cipher.SHA256) ResendResult { } // URI: /api/v1/resendUnconfirmedTxns -// Method: GET +// Method: POST // Broadcasts all unconfirmed transactions from the unconfirmed transaction pool +// Response: +// 200 - ok, returns the transaction hashes that were resent +// 405 - method not POST +// 500 - other error +// 503 - network unavailable for broadcasting transaction func resendUnconfirmedTxnsHandler(gateway Gatewayer) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { + if r.Method != http.MethodPost { wh.Error405(w) return } hashes, err := gateway.ResendUnconfirmedTxns() if err != nil { - wh.Error500(w, err.Error()) + if daemon.IsBroadcastFailure(err) { + wh.Error503(w, err.Error()) + } else { + wh.Error500(w, err.Error()) + } return } @@ -491,7 +498,7 @@ func verifyTxnHandler(gateway Gatewayer) http.HandlerFunc { return } - if r.Header.Get("Content-Type") != "application/json" { + if r.Header.Get("Content-Type") != ContentTypeJSON { resp := NewHTTPErrorResponse(http.StatusUnsupportedMediaType, "") writeHTTPResponse(w, resp) return diff --git a/src/api/transaction_test.go b/src/api/transaction_test.go index 170875c686..671081151c 100644 --- a/src/api/transaction_test.go +++ b/src/api/transaction_test.go @@ -5,6 +5,7 @@ import ( "encoding/hex" "encoding/json" "errors" + "io" "math" "net/http" "net/http/httptest" @@ -679,13 +680,13 @@ func TestInjectTransaction(t *testing.T) { httpBody: string(invalidTxnBodyJSON), }, { - name: "503 - daemon.ErrOutgoingConnectionsDisabled", + name: "503 - daemon.ErrNetworkingDisabled", method: http.MethodPost, status: http.StatusServiceUnavailable, - err: "503 Service Unavailable - Outgoing connections are disabled", + err: "503 Service Unavailable - Networking is disabled", httpBody: string(validTxnBodyJSON), injectTransactionArg: validTransaction, - injectTransactionError: daemon.ErrOutgoingConnectionsDisabled, + injectTransactionError: daemon.ErrNetworkingDisabled, }, { name: "503 - gnet.ErrNoReachableConnections", @@ -700,7 +701,7 @@ func TestInjectTransaction(t *testing.T) { name: "503 - gnet.ErrPoolEmpty", method: http.MethodPost, status: http.StatusServiceUnavailable, - err: "503 Service Unavailable - Connection pool is empty", + err: "503 Service Unavailable - Connection pool is empty after filtering connections", httpBody: string(validTxnBodyJSON), injectTransactionArg: validTransaction, injectTransactionError: gnet.ErrPoolEmpty, @@ -739,7 +740,7 @@ func TestInjectTransaction(t *testing.T) { gateway := &MockGatewayer{} gateway.On("InjectBroadcastTransaction", tc.injectTransactionArg).Return(tc.injectTransactionError) - req, err := http.NewRequest(tc.method, endpoint, bytes.NewBufferString(tc.httpBody)) + req, err := http.NewRequest(tc.method, endpoint, strings.NewReader(tc.httpBody)) require.NoError(t, err) csrfStore := &CSRFStore{ @@ -786,29 +787,40 @@ func TestResendUnconfirmedTxns(t *testing.T) { }{ { name: "405", - method: http.MethodPost, + method: http.MethodGet, status: http.StatusMethodNotAllowed, err: "405 Method Not Allowed", }, + { - name: "500 resend failed", - method: http.MethodGet, + name: "500 resend failed network error", + method: http.MethodPost, + status: http.StatusServiceUnavailable, + err: "503 Service Unavailable - All pool connections are unreachable at this time", + resendUnconfirmedTxnsErr: gnet.ErrNoReachableConnections, + }, + + { + name: "500 resend failed unknown error", + method: http.MethodPost, status: http.StatusInternalServerError, err: "500 Internal Server Error - ResendUnconfirmedTxns failed", resendUnconfirmedTxnsErr: errors.New("ResendUnconfirmedTxns failed"), }, + { name: "200", - method: http.MethodGet, + method: http.MethodPost, status: http.StatusOK, resendUnconfirmedTxnsResponse: nil, httpResponse: ResendResult{ Txids: []string{}, }, }, + { name: "200 with hashes", - method: http.MethodGet, + method: http.MethodPost, status: http.StatusOK, resendUnconfirmedTxnsResponse: []cipher.SHA256{validHash1, validHash2}, httpResponse: ResendResult{ @@ -823,7 +835,7 @@ func TestResendUnconfirmedTxns(t *testing.T) { gateway := &MockGatewayer{} gateway.On("ResendUnconfirmedTxns").Return(tc.resendUnconfirmedTxnsResponse, tc.resendUnconfirmedTxnsErr) - req, err := http.NewRequest(tc.method, endpoint, bytes.NewBufferString(tc.httpBody)) + req, err := http.NewRequest(tc.method, endpoint, strings.NewReader(tc.httpBody)) require.NoError(t, err) csrfStore := &CSRFStore{ @@ -1019,7 +1031,7 @@ func TestGetTransactions(t *testing.T) { }{ { name: "405", - method: http.MethodPost, + method: http.MethodDelete, status: http.StatusMethodNotAllowed, err: "405 Method Not Allowed", }, @@ -1135,11 +1147,27 @@ func TestGetTransactions(t *testing.T) { }, httpResponse: []readable.TransactionWithStatusVerbose{}, }, + + { + name: "200 POST", + method: http.MethodPost, + status: http.StatusOK, + httpBody: &httpBody{ + addrs: addrsStr, + confirmed: "true", + }, + getTransactionsArg: []visor.TxFilter{ + visor.NewAddrsFilter(addrs), + visor.NewConfirmedTxFilter(true), + }, + getTransactionsResponse: []visor.Transaction{}, + httpResponse: []readable.TransactionWithStatus{}, + }, } for _, tc := range tt { - endpoint := "/api/v1/transactions" t.Run(tc.name, func(t *testing.T) { + endpoint := "/api/v1/transactions" gateway := &MockGatewayer{} // Custom argument matching function for matching TxFilter args @@ -1209,13 +1237,23 @@ func TestGetTransactions(t *testing.T) { v.Add("verbose", tc.httpBody.verbose) } } + + var reqBody io.Reader if len(v) > 0 { - endpoint += "?" + v.Encode() + if tc.method == http.MethodPost { + reqBody = strings.NewReader(v.Encode()) + } else { + endpoint += "?" + v.Encode() + } } - req, err := http.NewRequest(tc.method, endpoint, nil) + req, err := http.NewRequest(tc.method, endpoint, reqBody) require.NoError(t, err) + if tc.method == http.MethodPost { + req.Header.Set("Content-Type", ContentTypeForm) + } + csrfStore := &CSRFStore{ Enabled: true, } @@ -1343,7 +1381,7 @@ func TestVerifyTransaction(t *testing.T) { { name: "400 - EOF", method: http.MethodPost, - contentType: "application/json", + contentType: ContentTypeJSON, status: http.StatusBadRequest, httpResponse: NewHTTPErrorResponse(http.StatusBadRequest, "EOF"), }, @@ -1357,7 +1395,7 @@ func TestVerifyTransaction(t *testing.T) { { name: "400 - Invalid transaction: Not enough buffer data to deserialize", method: http.MethodPost, - contentType: "application/json", + contentType: ContentTypeJSON, status: http.StatusBadRequest, httpBody: `{"wrongKey":"wrongValue"}`, httpResponse: NewHTTPErrorResponse(http.StatusBadRequest, "decode transaction failed: Invalid transaction: Not enough buffer data to deserialize"), @@ -1365,7 +1403,7 @@ func TestVerifyTransaction(t *testing.T) { { name: "400 - encoding/hex: odd length hex string", method: http.MethodPost, - contentType: "application/json", + contentType: ContentTypeJSON, status: http.StatusBadRequest, httpBody: `{"encoded_transaction":"aab"}`, httpResponse: NewHTTPErrorResponse(http.StatusBadRequest, "decode transaction failed: encoding/hex: odd length hex string"), @@ -1373,7 +1411,7 @@ func TestVerifyTransaction(t *testing.T) { { name: "400 - deserialization error", method: http.MethodPost, - contentType: "application/json", + contentType: ContentTypeJSON, status: http.StatusBadRequest, httpBody: string(invalidTxnBodyJSON), httpResponse: NewHTTPErrorResponse(http.StatusBadRequest, "decode transaction failed: Invalid transaction: Not enough buffer data to deserialize"), @@ -1381,7 +1419,7 @@ func TestVerifyTransaction(t *testing.T) { { name: "422 - txn sends to empty address", method: http.MethodPost, - contentType: "application/json", + contentType: ContentTypeJSON, status: http.StatusUnprocessableEntity, httpBody: string(invalidTxnEmptyAddressBodyJSON), gatewayVerifyTxnVerboseArg: invalidTxnEmptyAddress.txn, @@ -1400,7 +1438,7 @@ func TestVerifyTransaction(t *testing.T) { { name: "500 - internal server error", method: http.MethodPost, - contentType: "application/json", + contentType: ContentTypeJSON, status: http.StatusInternalServerError, httpBody: string(validTxnBodyJSON), gatewayVerifyTxnVerboseArg: txnAndInputs.txn, @@ -1412,7 +1450,7 @@ func TestVerifyTransaction(t *testing.T) { { name: "422 - txn is confirmed", method: http.MethodPost, - contentType: "application/json", + contentType: ContentTypeJSON, status: http.StatusUnprocessableEntity, httpBody: string(validTxnBodyJSON), gatewayVerifyTxnVerboseArg: txnAndInputs.txn, @@ -1431,7 +1469,7 @@ func TestVerifyTransaction(t *testing.T) { { name: "200", method: http.MethodPost, - contentType: "application/json", + contentType: ContentTypeJSON, status: http.StatusOK, httpBody: string(validTxnBodyJSON), gatewayVerifyTxnVerboseArg: txnAndInputs.txn, @@ -1451,7 +1489,7 @@ func TestVerifyTransaction(t *testing.T) { gateway.On("VerifyTxnVerbose", &tc.gatewayVerifyTxnVerboseArg).Return(tc.gatewayVerifyTxnVerboseResult.Uxouts, tc.gatewayVerifyTxnVerboseResult.IsTxnConfirmed, tc.gatewayVerifyTxnVerboseResult.Err) - req, err := http.NewRequest(tc.method, endpoint, bytes.NewBufferString(tc.httpBody)) + req, err := http.NewRequest(tc.method, endpoint, strings.NewReader(tc.httpBody)) require.NoError(t, err) req.Header.Set("Content-Type", tc.contentType) diff --git a/src/api/wallet.go b/src/api/wallet.go index 8a31ba57e2..1ffaeebb78 100644 --- a/src/api/wallet.go +++ b/src/api/wallet.go @@ -128,12 +128,12 @@ func walletBalanceHandler(gateway Gatewayer) http.HandlerFunc { // Returns the balance of one or more addresses, both confirmed and predicted. The predicted // balance is the confirmed balance minus the pending spends. // URI: /api/v1/balance -// Method: GET +// Method: GET, POST // Args: // addrs: command separated list of addresses [required] func balanceHandler(gateway Gatewayer) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { + if r.Method != http.MethodGet && r.Method != http.MethodPost { wh.Error405(w) return } @@ -949,7 +949,7 @@ func walletRecoverHandler(gateway Gatewayer) http.HandlerFunc { return } - if r.Header.Get("Content-Type") != "application/json" { + if r.Header.Get("Content-Type") != ContentTypeJSON { resp := NewHTTPErrorResponse(http.StatusUnsupportedMediaType, "") writeHTTPResponse(w, resp) return diff --git a/src/api/wallet_test.go b/src/api/wallet_test.go index 66f801b65e..284c36afda 100644 --- a/src/api/wallet_test.go +++ b/src/api/wallet_test.go @@ -1,8 +1,8 @@ package api import ( - "bytes" "errors" + "io" "math" "net/http" "net/http/httptest" @@ -45,7 +45,7 @@ func TestGetBalanceHandler(t *testing.T) { }{ { name: "405", - method: http.MethodPost, + method: http.MethodDelete, status: http.StatusMethodNotAllowed, err: "405 Method Not Allowed", }, @@ -137,6 +137,27 @@ func TestGetBalanceHandler(t *testing.T) { }, httpResponse: readable.BalancePair{}, }, + { + name: "200 - OK POST", + method: http.MethodPost, + status: http.StatusOK, + err: "200 - OK", + httpBody: &httpBody{ + addrs: validAddr, + }, + getBalanceOfAddrsArg: []cipher.Address{address}, + getBalanceOfAddrsResponse: []wallet.BalancePair{ + { + Confirmed: wallet.Balance{Coins: 0, Hours: 0}, + Predicted: wallet.Balance{Coins: 0, Hours: 0}, + }, + { + Confirmed: wallet.Balance{Coins: 0, Hours: 0}, + Predicted: wallet.Balance{Coins: 0, Hours: 0}, + }, + }, + httpResponse: readable.BalancePair{}, + }, } for _, tc := range tt { @@ -152,13 +173,22 @@ func TestGetBalanceHandler(t *testing.T) { } } + var reqBody io.Reader if len(v) > 0 { - endpoint += "?" + v.Encode() + if tc.method == http.MethodPost { + reqBody = strings.NewReader(v.Encode()) + } else { + endpoint += "?" + v.Encode() + } } - req, err := http.NewRequest(tc.method, endpoint, nil) + req, err := http.NewRequest(tc.method, endpoint, reqBody) require.NoError(t, err) + if tc.method == http.MethodPost { + req.Header.Set("Content-Type", ContentTypeForm) + } + rr := httptest.NewRecorder() handler := newServerMux(defaultMuxConfig(), gateway, &CSRFStore{}, nil) handler.ServeHTTP(rr, req) @@ -599,9 +629,9 @@ func TestWalletSpendHandler(t *testing.T) { } } - req, err := http.NewRequest(tc.method, endpoint, bytes.NewBufferString(v.Encode())) + req, err := http.NewRequest(tc.method, endpoint, strings.NewReader(v.Encode())) require.NoError(t, err) - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + req.Header.Add("Content-Type", ContentTypeForm) csrfStore := &CSRFStore{ Enabled: !tc.csrfDisabled, @@ -867,9 +897,9 @@ func TestWalletBalanceHandler(t *testing.T) { if len(v) > 0 { endpoint += "?" + v.Encode() } - req, err := http.NewRequest(tc.method, endpoint, bytes.NewBufferString(v.Encode())) + req, err := http.NewRequest(tc.method, endpoint, strings.NewReader(v.Encode())) require.NoError(t, err) - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + req.Header.Add("Content-Type", ContentTypeForm) csrfStore := &CSRFStore{ Enabled: true, @@ -1012,9 +1042,9 @@ func TestUpdateWalletLabelHandler(t *testing.T) { } } - req, err := http.NewRequest(tc.method, endpoint, bytes.NewBufferString(v.Encode())) + req, err := http.NewRequest(tc.method, endpoint, strings.NewReader(v.Encode())) require.NoError(t, err) - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + req.Header.Add("Content-Type", ContentTypeForm) csrfStore := &CSRFStore{ Enabled: true, @@ -1533,8 +1563,8 @@ func TestWalletCreateHandler(t *testing.T) { } } - req, err := http.NewRequest(tc.method, endpoint, bytes.NewBufferString(v.Encode())) - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + req, err := http.NewRequest(tc.method, endpoint, strings.NewReader(v.Encode())) + req.Header.Add("Content-Type", ContentTypeForm) require.NoError(t, err) csrfStore := &CSRFStore{ @@ -1655,9 +1685,9 @@ func TestWalletNewSeed(t *testing.T) { endpoint += "?" + v.Encode() } - req, err := http.NewRequest(tc.method, endpoint, bytes.NewBufferString(v.Encode())) + req, err := http.NewRequest(tc.method, endpoint, strings.NewReader(v.Encode())) require.NoError(t, err) - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + req.Header.Add("Content-Type", ContentTypeForm) csrfStore := &CSRFStore{ Enabled: true, @@ -1809,9 +1839,9 @@ func TestGetWalletSeed(t *testing.T) { v.Add("password", tc.password) } - req, err := http.NewRequest(tc.method, endpoint, bytes.NewBufferString(v.Encode())) + req, err := http.NewRequest(tc.method, endpoint, strings.NewReader(v.Encode())) require.NoError(t, err) - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + req.Header.Add("Content-Type", ContentTypeForm) csrfStore := &CSRFStore{ Enabled: !tc.csrfDisabled, @@ -2023,9 +2053,9 @@ func TestWalletNewAddressesHandler(t *testing.T) { } } - req, err := http.NewRequest(tc.method, endpoint, bytes.NewBufferString(v.Encode())) + req, err := http.NewRequest(tc.method, endpoint, strings.NewReader(v.Encode())) require.NoError(t, err) - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + req.Header.Add("Content-Type", ContentTypeForm) csrfStore := &CSRFStore{ Enabled: !tc.csrfDisabled, @@ -2408,7 +2438,7 @@ func TestWalletUnloadHandler(t *testing.T) { req, err := http.NewRequest(tc.method, endpoint, strings.NewReader(v.Encode())) require.NoError(t, err) - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + req.Header.Add("Content-Type", ContentTypeForm) csrfStore := &CSRFStore{ Enabled: !tc.csrfDisabled, @@ -2561,7 +2591,7 @@ func TestEncryptWallet(t *testing.T) { req, err := http.NewRequest(tc.method, endpoint, strings.NewReader(v.Encode())) require.NoError(t, err) - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + req.Header.Add("Content-Type", ContentTypeForm) csrfStore := &CSRFStore{ Enabled: true, @@ -2744,7 +2774,7 @@ func TestDecryptWallet(t *testing.T) { req, err := http.NewRequest(tc.method, endpoint, strings.NewReader(v.Encode())) require.NoError(t, err) - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + req.Header.Add("Content-Type", ContentTypeForm) csrfStore := &CSRFStore{ Enabled: !tc.csrfDisabled, @@ -2843,7 +2873,7 @@ func TestWalletRecover(t *testing.T) { name: "method not allowed", method: http.MethodGet, status: http.StatusMethodNotAllowed, - contentType: "application/json", + contentType: ContentTypeJSON, httpBody: toJSON(t, WalletRecoverRequest{}), httpResponse: NewHTTPErrorResponse(http.StatusMethodNotAllowed, "Method Not Allowed"), }, @@ -2851,7 +2881,7 @@ func TestWalletRecover(t *testing.T) { name: "wrong content-type", method: http.MethodPost, status: http.StatusUnsupportedMediaType, - contentType: "application/x-www-form-urlencoded", + contentType: ContentTypeForm, httpBody: toJSON(t, WalletRecoverRequest{}), httpResponse: NewHTTPErrorResponse(http.StatusUnsupportedMediaType, "Unsupported Media Type"), }, @@ -2859,7 +2889,7 @@ func TestWalletRecover(t *testing.T) { name: "empty json body", method: http.MethodPost, status: http.StatusBadRequest, - contentType: "application/json", + contentType: ContentTypeJSON, httpBody: "", httpResponse: NewHTTPErrorResponse(http.StatusBadRequest, "EOF"), }, @@ -2867,7 +2897,7 @@ func TestWalletRecover(t *testing.T) { name: "id missing", method: http.MethodPost, status: http.StatusBadRequest, - contentType: "application/json", + contentType: ContentTypeJSON, req: &WalletRecoverRequest{ Seed: "fooseed", }, @@ -2877,7 +2907,7 @@ func TestWalletRecover(t *testing.T) { name: "seed missing", method: http.MethodPost, status: http.StatusBadRequest, - contentType: "application/json", + contentType: ContentTypeJSON, req: &WalletRecoverRequest{ ID: "foo", }, @@ -2887,7 +2917,7 @@ func TestWalletRecover(t *testing.T) { name: "wallet not encrypted", method: http.MethodPost, status: http.StatusBadRequest, - contentType: "application/json", + contentType: ContentTypeJSON, req: &WalletRecoverRequest{ ID: "foo", Seed: "fooseed", @@ -2901,7 +2931,7 @@ func TestWalletRecover(t *testing.T) { name: "wallet seed wrong", method: http.MethodPost, status: http.StatusBadRequest, - contentType: "application/json", + contentType: ContentTypeJSON, req: &WalletRecoverRequest{ ID: "foo", Seed: "fooseed", @@ -2915,7 +2945,7 @@ func TestWalletRecover(t *testing.T) { name: "wallet does not exist", method: http.MethodPost, status: http.StatusNotFound, - contentType: "application/json", + contentType: ContentTypeJSON, req: &WalletRecoverRequest{ ID: "foo", Seed: "fooseed", @@ -2929,7 +2959,7 @@ func TestWalletRecover(t *testing.T) { name: "wallet api disabled", method: http.MethodPost, status: http.StatusForbidden, - contentType: "application/json", + contentType: ContentTypeJSON, req: &WalletRecoverRequest{ ID: "foo", Seed: "fooseed", @@ -2943,7 +2973,7 @@ func TestWalletRecover(t *testing.T) { name: "wallet other error", method: http.MethodPost, status: http.StatusInternalServerError, - contentType: "application/json", + contentType: ContentTypeJSON, req: &WalletRecoverRequest{ ID: "foo", Seed: "fooseed", @@ -2957,7 +2987,7 @@ func TestWalletRecover(t *testing.T) { name: "ok, no password", method: http.MethodPost, status: http.StatusOK, - contentType: "application/json", + contentType: ContentTypeJSON, req: &WalletRecoverRequest{ ID: "foo", Seed: "fooseed", @@ -2973,7 +3003,7 @@ func TestWalletRecover(t *testing.T) { name: "ok, password", method: http.MethodPost, status: http.StatusOK, - contentType: "application/json", + contentType: ContentTypeJSON, req: &WalletRecoverRequest{ ID: "foo", Seed: "fooseed", @@ -3004,12 +3034,12 @@ func TestWalletRecover(t *testing.T) { } endpoint := "/api/v2/wallet/recover" - req, err := http.NewRequest(tc.method, endpoint, bytes.NewBufferString(tc.httpBody)) + req, err := http.NewRequest(tc.method, endpoint, strings.NewReader(tc.httpBody)) require.NoError(t, err) contentType := tc.contentType if contentType == "" { - contentType = "application/json" + contentType = ContentTypeJSON } req.Header.Set("Content-Type", contentType) diff --git a/src/cipher/crypto.go b/src/cipher/crypto.go index 1f5a998569..073b0566e8 100644 --- a/src/cipher/crypto.go +++ b/src/cipher/crypto.go @@ -39,8 +39,7 @@ var ( // ErrPubKeyFromNullSecKey Attempt to load null seckey, unsafe ErrPubKeyFromNullSecKey = errors.New("Attempt to load null seckey, unsafe") // ErrPubKeyFromBadSecKey PubKeyFromSecKey, pubkey recovery failed. Function - ErrPubKeyFromBadSecKey = errors.New("PubKeyFromSecKey, pubkey recovery failed. Function " + - "assumes seckey is valid. Check seckey") + ErrPubKeyFromBadSecKey = errors.New("PubKeyFromSecKey, pubkey recovery failed. Function assumes seckey is valid. Check seckey") // ErrInvalidLengthSecKey Invalid secret key length ErrInvalidLengthSecKey = errors.New("Invalid secret key length") // ErrECHDInvalidPubKey ECDH invalid pubkey input @@ -55,18 +54,18 @@ var ( ErrInvalidSecKey = errors.New("Invalid secret key") // ErrInvalidSig Invalid signature ErrInvalidSig = errors.New("Invalid signature") - // ErrInvalidSigForPubKey Invalid sig: PubKey recovery failed - ErrInvalidSigForPubKey = errors.New("Invalid sig: PubKey recovery failed") - // ErrInvalidAddressForSig Invalid sig: address does not match output address - ErrInvalidAddressForSig = errors.New("Invalid sig: address does not match output address") + // ErrInvalidSigPubKeyRecovery could not recover pubkey from sig + ErrInvalidSigPubKeyRecovery = errors.New("Failed to recover pubkey from signature") + // ErrInvalidAddressForSig the address derived from the pubkey recovered from the signature does not match a provided address + ErrInvalidAddressForSig = errors.New("Address does not match recovered signing address") // ErrInvalidHashForSig Signature invalid for hash - ErrInvalidHashForSig = errors.New("Invalid sig: invalid for hash") + ErrInvalidHashForSig = errors.New("Signature not valid for hash") // ErrPubKeyRecoverMismatch Recovered pubkey does not match pubkey ErrPubKeyRecoverMismatch = errors.New("Recovered pubkey does not match pubkey") - // ErrInvalidSigInvalidPubKey VerifySignature, secp256k1.VerifyPubkey failed - ErrInvalidSigInvalidPubKey = errors.New("VerifySignature, secp256k1.VerifyPubkey failed") - // ErrInvalidSigValidity VerifySignature, VerifySignatureValidity failed - ErrInvalidSigValidity = errors.New("VerifySignature, VerifySignatureValidity failed") + // ErrInvalidSigInvalidPubKey VerifySignedHash, secp256k1.VerifyPubkey failed + ErrInvalidSigInvalidPubKey = errors.New("VerifySignedHash, secp256k1.VerifyPubkey failed") + // ErrInvalidSigValidity VerifySignedHash, VerifySignatureValidity failed + ErrInvalidSigValidity = errors.New("VerifySignedHash, VerifySignatureValidity failed") // ErrInvalidSigForMessage Invalid signature for this message ErrInvalidSigForMessage = errors.New("Invalid signature for this message") // ErrInvalidSecKyVerification Seckey secp256k1 verification failed @@ -160,7 +159,7 @@ func MustPubKeyFromSecKey(seckey SecKey) PubKey { func PubKeyFromSig(sig Sig, hash SHA256) (PubKey, error) { rawPubKey := secp256k1.RecoverPubkey(hash[:], sig[:]) if rawPubKey == nil { - return PubKey{}, ErrInvalidSigForPubKey + return PubKey{}, ErrInvalidSigPubKeyRecovery } return NewPubKey(rawPubKey) } @@ -369,13 +368,13 @@ func SignHash(hash SHA256, sec SecKey) (Sig, error) { // make sure that the signature is valid pubkey, err := PubKeyFromSig(sig, hash) if err != nil { - log.Panic("MustSignHash error: pubkey from sig recovery failure") + log.Panic("SignHash error: pubkey from sig recovery failure") } - if VerifySignature(pubkey, sig, hash) != nil { - log.Panic("MustSignHash error: secp256k1.Sign returned non-null invalid non-null signature") + if VerifyPubKeySignedHash(pubkey, sig, hash) != nil { + log.Panic("SignHash error: secp256k1.Sign returned non-null invalid non-null signature") } - if ChkSig(AddressFromPubKey(pubkey), hash, sig) != nil { - log.Panic("MustSignHash error: ChkSig failed for signature") + if VerifyAddressSignedHash(AddressFromPubKey(pubkey), sig, hash) != nil { + log.Panic("SignHash error: VerifyAddressSignedHash failed for signature") } } @@ -391,16 +390,16 @@ func MustSignHash(hash SHA256, sec SecKey) Sig { return sig } -// ChkSig checks whether PubKey corresponding to address hash signed hash +// VerifyAddressSignedHash checks whether PubKey corresponding to address hash signed hash // - recovers the PubKey from sig and hash // - fail if PubKey cannot be be recovered // - computes the address from the PubKey // - fail if recovered address does not match PubKey hash // - verify that signature is valid for hash for PubKey -func ChkSig(address Address, hash SHA256, sig Sig) error { +func VerifyAddressSignedHash(address Address, sig Sig, hash SHA256) error { rawPubKey := secp256k1.RecoverPubkey(hash[:], sig[:]) if rawPubKey == nil { - return ErrInvalidSigForPubKey + return ErrInvalidSigPubKeyRecovery } pubKey, err := NewPubKey(rawPubKey) @@ -419,27 +418,11 @@ func ChkSig(address Address, hash SHA256, sig Sig) error { return nil } -// VerifySignedHash this only checks that the signature can be converted to a public key -// Since there is no pubkey or address argument, it cannot check that the -// signature is valid in that context. -func VerifySignedHash(sig Sig, hash SHA256) error { - rawPubKey := secp256k1.RecoverPubkey(hash[:], sig[:]) - if rawPubKey == nil { - return ErrInvalidSigForPubKey - } - if secp256k1.VerifySignature(hash[:], sig[:], rawPubKey) != 1 { - // If this occurs, secp256k1 is bugged - log.Printf("Recovered public key is not valid for signed hash") - return ErrInvalidHashForSig - } - return nil -} - -// VerifySignature verifies that hash was signed by PubKey -func VerifySignature(pubkey PubKey, sig Sig, hash SHA256) error { - pubkeyRec, err := PubKeyFromSig(sig, hash) //recovered pubkey +// VerifyPubKeySignedHash verifies that hash was signed by PubKey +func VerifyPubKeySignedHash(pubkey PubKey, sig Sig, hash SHA256) error { + pubkeyRec, err := PubKeyFromSig(sig, hash) // recovered pubkey if err != nil { - return ErrInvalidSigForPubKey + return ErrInvalidSigPubKeyRecovery } if pubkeyRec != pubkey { return ErrPubKeyRecoverMismatch @@ -447,7 +430,7 @@ func VerifySignature(pubkey PubKey, sig Sig, hash SHA256) error { if secp256k1.VerifyPubkey(pubkey[:]) != 1 { if DebugLevel2 { if secp256k1.VerifySignature(hash[:], sig[:], pubkey[:]) == 1 { - log.Panic("VerifySignature warning, ") + log.Panic("VerifyPubKeySignedHash warning, invalid pubkey is valid for signature") } } return ErrInvalidSigInvalidPubKey @@ -461,6 +444,20 @@ func VerifySignature(pubkey PubKey, sig Sig, hash SHA256) error { return nil } +// VerifySignedHash this only checks that the signature can be converted to a public key +// Since there is no pubkey or address argument, it cannot check that the +// signature is valid in that context. +func VerifySignedHash(sig Sig, hash SHA256) error { + rawPubKey := secp256k1.RecoverPubkey(hash[:], sig[:]) + if rawPubKey == nil { + return ErrInvalidSigPubKeyRecovery + } + if secp256k1.VerifySignature(hash[:], sig[:], rawPubKey) != 1 { + return ErrInvalidHashForSig + } + return nil +} + // GenerateKeyPair creates key pair func GenerateKeyPair() (PubKey, SecKey) { public, secret := secp256k1.GenerateKeyPair() @@ -649,23 +646,23 @@ func CheckSecKeyHash(seckey SecKey, hash SHA256) error { // check pubkey recovered from sig recoveredPubkey, err := PubKeyFromSig(sig, hash) if err != nil { - return fmt.Errorf("impossible error, CheckSecKey, pubkey recovery from signature failed: %v", err) + return fmt.Errorf("impossible error, CheckSecKeyHash, pubkey recovery from signature failed: %v", err) } if pubkey != recoveredPubkey { return ErrPubKeyFromSecKeyMismatch } // verify produced signature - err = VerifySignature(pubkey, sig, hash) + err = VerifyPubKeySignedHash(pubkey, sig, hash) if err != nil { - return fmt.Errorf("impossible error, CheckSecKey, verify signature failed for sig: %v", err) + return fmt.Errorf("impossible error, CheckSecKeyHash, VerifyPubKeySignedHash failed for sig: %v", err) } - // verify ChkSig + // verify VerifyAddressSignedHash addr := AddressFromPubKey(pubkey) - err = ChkSig(addr, hash, sig) + err = VerifyAddressSignedHash(addr, sig, hash) if err != nil { - return fmt.Errorf("impossible error CheckSecKey, ChkSig Failed, should not get this far: %v", err) + return fmt.Errorf("impossible error CheckSecKeyHash, VerifyAddressSignedHash Failed, should not get this far: %v", err) } // verify VerifySignedHash diff --git a/src/cipher/crypto_test.go b/src/cipher/crypto_test.go index 4fc5dee5c4..8d0afecd9d 100644 --- a/src/cipher/crypto_test.go +++ b/src/cipher/crypto_test.go @@ -403,7 +403,7 @@ func TestSigHex(t *testing.T) { require.Equal(t, p2.Hex(), h) } -func TestChkSig(t *testing.T) { +func TestVerifyAddressSignedHash(t *testing.T) { p, s := GenerateKeyPair() require.NoError(t, p.Verify()) require.NoError(t, s.Verify()) @@ -412,19 +412,19 @@ func TestChkSig(t *testing.T) { b := randBytes(t, 256) h := SumSHA256(b) sig := MustSignHash(h, s) - require.NoError(t, ChkSig(a, h, sig)) + require.NoError(t, VerifyAddressSignedHash(a, sig, h)) // Empty sig should be invalid - require.Error(t, ChkSig(a, h, Sig{})) + require.Error(t, VerifyAddressSignedHash(a, Sig{}, h)) // Random sigs should not pass for i := 0; i < 100; i++ { - require.Error(t, ChkSig(a, h, MustNewSig(randBytes(t, 65)))) + require.Error(t, VerifyAddressSignedHash(a, MustNewSig(randBytes(t, 65)), h)) } // Sig for one hash does not work for another hash h2 := SumSHA256(randBytes(t, 256)) sig2 := MustSignHash(h2, s) - require.NoError(t, ChkSig(a, h2, sig2)) - require.Error(t, ChkSig(a, h, sig2)) - require.Error(t, ChkSig(a, h2, sig)) + require.NoError(t, VerifyAddressSignedHash(a, sig2, h2)) + require.Error(t, VerifyAddressSignedHash(a, sig2, h)) + require.Error(t, VerifyAddressSignedHash(a, sig, h2)) // Different secret keys should not create same sig p2, s2 := GenerateKeyPair() @@ -432,19 +432,19 @@ func TestChkSig(t *testing.T) { h = SHA256{} sig = MustSignHash(h, s) sig2 = MustSignHash(h, s2) - require.NoError(t, ChkSig(a, h, sig)) - require.NoError(t, ChkSig(a2, h, sig2)) + require.NoError(t, VerifyAddressSignedHash(a, sig, h)) + require.NoError(t, VerifyAddressSignedHash(a2, sig2, h)) require.NotEqual(t, sig, sig2) h = SumSHA256(randBytes(t, 256)) sig = MustSignHash(h, s) sig2 = MustSignHash(h, s2) - require.NoError(t, ChkSig(a, h, sig)) - require.NoError(t, ChkSig(a2, h, sig2)) + require.NoError(t, VerifyAddressSignedHash(a, sig, h)) + require.NoError(t, VerifyAddressSignedHash(a2, sig2, h)) require.NotEqual(t, sig, sig2) // Bad address should be invalid - require.Error(t, ChkSig(a, h, sig2)) - require.Error(t, ChkSig(a2, h, sig)) + require.Error(t, VerifyAddressSignedHash(a, sig2, h)) + require.Error(t, VerifyAddressSignedHash(a2, sig, h)) } func TestSignHash(t *testing.T) { @@ -454,8 +454,8 @@ func TestSignHash(t *testing.T) { sig, err := SignHash(h, s) require.NoError(t, err) require.NotEqual(t, sig, Sig{}) - require.NoError(t, ChkSig(a, h, sig)) - require.NoError(t, VerifySignature(p, sig, h)) + require.NoError(t, VerifyAddressSignedHash(a, sig, h)) + require.NoError(t, VerifyPubKeySignedHash(p, sig, h)) p2, err := PubKeyFromSig(sig, h) require.NoError(t, err) @@ -471,7 +471,7 @@ func TestMustSignHash(t *testing.T) { h := SumSHA256(randBytes(t, 256)) sig := MustSignHash(h, s) require.NotEqual(t, sig, Sig{}) - require.NoError(t, ChkSig(a, h, sig)) + require.NoError(t, VerifyAddressSignedHash(a, sig, h)) require.Panics(t, func() { MustSignHash(h, SecKey{}) @@ -517,17 +517,17 @@ func TestMustPubKeyFromSig(t *testing.T) { }) } -func TestVerifySignature(t *testing.T) { +func TestVerifyPubKeySignedHash(t *testing.T) { p, s := GenerateKeyPair() h := SumSHA256(randBytes(t, 256)) h2 := SumSHA256(randBytes(t, 256)) sig := MustSignHash(h, s) - require.NoError(t, VerifySignature(p, sig, h)) - require.Error(t, VerifySignature(p, Sig{}, h)) - require.Error(t, VerifySignature(p, sig, h2)) + require.NoError(t, VerifyPubKeySignedHash(p, sig, h)) + require.Error(t, VerifyPubKeySignedHash(p, Sig{}, h)) + require.Error(t, VerifyPubKeySignedHash(p, sig, h2)) p2, _ := GenerateKeyPair() - require.Error(t, VerifySignature(p2, sig, h)) - require.Error(t, VerifySignature(PubKey{}, sig, h)) + require.Error(t, VerifyPubKeySignedHash(p2, sig, h)) + require.Error(t, VerifyPubKeySignedHash(PubKey{}, sig, h)) } func TestGenerateKeyPair(t *testing.T) { @@ -701,3 +701,22 @@ func TestSecKeyPubKeyNull(t *testing.T) { require.False(t, sk.Null()) require.False(t, pk.Null()) } + +func TestVerifySignedHash(t *testing.T) { + h := MustSHA256FromHex("127e9b0d6b71cecd0363b366413f0f19fcd924ae033513498e7486570ff2a1c8") + sig := MustSigFromHex("63c035b0c95d0c5744fc1c0bdf38af02cef2d2f65a8f923732ab44e436f8a491216d9ab5ff795e3144f4daee37077b8b9db54d2ba3a3df8d4992f06bb21f724401") + + err := VerifySignedHash(sig, h) + require.NoError(t, err) + + // Fails with ErrInvalidHashForSig + badSigHex := "71f2c01516fe696328e79bcf464eb0db374b63d494f7a307d1e77114f18581d7a81eed5275a9e04a336292dd2fd16977d9bef2a54ea3161d0876603d00c53bc9dd" + badSig := MustSigFromHex(badSigHex) + err = VerifySignedHash(badSig, h) + require.Equal(t, ErrInvalidHashForSig, err) + + // Fails with ErrInvalidSigPubKeyRecovery + badSig = MustSigFromHex("63c035b0c95d0c5744fc1c0bdf39af02cef2d2f65a8f923732ab44e436f8a491216d9ab5ff795e3144f4daee37077b8b9db54d2ba3a3df8d4992f06bb21f724401") + err = VerifySignedHash(badSig, h) + require.Equal(t, ErrInvalidSigPubKeyRecovery, err) +} diff --git a/src/cipher/encoder/encoder.go b/src/cipher/encoder/encoder.go index 805ab58b30..6d4ceef4ac 100644 --- a/src/cipher/encoder/encoder.go +++ b/src/cipher/encoder/encoder.go @@ -16,6 +16,13 @@ // // Encoding of maps is supported, but note that the use of them results in non-deterministic output. // If determinism is required, do not use map. +// +// A length restriction to certain fields can be applied when decoding. +// Use the tag `,maxlen=` on a struct field to apply this restriction. +// `maxlen` works for string and slice types. The length is interpreted as the length +// of the string or the number of elements in the slice. +// Note that maxlen does not affect serialization; it may serialize objects which could fail deserialization. +// Callers should check their length restricted values manually prior to serialization. package encoder import ( @@ -24,6 +31,7 @@ import ( "log" "math" "reflect" + "strconv" "strings" ) @@ -36,6 +44,8 @@ var ( ErrInvalidOmitEmpty = errors.New("omitempty only supported for the final field in the struct") // ErrRemainingBytes bytes remain in buffer after deserializing object ErrRemainingBytes = errors.New("Bytes remain in buffer after deserializing object") + // ErrMaxLenExceeded a specified maximum length was exceeded when serializing or deserializing a variable length field + ErrMaxLenExceeded = errors.New("Maximum length exceeded for variable length field") ) // SerializeAtomic encoder an integer or boolean contained in `data` to bytes. @@ -150,6 +160,37 @@ func DeserializeAtomic(in []byte, data interface{}) (int, error) { } } +// SerializeString serializes a string to []byte +func SerializeString(s string) []byte { + v := reflect.ValueOf(s) + size, err := datasizeWrite(v) + if err != nil { + log.Panic(err) + } + buf := make([]byte, size) + e := &encoder{buf: buf} + e.value(v) + return buf +} + +// DeserializeString deserializes a string from []byte, returning the string and the number of bytes read +func DeserializeString(in []byte, maxlen int) (string, int, error) { + var s string + v := reflect.ValueOf(&s) + v = v.Elem() + + inlen := len(in) + d1 := &decoder{buf: make([]byte, inlen)} + copy(d1.buf, in) + + err := d1.value(v, maxlen) + if err != nil { + return "", 0, err + } + + return s, inlen - len(d1.buf), nil +} + // DeserializeRaw deserializes `in` buffer into return // parameter. If `data` is not a Pointer or Map type an error // is returned. If `in` buffer can't be deserialized, @@ -168,7 +209,7 @@ func DeserializeRaw(in []byte, data interface{}) error { d1 := &decoder{buf: make([]byte, len(in))} copy(d1.buf, in) - if err := d1.value(v); err != nil { + if err := d1.value(v, 0); err != nil { return err } @@ -197,8 +238,12 @@ func DeserializeRawToValue(in []byte, v reflect.Value) (int, error) { d1 := &decoder{buf: make([]byte, inlen)} copy(d1.buf, in) - err := d1.value(v) - return inlen - len(d1.buf), err + err := d1.value(v, 0) + if err != nil { + return 0, err + } + + return inlen - len(d1.buf), nil } // Serialize returns serialized basic type-based `data` @@ -333,21 +378,24 @@ func datasizeWrite(v reflect.Value) (int, error) { continue } - tag, omitempty := ParseTag(ff.Tag.Get("enc")) + tag := ff.Tag.Get("enc") + omitempty := TagOmitempty(tag) if omitempty && i != nFields-1 { log.Panic(ErrInvalidOmitEmpty) } - if tag != "-" { - fv := v.Field(i) - if !omitempty || !isEmpty(fv) { - s, err := datasizeWrite(fv) - if err != nil { - return 0, err - } - sum += s + if len(tag) > 0 && tag[0] == '-' { + continue + } + + fv := v.Field(i) + if !omitempty || !isEmpty(fv) { + s, err := datasizeWrite(fv) + if err != nil { + return 0, err } + sum += s } } return sum, nil @@ -368,18 +416,38 @@ func datasizeWrite(v reflect.Value) (int, error) { } } -// ParseTag to extract encoder args from raw string. Returns the tag name and if omitempty was specified -func ParseTag(tag string) (string, bool) { +// TagOmitempty returns true if the tag specifies omitempty +func TagOmitempty(tag string) bool { + return strings.Contains(tag, ",omitempty") +} + +func tagName(tag string) string { // nolint: deadcode,megacheck commaIndex := strings.Index(tag, ",") if commaIndex == -1 { - return tag, false + return tag + } + + return tag[:commaIndex] +} + +func tagMaxLen(tag string) int { + maxlenIndex := strings.Index(tag, ",maxlen=") + if maxlenIndex == -1 { + return 0 } - if tag[commaIndex+1:] == "omitempty" { - return tag[:commaIndex], true + maxlenRem := tag[maxlenIndex+len(",maxlen="):] + commaIndex := strings.Index(maxlenRem, ",") + if commaIndex != -1 { + maxlenRem = maxlenRem[:commaIndex] } - return tag[:commaIndex], false + maxlen, err := strconv.Atoi(maxlenRem) + if err != nil { + panic("maxlen must be a number") + } + + return maxlen } /* @@ -556,7 +624,7 @@ func (d *decoder) int64() (int64, error) { func (e *encoder) int64(x int64) { e.uint64(uint64(x)) } -func (d *decoder) value(v reflect.Value) error { +func (d *decoder) value(v reflect.Value, maxlen int) error { kind := v.Kind() switch kind { case reflect.Array: @@ -577,7 +645,7 @@ func (d *decoder) value(v reflect.Value) error { d.buf = d.buf[length:] default: for i := 0; i < length; i++ { - if err := d.value(v.Index(i)); err != nil { + if err := d.value(v.Index(i), 0); err != nil { return err } } @@ -609,10 +677,10 @@ func (d *decoder) value(v reflect.Value) error { for i := 0; i < length; i++ { keyv := reflect.Indirect(reflect.New(key)) elemv := reflect.Indirect(reflect.New(elem)) - if err := d.value(keyv); err != nil { + if err := d.value(keyv, 0); err != nil { return err } - if err := d.value(elemv); err != nil { + if err := d.value(elemv, 0); err != nil { return err } v.SetMapIndex(keyv, elemv) @@ -637,6 +705,10 @@ func (d *decoder) value(v reflect.Value) error { return nil } + if maxlen > 0 && length > maxlen { + return ErrMaxLenExceeded + } + t := v.Type() elem := t.Elem() @@ -648,7 +720,7 @@ func (d *decoder) value(v reflect.Value) error { elemvs := reflect.MakeSlice(t, length, length) for i := 0; i < length; i++ { elemv := reflect.Indirect(elemvs.Index(i)) - if err := d.value(elemv); err != nil { + if err := d.value(elemv, 0); err != nil { return err } } @@ -665,20 +737,29 @@ func (d *decoder) value(v reflect.Value) error { continue } - tag, omitempty := ParseTag(ff.Tag.Get("enc")) + tag := ff.Tag.Get("enc") + omitempty := TagOmitempty(tag) if omitempty && i != nFields-1 { log.Panic(ErrInvalidOmitEmpty) } - if tag != "-" { - fv := v.Field(i) - if fv.CanSet() && ff.Name != "_" { - if err := d.value(fv); err != nil { - // omitempty fields at the end of the buffer are ignored - if !(omitempty && len(d.buf) == 0) { - return err - } + if len(tag) > 0 && tag[0] == '-' { + continue + } + + fv := v.Field(i) + if fv.CanSet() && ff.Name != "_" { + maxlen := tagMaxLen(tag) + + if err := d.value(fv, maxlen); err != nil { + if err == ErrMaxLenExceeded { + return err + } + + // omitempty fields at the end of the buffer are ignored if missing + if !omitempty || len(d.buf) != 0 { + return err } } } @@ -699,6 +780,10 @@ func (d *decoder) value(v reflect.Value) error { return ErrBufferUnderflow } + if maxlen > 0 && length > maxlen { + return ErrMaxLenExceeded + } + v.SetString(string(d.buf[:length])) d.buf = d.buf[length:] @@ -828,17 +913,20 @@ func (e *encoder) value(v reflect.Value) { continue } - tag, omitempty := ParseTag(ff.Tag.Get("enc")) + tag := ff.Tag.Get("enc") + omitempty := TagOmitempty(tag) if omitempty && i != nFields-1 { log.Panic(ErrInvalidOmitEmpty) } - if tag != "-" { - fv := v.Field(i) - if !(omitempty && isEmpty(fv)) && (fv.CanSet() || ff.Name != "_") { - e.value(fv) - } + if len(tag) > 0 && tag[0] == '-' { + continue + } + + fv := v.Field(i) + if !(omitempty && isEmpty(fv)) && (fv.CanSet() || ff.Name != "_") { + e.value(fv) } } diff --git a/src/cipher/encoder/encoder_test.go b/src/cipher/encoder/encoder_test.go index 8d09718478..34cdf99726 100644 --- a/src/cipher/encoder/encoder_test.go +++ b/src/cipher/encoder/encoder_test.go @@ -5,6 +5,7 @@ import ( "crypto/rand" "encoding/hex" "errors" + "fmt" "io/ioutil" "math" "os" @@ -881,50 +882,36 @@ func TestOmitEmptyFinalFieldOnly(t *testing.T) { }) } -func TestParseTag(t *testing.T) { +func TestTagOmitempty(t *testing.T) { cases := []struct { tag string - name string omitempty bool }{ - { - tag: "foo", - name: "foo", - }, - { - tag: "foo,", - name: "foo", - }, - { - tag: "foo,asdasd", - name: "foo", - }, { tag: "foo,omitempty", - name: "foo", omitempty: true, }, { - tag: "omitempty", - name: "omitempty", + tag: "omitempty", + omitempty: false, }, { tag: ",omitempty", omitempty: true, }, { - tag: "", + tag: "", + omitempty: false, }, { - tag: "-", - name: "-", + tag: "-", + omitempty: false, }, } for _, tc := range cases { t.Run(tc.tag, func(t *testing.T) { - name, omitempty := ParseTag(tc.tag) - require.Equal(t, tc.name, name) + omitempty := TagOmitempty(tc.tag) require.Equal(t, tc.omitempty, omitempty) }) } @@ -1191,3 +1178,175 @@ func TestDeserializeRawNotPointer(t *testing.T) { b = Serialize(m) require.NotEmpty(t, b) } + +func TestDeserializeMaxLenExceeded(t *testing.T) { + // maxlen for strings + type Foo struct { + X string `enc:",maxlen=2"` + } + + b := Serialize(Foo{X: "foo"}) + require.NotEmpty(t, b) + + var f Foo + err := DeserializeRaw(b, &f) + require.Equal(t, ErrMaxLenExceeded, err) + + g := Foo{X: "fo"} + b = Serialize(g) + require.NotEmpty(t, b) + + f = Foo{} + err = DeserializeRaw(b, &f) + require.NoError(t, err) + require.Equal(t, g, f) + + // maxlen for slices + type Bar struct { + X []string `enc:",maxlen=2"` + } + + b = Serialize(Bar{X: []string{"f", "o", "o"}}) + require.NotEmpty(t, b) + + var k Bar + err = DeserializeRaw(b, &k) + require.Equal(t, ErrMaxLenExceeded, err) + + c := Bar{X: []string{"f", "o"}} + b = Serialize(c) + require.NotEmpty(t, b) + + k = Bar{} + err = DeserializeRaw(b, &k) + require.NoError(t, err) + require.Equal(t, c, k) + + // Invalid maxlen value panics + type Baz struct { + X string `enc:",maxlen=foo"` + } + + b = Serialize(Baz{X: "foo"}) + require.NotEmpty(t, b) + + var z Baz + require.Panics(t, func() { + _ = DeserializeRaw(b, &z) // nolint: errcheck + }) + + // maxlen for final omitempty byte array + type Car struct { + X string + Y []byte `enc:",omitempty,maxlen=2"` + } + + b = Serialize(Car{ + X: "foo", + Y: []byte("foo"), + }) + require.NotEmpty(t, b) + + var w Car + err = DeserializeRaw(b, &w) + require.Equal(t, ErrMaxLenExceeded, err) + + v := Car{ + X: "foo", + Y: []byte("fo"), + } + b = Serialize(v) + require.NotEmpty(t, b) + + w = Car{} + err = DeserializeRaw(b, &w) + require.NoError(t, err) + require.Equal(t, v, w) +} + +func TestSerializeString(t *testing.T) { + cases := []struct { + s string + x []byte + }{ + { + s: "", + x: []byte{0, 0, 0, 0}, + }, + { + s: "foo", + x: []byte{3, 0, 0, 0, 'f', 'o', 'o'}, + }, + } + + for _, tc := range cases { + t.Run(tc.s, func(t *testing.T) { + require.Equal(t, tc.x, SerializeString(tc.s)) + }) + } +} + +func TestDeserializeString(t *testing.T) { + cases := []struct { + s string + x []byte + n int + maxLen int + err error + }{ + { + s: "", + x: []byte{0, 0, 0, 0}, + n: 4, + }, + { + s: "foo", + x: []byte{3, 0, 0, 0, 'f', 'o', 'o'}, + n: 7, + }, + { + x: []byte{3, 0, 0}, + err: ErrBufferUnderflow, + }, + { + x: nil, + err: ErrBufferUnderflow, + }, + { + x: []byte{3, 0, 0, 0, 'f'}, + err: ErrBufferUnderflow, + }, + { + s: "foo", + x: []byte{3, 0, 0, 0, 'f', 'o', 'o', 'x'}, + n: 7, + }, + { + s: "foo", + x: []byte{3, 0, 0, 0, 'f', 'o', 'o', 'x'}, + maxLen: 2, + err: ErrMaxLenExceeded, + }, + { + s: "foo", + x: []byte{3, 0, 0, 0, 'f', 'o', 'o', 'x'}, + maxLen: 3, + n: 7, + }, + } + + for _, tc := range cases { + t.Run(fmt.Sprintf("s=%s err=%s", tc.s, tc.err), func(t *testing.T) { + s, n, err := DeserializeString(tc.x, tc.maxLen) + if tc.err != nil { + require.Equal(t, tc.err, err) + require.Equal(t, tc.n, n) + return + } + + require.NoError(t, err) + require.Equal(t, tc.s, s) + require.Equal(t, tc.n, n) + }) + } +} diff --git a/src/cipher/encrypt/sha256xor.go b/src/cipher/encrypt/sha256xor.go index 0f60d083e4..cb1dc55983 100644 --- a/src/cipher/encrypt/sha256xor.go +++ b/src/cipher/encrypt/sha256xor.go @@ -22,6 +22,21 @@ const ( sha256XorChecksumSize = 32 // 32 bytes // Data length size sha256XorDataLengthSize = 4 // 4 bytes + +) + +// Error definition +var ( + ErrMissingPassword = errors.New("missing password") + ErrDataTooLarge = errors.New("data length overflowed, it must <= math.MaxUint32(4294967295)") + ErrInvalidChecksumLength = errors.New("invalid checksum length") + ErrInvalidChecksum = errors.New("invalid data, checksum is not matched") + ErrInvalidNonceLength = errors.New("invalid nonce length") + ErrInvalidBlockSize = errors.New("invalid block size, must be multiple of 32 bytes") + ErrReadDataHashFailed = errors.New("read data hash failed: read length != 32") + ErrInvalidPassword = errors.New("invalid password") + ErrReadDataLengthFailed = errors.New("read data length failed") + ErrInvalidDataLength = errors.New("invalid data length") ) // DefaultSha256Xor default sha256xor encryptor @@ -42,11 +57,11 @@ type Sha256Xor struct{} // 6> Finally, the data format is: base64() func (s Sha256Xor) Encrypt(data []byte, password []byte) ([]byte, error) { if len(password) == 0 { - return nil, errors.New("missing password") + return nil, ErrMissingPassword } if uint(len(data)) > math.MaxUint32 { - return nil, errors.New("data length overflowed, it must <= math.MaxUint32(4294967295)") + return nil, ErrDataTooLarge } // Sets data length prefix @@ -110,7 +125,7 @@ func (s Sha256Xor) Encrypt(data []byte, password []byte) ([]byte, error) { // Decrypt decrypts the data func (s Sha256Xor) Decrypt(data []byte, password []byte) ([]byte, error) { if len(password) == 0 { - return nil, errors.New("missing password") + return nil, ErrMissingPassword } // Base64 decodes data @@ -136,13 +151,13 @@ func (s Sha256Xor) Decrypt(data []byte, password []byte) ([]byte, error) { } if n != sha256XorChecksumSize { - return nil, errors.New("invalid checksum length") + return nil, ErrInvalidChecksumLength } // Checks the checksum csh := cipher.SumSHA256(buf.Bytes()) if csh != checkSum { - return nil, errors.New("invalid data, checksum is not matched") + return nil, ErrInvalidChecksum } // Gets the nonce @@ -153,7 +168,7 @@ func (s Sha256Xor) Decrypt(data []byte, password []byte) ([]byte, error) { } if n != sha256XorNonceSize { - return nil, errors.New("invalid nonce length") + return nil, ErrInvalidNonceLength } var decodeData []byte @@ -167,7 +182,7 @@ func (s Sha256Xor) Decrypt(data []byte, password []byte) ([]byte, error) { } if n != sha256XorBlockSize { - return nil, errors.New("invalid block size, must be multiple of 32 bytes") + return nil, ErrInvalidBlockSize } // Decodes the block @@ -186,12 +201,12 @@ func (s Sha256Xor) Decrypt(data []byte, password []byte) ([]byte, error) { } if n != 32 { - return nil, errors.New("read data hash failed: read length != 32") + return nil, ErrReadDataHashFailed } // Checks the hash if dataHash != cipher.SumSHA256(buf.Bytes()) { - return nil, errors.New("invalid password") + return nil, ErrInvalidPassword } // Reads out the data length @@ -202,16 +217,16 @@ func (s Sha256Xor) Decrypt(data []byte, password []byte) ([]byte, error) { } if n != sha256XorDataLengthSize { - return nil, errors.New("read data length failed") + return nil, ErrReadDataLengthFailed } l := binary.LittleEndian.Uint32(dataLenBytes) if l > math.MaxUint32 { - return nil, errors.New("data length overflowed, it must <= math.MaxUint32(4294967295)") + return nil, ErrDataTooLarge } if l > uint32(buf.Len()) { - return nil, errors.New("invalid data length") + return nil, ErrInvalidDataLength } // Reads out the raw data diff --git a/src/cipher/hash.go b/src/cipher/hash.go index 83bca19951..48d6a0bf91 100644 --- a/src/cipher/hash.go +++ b/src/cipher/hash.go @@ -32,7 +32,7 @@ type Ripemd160 [20]byte // MustSet sets value, panics on error func (rd *Ripemd160) MustSet(b []byte) { if len(b) != 20 { - log.Panic("Invalid ripemd160 length") + log.Panic(ErrInvalidLengthRipemd160) } copy(rd[:], b[:]) } @@ -66,7 +66,7 @@ type SHA256 [32]byte // MustSet sets value, panics on error func (g *SHA256) MustSet(b []byte) { if len(b) != 32 { - log.Panic("Invalid sha256 length") + panic(ErrInvalidLengthSHA256) } copy(g[:], b[:]) } diff --git a/src/cipher/secp256k1-go/secp256k1.go b/src/cipher/secp256k1-go/secp256k1.go index 9aa4634909..75165f9264 100644 --- a/src/cipher/secp256k1-go/secp256k1.go +++ b/src/cipher/secp256k1-go/secp256k1.go @@ -12,6 +12,9 @@ import ( secp "github.com/skycoin/skycoin/src/cipher/secp256k1-go/secp256k1-go2" ) +// DebugPrint enable debug print statements +var DebugPrint = false + //intenal, may fail //may return nil func pubkeyFromSeckey(seckey []byte) []byte { @@ -39,7 +42,6 @@ func pubkeyFromSeckey(seckey []byte) []byte { } if ret := VerifyPubkey(pubkey); ret != 1 { - log.Printf("seckey= %s", hex.EncodeToString(seckey)) log.Printf("pubkey= %s", hex.EncodeToString(pubkey)) log.Panicf("ERROR: pubkey verification failed, for deterministic. ret=%d", ret) @@ -168,7 +170,9 @@ new_seckey: log.Panic() } if secp.SeckeyIsValid(seckey) != 1 { - log.Printf("generateDeterministicKeyPair, secp.SeckeyIsValid fail") + if DebugPrint { + log.Printf("generateDeterministicKeyPair, secp.SeckeyIsValid fail") + } goto new_seckey //regen } @@ -360,7 +364,7 @@ func VerifyPubkey(pubkey []byte) int { return 1 //valid } -// VerifySignatureValidity renames ChkSignatureValidity +// VerifySignatureValidity verifies a signature is well formed and not malleable func VerifySignatureValidity(sig []byte) int { //64+1 if len(sig) != 65 { @@ -383,7 +387,6 @@ func VerifySignatureValidity(sig []byte) int { } // VerifySignature for compressed signatures, does not need pubkey -// Rename SignatureChk func VerifySignature(msg []byte, sig []byte, pubkey1 []byte) int { if msg == nil || sig == nil || pubkey1 == nil { log.Panic("VerifySignature, ERROR: invalid input, nils") @@ -466,7 +469,9 @@ func RecoverPubkey(msg []byte, sig []byte) []byte { recid) if ret != 1 { - log.Printf("RecoverPubkey: code %d", ret) + if DebugPrint { + log.Printf("RecoverPubkey: code %d", ret) + } return nil } //var pubkey2 []byte = pubkey1.Bytes() //compressed @@ -494,11 +499,15 @@ func ECDH(pub []byte, sec []byte) []byte { } if VerifySeckey(sec) != 1 { - log.Printf("Invalid Seckey") + if DebugPrint { + log.Printf("Invalid Seckey") + } } if ret := VerifyPubkey(pub); ret != 1 { - log.Printf("Invalid Pubkey, %d", ret) + if DebugPrint { + log.Printf("Invalid Pubkey, %d", ret) + } return nil } diff --git a/src/cipher/testsuite/testsuite.go b/src/cipher/testsuite/testsuite.go index c9639b8206..eeb62c5320 100644 --- a/src/cipher/testsuite/testsuite.go +++ b/src/cipher/testsuite/testsuite.go @@ -250,14 +250,14 @@ func ValidateSeedData(seedData *SeedTestData, inputData *InputTestData) error { return errors.New("provided signature is null") } - err := cipher.VerifySignature(p, sig, h) + err := cipher.VerifyPubKeySignedHash(p, sig, h) if err != nil { - return fmt.Errorf("cipher.VerifySignature failed: %v", err) + return fmt.Errorf("cipher.VerifyPubKeySignedHash failed: %v", err) } - err = cipher.ChkSig(addr1, h, sig) + err = cipher.VerifyAddressSignedHash(addr1, sig, h) if err != nil { - return fmt.Errorf("cipher.ChkSig failed: %v", err) + return fmt.Errorf("cipher.VerifyAddressSignedHash failed: %v", err) } err = cipher.VerifySignedHash(sig, h) diff --git a/src/cli/cli.go b/src/cli/cli.go index d88dd81a3e..88a475b6e9 100644 --- a/src/cli/cli.go +++ b/src/cli/cli.go @@ -24,9 +24,12 @@ import ( "github.com/skycoin/skycoin/src/util/file" ) -const ( +var ( // Version is the CLI Version - Version = "0.24.1" + Version = "0.25.0-rc1" +) + +const ( walletExt = ".wlt" defaultCoin = "skycoin" defaultWalletName = "$COIN_cli" + walletExt diff --git a/src/cli/create_rawtx.go b/src/cli/create_rawtx.go index c8bfb25e55..e742b168cb 100644 --- a/src/cli/create_rawtx.go +++ b/src/cli/create_rawtx.go @@ -9,6 +9,7 @@ import ( "os" "strings" + "github.com/skycoin/skycoin/src/params" "github.com/skycoin/skycoin/src/readable" "github.com/skycoin/skycoin/src/util/droplet" "github.com/skycoin/skycoin/src/util/fee" @@ -539,7 +540,7 @@ func CreateRawTx(c GetOutputser, wlt *wallet.Wallet, inAddrs []string, chgAddr s return nil, err } - if err := visor.VerifySingleTxnSoftConstraints(*txn, head.Time, inUxsFiltered, visor.DefaultMaxBlockSize); err != nil { + if err := visor.VerifySingleTxnSoftConstraints(*txn, head.Time, inUxsFiltered, params.MaxUserTransactionSize, params.UserBurnFactor); err != nil { return nil, err } if err := visor.VerifySingleTxnHardConstraints(*txn, head, inUxsFiltered); err != nil { @@ -666,7 +667,7 @@ func makeChangeOut(outs []wallet.UxBalance, chgAddr string, toAddrs []SendAmount nAddrs := uint64(len(toAddrs)) changeHours, addrHours, totalOutHours := wallet.DistributeSpendHours(totalInHours, nAddrs, haveChange) - if err := fee.VerifyTransactionFeeForHours(totalOutHours, totalInHours-totalOutHours); err != nil { + if err := fee.VerifyTransactionFeeForHours(totalOutHours, totalInHours-totalOutHours, params.UserBurnFactor); err != nil { return nil, err } diff --git a/src/cli/integration/testdata/generate-addresses-encrypted.golden b/src/cli/integration/testdata/generate-addresses-encrypted.golden index 4383c8b10f..1d1474805d 100644 --- a/src/cli/integration/testdata/generate-addresses-encrypted.golden +++ b/src/cli/integration/testdata/generate-addresses-encrypted.golden @@ -24,4 +24,4 @@ "secret_key": "" } ] -} \ No newline at end of file +} diff --git a/src/cli/integration/testdata/integration-test-encrypted.wlt b/src/cli/integration/testdata/integration-test-encrypted.wlt index fc39e6dd22..e1738d49f3 100644 --- a/src/cli/integration/testdata/integration-test-encrypted.wlt +++ b/src/cli/integration/testdata/integration-test-encrypted.wlt @@ -19,4 +19,4 @@ "secret_key": "" } ] -} \ No newline at end of file +} diff --git a/src/cli/integration/testdata/status-csrf-enabled-no-unconfirmed.golden b/src/cli/integration/testdata/status-csrf-enabled-no-unconfirmed.golden index 5e98314401..c7f25c9bbb 100644 --- a/src/cli/integration/testdata/status-csrf-enabled-no-unconfirmed.golden +++ b/src/cli/integration/testdata/status-csrf-enabled-no-unconfirmed.golden @@ -8,7 +8,8 @@ "timestamp": 1431574528, "fee": 2265261, "version": 0, - "tx_body_hash": "0a610a34a8408effe8f2f70e4a85a3a8f4aca923f43e10a8a6e08cf410d7a35d" + "tx_body_hash": "0a610a34a8408effe8f2f70e4a85a3a8f4aca923f43e10a8a6e08cf410d7a35d", + "ux_hash": "058d1d0a22be7b9f5567a236866836a87d922760581832cfb8bfbd8b337d64b1" }, "unspents": 218, "unconfirmed": 0, @@ -19,14 +20,19 @@ "commit": "", "branch": "" }, + "coin": "skycoin", + "user_agent": "skycoin:0.25.0-rc1", "open_connections": 0, + "outgoing_connections": 0, + "incoming_connections": 0, "uptime": "0s", "csrf_enabled": true, "csp_enabled": true, "wallet_api_enabled": true, "gui_enabled": false, "unversioned_api_enabled": false, - "json_rpc_enabled": false + "json_rpc_enabled": false, + "coinhour_burn_factor": 2 }, "cli_config": { "webrpc_address": "http://127.0.0.1:1024" diff --git a/src/cli/integration/testdata/status-csrf-enabled.golden b/src/cli/integration/testdata/status-csrf-enabled.golden index 77dcc4ef05..69a3b051f4 100644 --- a/src/cli/integration/testdata/status-csrf-enabled.golden +++ b/src/cli/integration/testdata/status-csrf-enabled.golden @@ -20,14 +20,19 @@ "commit": "", "branch": "" }, + "coin": "skycoin", + "user_agent": "skycoin:0.25.0-rc1", "open_connections": 0, + "outgoing_connections": 0, + "incoming_connections": 0, "uptime": "0s", "csrf_enabled": true, "csp_enabled": true, "wallet_api_enabled": true, "gui_enabled": false, "unversioned_api_enabled": false, - "json_rpc_enabled": false + "json_rpc_enabled": false, + "coinhour_burn_factor": 2 }, "cli_config": { "webrpc_address": "http://127.0.0.1:1024" diff --git a/src/cli/integration/testdata/status-no-unconfirmed.golden b/src/cli/integration/testdata/status-no-unconfirmed.golden index b2e6275475..08590d06f2 100644 --- a/src/cli/integration/testdata/status-no-unconfirmed.golden +++ b/src/cli/integration/testdata/status-no-unconfirmed.golden @@ -20,14 +20,19 @@ "commit": "", "branch": "" }, + "coin": "skycoin", + "user_agent": "skycoin:0.25.0-rc1", "open_connections": 0, + "outgoing_connections": 0, + "incoming_connections": 0, "uptime": "0s", "csrf_enabled": false, "csp_enabled": true, "wallet_api_enabled": true, "gui_enabled": false, "unversioned_api_enabled": false, - "json_rpc_enabled": false + "json_rpc_enabled": false, + "coinhour_burn_factor": 2 }, "cli_config": { "webrpc_address": "http://127.0.0.1:1024" diff --git a/src/cli/integration/testdata/status.golden b/src/cli/integration/testdata/status.golden index be4eb2f1ef..53ff5f4361 100644 --- a/src/cli/integration/testdata/status.golden +++ b/src/cli/integration/testdata/status.golden @@ -20,14 +20,19 @@ "commit": "", "branch": "" }, + "coin": "skycoin", + "user_agent": "skycoin:0.25.0-rc1", "open_connections": 0, + "outgoing_connections": 0, + "incoming_connections": 0, "uptime": "0s", "csrf_enabled": false, "csp_enabled": true, "wallet_api_enabled": true, "gui_enabled": false, "unversioned_api_enabled": false, - "json_rpc_enabled": false + "json_rpc_enabled": false, + "coinhour_burn_factor": 2 }, "cli_config": { "webrpc_address": "http://127.0.0.1:1024" diff --git a/src/coin/block.go b/src/coin/block.go index 10513c756f..9f7e899b6e 100644 --- a/src/coin/block.go +++ b/src/coin/block.go @@ -52,7 +52,7 @@ type SignedBlock struct { // VerifySignature verifies that the block is signed by pubkey func (b SignedBlock) VerifySignature(pubkey cipher.PubKey) error { - return cipher.VerifySignature(pubkey, b.Sig, b.Block.HashHeader()) + return cipher.VerifyPubKeySignedHash(pubkey, b.Sig, b.HashHeader()) } // NewBlock creates new block. diff --git a/src/coin/transactions.go b/src/coin/transactions.go index 07af3c18d5..638c2c9e88 100644 --- a/src/coin/transactions.go +++ b/src/coin/transactions.go @@ -165,7 +165,7 @@ func (txn Transaction) VerifyInput(uxIn UxArray) error { // Check signatures against unspent address for i := range txn.In { hash := cipher.AddSHA256(txn.InnerHash, txn.In[i]) // use inner hash, not outer hash - err := cipher.ChkSig(uxIn[i].Body.Address, hash, txn.Sigs[i]) + err := cipher.VerifyAddressSignedHash(uxIn[i].Body.Address, txn.Sigs[i], hash) if err != nil { return errors.New("Signature not valid for output being spent") } diff --git a/src/coin/transactions_test.go b/src/coin/transactions_test.go index 5ee7cf2b49..b7d5965cac 100644 --- a/src/coin/transactions_test.go +++ b/src/coin/transactions_test.go @@ -112,7 +112,7 @@ func TestTransactionVerify(t *testing.T) { // Invalid signature, empty tx = makeTransaction(t) tx.Sigs[0] = cipher.Sig{} - testutil.RequireError(t, tx.Verify(), "Invalid sig: PubKey recovery failed") + testutil.RequireError(t, tx.Verify(), "Failed to recover pubkey from signature") // We can't check here for other invalid signatures: // - Signatures signed by someone else, spending coins they don't own // - Signature is for wrong hash @@ -262,10 +262,10 @@ func TestTransactionSignInputs(t *testing.T) { a := cipher.AddressFromPubKey(p) p = cipher.MustPubKeyFromSecKey(s2) a2 := cipher.AddressFromPubKey(p) - require.Nil(t, cipher.ChkSig(a, cipher.AddSHA256(h, tx.In[0]), tx.Sigs[0])) - require.Nil(t, cipher.ChkSig(a2, cipher.AddSHA256(h, tx.In[1]), tx.Sigs[1])) - require.NotNil(t, cipher.ChkSig(a, h, tx.Sigs[1])) - require.NotNil(t, cipher.ChkSig(a2, h, tx.Sigs[0])) + require.NoError(t, cipher.VerifyAddressSignedHash(a, tx.Sigs[0], cipher.AddSHA256(h, tx.In[0]))) + require.NoError(t, cipher.VerifyAddressSignedHash(a2, tx.Sigs[1], cipher.AddSHA256(h, tx.In[1]))) + require.Error(t, cipher.VerifyAddressSignedHash(a, tx.Sigs[1], h)) + require.Error(t, cipher.VerifyAddressSignedHash(a2, tx.Sigs[0], h)) } func TestTransactionHash(t *testing.T) { diff --git a/src/daemon/connections.go b/src/daemon/connections.go new file mode 100644 index 0000000000..7b4be43991 --- /dev/null +++ b/src/daemon/connections.go @@ -0,0 +1,496 @@ +package daemon + +import ( + "errors" + "fmt" + "sync" + "time" + + "github.com/sirupsen/logrus" + + "github.com/skycoin/skycoin/src/util/iputil" + "github.com/skycoin/skycoin/src/util/useragent" +) + +// ConnectionState connection state in the state machine +// Connections have three states: "pending", "connected" and "introduced" +// A connection in the "pending" state has been selected to establish a TCP connection, +// but the connection has not been established yet. +// Only outgoing connections will ever be in the "pending" state; +// incoming connections begin at the "connected" state. +// A connection in the "connected" state has established a TCP connection, +// but has not completed the introduction handshake. +// A connection in the "introduced" state has completed the introduction handshake. +type ConnectionState string + +const ( + // ConnectionStatePending prior to establishing a connection + ConnectionStatePending ConnectionState = "pending" + // ConnectionStateConnected connected, but not introduced + ConnectionStateConnected ConnectionState = "connected" + // ConnectionStateIntroduced connection has introduced itself + ConnectionStateIntroduced ConnectionState = "introduced" +) + +var ( + // ErrConnectionNotExist connection does not exist when performing an operation that requires it to exist + ErrConnectionNotExist = errors.New("Connection does not exist") + // ErrConnectionExists connection exists in Connections + ErrConnectionExists = errors.New("Connection exists") + // ErrConnectionIPMirrorExists connection exists for a given base IP and mirror + ErrConnectionIPMirrorExists = errors.New("Connection exists with this base IP and mirror") + // ErrConnectionStateNotConnected connect state is not "connected" + ErrConnectionStateNotConnected = errors.New("Connection state is not \"connected\"") + // ErrConnectionGnetIDMismatch gnet ID in argument does not match gnet ID on record + ErrConnectionGnetIDMismatch = errors.New("Connection gnet ID does not match") + // ErrConnectionAlreadyIntroduced attempted to make invalid state transition from introduced state + ErrConnectionAlreadyIntroduced = errors.New("Connection is already in introduced state") + // ErrConnectionAlreadyConnected attempted to make invalid state transition from connected state + ErrConnectionAlreadyConnected = errors.New("Connection is already in connected state") + // ErrInvalidGnetID invalid gnet ID value used as argument + ErrInvalidGnetID = errors.New("Invalid gnet ID") +) + +// ConnectionDetails connection data managed by daemon +type ConnectionDetails struct { + State ConnectionState + Outgoing bool + ConnectedAt time.Time + Mirror uint32 + ListenPort uint16 + ProtocolVersion int32 + Height uint64 + UserAgent useragent.Data +} + +// HasIntroduced returns true if the connection has introduced +func (c ConnectionDetails) HasIntroduced() bool { + switch c.State { + case ConnectionStateIntroduced: + return true + default: + return false + } +} + +type connection struct { + Addr string + ConnectionDetails + gnetID uint64 +} + +// ListenAddr returns the addr that connection listens on, if available +func (c *connection) ListenAddr() string { + if c.ListenPort == 0 { + return "" + } + + ip, _, err := iputil.SplitAddr(c.Addr) + if err != nil { + logger.Critical().WithError(err).WithField("addr", c.Addr).Error("connection.ListenAddr addr could not be split") + return "" + } + + return fmt.Sprintf("%s:%d", ip, c.ListenPort) +} + +// Connections manages a collection of Connection +type Connections struct { + conns map[string]*connection + mirrors map[uint32]map[string]uint16 + ipCounts map[string]int + sync.Mutex +} + +// NewConnections creates Connections +func NewConnections() *Connections { + return &Connections{ + conns: make(map[string]*connection, 32), + mirrors: make(map[uint32]map[string]uint16, 32), + ipCounts: make(map[string]int, 32), + } +} + +// pending adds a new pending outgoing connection +func (c *Connections) pending(addr string) (*connection, error) { + c.Lock() + defer c.Unlock() + + ip, port, err := iputil.SplitAddr(addr) + if err != nil { + logger.Critical().WithField("addr", addr).WithError(err).Error("Connections.pending called with invalid addr") + return nil, err + } + + if _, ok := c.conns[addr]; ok { + return nil, ErrConnectionExists + } + + c.ipCounts[ip]++ + + c.conns[addr] = &connection{ + Addr: addr, + ConnectionDetails: ConnectionDetails{ + State: ConnectionStatePending, + Outgoing: true, + ListenPort: port, + }, + } + + logger.WithField("addr", addr).Debug("Connections.pending") + + return c.conns[addr], nil +} + +// connected the connection has connected +func (c *Connections) connected(addr string, gnetID uint64) (*connection, error) { + c.Lock() + defer c.Unlock() + + fields := logrus.Fields{ + "addr": addr, + "gnetID": gnetID, + } + + if gnetID == 0 { + logger.Critical().WithFields(fields).WithError(ErrInvalidGnetID).Error("Connections.connected called with invalid gnetID") + return nil, ErrInvalidGnetID + } + + ip, _, err := iputil.SplitAddr(addr) + if err != nil { + logger.Critical().WithFields(fields).WithError(err).Error("Connections.connected called with invalid addr") + return nil, err + } + + conn := c.conns[addr] + + if conn == nil { + c.ipCounts[ip]++ + + conn = &connection{ + Addr: addr, + } + + c.conns[addr] = conn + } else { + fields := logrus.Fields{ + "addr": addr, + "gnetID": gnetID, + "state": conn.State, + "outgoing": conn.Outgoing, + "connGnetID": conn.gnetID, + } + + switch conn.State { + case ConnectionStatePending: + case ConnectionStateConnected: + logger.Critical().WithFields(fields).Error("Connections.connected called on already connected connection") + return nil, ErrConnectionAlreadyConnected + case ConnectionStateIntroduced: + logger.Critical().WithFields(fields).Error("Connections.connected called on already introduced connection") + return nil, ErrConnectionAlreadyIntroduced + default: + logger.WithFields(fields).Panic("Connection state invalid") + } + } + + conn.gnetID = gnetID + conn.ConnectedAt = time.Now().UTC() + conn.State = ConnectionStateConnected + + fields["outgoing"] = conn.Outgoing + + logger.WithFields(fields).Debug("Connections.connected") + + return conn, nil +} + +// introduced the connection has introduced itself +func (c *Connections) introduced(addr string, gnetID uint64, m *IntroductionMessage, userAgent *useragent.Data) (*connection, error) { + c.Lock() + defer c.Unlock() + + fields := logrus.Fields{ + "addr": addr, + "gnetID": gnetID, + } + + if gnetID == 0 { + logger.Critical().WithFields(fields).WithError(ErrInvalidGnetID).Error("Connections.introduced called with invalid gnetID") + return nil, ErrInvalidGnetID + } + + ip, _, err := iputil.SplitAddr(addr) + if err != nil { + logger.Critical().WithFields(fields).WithError(err).Error("Connections.introduced called with invalid addr") + return nil, err + } + + conn := c.conns[addr] + if conn == nil { + return nil, ErrConnectionNotExist + } + + fields["outgoing"] = conn.Outgoing + + errorFields := logrus.Fields{ + "state": conn.State, + "connGnetID": conn.gnetID, + } + + switch conn.State { + case ConnectionStatePending: + logger.Critical().WithFields(fields).WithFields(errorFields).Error("Connections.introduced called on pending connection") + return nil, ErrConnectionStateNotConnected + case ConnectionStateConnected: + if gnetID != conn.gnetID { + logger.Critical().WithFields(fields).WithFields(errorFields).Error("Connections.introduced called with different gnet ID") + return nil, ErrConnectionGnetIDMismatch + } + case ConnectionStateIntroduced: + logger.Critical().WithFields(fields).WithFields(errorFields).Error("Connections.introduced called on already introduced connection") + return nil, ErrConnectionAlreadyIntroduced + default: + logger.WithFields(fields).WithFields(errorFields).Panic("invalid connection state") + } + + if err := c.canUpdateMirror(ip, m.Mirror); err != nil { + logger.WithFields(fields).WithFields(errorFields).WithField("mirror", m.Mirror).WithError(err).Debug("canUpdateMirror failed") + return nil, err + } + + // For outgoing connections, which are created by pending, + // the listen port is set from the addr's port number. + // Since we are connecting to it, it is presumed to be that peer's open listening port. + // A misbehaving peer could report a different ListenPort in their IntroductionMessage, + // but it shouldn't affect our records. + if conn.Outgoing && conn.ListenPort != m.ListenPort { + logger.Critical().WithFields(fields).WithFields(logrus.Fields{ + "connListenPort": conn.ListenPort, + "introListenPort": m.ListenPort, + }).Warning("Outgoing connection's ListenPort does not match reported IntroductionMessage ListenPort") + } + + listenPort := conn.ListenPort + if !conn.Outgoing { + listenPort = m.ListenPort + } + + if err := c.updateMirror(ip, m.Mirror, listenPort); err != nil { + logger.WithFields(fields).WithField("mirror", m.Mirror).WithError(err).Panic("updateMirror failed, but shouldn't") + } + + conn.State = ConnectionStateIntroduced + conn.Mirror = m.Mirror + conn.ProtocolVersion = m.ProtocolVersion + conn.ListenPort = listenPort + if userAgent != nil { + conn.UserAgent = *userAgent + } + + logger.WithFields(fields).Debug("Connections.introduced") + + return conn, nil +} + +// get returns a connection by address +func (c *Connections) get(addr string) *connection { + c.Lock() + defer c.Unlock() + + return c.conns[addr] +} + +func (c *Connections) getByGnetID(gnetID uint64) *connection { + c.Lock() + defer c.Unlock() + + if gnetID == 0 { + return nil + } + + for _, c := range c.conns { + if c.gnetID == gnetID { + return c + } + } + + return nil +} + +// modify modifies a connection. +// It is unsafe to modify the Mirror value with this method +func (c *Connections) modify(addr string, gnetID uint64, f func(c *ConnectionDetails)) error { + conn := c.conns[addr] + if conn == nil { + return ErrConnectionNotExist + } + + if conn.gnetID != gnetID { + return ErrConnectionGnetIDMismatch + } + + // copy and modify + cd := conn.ConnectionDetails + + f(&cd) + + // compare to original + if cd.Mirror != conn.ConnectionDetails.Mirror { + logger.WithFields(logrus.Fields{ + "addr": addr, + "gnetID": gnetID, + }).Panic("Connections.modify connection mirror value was changed") + } + + conn.ConnectionDetails = cd + + return nil +} + +// SetHeight sets the height for a connection +func (c *Connections) SetHeight(addr string, gnetID uint64, height uint64) error { + c.Lock() + defer c.Unlock() + + return c.modify(addr, gnetID, func(c *ConnectionDetails) { + c.Height = height + }) +} + +func (c *Connections) updateMirror(ip string, mirror uint32, port uint16) error { + x := c.mirrors[mirror] + if x == nil { + x = make(map[string]uint16, 2) + } + + if _, ok := x[ip]; ok { + return ErrConnectionIPMirrorExists + } + + x[ip] = port + c.mirrors[mirror] = x + + return nil +} + +// canUpdateMirror returns false if a connection already exists with the same base IP and mirror value. +// This prevents duplicate connections to/from a single client. +func (c *Connections) canUpdateMirror(ip string, mirror uint32) error { + x := c.mirrors[mirror] + if x == nil { + return nil + } + + if _, ok := x[ip]; ok { + return ErrConnectionIPMirrorExists + } + + return nil +} + +// IPCount returns the number of connections for a given base IP (without port) +func (c *Connections) IPCount(ip string) int { + c.Lock() + defer c.Unlock() + return c.ipCounts[ip] +} + +// Len returns number of connections +func (c *Connections) Len() int { + c.Lock() + defer c.Unlock() + return len(c.conns) +} + +// OutgoingLen returns number of outgoing connections +func (c *Connections) OutgoingLen() int { + c.Lock() + defer c.Unlock() + n := 0 + for _, conn := range c.conns { + if conn.Outgoing { + n++ + } + } + return n +} + +// PendingLen returns the number of status pending connections +func (c *Connections) PendingLen() int { + c.Lock() + defer c.Unlock() + n := 0 + for _, conn := range c.conns { + if conn.State == ConnectionStatePending { + n++ + } + } + return n +} + +// remove removes connection. Returns an error if the addr is invalid. +// If a connection with this address does not exist, nothing happens. +func (c *Connections) remove(addr string, gnetID uint64) error { + c.Lock() + defer c.Unlock() + + ip, _, err := iputil.SplitAddr(addr) + if err != nil { + logger.Critical().WithError(err).Error("Connections.remove called with invalid addr") + return err + } + + conn := c.conns[addr] + if conn == nil { + return ErrConnectionNotExist + } + + fields := logrus.Fields{ + "addr": addr, + "connGnetID": conn.gnetID, + "gnetID": gnetID, + } + + if conn.gnetID != gnetID { + logger.Critical().WithFields(fields).Warning("Connections.remove gnetID does not match") + return ErrConnectionGnetIDMismatch + } + + x, ok := c.mirrors[conn.Mirror] + if ok { + if x[ip] != conn.ListenPort { + logger.Critical().WithFields(fields).Warning("Indexed IP+Mirror value found but the ListenPort doesn't match") + } + + delete(x, ip) + } + + if len(x) == 0 { + delete(c.mirrors, conn.Mirror) + } + + if c.ipCounts[ip] > 0 { + c.ipCounts[ip]-- + } else { + logger.Critical().WithFields(fields).Warning("ipCount was already 0 when removing existing address") + } + + delete(c.conns, addr) + + return nil +} + +// all returns a copy of all connections +func (c *Connections) all() []connection { + c.Lock() + defer c.Unlock() + + conns := make([]connection, 0, len(c.conns)) + for _, c := range c.conns { + conns = append(conns, *c) + } + + return conns +} diff --git a/src/daemon/connections_test.go b/src/daemon/connections_test.go new file mode 100644 index 0000000000..2c4d76860a --- /dev/null +++ b/src/daemon/connections_test.go @@ -0,0 +1,428 @@ +package daemon + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/skycoin/skycoin/src/testutil" + "github.com/skycoin/skycoin/src/util/useragent" +) + +var userAgent = useragent.MustParse("skycoin:0.24.1(foo)") + +func getMirrorPort(c *Connections, ip string, mirror uint32) uint16 { + c.Lock() + defer c.Unlock() + + x := c.mirrors[mirror] + if x == nil { + return 0 + } + + return x[ip] +} + +func TestConnectionsOutgoingFlow(t *testing.T) { + conns := NewConnections() + + ip := "127.0.0.1" + port := uint16(6060) + addr := fmt.Sprintf("%s:%d", ip, port) + + all := conns.all() + require.Empty(t, all) + + require.Equal(t, 0, conns.IPCount(ip)) + require.Equal(t, 0, conns.OutgoingLen()) + require.Equal(t, 0, conns.PendingLen()) + require.Equal(t, 0, conns.Len()) + require.Empty(t, conns.mirrors) + + // Flow: pending, connected, introduced + + c, err := conns.pending(addr) + require.NoError(t, err) + + require.True(t, c.Outgoing) + require.Equal(t, addr, c.Addr) + require.Equal(t, port, c.ListenPort) + require.Equal(t, ConnectionStatePending, c.State) + require.Equal(t, 1, conns.IPCount(ip)) + require.Equal(t, 1, conns.OutgoingLen()) + require.Equal(t, 1, conns.PendingLen()) + require.Equal(t, 1, conns.Len()) + require.Empty(t, c.Mirror) + require.Empty(t, conns.mirrors) + require.False(t, c.HasIntroduced()) + require.Equal(t, addr, c.ListenAddr()) + + all = conns.all() + require.Equal(t, []connection{*c}, all) + + _, err = conns.pending(addr) + require.Equal(t, ErrConnectionExists, err) + require.Equal(t, 1, conns.IPCount(ip)) + require.Equal(t, 1, conns.OutgoingLen()) + require.Equal(t, 1, conns.PendingLen()) + require.Equal(t, 1, conns.Len()) + require.Empty(t, conns.mirrors) + require.False(t, c.HasIntroduced()) + require.Equal(t, addr, c.ListenAddr()) + + all = conns.all() + require.Equal(t, []connection{*c}, all) + + _, err = conns.introduced(addr, 1, &IntroductionMessage{}, &userAgent) + require.Equal(t, ErrConnectionStateNotConnected, err) + require.Equal(t, 1, conns.PendingLen()) + + c, err = conns.connected(addr, 1) + require.NoError(t, err) + + require.True(t, c.Outgoing) + require.Equal(t, addr, c.Addr) + require.Equal(t, port, c.ListenPort) + require.Equal(t, ConnectionStateConnected, c.State) + require.Equal(t, 1, conns.IPCount(ip)) + require.Equal(t, 1, conns.OutgoingLen()) + require.Equal(t, 0, conns.PendingLen()) + require.Equal(t, 1, conns.Len()) + require.Empty(t, c.Mirror) + require.Empty(t, conns.mirrors) + require.False(t, c.HasIntroduced()) + require.Equal(t, addr, c.ListenAddr()) + + all = conns.all() + require.Equal(t, []connection{*c}, all) + + m := &IntroductionMessage{ + // use a different port to make sure we don't overwrite the true listen port + ListenPort: port + 1, + Mirror: 1111, + ProtocolVersion: 2, + } + + c, err = conns.introduced(addr, 1, m, &userAgent) + require.NoError(t, err) + + require.True(t, c.Outgoing) + require.Equal(t, addr, c.Addr) + require.Equal(t, port, c.ListenPort) + require.Equal(t, ConnectionStateIntroduced, c.State) + require.Equal(t, 1, conns.IPCount(ip)) + require.Equal(t, 1, conns.OutgoingLen()) + require.Equal(t, 0, conns.PendingLen()) + require.Equal(t, 1, conns.Len()) + require.Equal(t, m.Mirror, c.Mirror) + require.Equal(t, m.ProtocolVersion, c.ProtocolVersion) + require.Len(t, conns.mirrors, 1) + require.Equal(t, port, getMirrorPort(conns, ip, c.Mirror)) + require.True(t, c.HasIntroduced()) + require.Equal(t, addr, c.ListenAddr()) + require.Equal(t, userAgent, c.UserAgent) + + all = conns.all() + require.Equal(t, []connection{*c}, all) + + err = conns.remove(addr, 1) + require.NoError(t, err) + + require.Equal(t, 0, conns.IPCount(ip)) + require.Equal(t, 0, conns.OutgoingLen()) + require.Equal(t, 0, conns.PendingLen()) + require.Equal(t, 0, conns.Len()) + require.Empty(t, conns.mirrors) + + all = conns.all() + require.Empty(t, all) + + c = conns.get(addr) + require.Nil(t, c) +} + +func TestConnectionsIncomingFlow(t *testing.T) { + conns := NewConnections() + + ip := "127.0.0.1" + port := uint16(6060) + addr := fmt.Sprintf("%s:%d", ip, port) + + all := conns.all() + require.Empty(t, all) + + require.Equal(t, 0, conns.IPCount(ip)) + require.Equal(t, 0, conns.OutgoingLen()) + require.Equal(t, 0, conns.PendingLen()) + require.Equal(t, 0, conns.Len()) + require.Empty(t, conns.mirrors) + + // Flow: connected, introduced + + c, err := conns.connected(addr, 1) + require.NoError(t, err) + + require.False(t, c.Outgoing) + require.Equal(t, addr, c.Addr) + require.Empty(t, c.ListenPort) + require.Equal(t, ConnectionStateConnected, c.State) + require.Equal(t, 1, conns.IPCount(ip)) + require.Equal(t, 0, conns.OutgoingLen()) + require.Equal(t, 0, conns.PendingLen()) + require.Equal(t, 1, conns.Len()) + require.Empty(t, c.Mirror) + require.Empty(t, conns.mirrors) + require.False(t, c.HasIntroduced()) + require.Empty(t, c.ListenAddr()) + + all = conns.all() + require.Equal(t, []connection{*c}, all) + + m := &IntroductionMessage{ + // use a different port to make sure that we use the self-reported listen port for incoming connections + ListenPort: port + 1, + Mirror: 1111, + ProtocolVersion: 2, + } + + c, err = conns.introduced(addr, 1, m, &userAgent) + require.NoError(t, err) + + require.False(t, c.Outgoing) + require.Equal(t, addr, c.Addr) + require.Equal(t, m.ListenPort, c.ListenPort) + require.Equal(t, ConnectionStateIntroduced, c.State) + require.Equal(t, 1, conns.IPCount(ip)) + require.Equal(t, 0, conns.OutgoingLen()) + require.Equal(t, 0, conns.PendingLen()) + require.Equal(t, 1, conns.Len()) + require.Equal(t, m.Mirror, c.Mirror) + require.Equal(t, m.ProtocolVersion, c.ProtocolVersion) + require.Len(t, conns.mirrors, 1) + require.Equal(t, m.ListenPort, getMirrorPort(conns, ip, c.Mirror)) + require.True(t, c.HasIntroduced()) + require.Equal(t, fmt.Sprintf("%s:%d", ip, m.ListenPort), c.ListenAddr()) + require.Equal(t, userAgent, c.UserAgent) + + all = conns.all() + require.Equal(t, []connection{*c}, all) + + err = conns.remove(addr, 1) + require.NoError(t, err) + + require.Equal(t, 0, conns.IPCount(ip)) + require.Equal(t, 0, conns.OutgoingLen()) + require.Equal(t, 0, conns.PendingLen()) + require.Equal(t, 0, conns.Len()) + require.Empty(t, conns.mirrors) + + all = conns.all() + require.Empty(t, all) + + c = conns.get(addr) + require.Nil(t, c) +} + +func TestConnectionsMultiple(t *testing.T) { + conns := NewConnections() + + addr1 := "127.0.0.1:6060" + addr2 := "127.0.0.1:6061" + + _, err := conns.pending(addr1) + require.NoError(t, err) + + _, err = conns.pending(addr2) + require.NoError(t, err) + + require.Equal(t, 2, conns.OutgoingLen()) + require.Equal(t, 2, conns.PendingLen()) + require.Equal(t, 2, conns.IPCount("127.0.0.1")) + + _, err = conns.connected(addr1, 1) + require.NoError(t, err) + require.Equal(t, 1, conns.PendingLen()) + + _, err = conns.connected(addr2, 2) + require.NoError(t, err) + require.Equal(t, 0, conns.PendingLen()) + + _, err = conns.introduced(addr1, 1, &IntroductionMessage{ + Mirror: 6, + ListenPort: 6060, + ProtocolVersion: 2, + }, &userAgent) + require.NoError(t, err) + require.Equal(t, 0, conns.PendingLen()) + require.Equal(t, 1, len(conns.mirrors)) + + // introduction fails if a base IP + mirror is already in use + _, err = conns.introduced(addr2, 2, &IntroductionMessage{ + Mirror: 6, + ListenPort: 6061, + ProtocolVersion: 2, + }, &userAgent) + require.Equal(t, ErrConnectionIPMirrorExists, err) + require.Equal(t, 0, conns.PendingLen()) + + c := conns.get(addr2) + require.Equal(t, ConnectionStateConnected, c.State) + + _, err = conns.introduced(addr2, 2, &IntroductionMessage{ + Mirror: 7, + ListenPort: 6061, + ProtocolVersion: 2, + }, &userAgent) + require.NoError(t, err) + require.Equal(t, 2, len(conns.mirrors)) + require.Equal(t, 2, conns.OutgoingLen()) + require.Equal(t, 0, conns.PendingLen()) + require.Equal(t, 2, conns.Len()) + require.Equal(t, 2, conns.IPCount("127.0.0.1")) + + // Add another connection with a different base IP but same mirror value + addr3 := "127.1.1.1:12345" + _, err = conns.connected(addr3, 3) + require.NoError(t, err) + + _, err = conns.introduced(addr3, 3, &IntroductionMessage{ + Mirror: 6, + ListenPort: 6060, + ProtocolVersion: 2, + }, &userAgent) + require.NoError(t, err) + + require.Equal(t, 2, len(conns.mirrors)) + require.Equal(t, 2, len(conns.mirrors[6])) + require.Equal(t, uint16(6060), getMirrorPort(conns, "127.0.0.1", 6)) + require.Equal(t, uint16(6061), getMirrorPort(conns, "127.0.0.1", 7)) + require.Equal(t, uint16(6060), getMirrorPort(conns, "127.1.1.1", 6)) + + require.Equal(t, 2, conns.OutgoingLen()) + require.Equal(t, 0, conns.PendingLen()) + require.Equal(t, 3, conns.Len()) + require.Equal(t, 2, conns.IPCount("127.0.0.1")) + require.Equal(t, 1, conns.IPCount("127.1.1.1")) + + err = conns.remove(addr1, 2) + require.Equal(t, ErrConnectionGnetIDMismatch, err) + require.Equal(t, 3, conns.Len()) + + err = conns.remove(addr1, 1) + require.NoError(t, err) + err = conns.remove(addr2, 2) + require.NoError(t, err) + err = conns.remove(addr3, 3) + require.NoError(t, err) + require.Empty(t, conns.mirrors) + require.Equal(t, 0, conns.Len()) + + err = conns.remove(addr1, 1) + require.Equal(t, ErrConnectionNotExist, err) +} + +func TestConnectionsErrors(t *testing.T) { + conns := NewConnections() + + _, err := conns.pending("foo") + testutil.RequireError(t, err, "address foo: missing port in address") + + _, err = conns.connected("foo", 1) + testutil.RequireError(t, err, "address foo: missing port in address") + + _, err = conns.introduced("foo", 1, &IntroductionMessage{}, &userAgent) + testutil.RequireError(t, err, "address foo: missing port in address") + + err = conns.remove("foo", 0) + testutil.RequireError(t, err, "address foo: missing port in address") + + _, err = conns.introduced("127.0.0.1:6060", 1, &IntroductionMessage{}, &userAgent) + require.Equal(t, ErrConnectionNotExist, err) + + _, err = conns.connected("127.0.0.1:6060", 0) + require.Equal(t, ErrInvalidGnetID, err) + + _, err = conns.introduced("127.0.0.1:6060", 0, &IntroductionMessage{}, &userAgent) + require.Equal(t, ErrInvalidGnetID, err) +} + +func TestConnectionsSetHeight(t *testing.T) { + conns := NewConnections() + addr := "127.0.0.1:6060" + height := uint64(1010) + + err := conns.SetHeight(addr, 1, height) + require.Equal(t, ErrConnectionNotExist, err) + + c, err := conns.connected(addr, 1) + require.NoError(t, err) + require.Empty(t, c.Height) + + err = conns.SetHeight(addr, 1, height) + require.NoError(t, err) + + err = conns.SetHeight(addr, 2, height) + require.Equal(t, ErrConnectionGnetIDMismatch, err) + + c = conns.get(addr) + require.NotNil(t, c) + require.Equal(t, height, c.Height) +} + +func TestConnectionsModifyMirrorPanics(t *testing.T) { + conns := NewConnections() + addr := "127.0.0.1:6060" + + _, err := conns.connected(addr, 1) + require.NoError(t, err) + + // modifying mirror value causes panic + require.Panics(t, func() { + conns.modify(addr, 1, func(c *ConnectionDetails) { // nolint: errcheck + c.Mirror++ + }) + }) +} + +func TestConnectionsStateTransitionErrors(t *testing.T) { + conns := NewConnections() + addr := "127.0.0.1:6060" + + _, err := conns.pending(addr) + require.NoError(t, err) + + // pending -> pending fails + _, err = conns.pending(addr) + require.Equal(t, ErrConnectionExists, err) + + // pending -> introduced fails + _, err = conns.introduced(addr, 1, &IntroductionMessage{}, &userAgent) + require.Equal(t, ErrConnectionStateNotConnected, err) + + _, err = conns.connected(addr, 1) + require.NoError(t, err) + + // connected -> connected fails + _, err = conns.connected(addr, 1) + require.Equal(t, ErrConnectionAlreadyConnected, err) + + // connected -> introduced fails if gnet ID does not match + _, err = conns.introduced(addr, 2, &IntroductionMessage{}, &userAgent) + require.Equal(t, ErrConnectionGnetIDMismatch, err) + + _, err = conns.introduced(addr, 1, &IntroductionMessage{}, &userAgent) + require.NoError(t, err) + + // introduced -> connected fails + _, err = conns.connected(addr, 1) + require.Equal(t, ErrConnectionAlreadyIntroduced, err) + + // introduced -> pending fails + _, err = conns.pending(addr) + require.Equal(t, ErrConnectionExists, err) + + // introduced -> introduced fails + _, err = conns.introduced(addr, 1, &IntroductionMessage{}, &userAgent) + require.Equal(t, ErrConnectionAlreadyIntroduced, err) +} diff --git a/src/daemon/daemon.go b/src/daemon/daemon.go index 3fcaac6284..2ef8f97c93 100644 --- a/src/daemon/daemon.go +++ b/src/daemon/daemon.go @@ -4,12 +4,16 @@ Package daemon controls the networking layer of the skycoin daemon package daemon import ( - "bytes" "errors" + "fmt" + "math/rand" "reflect" + "strings" "sync" "time" + "github.com/sirupsen/logrus" + "github.com/skycoin/skycoin/src/cipher" "github.com/skycoin/skycoin/src/coin" "github.com/skycoin/skycoin/src/daemon/gnet" @@ -17,46 +21,32 @@ import ( "github.com/skycoin/skycoin/src/util/elapse" "github.com/skycoin/skycoin/src/util/iputil" "github.com/skycoin/skycoin/src/util/logging" + "github.com/skycoin/skycoin/src/util/useragent" "github.com/skycoin/skycoin/src/visor" "github.com/skycoin/skycoin/src/visor/dbutil" ) var ( - // ErrDisconnectInvalidVersion invalid version - ErrDisconnectInvalidVersion gnet.DisconnectReason = errors.New("Invalid version") - // ErrDisconnectIntroductionTimeout timeout - ErrDisconnectIntroductionTimeout gnet.DisconnectReason = errors.New("Version timeout") - // ErrDisconnectVersionSendFailed version send failed - ErrDisconnectVersionSendFailed gnet.DisconnectReason = errors.New("Version send failed") - // ErrDisconnectIsBlacklisted is blacklisted - ErrDisconnectIsBlacklisted gnet.DisconnectReason = errors.New("Blacklisted") - // ErrDisconnectSelf self connnect - ErrDisconnectSelf gnet.DisconnectReason = errors.New("Self connect") - // ErrDisconnectConnectedTwice connect twice - ErrDisconnectConnectedTwice gnet.DisconnectReason = errors.New("Already connected") - // ErrDisconnectIdle idle - ErrDisconnectIdle gnet.DisconnectReason = errors.New("Idle") - // ErrDisconnectNoIntroduction no introduction - ErrDisconnectNoIntroduction gnet.DisconnectReason = errors.New("First message was not an Introduction") - // ErrDisconnectIPLimitReached ip limit reached - ErrDisconnectIPLimitReached gnet.DisconnectReason = errors.New("Maximum number of connections for this IP was reached") - // ErrDisconnectOtherError this is returned when a seemingly impossible error is encountered - // e.g. net.Conn.Addr() returns an invalid ip:port - ErrDisconnectOtherError gnet.DisconnectReason = errors.New("Incomprehensible error") - // ErrDisconnectMaxOutgoingConnectionsReached is returned when connection pool size is greater than the maximum allowed - ErrDisconnectMaxOutgoingConnectionsReached gnet.DisconnectReason = errors.New("Maximum outgoing connections was reached") - // ErrDisconnectBlockchainPubkeyNotMatched is returned when the blockchain pubkey in introduction does not match - ErrDisconnectBlockchainPubkeyNotMatched gnet.DisconnectReason = errors.New("Blockchain pubkey in Introduction message is not matched ") - // ErrDisconnectInvalidExtraData is returned when extra field can't be parsed as specific data type. - // e.g. ExtraData length in IntroductionMessage is not the same as cipher.PubKey - ErrDisconnectInvalidExtraData gnet.DisconnectReason = errors.New("Invalid extra data") - - // ErrOutgoingConnectionsDisabled is returned if outgoing connections are disabled - ErrOutgoingConnectionsDisabled = errors.New("Outgoing connections are disabled") + // ErrNetworkingDisabled is returned if networking is disabled + ErrNetworkingDisabled = errors.New("Networking is disabled") logger = logging.MustGetLogger("daemon") ) +// IsBroadcastFailure returns true if an error indicates that a broadcast operation failed +func IsBroadcastFailure(err error) bool { + switch err { + case ErrNetworkingDisabled, + gnet.ErrPoolEmpty, + gnet.ErrNoMatchingConnections, + gnet.ErrNoReachableConnections, + gnet.ErrNoAddresses: + return true + default: + return false + } +} + const ( daemonRunDurationThreshold = time.Millisecond * 200 ) @@ -79,23 +69,23 @@ func NewConfig() Config { Pex: pex.NewConfig(), Gateway: NewGatewayConfig(), Messages: NewMessagesConfig(), - Visor: visor.NewVisorConfig(), + Visor: visor.NewConfig(), } } // preprocess preprocess for config -func (cfg *Config) preprocess() Config { +func (cfg *Config) preprocess() (Config, error) { config := *cfg if config.Daemon.LocalhostOnly { if config.Daemon.Address == "" { local, err := iputil.LocalhostIP() if err != nil { - logger.Panicf("Failed to obtain localhost IP: %v", err) + logger.WithError(err).Panic("Failed to obtain localhost IP") } config.Daemon.Address = local } else { if !iputil.IsLocalhost(config.Daemon.Address) { - logger.Panicf("Invalid address for localhost-only: %s", config.Daemon.Address) + logger.WithField("addr", config.Daemon.Address).Panic("Invalid address for localhost-only") } } config.Pex.AllowLocalhost = true @@ -117,13 +107,35 @@ func (cfg *Config) preprocess() Config { } } - return config + if config.Daemon.MaxConnections < config.Daemon.MaxOutgoingConnections { + return Config{}, errors.New("MaxOutgoingConnections cannot be more than MaxConnections") + } + + if config.Daemon.MaxPendingConnections > config.Daemon.MaxOutgoingConnections { + config.Daemon.MaxPendingConnections = config.Daemon.MaxOutgoingConnections + } + + config.Pool.MaxConnections = config.Daemon.MaxConnections + config.Pool.MaxOutgoingConnections = config.Daemon.MaxOutgoingConnections + + userAgent, err := config.Daemon.UserAgent.Build() + if err != nil { + return Config{}, err + } + if userAgent == "" { + return Config{}, errors.New("user agent is required") + } + config.Daemon.userAgent = userAgent + + return config, nil } // DaemonConfig configuration for the Daemon type DaemonConfig struct { // nolint: golint // Protocol version. TODO -- manage version better - Version int32 + ProtocolVersion int32 + // Minimum accepted protocol version + MinProtocolVersion int32 // IP Address to serve on. Leave empty for automatic assignment Address string // BlockchainPubkey blockchain pubkey string @@ -134,13 +146,14 @@ type DaemonConfig struct { // nolint: golint DataDirectory string // How often to check and initiate an outgoing connection if needed OutgoingRate time.Duration - // How often to re-attempt to fill any missing private (aka required) - // connections + // How often to re-attempt to fill any missing private (aka required) connections PrivateRate time.Duration + // Maximum number of connections + MaxConnections int // Number of outgoing connections to maintain - OutgoingMax int + MaxOutgoingConnections int // Maximum number of connections to try at once - PendingMax int + MaxPendingConnections int // How long to wait for a version packet IntroductionWait time.Duration // How often to check for peers that have decided to stop communicating @@ -175,18 +188,25 @@ type DaemonConfig struct { // nolint: golint UnconfirmedRemoveInvalidRate time.Duration // Default "trusted" peers DefaultConnections []string + // User agent (sent in introduction messages) + UserAgent useragent.Data + userAgent string // parsed from UserAgent in preprocess() + // Random nonce value for detecting self-connection in introduction messages + Mirror uint32 } // NewDaemonConfig creates daemon config func NewDaemonConfig() DaemonConfig { return DaemonConfig{ - Version: 2, + ProtocolVersion: 2, + MinProtocolVersion: 2, Address: "", Port: 6677, OutgoingRate: time.Second * 5, PrivateRate: time.Second * 5, - OutgoingMax: 8, - PendingMax: 8, + MaxConnections: 128, + MaxOutgoingConnections: 8, + MaxPendingConnections: 8, IntroductionWait: time.Second * 30, CullInvalidRate: time.Second * 3, FlushAnnouncedTxnsRate: time.Second * 3, @@ -203,42 +223,34 @@ func NewDaemonConfig() DaemonConfig { BlockCreationInterval: 10, UnconfirmedRefreshRate: time.Minute, UnconfirmedRemoveInvalidRate: time.Minute, + Mirror: rand.New(rand.NewSource(time.Now().UTC().UnixNano())).Uint32(), } } //go:generate go install -//go:generate mockery -name Daemoner -case underscore -inpkg -testonly +//go:generate mockery -name daemoner -case underscore -inpkg -testonly -// Daemoner Daemon interface -type Daemoner interface { - SendMessage(addr string, msg gnet.Message) error - BroadcastMessage(msg gnet.Message) error +// daemoner Daemon interface +type daemoner interface { Disconnect(addr string, r gnet.DisconnectReason) error - IsDefaultConnection(addr string) bool - IsMaxDefaultConnectionsReached() (bool, error) - PexConfig() pex.Config - RandomExchangeable(n int) pex.Peers - AddPeer(addr string) error - AddPeers(addrs []string) int - SetHasIncomingPort(addr string) error - IncreaseRetryTimes(addr string) - ResetRetryTimes(addr string) - RecordPeerHeight(addr string, height uint64) - GetSignedBlocksSince(seq, count uint64) ([]coin.SignedBlock, error) - HeadBkSeq() (uint64, bool, error) - ExecuteSignedBlock(b coin.SignedBlock) error - GetUnconfirmedUnknown(txns []cipher.SHA256) ([]cipher.SHA256, error) - GetUnconfirmedKnown(txns []cipher.SHA256) (coin.Transactions, error) - InjectTransaction(txn coin.Transaction) (bool, *visor.ErrTxnViolatesSoftConstraint, error) - Mirror() uint32 - DaemonConfig() DaemonConfig - BlockchainPubkey() cipher.PubKey - RecordMessageEvent(m AsyncMessage, c *gnet.MessageContext) error - RecordConnectionMirror(addr string, mirror uint32) error - GetMirrorPort(addr string, mirror uint32) (uint16, bool) - RemoveFromExpectingIntroductions(addr string) - RequestBlocksFromAddr(addr string) error - AnnounceAllTxns() error + sendMessage(addr string, msg gnet.Message) error + broadcastMessage(msg gnet.Message) (int, error) + disconnectNow(addr string, r gnet.DisconnectReason) error + addPeers(addrs []string) int + recordPeerHeight(addr string, gnetID, height uint64) + getSignedBlocksSince(seq, count uint64) ([]coin.SignedBlock, error) + headBkSeq() (uint64, bool, error) + executeSignedBlock(b coin.SignedBlock) error + filterKnownUnconfirmed(txns []cipher.SHA256) ([]cipher.SHA256, error) + getKnownUnconfirmed(txns []cipher.SHA256) (coin.Transactions, error) + requestBlocksFromAddr(addr string) error + announceAllTxns() error + daemonConfig() DaemonConfig + pexConfig() pex.Config + injectTransaction(txn coin.Transaction) (bool, *visor.ErrTxnViolatesSoftConstraint, error) + recordMessageEvent(m asyncMessage, c *gnet.MessageContext) error + connectionIntroduced(addr string, gnetID uint64, m *IntroductionMessage, userAgent *useragent.Data) (*connection, error) + sendRandomPeers(addr string) error } // Daemon stateful properties of the daemon @@ -255,47 +267,23 @@ type Daemon struct { // Cache of announced transactions that are flushed to the database periodically announcedTxns *announcedTxnsCache - // Cache of reported peer blockchain heights - Heights *peerBlockchainHeights - - // Separate index of outgoing connections. The pool aggregates all - // connections. - outgoingConnections *OutgoingConnections - // Number of connections waiting to be formed or timeout - pendingConnections *PendingConnections - // Keep track of unsolicited clients who should notify us of their version - expectingIntroductions *ExpectIntroductions - // Keep track of a connection's mirror value, to avoid double - // connections (one to their listener, and one to our listener) - // Maps from addr to mirror value - connectionMirrors *ConnectionMirrors - // Maps from mirror value to a map of ip (no port) - // We use a map of ip as value because multiple peers can have the same - // mirror (to avoid attacks enabled by our use of mirrors), - // but only one per base ip - mirrorConnections *MirrorConnections - // Client connection callbacks - onConnectEvent chan ConnectEvent - // Client disconnection callbacks - onDisconnectEvent chan DisconnectEvent - // Connection failure events - connectionErrors chan ConnectionError - // Tracking connections from the same base IP. Multiple connections - // from the same base IP are allowed but limited. - ipCounts *IPCount - // Message handling queue - messageEvents chan MessageEvent + // Cache of connection metadata + connections *Connections + // connect, disconnect, message, error events channel + events chan interface{} // quit channel quit chan struct{} // done channel done chan struct{} - // log buffer - LogBuff bytes.Buffer } // NewDaemon returns a Daemon with primitives allocated func NewDaemon(config Config, db *dbutil.DB) (*Daemon, error) { - config = config.preprocess() + config, err := config.preprocess() + if err != nil { + return nil, err + } + vs, err := visor.NewVisor(config.Visor, db) if err != nil { return nil, err @@ -313,23 +301,10 @@ func NewDaemon(config Config, db *dbutil.DB) (*Daemon, error) { visor: vs, announcedTxns: newAnnouncedTxnsCache(), - Heights: newPeerBlockchainHeights(), - - expectingIntroductions: NewExpectIntroductions(), - connectionMirrors: NewConnectionMirrors(), - mirrorConnections: NewMirrorConnections(), - ipCounts: NewIPCount(), - // TODO -- if there are performance problems from blocking chans, - // Its because we are connecting to more things than OutgoingMax - // if we have private peers - onConnectEvent: make(chan ConnectEvent, config.Pool.MaxConnections*2), - onDisconnectEvent: make(chan DisconnectEvent, config.Pool.MaxConnections*2), - connectionErrors: make(chan ConnectionError, config.Pool.MaxConnections*2), - outgoingConnections: NewOutgoingConnections(config.Daemon.OutgoingMax), - pendingConnections: NewPendingConnections(config.Daemon.PendingMax), - messageEvents: make(chan MessageEvent, config.Pool.EventChannelSize), - quit: make(chan struct{}), - done: make(chan struct{}), + connections: NewConnections(), + events: make(chan interface{}, config.Pool.EventChannelSize), + quit: make(chan struct{}), + done: make(chan struct{}), } d.Gateway = NewGateway(config.Gateway, d) @@ -341,25 +316,28 @@ func NewDaemon(config Config, db *dbutil.DB) (*Daemon, error) { // ConnectEvent generated when a client connects type ConnectEvent struct { + GnetID uint64 Addr string Solicited bool } // DisconnectEvent generated when a connection terminated type DisconnectEvent struct { + GnetID uint64 Addr string Reason gnet.DisconnectReason } -// ConnectionError represent a failure to connect/dial a connection, with context -type ConnectionError struct { - Addr string - Error error +// ConnectFailureEvent represent a failure to connect/dial a connection, with context +type ConnectFailureEvent struct { + Addr string + Solicited bool + Error error } -// MessageEvent encapsulates a deserialized message from the network -type MessageEvent struct { - Message AsyncMessage +// messageEvent encapsulates a deserialized message from the network +type messageEvent struct { + Message asyncMessage Context *gnet.MessageContext } @@ -401,6 +379,8 @@ func (dm *Daemon) Run() error { defer logger.Info("Daemon closed") defer close(dm.done) + logger.Infof("Daemon UserAgent is %s", dm.Config.userAgent) + errC := make(chan error, 5) var wg sync.WaitGroup @@ -431,7 +411,7 @@ func (dm *Daemon) Run() error { blockInterval := time.Duration(dm.Config.BlockCreationInterval) blockCreationTicker := time.NewTicker(time.Second * blockInterval) - if !dm.visor.Config.IsMaster { + if !dm.visor.Config.IsBlockPublisher { blockCreationTicker.Stop() } @@ -460,12 +440,16 @@ func (dm *Daemon) Run() error { flushAnnouncedTxnsTicker := time.NewTicker(dm.Config.FlushAnnouncedTxnsRate) defer flushAnnouncedTxnsTicker.Stop() - // Connect to trusted peers + // Connect to all trusted peers on startup to try to ensure a connection + // establishes quickly. + // The number of connections to default peers is restricted; + // if multiple connections succeed, extra connections beyond the limit will + // be disconnected. if !dm.Config.DisableOutgoingConnections { wg.Add(1) go func() { defer wg.Done() - dm.connectToTrustPeer() + dm.connectToTrustedPeers() }() } @@ -525,15 +509,26 @@ loop: } m := NewGetPeersMessage() - if err := dm.pool.Pool.BroadcastMessage(m); err != nil { - logger.Error(err) + if _, err := dm.broadcastMessage(m); err != nil { + logger.WithError(err).Error("Broadcast GetPeersMessage failed") + continue } case <-clearStaleConnectionsTicker.C: // Remove connections that haven't said anything in a while elapser.Register("clearStaleConnectionsTicker") if !dm.Config.DisableNetworking { - dm.pool.clearStaleConnections() + conns, err := dm.pool.getStaleConnections() + if err != nil { + logger.WithError(err).Error("getStaleConnections failed") + continue + } + + for _, addr := range conns { + if err := dm.Disconnect(addr, ErrDisconnectIdle); err != nil { + logger.WithError(err).WithField("addr", addr).Error("Disconnect") + } + } } case <-idleCheckTicker.C: @@ -546,12 +541,7 @@ loop: case <-outgoingConnectionsTicker.C: // Fill up our outgoing connections elapser.Register("outgoingConnectionsTicker") - trustPeerNum := len(dm.pex.Trusted()) - if !dm.Config.DisableOutgoingConnections && - dm.outgoingConnections.Len() < (dm.Config.OutgoingMax+trustPeerNum) && - dm.pendingConnections.Len() < dm.Config.PendingMax { - dm.connectToRandomPeer() - } + dm.connectToRandomPeer() case <-privateConnectionsTicker.C: // Always try to stay connected to our private peers @@ -561,33 +551,13 @@ loop: dm.makePrivateConnections() } - case r := <-dm.onConnectEvent: - // Process callbacks for when a client connects. No disconnect chan - // is needed because the callback is triggered by HandleDisconnectEvent - // which is already select{}ed here - elapser.Register("dm.onConnectEvent") - if dm.Config.DisableNetworking { - logger.Error("There should be no connect events") - return nil - } - dm.onConnect(r) - - case de := <-dm.onDisconnectEvent: - elapser.Register("dm.onDisconnectEvent") + case r := <-dm.events: + elapser.Register("dm.event") if dm.Config.DisableNetworking { - logger.Error("There should be no disconnect events") - return nil + logger.Critical().Error("Networking is disabled, there should be no events") + } else { + dm.handleEvent(r) } - dm.onDisconnect(de) - - case r := <-dm.connectionErrors: - // Handle connection errors - elapser.Register("dm.connectionErrors") - if dm.Config.DisableNetworking { - logger.Error("There should be no connection errors") - return nil - } - dm.handleConnectionError(r) case <-flushAnnouncedTxnsTicker.C: elapser.Register("flushAnnouncedTxnsTicker") @@ -595,17 +565,7 @@ loop: if err := dm.visor.SetTransactionsAnnounced(txns); err != nil { logger.WithError(err).Error("Failed to set unconfirmed txn announce time") - return err - } - - case m := <-dm.messageEvents: - // Message handlers - elapser.Register("dm.messageEvents") - if dm.Config.DisableNetworking { - logger.Error("There should be no message events") - return nil } - dm.processMessageEvent(m) case req := <-dm.Gateway.requests: // Process any pending RPC requests @@ -615,18 +575,22 @@ loop: } case <-blockCreationTicker.C: - // Create blocks, if master chain + // Create blocks, if block publisher elapser.Register("blockCreationTicker.C") - if dm.visor.Config.IsMaster { - sb, err := dm.CreateAndPublishBlock() + if dm.visor.Config.IsBlockPublisher { + sb, err := dm.createAndPublishBlock() if err != nil { - logger.Errorf("Failed to create and publish block: %v", err) + logger.WithError(err).Error("Failed to create and publish block") continue } // Not a critical error, but we want it visible in logs head := sb.Block.Head - logger.Critical().Infof("Created and published a new block, version=%d seq=%d time=%d", head.Version, head.BkSeq, head.Time) + logger.Critical().WithFields(logrus.Fields{ + "version": head.Version, + "seq": head.BkSeq, + "time": head.Time, + }).Info("Created and published a new block") } case <-unconfirmedRefreshTicker.C: @@ -634,12 +598,12 @@ loop: // Get the transactions that turn to valid validTxns, err := dm.visor.RefreshUnconfirmed() if err != nil { - logger.Errorf("dm.Visor.RefreshUnconfirmed failed: %v", err) + logger.WithError(err).Error("dm.Visor.RefreshUnconfirmed failed") continue } // Announce these transactions - if err := dm.AnnounceTxns(validTxns); err != nil { - logger.WithError(err).Warning("AnnounceTxns failed") + if err := dm.announceTxns(validTxns); err != nil { + logger.WithError(err).Warning("announceTxns failed") } case <-unconfirmedRemoveInvalidTicker.C: @@ -647,7 +611,7 @@ loop: // Remove transactions that become invalid (violating hard constraints) removedTxns, err := dm.visor.RemoveInvalidUnconfirmed() if err != nil { - logger.Errorf("dm.Visor.RemoveInvalidUnconfirmed failed: %v", err) + logger.WithError(err).Error("dm.Visor.RemoveInvalidUnconfirmed failed") continue } if len(removedTxns) > 0 { @@ -656,14 +620,14 @@ loop: case <-blocksRequestTicker.C: elapser.Register("blocksRequestTicker") - if err := dm.RequestBlocks(); err != nil { - logger.WithError(err).Warning("RequestBlocks failed") + if err := dm.requestBlocks(); err != nil { + logger.WithError(err).Warning("requestBlocks failed") } case <-blocksAnnounceTicker.C: elapser.Register("blocksAnnounceTicker") - if err := dm.AnnounceBlocks(); err != nil { - logger.WithError(err).Warning("AnnounceBlocks failed") + if err := dm.announceBlocks(); err != nil { + logger.WithError(err).Warning("announceBlocks failed") } case setupErr = <-errC: @@ -681,27 +645,6 @@ loop: return nil } -// GetListenPort returns the ListenPort for a given address. -// If no port is found, 0 is returned. -func (dm *Daemon) GetListenPort(addr string) uint16 { - m, ok := dm.connectionMirrors.Get(addr) - if !ok { - return 0 - } - - ip, _, err := iputil.SplitAddr(addr) - if err != nil { - logger.Errorf("GetListenPort received invalid addr: %v", err) - return 0 - } - - p, ok := dm.mirrorConnections.Get(m, ip) - if !ok { - return 0 - } - return p -} - // Connects to a given peer. Returns an error if no connection attempt was // made. If the connection attempt itself fails, the error is sent to // the connectionErrors channel. @@ -712,35 +655,37 @@ func (dm *Daemon) connectToPeer(p pex.Peer) error { a, _, err := iputil.SplitAddr(p.Addr) if err != nil { - logger.Warningf("PEX gave us an invalid peer: %v", err) + logger.Critical().WithField("addr", p.Addr).WithError(err).Warning("PEX gave us an invalid peer") return errors.New("Invalid peer") } + if dm.Config.LocalhostOnly && !iputil.IsLocalhost(a) { return errors.New("Not localhost") } - conned, err := dm.pool.Pool.IsConnExist(p.Addr) - if err != nil { - return err + if c := dm.connections.get(p.Addr); c != nil { + return errors.New("Already connected to this peer") } - if conned { - return errors.New("Already connected") + cnt := dm.connections.IPCount(a) + if !dm.Config.LocalhostOnly && cnt != 0 { + return errors.New("Already connected to a peer with this base IP") } - if _, ok := dm.pendingConnections.Get(p.Addr); ok { - return errors.New("Connection is pending") - } - cnt, ok := dm.ipCounts.Get(a) - if !dm.Config.LocalhostOnly && ok && cnt != 0 { - return errors.New("Already connected to a peer with this base IP") + logger.WithField("addr", p.Addr).Debug("Establishing outgoing connection") + + if _, err := dm.connections.pending(p.Addr); err != nil { + logger.Critical().WithError(err).WithField("addr", p.Addr).Error("dm.connections.pending failed") + return err } - logger.Debugf("Trying to connect to %s", p.Addr) - dm.pendingConnections.Add(p) go func() { if err := dm.pool.Pool.Connect(p.Addr); err != nil { - dm.connectionErrors <- ConnectionError{p.Addr, err} + dm.events <- ConnectFailureEvent{ + Addr: p.Addr, + Solicited: true, + Error: err, + } } }() return nil @@ -754,14 +699,14 @@ func (dm *Daemon) makePrivateConnections() { peers := dm.pex.Private() for _, p := range peers { - logger.Infof("Private peer attempt: %s", p.Addr) + logger.WithField("addr", p.Addr).Info("Private peer attempt") if err := dm.connectToPeer(p); err != nil { - logger.Debugf("Did not connect to private peer: %v", err) + logger.WithField("addr", p.Addr).WithError(err).Debug("Did not connect to private peer") } } } -func (dm *Daemon) connectToTrustPeer() { +func (dm *Daemon) connectToTrustedPeers() { if dm.Config.DisableOutgoingConnections { return } @@ -781,94 +726,49 @@ func (dm *Daemon) connectToRandomPeer() { if dm.Config.DisableOutgoingConnections { return } + if dm.connections.OutgoingLen() >= dm.Config.MaxOutgoingConnections { + return + } + if dm.connections.PendingLen() >= dm.Config.MaxPendingConnections { + return + } + if dm.connections.Len() >= dm.Config.MaxConnections { + return + } // Make a connection to a random (public) peer - peers := dm.pex.RandomPublic(dm.Config.OutgoingMax) + peers := dm.pex.RandomPublic(dm.Config.MaxOutgoingConnections - dm.connections.OutgoingLen()) for _, p := range peers { - // Check if the peer has public port - if p.HasIncomingPort { - // Try to connect the peer if it's ip:mirror does not exist - if _, exist := dm.GetMirrorPort(p.Addr, dm.Messages.Mirror); !exist { - if err := dm.connectToPeer(p); err != nil { - logger.WithError(err).WithField("addr", p.Addr).Warning("connectToPeer failed") - } - continue - } - } else { - // Try to connect to the peer if we don't know whether the peer have public port - if err := dm.connectToPeer(p); err != nil { - logger.WithError(err).WithField("addr", p.Addr).Warning("connectToPeer failed") - } + if err := dm.connectToPeer(p); err != nil { + logger.WithError(err).WithField("addr", p.Addr).Warning("connectToPeer failed") } } + // TODO -- don't reset if not needed? if len(peers) == 0 { - // Reset the retry times of all peers, dm.pex.ResetAllRetryTimes() } } -// We remove a peer from the Pex if we failed to connect -// TODO - On failure to connect, use exponential backoff, not peer list -func (dm *Daemon) handleConnectionError(c ConnectionError) { - logger.Debugf("Failed to connect to %s with error: %v", c.Addr, c.Error) - dm.pendingConnections.Remove(c.Addr) - - dm.pex.IncreaseRetryTimes(c.Addr) -} - -// Removes unsolicited connections who haven't sent a version +// Removes connections who haven't sent a version after connecting func (dm *Daemon) cullInvalidConnections() { - // This method only handles the erroneous people from the DHT, but not - // malicious nodes now := time.Now().UTC() - addrs, err := dm.expectingIntroductions.CullInvalidConns( - func(addr string, t time.Time) (bool, error) { - conned, err := dm.pool.Pool.IsConnExist(addr) - if err != nil { - return false, err - } - - // Do not remove trusted peers - if dm.isTrustedPeer(addr) { - return false, nil - } - - if !conned { - return true, nil - } - - if t.Add(dm.Config.IntroductionWait).Before(now) { - return true, nil - } - return false, nil - }) - - if err != nil { - logger.Errorf("expectingIntroduction cull invalid connections failed: %v", err) - return - } - - for _, a := range addrs { - exist, err := dm.pool.Pool.IsConnExist(a) - if err != nil { - logger.Error(err) - return + for _, c := range dm.connections.all() { + if c.State != ConnectionStateConnected { + continue } - if exist { - logger.Infof("Removing %s for not sending a version", a) - if err := dm.pool.Pool.Disconnect(a, ErrDisconnectIntroductionTimeout); err != nil { - logger.Error(err) - return + if c.ConnectedAt.Add(dm.Config.IntroductionWait).Before(now) { + logger.WithField("addr", c.Addr).Info("Disconnecting peer for not sending a version") + if err := dm.Disconnect(c.Addr, ErrDisconnectIntroductionTimeout); err != nil { + logger.WithError(err).WithField("addr", c.Addr).Error("Disconnect") } - dm.pex.RemovePeer(a) } } } func (dm *Daemon) isTrustedPeer(addr string) bool { - peer, ok := dm.pex.GetPeerByAddr(addr) + peer, ok := dm.pex.GetPeer(addr) if !ok { return false } @@ -876,198 +776,206 @@ func (dm *Daemon) isTrustedPeer(addr string) bool { return peer.Trusted } -// RecordMessageEvent records an AsyncMessage to the messageEvent chan. Do not access +// recordMessageEvent records an asyncMessage to the messageEvent chan. Do not access // messageEvent directly. -func (dm *Daemon) RecordMessageEvent(m AsyncMessage, c *gnet.MessageContext) error { - dm.messageEvents <- MessageEvent{m, c} +func (dm *Daemon) recordMessageEvent(m asyncMessage, c *gnet.MessageContext) error { + dm.events <- messageEvent{ + Message: m, + Context: c, + } return nil } -// check if the connection needs introduction message -func (dm *Daemon) needsIntro(addr string) bool { - _, exist := dm.expectingIntroductions.Get(addr) - return exist -} +func (dm *Daemon) handleEvent(e interface{}) { + switch x := e.(type) { + case messageEvent: + dm.onMessageEvent(x) + case ConnectEvent: + dm.onConnectEvent(x) + case DisconnectEvent: + dm.onDisconnectEvent(x) + case ConnectFailureEvent: + dm.onConnectFailure(x) + default: + logger.WithFields(logrus.Fields{ + "type": fmt.Sprintf("%T", e), + "value": fmt.Sprintf("%+v", e), + }).Panic("Invalid object in events queue") + } +} + +func (dm *Daemon) onMessageEvent(e messageEvent) { + // If the connection does not exist or the gnet ID is different, abort message processing + // This can occur because messageEvents for a given connection may occur + // after that connection has disconnected. + c := dm.connections.get(e.Context.Addr) + if c == nil { + logger.WithFields(logrus.Fields{ + "addr": e.Context.Addr, + "messageType": fmt.Sprintf("%T", e.Message), + }).Info("onMessageEvent no connection found") + return + } -// Processes a queued AsyncMessage. -func (dm *Daemon) processMessageEvent(e MessageEvent) { - // The first message received must be an Introduction - // We have to check at process time and not record time because - // Introduction message does not update ExpectingIntroductions until its - // Process() is called - // _, needsIntro := self.expectingIntroductions[e.Context.Addr] - // if needsIntro { - if dm.needsIntro(e.Context.Addr) { - _, isIntro := e.Message.(*IntroductionMessage) - if !isIntro { - if err := dm.pool.Pool.Disconnect(e.Context.Addr, ErrDisconnectNoIntroduction); err != nil { + if c.gnetID != e.Context.ConnID { + logger.WithFields(logrus.Fields{ + "addr": e.Context.Addr, + "connGnetID": c.gnetID, + "contextGnetID": e.Context.ConnID, + "messageType": fmt.Sprintf("%T", e.Message), + }).Info("onMessageEvent connection gnetID does not match") + return + } + + // The first message received must be INTR, DISC or GIVP + if !c.HasIntroduced() { + switch e.Message.(type) { + case *IntroductionMessage, *DisconnectMessage, *GivePeersMessage: + default: + logger.WithFields(logrus.Fields{ + "addr": e.Context.Addr, + "messageType": fmt.Sprintf("%T", e.Message), + }).Info("needsIntro but first message is not INTR, DISC or GIVP") + if err := dm.Disconnect(e.Context.Addr, ErrDisconnectNoIntroduction); err != nil { logger.WithError(err).WithField("addr", e.Context.Addr).Error("Disconnect") } + return } } - e.Message.Process(dm) -} -// Called when a ConnectEvent is processed off the onConnectEvent channel -func (dm *Daemon) onConnect(e ConnectEvent) { - a := e.Addr + e.Message.process(dm) +} - if e.Solicited { - logger.Infof("Connected to peer: %s (outgoing)", a) - } else { - logger.Infof("Connected to peer: %s (incoming)", a) +func (dm *Daemon) onConnectEvent(e ConnectEvent) { + fields := logrus.Fields{ + "addr": e.Addr, + "outgoing": e.Solicited, + "gnetID": e.GnetID, } + logger.WithFields(fields).Info("onConnectEvent") - dm.pendingConnections.Remove(a) - - exist, err := dm.pool.Pool.IsConnExist(a) + // Update the connections state machine first + c, err := dm.connections.connected(e.Addr, e.GnetID) if err != nil { - logger.Error(err) + logger.Critical().WithError(err).WithFields(fields).Error("connections.Connected failed") + if err := dm.Disconnect(e.Addr, ErrDisconnectUnexpectedError); err != nil { + logger.WithError(err).WithFields(fields).Error("Disconnect") + } return } - if !exist { - logger.Warning("While processing an onConnect event, no pool connection was found") - return + // The connection should already be known as outgoing/solicited due to an earlier connections.pending call. + // If they do not match, there is e.Addr flaw in the concept or implementation of the state machine. + if c.Outgoing != e.Solicited { + logger.Critical().WithFields(fields).Warning("Connection.Outgoing does not match ConnectEvent.Solicited state") } - if dm.ipCountMaxed(a) { - logger.Infof("Max connections for %s reached, disconnecting", a) - if err := dm.pool.Pool.Disconnect(a, ErrDisconnectIPLimitReached); err != nil { - logger.WithError(err).WithField("addr", a).Error("Disconnect") + if dm.ipCountMaxed(e.Addr) { + logger.WithFields(fields).Info("Max connections for this IP address reached, disconnecting") + if err := dm.Disconnect(e.Addr, ErrDisconnectIPLimitReached); err != nil { + logger.WithError(err).WithFields(fields).Error("Disconnect") } return } - dm.recordIPCount(a) - - if e.Solicited { - // Disconnect if the max outgoing connections is reached - n, err := dm.pool.Pool.OutgoingConnectionsNum() - if err != nil { - logger.WithError(err).Error("get outgoing connections number failed") - return - } - - if n > dm.Config.OutgoingMax { - logger.Warningf("max outgoing connections is reached, disconnecting %v", a) - if err := dm.pool.Pool.Disconnect(a, ErrDisconnectMaxOutgoingConnectionsReached); err != nil { - logger.WithError(err).WithField("addr", a).Error("Disconnect") - } - return - } - - dm.outgoingConnections.Add(a) - } + logger.WithFields(fields).Debug("Sending introduction message") - dm.expectingIntroductions.Add(a, time.Now().UTC()) - logger.Debugf("Sending introduction message to %s, mirror:%d", a, dm.Messages.Mirror) - m := NewIntroductionMessage(dm.Messages.Mirror, dm.Config.Version, dm.pool.Pool.Config.Port, dm.Config.BlockchainPubkey[:]) - if err := dm.pool.Pool.SendMessage(a, m); err != nil { - logger.Errorf("Send IntroductionMessage to %s failed: %v", a, err) + m := NewIntroductionMessage(dm.Config.Mirror, dm.Config.ProtocolVersion, dm.pool.Pool.Config.Port, dm.Config.BlockchainPubkey, dm.Config.userAgent) + if err := dm.sendMessage(e.Addr, m); err != nil { + logger.WithFields(fields).WithError(err).Error("Send IntroductionMessage failed") + return } } -func (dm *Daemon) onDisconnect(e DisconnectEvent) { - logger.Infof("%s disconnected because: %v", e.Addr, e.Reason) - - dm.outgoingConnections.Remove(e.Addr) - dm.expectingIntroductions.Remove(e.Addr) - dm.Heights.Remove(e.Addr) - dm.removeIPCount(e.Addr) - dm.removeConnectionMirror(e.Addr) -} +func (dm *Daemon) onDisconnectEvent(e DisconnectEvent) { + fields := logrus.Fields{ + "addr": e.Addr, + "reason": e.Reason, + "gnetID": e.GnetID, + } + logger.WithFields(fields).Info("onDisconnectEvent") -// Triggered when an gnet.Connection terminates -func (dm *Daemon) onGnetDisconnect(addr string, reason gnet.DisconnectReason) { - e := DisconnectEvent{ - Addr: addr, - Reason: reason, + if err := dm.connections.remove(e.Addr, e.GnetID); err != nil { + logger.WithError(err).WithFields(fields).Error("connections.Remove failed") + return } - select { - case dm.onDisconnectEvent <- e: + + // TODO -- blacklist peer for certain reasons, not just remove + switch e.Reason { + case ErrDisconnectIntroductionTimeout, + ErrDisconnectBlockchainPubkeyNotMatched, + ErrDisconnectInvalidExtraData, + ErrDisconnectInvalidUserAgent: + if !dm.isTrustedPeer(e.Addr) { + dm.pex.RemovePeer(e.Addr) + } + case ErrDisconnectNoIntroduction, + ErrDisconnectVersionNotSupported, + ErrDisconnectSelf: + dm.pex.IncreaseRetryTimes(e.Addr) default: - logger.Warning("onDisconnectEvent channel is full") + switch e.Reason.Error() { + case "read failed: EOF": + dm.pex.IncreaseRetryTimes(e.Addr) + } } } -// Triggered when an gnet.Connection is connected -func (dm *Daemon) onGnetConnect(addr string, solicited bool) { - dm.onConnectEvent <- ConnectEvent{Addr: addr, Solicited: solicited} -} +func (dm *Daemon) onConnectFailure(c ConnectFailureEvent) { + // Remove the pending connection from connections and update the retry times in pex + logger.WithField("addr", c.Addr).WithError(c.Error).Debug("onConnectFailure") -// Returns whether the ipCount maximum has been reached -func (dm *Daemon) ipCountMaxed(addr string) bool { - ip, _, err := iputil.SplitAddr(addr) - if err != nil { - logger.Warningf("ipCountMaxed called with invalid addr: %v", err) - return true + // onConnectFailure should only trigger for "pending" connections which have gnet ID 0; + // connections in any other state will have a nonzero gnet ID. + // if the connection is in a different state, the gnet ID will not match, the connection + // won't be removed and we'll receive an error. + // If this happens, it is a bug, and the connections state may be corrupted. + if err := dm.connections.remove(c.Addr, 0); err != nil { + logger.Critical().WithField("addr", c.Addr).WithError(err).Error("connections.remove") } - if cnt, ok := dm.ipCounts.Get(ip); ok { - return cnt >= dm.Config.IPCountsMax + if strings.HasSuffix(c.Error.Error(), "connect: connection refused") { + dm.pex.IncreaseRetryTimes(c.Addr) } - return false } -// Adds base IP to ipCount or returns error if max is reached -func (dm *Daemon) recordIPCount(addr string) { - ip, _, err := iputil.SplitAddr(addr) - if err != nil { - logger.Warningf("recordIPCount called with invalid addr: %v", err) - return +// onGnetDisconnect triggered when a gnet.Connection terminates +func (dm *Daemon) onGnetDisconnect(addr string, gnetID uint64, reason gnet.DisconnectReason) { + dm.events <- DisconnectEvent{ + GnetID: gnetID, + Addr: addr, + Reason: reason, } - dm.ipCounts.Increase(ip) } -// Removes base IP from ipCount -func (dm *Daemon) removeIPCount(addr string) { - ip, _, err := iputil.SplitAddr(addr) - if err != nil { - logger.Warningf("removeIPCount called with invalid addr: %v", err) - return +// onGnetConnect Triggered when a gnet.Connection connects +func (dm *Daemon) onGnetConnect(addr string, gnetID uint64, solicited bool) { + dm.events <- ConnectEvent{ + GnetID: gnetID, + Addr: addr, + Solicited: solicited, } - dm.ipCounts.Decrease(ip) } -// RecordConnectionMirror adds addr + mirror to the connectionMirror mappings -func (dm *Daemon) RecordConnectionMirror(addr string, mirror uint32) error { - ip, port, err := iputil.SplitAddr(addr) - if err != nil { - logger.Warningf("RecordConnectionMirror called with invalid addr: %v", err) - return err +// onGnetConnectFailure triggered when a gnet.Connection fails to connect +func (dm *Daemon) onGnetConnectFailure(addr string, solicited bool, err error) { + dm.events <- ConnectFailureEvent{ + Addr: addr, + Solicited: solicited, + Error: err, } - dm.connectionMirrors.Add(addr, mirror) - dm.mirrorConnections.Add(mirror, ip, port) - return nil } -// Removes an addr from the connectionMirror mappings -func (dm *Daemon) removeConnectionMirror(addr string) { - mirror, ok := dm.connectionMirrors.Get(addr) - if !ok { - return - } +// Returns whether the ipCount maximum has been reached. +// Always false when using LocalhostOnly config. +func (dm *Daemon) ipCountMaxed(addr string) bool { ip, _, err := iputil.SplitAddr(addr) if err != nil { - logger.Warningf("removeConnectionMirror called with invalid addr: %v", err) - return + logger.Critical().WithField("addr", addr).Error("ipCountMaxed called with invalid addr") + return true } - // remove ip from specific mirror - dm.mirrorConnections.Remove(mirror, ip) - - dm.connectionMirrors.Remove(addr) -} - -// GetMirrorPort returns whether an addr+mirror's port and whether the port exists -func (dm *Daemon) GetMirrorPort(addr string, mirror uint32) (uint16, bool) { - ip, _, err := iputil.SplitAddr(addr) - if err != nil { - logger.Warningf("getMirrorPort called with invalid addr: %v", err) - return 0, false - } - return dm.mirrorConnections.Get(mirror, ip) + return !dm.Config.LocalhostOnly && dm.connections.IPCount(ip) >= dm.Config.IPCountsMax } // When an async message send finishes, its result is handled by this. @@ -1075,23 +983,31 @@ func (dm *Daemon) GetMirrorPort(addr string, mirror uint32) (uint16, bool) { // outside of the daemon run loop func (dm *Daemon) handleMessageSendResult(r gnet.SendResult) { if r.Error != nil { - logger.Warningf("Failed to send %s to %s: %v", reflect.TypeOf(r.Message), r.Addr, r.Error) + logger.WithError(r.Error).WithFields(logrus.Fields{ + "addr": r.Addr, + "msgType": reflect.TypeOf(r.Message), + }).Warning("Failed to send message") return } - switch r.Message.(type) { - case SendingTxnsMessage: - dm.announcedTxns.add(r.Message.(SendingTxnsMessage).GetFiltered()) - default: + + if m, ok := r.Message.(SendingTxnsMessage); ok { + dm.announcedTxns.add(m.GetFiltered()) + } + + if m, ok := r.Message.(*DisconnectMessage); ok { + if err := dm.disconnectNow(r.Addr, m.reason); err != nil { + logger.WithError(err).WithField("addr", r.Addr).Warning("disconnectNow") + } } } -// RequestBlocks Sends a GetBlocksMessage to all connections -func (dm *Daemon) RequestBlocks() error { - if dm.Config.DisableOutgoingConnections { - return nil +// requestBlocks sends a GetBlocksMessage to all connections +func (dm *Daemon) requestBlocks() error { + if dm.Config.DisableNetworking { + return ErrNetworkingDisabled } - headSeq, ok, err := dm.HeadBkSeq() + headSeq, ok, err := dm.visor.HeadBkSeq() if err != nil { return err } @@ -1101,21 +1017,21 @@ func (dm *Daemon) RequestBlocks() error { m := NewGetBlocksMessage(headSeq, dm.Config.BlocksResponseCount) - err = dm.pool.Pool.BroadcastMessage(m) - if err != nil { - logger.Debugf("Broadcast GetBlocksMessage failed: %v", err) + if _, err := dm.broadcastMessage(m); err != nil { + logger.WithError(err).Debug("Broadcast GetBlocksMessage failed") + return err } - return err + return nil } -// AnnounceBlocks sends an AnnounceBlocksMessage to all connections -func (dm *Daemon) AnnounceBlocks() error { - if dm.Config.DisableOutgoingConnections { - return nil +// announceBlocks sends an AnnounceBlocksMessage to all connections +func (dm *Daemon) announceBlocks() error { + if dm.Config.DisableNetworking { + return ErrNetworkingDisabled } - headSeq, ok, err := dm.HeadBkSeq() + headSeq, ok, err := dm.visor.HeadBkSeq() if err != nil { return err } @@ -1125,72 +1041,18 @@ func (dm *Daemon) AnnounceBlocks() error { m := NewAnnounceBlocksMessage(headSeq) - err = dm.pool.Pool.BroadcastMessage(m) - if err != nil { - logger.Debugf("Broadcast AnnounceBlocksMessage failed: %v", err) - } - - return err -} - -// AnnounceAllTxns announces local unconfirmed transactions -func (dm *Daemon) AnnounceAllTxns() error { - if dm.Config.DisableOutgoingConnections { - return nil - } - - // Get local unconfirmed transaction hashes. - hashes, err := dm.visor.GetAllValidUnconfirmedTxHashes() - if err != nil { + if _, err := dm.broadcastMessage(m); err != nil { + logger.WithError(err).Debug("Broadcast AnnounceBlocksMessage failed") return err } - // Divide hashes into multiple sets of max size - hashesSet := divideHashes(hashes, dm.Config.MaxTxnAnnounceNum) - - for _, hs := range hashesSet { - m := NewAnnounceTxnsMessage(hs) - if err = dm.pool.Pool.BroadcastMessage(m); err != nil { - break - } - } - - if err != nil { - logger.Debugf("Broadcast AnnounceTxnsMessage failed, err: %v", err) - } - - return err -} - -func divideHashes(hashes []cipher.SHA256, n int) [][]cipher.SHA256 { - if len(hashes) == 0 { - return [][]cipher.SHA256{} - } - - var j int - var hashesArray [][]cipher.SHA256 - - if len(hashes) > n { - for i := range hashes { - if len(hashes[j:i]) == n { - hs := make([]cipher.SHA256, n) - copy(hs, hashes[j:i]) - hashesArray = append(hashesArray, hs) - j = i - } - } - } - - hs := make([]cipher.SHA256, len(hashes)-j) - copy(hs, hashes[j:]) - hashesArray = append(hashesArray, hs) - return hashesArray + return nil } -// AnnounceTxns announces given transaction hashes. -func (dm *Daemon) AnnounceTxns(txns []cipher.SHA256) error { - if dm.Config.DisableOutgoingConnections { - return nil +// announceTxns announces given transaction hashes +func (dm *Daemon) announceTxns(txns []cipher.SHA256) error { + if dm.Config.DisableNetworking { + return ErrNetworkingDisabled } if len(txns) == 0 { @@ -1199,38 +1061,41 @@ func (dm *Daemon) AnnounceTxns(txns []cipher.SHA256) error { m := NewAnnounceTxnsMessage(txns) - err := dm.pool.Pool.BroadcastMessage(m) - if err != nil { - logger.Debugf("Broadcast AnnounceTxnsMessage failed: %v", err) + if _, err := dm.broadcastMessage(m); err != nil { + logger.WithError(err).Debug("Broadcast AnnounceTxnsMessage failed") + return err } - return err + return nil } -// RequestBlocksFromAddr sends a GetBlocksMessage to one connected address -func (dm *Daemon) RequestBlocksFromAddr(addr string) error { - if dm.Config.DisableOutgoingConnections { - return errors.New("Outgoing connections disabled") +// createAndPublishBlock creates a block from unconfirmed transactions and sends it to the network. +// Will panic if not running as a block publisher. +// Will not create a block if outgoing connections are disabled. +// If the block was created but the broadcast failed, the error will be non-nil but the +// SignedBlock value will not be empty. +// TODO -- refactor this method -- it should either always create a block and maybe broadcast it, +// or use a database transaction to rollback block publishing if broadcast failed (however, this will cause a slow DB write) +func (dm *Daemon) createAndPublishBlock() (*coin.SignedBlock, error) { + if dm.Config.DisableNetworking { + return nil, ErrNetworkingDisabled } - headSeq, ok, err := dm.visor.HeadBkSeq() + sb, err := dm.visor.CreateAndExecuteBlock() if err != nil { - return err - } - if !ok { - return errors.New("Cannot request blocks from addr, there is no head block") + return nil, err } - m := NewGetBlocksMessage(headSeq, dm.Config.BlocksResponseCount) + err = dm.broadcastBlock(sb) - return dm.pool.Pool.SendMessage(addr, m) + return &sb, err } // ResendUnconfirmedTxns resends all unconfirmed transactions and returns the hashes that were successfully rebroadcast. // It does not return an error if broadcasting fails. func (dm *Daemon) ResendUnconfirmedTxns() ([]cipher.SHA256, error) { - if dm.Config.DisableOutgoingConnections { - return nil, nil + if dm.Config.DisableNetworking { + return nil, ErrNetworkingDisabled } txns, err := dm.visor.GetAllUnconfirmedTransactions() @@ -1240,7 +1105,7 @@ func (dm *Daemon) ResendUnconfirmedTxns() ([]cipher.SHA256, error) { var txids []cipher.SHA256 for i := range txns { - logger.Debugf("Rebroadcast txn %s", txns[i].Hash().Hex()) + logger.WithField("txid", txns[i].Hash().Hex()).Debug("Rebroadcast transaction") if err := dm.BroadcastTransaction(txns[i].Transaction); err == nil { txids = append(txids, txns[i].Transaction.Hash()) } @@ -1251,185 +1116,250 @@ func (dm *Daemon) ResendUnconfirmedTxns() ([]cipher.SHA256, error) { // BroadcastTransaction broadcasts a single transaction to all peers. func (dm *Daemon) BroadcastTransaction(t coin.Transaction) error { - if dm.Config.DisableOutgoingConnections { - return ErrOutgoingConnectionsDisabled + if dm.Config.DisableNetworking { + return ErrNetworkingDisabled } - l, err := dm.pool.Pool.Size() + m := NewGiveTxnsMessage(coin.Transactions{t}) + n, err := dm.broadcastMessage(m) if err != nil { + logger.WithError(err).Error("Broadcast GiveTxnsMessage failed") return err } - logger.Debugf("BroadcastTransaction to %d conns", l) - - m := NewGiveTxnsMessage(coin.Transactions{t}) - if err := dm.pool.Pool.BroadcastMessage(m); err != nil { - logger.WithError(err).Error("BroadcastTransaction Pool.BroadcastMessage failed") - return err - } + logger.Debugf("BroadcastTransaction to %d conns", n) return nil } -// CreateAndPublishBlock creates a block from unconfirmed transactions and sends it to the network. -// Will panic if not running as a master chain. -// Will not create a block if outgoing connections are disabled. -// If the block was created but the broadcast failed, the error will be non-nil but the -// SignedBlock value will not be empty. -// TODO -- refactor this method -- it should either always create a block and maybe broadcast it, -// or use a database transaction to rollback block publishing if broadcast failed (however, this will cause a slow DB write) -func (dm *Daemon) CreateAndPublishBlock() (*coin.SignedBlock, error) { - if dm.Config.DisableOutgoingConnections { - return nil, errors.New("Outgoing connections disabled") +// Disconnect sends a DisconnectMessage to a peer. After the DisconnectMessage is sent, the peer is disconnected. +// This allows all pending messages to be sent. Any message queued after a DisconnectMessage is unlikely to be sent +// to the peer (but possible). +func (dm *Daemon) Disconnect(addr string, r gnet.DisconnectReason) error { + logger.WithFields(logrus.Fields{ + "addr": addr, + "reason": r, + }).Debug("Sending DisconnectMessage") + return dm.sendMessage(addr, NewDisconnectMessage(r)) +} + +// Implements private daemoner interface methods: + +// requestBlocksFromAddr sends a GetBlocksMessage to one connected address +func (dm *Daemon) requestBlocksFromAddr(addr string) error { + if dm.Config.DisableNetworking { + return ErrNetworkingDisabled } - sb, err := dm.visor.CreateAndExecuteBlock() + headSeq, ok, err := dm.visor.HeadBkSeq() if err != nil { - return nil, err + return err + } + if !ok { + return errors.New("Cannot request blocks from addr, there is no head block") } - err = dm.broadcastBlock(sb) - - return &sb, err + m := NewGetBlocksMessage(headSeq, dm.Config.BlocksResponseCount) + return dm.sendMessage(addr, m) } -// Sends a signed block to all connections. +// broadcastBlock sends a signed block to all connections func (dm *Daemon) broadcastBlock(sb coin.SignedBlock) error { - if dm.Config.DisableOutgoingConnections { - return nil + if dm.Config.DisableNetworking { + return ErrNetworkingDisabled } m := NewGiveBlocksMessage([]coin.SignedBlock{sb}) - return dm.pool.Pool.BroadcastMessage(m) -} - -// Mirror returns the message mirror -func (dm *Daemon) Mirror() uint32 { - return dm.Messages.Mirror + _, err := dm.broadcastMessage(m) + return err } -// DaemonConfig returns the daemon config -func (dm *Daemon) DaemonConfig() DaemonConfig { +// daemonConfig returns the daemon config +func (dm *Daemon) daemonConfig() DaemonConfig { return dm.Config } -// BlockchainPubkey returns the blockchain pubkey -func (dm *Daemon) BlockchainPubkey() cipher.PubKey { - return dm.Config.BlockchainPubkey -} +// connectionIntroduced transfers a connection to the "introduced" state in the connections state machine +// and updates other state +func (dm *Daemon) connectionIntroduced(addr string, gnetID uint64, m *IntroductionMessage, userAgent *useragent.Data) (*connection, error) { + c, err := dm.connections.introduced(addr, gnetID, m, userAgent) + if err != nil { + return nil, err + } -// RemoveFromExpectingIntroductions removes the peer from expect introduction pool -func (dm *Daemon) RemoveFromExpectingIntroductions(addr string) { - dm.expectingIntroductions.Remove(addr) -} + listenAddr := c.ListenAddr() -// Implements pooler interface + fields := logrus.Fields{ + "addr": addr, + "gnetID": m.c.ConnID, + "connGnetID": c.gnetID, + "listenPort": m.ListenPort, + "listenAddr": listenAddr, + } -// SendMessage sends a Message to a Connection and pushes the result onto the -// SendResults channel. -func (dm *Daemon) SendMessage(addr string, msg gnet.Message) error { - return dm.pool.Pool.SendMessage(addr, msg) -} + if c.Outgoing { + // For successful outgoing connections, mark the peer as having an incoming port in the pex peerlist + // The peer should already be in the peerlist, since we use the peerlist to choose an outgoing connection to make + if err := dm.pex.SetHasIncomingPort(listenAddr, true); err != nil { + logger.Critical().WithError(err).WithFields(fields).Error("pex.SetHasIncomingPort failed") + return nil, err + } + } else { + // For successful incoming connections, add the peer to the peer list, with their self-reported listen port + if err := dm.pex.AddPeer(listenAddr); err != nil { + logger.Critical().WithError(err).WithFields(fields).Error("pex.AddPeer failed") + return nil, err + } + } -// BroadcastMessage sends a Message to all connections in the Pool. -func (dm *Daemon) BroadcastMessage(msg gnet.Message) error { - return dm.pool.Pool.BroadcastMessage(msg) -} + if err := dm.pex.SetUserAgent(listenAddr, c.UserAgent); err != nil { + logger.Critical().WithError(err).WithFields(fields).Error("pex.SetUserAgent failed") + return nil, err + } -// Disconnect removes a connection from the pool by address, and passes a Disconnection to -// the DisconnectCallback -func (dm *Daemon) Disconnect(addr string, r gnet.DisconnectReason) error { - return dm.pool.Pool.Disconnect(addr, r) -} + dm.pex.ResetRetryTimes(listenAddr) -// IsDefaultConnection returns if the addr is a default connection -func (dm *Daemon) IsDefaultConnection(addr string) bool { - return dm.pool.Pool.IsDefaultConnection(addr) + return c, nil } -// IsMaxDefaultConnectionsReached returns whether the max default connection number was reached. -func (dm *Daemon) IsMaxDefaultConnectionsReached() (bool, error) { - return dm.pool.Pool.IsMaxDefaultConnectionsReached() +// sendRandomPeers sends a random sample of peers to another peer +func (dm *Daemon) sendRandomPeers(addr string) error { + peers := dm.pex.RandomExchangeable(dm.pex.Config.ReplyCount) + if len(peers) == 0 { + logger.Debug("sendRandomPeers: no peers to send in reply") + return errors.New("No peers available") + } + + m := NewGivePeersMessage(peers) + return dm.sendMessage(addr, m) } -// Implements pexer interface +// announceAllTxns announces local unconfirmed transactions +func (dm *Daemon) announceAllTxns() error { + if dm.Config.DisableNetworking { + return ErrNetworkingDisabled + } -// RandomExchangeable returns N random exchangeable peers -func (dm *Daemon) RandomExchangeable(n int) pex.Peers { - return dm.pex.RandomExchangeable(n) -} + // Get local unconfirmed transaction hashes. + hashes, err := dm.visor.GetAllValidUnconfirmedTxHashes() + if err != nil { + return err + } -// PexConfig returns the pex config -func (dm *Daemon) PexConfig() pex.Config { - return dm.pex.Config -} + // Divide hashes into multiple sets of max size + hashesSet := divideHashes(hashes, dm.Config.MaxTxnAnnounceNum) -// AddPeer adds peer to the pex -func (dm *Daemon) AddPeer(addr string) error { - return dm.pex.AddPeer(addr) -} + for _, hs := range hashesSet { + m := NewAnnounceTxnsMessage(hs) + if _, err := dm.broadcastMessage(m); err != nil { + logger.WithError(err).Debug("Broadcast AnnounceTxnsMessage failed") + return err + } + } -// AddPeers adds peers to the pex -func (dm *Daemon) AddPeers(addrs []string) int { - return dm.pex.AddPeers(addrs) + return nil } -// SetHasIncomingPort sets the peer public peer -func (dm *Daemon) SetHasIncomingPort(addr string) error { - return dm.pex.SetHasIncomingPort(addr, true) +func divideHashes(hashes []cipher.SHA256, n int) [][]cipher.SHA256 { + if len(hashes) == 0 { + return [][]cipher.SHA256{} + } + + var j int + var hashesArray [][]cipher.SHA256 + + if len(hashes) > n { + for i := range hashes { + if len(hashes[j:i]) == n { + hs := make([]cipher.SHA256, n) + copy(hs, hashes[j:i]) + hashesArray = append(hashesArray, hs) + j = i + } + } + } + + hs := make([]cipher.SHA256, len(hashes)-j) + copy(hs, hashes[j:]) + hashesArray = append(hashesArray, hs) + return hashesArray } -// IncreaseRetryTimes increases the retry times of given peer -func (dm *Daemon) IncreaseRetryTimes(addr string) { - dm.pex.IncreaseRetryTimes(addr) +// sendMessage sends a Message to a Connection and pushes the result onto the SendResults channel. +func (dm *Daemon) sendMessage(addr string, msg gnet.Message) error { + return dm.pool.Pool.SendMessage(addr, msg) } -// ResetRetryTimes reset the retry times of given peer -func (dm *Daemon) ResetRetryTimes(addr string) { - dm.pex.ResetRetryTimes(addr) +// broadcastMessage sends a Message to all introduced connections in the Pool +func (dm *Daemon) broadcastMessage(msg gnet.Message) (int, error) { + if dm.Config.DisableNetworking { + return 0, ErrNetworkingDisabled + } + + conns := dm.connections.all() + var addrs []string + for _, c := range conns { + if c.HasIntroduced() { + addrs = append(addrs, c.Addr) + } + } + + return dm.pool.Pool.BroadcastMessage(msg, addrs) } -// Implements chain height store +// disconnectNow disconnects from a peer immediately without sending a DisconnectMessage. Any pending messages +// will not be sent to the peer. +func (dm *Daemon) disconnectNow(addr string, r gnet.DisconnectReason) error { + return dm.pool.Pool.Disconnect(addr, r) +} -// Record(addr string, height uint64) +// pexConfig returns the pex config +func (dm *Daemon) pexConfig() pex.Config { + return dm.pex.Config +} -// RecordPeerHeight records the height of specific peer -func (dm *Daemon) RecordPeerHeight(addr string, height uint64) { - dm.Heights.Record(addr, height) +// addPeers adds peers to the pex +func (dm *Daemon) addPeers(addrs []string) int { + return dm.pex.AddPeers(addrs) } -// Implements visorer interface +// recordPeerHeight records the height of specific peer +func (dm *Daemon) recordPeerHeight(addr string, gnetID, height uint64) { + if err := dm.connections.SetHeight(addr, gnetID, height); err != nil { + logger.Critical().WithError(err).WithField("addr", addr).Error("connections.SetHeight failed") + } +} -// GetSignedBlocksSince returns N signed blocks since given seq -func (dm *Daemon) GetSignedBlocksSince(seq, count uint64) ([]coin.SignedBlock, error) { +// getSignedBlocksSince returns N signed blocks since given seq +func (dm *Daemon) getSignedBlocksSince(seq, count uint64) ([]coin.SignedBlock, error) { return dm.visor.GetSignedBlocksSince(seq, count) } -// HeadBkSeq returns the head block sequence -func (dm *Daemon) HeadBkSeq() (uint64, bool, error) { +// headBkSeq returns the head block sequence +func (dm *Daemon) headBkSeq() (uint64, bool, error) { return dm.visor.HeadBkSeq() } -// ExecuteSignedBlock executes the signed block -func (dm *Daemon) ExecuteSignedBlock(b coin.SignedBlock) error { +// executeSignedBlock executes the signed block +func (dm *Daemon) executeSignedBlock(b coin.SignedBlock) error { return dm.visor.ExecuteSignedBlock(b) } -// GetUnconfirmedUnknown returns unconfirmed txn hashes with known ones removed -func (dm *Daemon) GetUnconfirmedUnknown(txns []cipher.SHA256) ([]cipher.SHA256, error) { - return dm.visor.GetUnconfirmedUnknown(txns) +// filterKnownUnconfirmed returns unconfirmed txn hashes with known ones removed +func (dm *Daemon) filterKnownUnconfirmed(txns []cipher.SHA256) ([]cipher.SHA256, error) { + return dm.visor.FilterKnownUnconfirmed(txns) } -// GetUnconfirmedKnown returns unconfirmed txn hashes with known ones removed -func (dm *Daemon) GetUnconfirmedKnown(txns []cipher.SHA256) (coin.Transactions, error) { - return dm.visor.GetUnconfirmedKnown(txns) +// getKnownUnconfirmed returns unconfirmed txn hashes with known ones removed +func (dm *Daemon) getKnownUnconfirmed(txns []cipher.SHA256) (coin.Transactions, error) { + return dm.visor.GetKnownUnconfirmed(txns) } -// InjectTransaction records a coin.Transaction to the UnconfirmedTxnPool if the txn is not +// injectTransaction records a coin.Transaction to the UnconfirmedTxnPool if the txn is not // already in the blockchain. // The bool return value is whether or not the transaction was already in the pool. // If the transaction violates hard constraints, it is rejected, and error will not be nil. // If the transaction only violates soft constraints, it is still injected, and the soft constraint violation is returned. -func (dm *Daemon) InjectTransaction(txn coin.Transaction) (bool, *visor.ErrTxnViolatesSoftConstraint, error) { - return dm.visor.InjectTransaction(txn) +func (dm *Daemon) injectTransaction(txn coin.Transaction) (bool, *visor.ErrTxnViolatesSoftConstraint, error) { + return dm.visor.InjectForeignTransaction(txn) } diff --git a/src/daemon/errors.go b/src/daemon/errors.go new file mode 100644 index 0000000000..b48080964f --- /dev/null +++ b/src/daemon/errors.go @@ -0,0 +1,101 @@ +package daemon + +import ( + "errors" + + "github.com/skycoin/skycoin/src/daemon/gnet" +) + +var ( + // ErrDisconnectVersionNotSupported version is below minimum supported version + ErrDisconnectVersionNotSupported gnet.DisconnectReason = errors.New("Version is below minimum supported version") + // ErrDisconnectIntroductionTimeout timeout + ErrDisconnectIntroductionTimeout gnet.DisconnectReason = errors.New("Introduction timeout") + // ErrDisconnectIsBlacklisted is blacklisted + ErrDisconnectIsBlacklisted gnet.DisconnectReason = errors.New("Blacklisted") + // ErrDisconnectSelf self connnect + ErrDisconnectSelf gnet.DisconnectReason = errors.New("Self connect") + // ErrDisconnectConnectedTwice connect twice + ErrDisconnectConnectedTwice gnet.DisconnectReason = errors.New("Already connected") + // ErrDisconnectIdle idle + ErrDisconnectIdle gnet.DisconnectReason = errors.New("Idle") + // ErrDisconnectNoIntroduction no introduction + ErrDisconnectNoIntroduction gnet.DisconnectReason = errors.New("First message was not an Introduction") + // ErrDisconnectIPLimitReached ip limit reached + ErrDisconnectIPLimitReached gnet.DisconnectReason = errors.New("Maximum number of connections for this IP was reached") + // ErrDisconnectUnexpectedError this is returned when a seemingly impossible error is encountered, e.g. net.Conn.Addr() returns an invalid ip:port + ErrDisconnectUnexpectedError gnet.DisconnectReason = errors.New("Unexpected error") + // ErrDisconnectMaxOutgoingConnectionsReached is returned when connection pool size is greater than the maximum allowed + ErrDisconnectMaxOutgoingConnectionsReached gnet.DisconnectReason = errors.New("Maximum outgoing connections was reached") + // ErrDisconnectBlockchainPubkeyNotMatched is returned when the blockchain pubkey in introduction does not match + ErrDisconnectBlockchainPubkeyNotMatched gnet.DisconnectReason = errors.New("Blockchain pubkey does not match") + // ErrDisconnectInvalidExtraData is returned when extra field can't be parsed + ErrDisconnectInvalidExtraData gnet.DisconnectReason = errors.New("Invalid extra data in message") + // ErrDisconnectReceivedDisconnect received a DisconnectMessage + ErrDisconnectReceivedDisconnect gnet.DisconnectReason = errors.New("Received DisconnectMessage") + // ErrDisconnectInvalidUserAgent is returned if the peer provides an invalid user agent + ErrDisconnectInvalidUserAgent gnet.DisconnectReason = errors.New("Invalid user agent") + // ErrDisconnectRequestedByOperator the operator of the node requested a disconnect + ErrDisconnectRequestedByOperator gnet.DisconnectReason = errors.New("Disconnect requested by the node operator") + // ErrDisconnectPeerlistFull the peerlist is full + ErrDisconnectPeerlistFull gnet.DisconnectReason = errors.New("Peerlist is full") + + // ErrDisconnectUnknownReason used when mapping an unknown reason code to an error. Is not sent over the network. + ErrDisconnectUnknownReason gnet.DisconnectReason = errors.New("Unknown DisconnectReason") + + disconnectReasonCodes = map[gnet.DisconnectReason]uint16{ + ErrDisconnectUnknownReason: 0, + + ErrDisconnectVersionNotSupported: 1, + ErrDisconnectIntroductionTimeout: 2, + ErrDisconnectIsBlacklisted: 3, + ErrDisconnectSelf: 4, + ErrDisconnectConnectedTwice: 5, + ErrDisconnectIdle: 6, + ErrDisconnectNoIntroduction: 7, + ErrDisconnectIPLimitReached: 8, + ErrDisconnectUnexpectedError: 9, + ErrDisconnectMaxOutgoingConnectionsReached: 10, + ErrDisconnectBlockchainPubkeyNotMatched: 11, + ErrDisconnectInvalidExtraData: 12, + ErrDisconnectReceivedDisconnect: 13, + ErrDisconnectInvalidUserAgent: 14, + ErrDisconnectRequestedByOperator: 15, + ErrDisconnectPeerlistFull: 16, + + // gnet codes are registered here, but they are not sent in a DISC + // message by gnet. Only daemon sends a DISC packet. + // If gnet chooses to disconnect it will not send a DISC packet. + gnet.ErrDisconnectSetReadDeadlineFailed: 1001, + gnet.ErrDisconnectInvalidMessageLength: 1002, + gnet.ErrDisconnectMalformedMessage: 1003, + gnet.ErrDisconnectUnknownMessage: 1004, + gnet.ErrDisconnectShutdown: 1005, + gnet.ErrDisconnectMessageDecodeUnderflow: 1006, + gnet.ErrDisconnectTruncatedMessageID: 1007, + } + + disconnectCodeReasons map[uint16]gnet.DisconnectReason +) + +func init() { + disconnectCodeReasons = make(map[uint16]gnet.DisconnectReason, len(disconnectReasonCodes)) + + for r, c := range disconnectReasonCodes { + disconnectCodeReasons[c] = r + } +} + +// DisconnectReasonToCode maps a gnet.DisconnectReason to a 16-byte code +func DisconnectReasonToCode(r gnet.DisconnectReason) uint16 { + return disconnectReasonCodes[r] +} + +// DisconnectCodeToReason maps a disconnect code to a gnet.DisconnectReason +func DisconnectCodeToReason(c uint16) gnet.DisconnectReason { + r, ok := disconnectCodeReasons[c] + if !ok { + return ErrDisconnectUnknownReason + } + return r +} diff --git a/src/daemon/errors_test.go b/src/daemon/errors_test.go new file mode 100644 index 0000000000..19e1a12796 --- /dev/null +++ b/src/daemon/errors_test.go @@ -0,0 +1,29 @@ +package daemon + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/skycoin/skycoin/src/daemon/gnet" +) + +func TestDisconnectReasonCode(t *testing.T) { + c := DisconnectReasonToCode(ErrDisconnectIdle) + require.NotEqual(t, uint16(0), c) + + r := DisconnectCodeToReason(c) + require.Equal(t, ErrDisconnectIdle, r) + + // unknown reason is fine + c = DisconnectReasonToCode(gnet.DisconnectReason(errors.New("foo"))) + require.Equal(t, uint16(0), c) + + r = DisconnectCodeToReason(c) + require.Equal(t, ErrDisconnectUnknownReason, r) + + // unknown code is fine + r = DisconnectCodeToReason(999) + require.Equal(t, ErrDisconnectUnknownReason, r) +} diff --git a/src/daemon/gateway.go b/src/daemon/gateway.go index e60a0b0b6e..1cb939a255 100644 --- a/src/daemon/gateway.go +++ b/src/daemon/gateway.go @@ -10,7 +10,9 @@ import ( "github.com/skycoin/skycoin/src/cipher" "github.com/skycoin/skycoin/src/coin" "github.com/skycoin/skycoin/src/daemon/gnet" + "github.com/skycoin/skycoin/src/daemon/pex" "github.com/skycoin/skycoin/src/daemon/strand" + "github.com/skycoin/skycoin/src/params" "github.com/skycoin/skycoin/src/visor" "github.com/skycoin/skycoin/src/visor/dbutil" "github.com/skycoin/skycoin/src/visor/historydb" @@ -87,50 +89,98 @@ func (gw *Gateway) strand(name string, f func()) { // Connection a connection's state within the daemon type Connection struct { - ID int - Addr string - LastSent int64 - LastReceived int64 - // Whether the connection is from us to them (true, outgoing), - // or from them to us (false, incoming) - Outgoing bool - // Whether the client has identified their version, mirror etc - Introduced bool - Mirror uint32 - ListenPort uint16 - Height uint64 -} - -// GetOutgoingConnections returns solicited (outgoing) connections -func (gw *Gateway) GetOutgoingConnections() ([]Connection, error) { + Addr string + Pex pex.Peer + Gnet GnetConnectionDetails + ConnectionDetails +} + +// GnetConnectionDetails connection data from gnet +type GnetConnectionDetails struct { + ID uint64 + LastSent time.Time + LastReceived time.Time +} + +func newConnection(dc *connection, gc *gnet.Connection, pp *pex.Peer) Connection { + c := Connection{} + + if dc != nil { + c.Addr = dc.Addr + c.ConnectionDetails = dc.ConnectionDetails + } + + if gc != nil { + c.Gnet = GnetConnectionDetails{ + ID: gc.ID, + LastSent: gc.LastSent, + LastReceived: gc.LastReceived, + } + } + + if pp != nil { + c.Pex = *pp + } + + return c +} + +// newConnection creates a Connection from daemon.connection, gnet.Connection and pex.Peer +func (gw *Gateway) newConnection(c *connection) (*Connection, error) { + if c == nil { + return nil, nil + } + + gc, err := gw.d.pool.Pool.GetConnection(c.Addr) + if err != nil { + return nil, err + } + + var pp *pex.Peer + listenAddr := c.ListenAddr() + if listenAddr != "" { + p, ok := gw.d.pex.GetPeer(listenAddr) + if ok { + pp = &p + } + } + + cc := newConnection(c, gc, pp) + return &cc, nil +} + +// GetConnections returns solicited (outgoing) connections +func (gw *Gateway) GetConnections(f func(c Connection) bool) ([]Connection, error) { var conns []Connection var err error - gw.strand("GetOutgoingConnections", func() { - conns, err = gw.getOutgoingConnections() + gw.strand("GetConnections", func() { + conns, err = gw.getConnections(f) }) return conns, err } -func (gw *Gateway) getOutgoingConnections() ([]Connection, error) { +func (gw *Gateway) getConnections(f func(c Connection) bool) ([]Connection, error) { if gw.d.pool.Pool == nil { return nil, nil } - cs, err := gw.d.pool.Pool.GetConnections() - if err != nil { - logger.Error(err) - return nil, err - } + cs := gw.d.connections.all() - conns := make([]Connection, 0, len(cs)) + conns := make([]Connection, 0) for _, c := range cs { - if c.Solicited { - conn := gw.newConnection(&c) - if conn != nil { - conns = append(conns, *conn) - } + cc, err := gw.newConnection(&c) + if err != nil { + return nil, err } + + ccc := *cc + + if !f(ccc) { + continue + } + + conns = append(conns, ccc) } // Sort connnections by IP address @@ -153,50 +203,31 @@ func (gw *Gateway) GetDefaultConnections() []string { // GetConnection returns a *Connection of specific address func (gw *Gateway) GetConnection(addr string) (*Connection, error) { - var conn *Connection - var err error + var c *connection gw.strand("GetConnection", func() { - if gw.d.pool.Pool == nil { - return - } - - var c *gnet.Connection - c, err = gw.d.pool.Pool.GetConnection(addr) - if err != nil { - logger.Error(err) - return - } - - conn = gw.newConnection(c) + c = gw.d.connections.get(addr) }) - return conn, err -} -func (gw *Gateway) newConnection(c *gnet.Connection) *Connection { if c == nil { - return nil + return nil, nil } - addr := c.Addr() - - mirror, exist := gw.d.connectionMirrors.Get(addr) - if !exist { - return nil - } + return gw.newConnection(c) +} - height, _ := gw.d.Heights.Get(addr) +// Disconnect disconnects a connection by gnet ID +func (gw *Gateway) Disconnect(gnetID uint64) error { + var err error + gw.strand("Disconnect", func() { + c := gw.d.connections.getByGnetID(gnetID) + if c == nil { + err = ErrConnectionNotExist + return + } - return &Connection{ - ID: c.ID, - Addr: addr, - LastSent: c.LastSent.Unix(), - LastReceived: c.LastReceived.Unix(), - Outgoing: gw.d.outgoingConnections.Get(addr), - Introduced: !gw.d.needsIntro(addr), - Mirror: mirror, - ListenPort: gw.d.GetListenPort(addr), - Height: height, - } + err = gw.d.Disconnect(c.Addr, ErrDisconnectRequestedByOperator) + }) + return err } // GetTrustConnections returns all trusted connections @@ -229,29 +260,67 @@ type BlockchainProgress struct { Peers []PeerBlockchainHeight } +// newBlockchainProgress creates BlockchainProgress from the local head blockchain sequence number +// and a list of remote peers +func newBlockchainProgress(headSeq uint64, conns []connection) *BlockchainProgress { + peers := newPeerBlockchainHeights(conns) + + return &BlockchainProgress{ + Current: headSeq, + Highest: EstimateBlockchainHeight(headSeq, peers), + Peers: peers, + } +} + +// PeerBlockchainHeight records blockchain height for an address +type PeerBlockchainHeight struct { + Address string + Height uint64 +} + +func newPeerBlockchainHeights(conns []connection) []PeerBlockchainHeight { + peers := make([]PeerBlockchainHeight, 0, len(conns)) + for _, c := range conns { + if c.State != ConnectionStatePending { + peers = append(peers, PeerBlockchainHeight{ + Address: c.Addr, + Height: c.Height, + }) + } + } + return peers +} + +// EstimateBlockchainHeight estimates the blockchain sync height. +// The highest height reported amongst all peers, and including the node itself, is returned. +func EstimateBlockchainHeight(headSeq uint64, peers []PeerBlockchainHeight) uint64 { + for _, c := range peers { + if c.Height > headSeq { + headSeq = c.Height + } + } + return headSeq +} + // GetBlockchainProgress returns a *BlockchainProgress func (gw *Gateway) GetBlockchainProgress() (*BlockchainProgress, error) { - var bcp *BlockchainProgress + var headSeq uint64 var err error + var conns []connection gw.strand("GetBlockchainProgress", func() { - var headSeq uint64 headSeq, _, err = gw.v.HeadBkSeq() if err != nil { return } - bcp = &BlockchainProgress{ - Current: headSeq, - Highest: gw.d.Heights.Estimate(headSeq), - Peers: gw.d.Heights.All(), - } + conns = gw.d.connections.all() }) if err != nil { return nil, err } - return bcp, nil + return newBlockchainProgress(headSeq, conns), nil } // ResendUnconfirmedTxns resents all unconfirmed transactions, returning the txids @@ -418,14 +487,14 @@ func (gw *Gateway) GetTransactionVerbose(txid cipher.SHA256) (*visor.Transaction // InjectBroadcastTransaction injects transaction to the unconfirmed pool and broadcasts it. // If the transaction violates either hard or soft constraints, it is not broadcast. // This method is to be used by user-initiated transaction injections. -// For transactions received over the network, use daemon.InjectTransaction and check the result to +// For transactions received over the network, use daemon.injectTransaction and check the result to // decide on repropagation. func (gw *Gateway) InjectBroadcastTransaction(txn coin.Transaction) error { var err error gw.strand("InjectBroadcastTransaction", func() { err = gw.v.WithUpdateTx("gateway.InjectBroadcastTransaction", func(tx *dbutil.Tx) error { - if _, err := gw.v.InjectTransactionStrictTx(tx, txn); err != nil { - logger.WithError(err).Error("InjectTransactionStrict failed") + if _, err := gw.v.InjectUserTransactionTx(tx, txn); err != nil { + logger.WithError(err).Error("InjectUserTransactionTx failed") return err } @@ -548,9 +617,9 @@ func (gw *Gateway) Spend(wltID string, password []byte, coins uint64, dest ciphe } // WARNING: This is not safe from races once we remove strand - _, err = gw.v.InjectTransactionStrict(*txn) + _, err = gw.v.InjectUserTransaction(*txn) if err != nil { - logger.WithError(err).Error("InjectTransactionStrict failed") + logger.WithError(err).Error("InjectUserTransaction failed") return } @@ -813,7 +882,7 @@ func (gw *Gateway) GetRichlist(includeDistribution bool) (visor.Richlist, error) } } - lockedAddrs := visor.GetLockedDistributionAddresses() + lockedAddrs := params.GetLockedDistributionAddresses() addrsMap := make(map[string]struct{}, len(lockedAddrs)) for _, a := range lockedAddrs { addrsMap[a] = struct{}{} @@ -825,7 +894,7 @@ func (gw *Gateway) GetRichlist(includeDistribution bool) (visor.Richlist, error) } if !includeDistribution { - unlockedAddrs := visor.GetUnlockedDistributionAddresses() + unlockedAddrs := params.GetUnlockedDistributionAddresses() for _, a := range unlockedAddrs { addrsMap[a] = struct{}{} } @@ -849,9 +918,10 @@ func (gw *Gateway) GetAddressCount() (uint64, error) { // Health is returned by the /health endpoint type Health struct { - BlockchainMetadata visor.BlockchainMetadata - OpenConnections int - Uptime time.Duration + BlockchainMetadata visor.BlockchainMetadata + OutgoingConnections int + IncomingConnections int + Uptime time.Duration } // GetHealth returns statistics about the running node @@ -865,15 +935,28 @@ func (gw *Gateway) GetHealth() (*Health, error) { return } - conns, err := gw.getOutgoingConnections() + conns, err := gw.getConnections(func(c Connection) bool { + return c.State != ConnectionStatePending + }) if err != nil { return } + outgoingConns := 0 + incomingConns := 0 + for _, c := range conns { + if c.Outgoing { + outgoingConns++ + } else { + incomingConns++ + } + } + health = &Health{ - BlockchainMetadata: *metadata, - OpenConnections: len(conns), - Uptime: time.Since(gw.v.StartedAt), + BlockchainMetadata: *metadata, + OutgoingConnections: outgoingConns, + IncomingConnections: incomingConns, + Uptime: time.Since(gw.v.StartedAt), } }) diff --git a/src/daemon/gnet/dispatcher.go b/src/daemon/gnet/dispatcher.go index 001da78719..30e69a03fe 100644 --- a/src/daemon/gnet/dispatcher.go +++ b/src/daemon/gnet/dispatcher.go @@ -7,6 +7,8 @@ import ( "reflect" "time" + "github.com/sirupsen/logrus" + "github.com/skycoin/skycoin/src/cipher/encoder" ) @@ -31,37 +33,63 @@ func sendMessage(conn net.Conn, msg Message, timeout time.Duration) error { return sendByteMessage(conn, m, timeout) } +// msgIDStringSafe formats msgID bytes to a string that is safe for logging (e.g. not impacted by ascii control chars) +func msgIDStringSafe(msgID [4]byte) string { + x := fmt.Sprintf("%q", msgID) + return x[1 : len(x)-1] // trim quotes that are added by %q formatting +} + // Event handler that is called after a Connection sends a complete message -func convertToMessage(id int, msg []byte, debugPrint bool) (Message, error) { +func convertToMessage(id uint64, msg []byte, debugPrint bool) (Message, error) { msgID := [4]byte{} if len(msg) < len(msgID) { - return nil, errors.New("Not enough data to read msg id") + logger.WithError(ErrDisconnectTruncatedMessageID).WithField("connID", id).Warning() + return nil, ErrDisconnectTruncatedMessageID } + copy(msgID[:], msg[:len(msgID)]) + + if debugPrint { + logger.WithField("msgID", msgIDStringSafe(msgID)).Debug("Received message") + } + msg = msg[len(msgID):] - t, succ := MessageIDReverseMap[msgID] - if !succ { - return nil, fmt.Errorf("Unknown message %s received", string(msgID[:])) + t, ok := MessageIDReverseMap[msgID] + if !ok { + logger.WithError(ErrDisconnectUnknownMessage).WithFields(logrus.Fields{ + "msgID": msgIDStringSafe(msgID), + "connID": id, + }).Warning() + return nil, ErrDisconnectUnknownMessage } if debugPrint { - logger.Debugf("convertToMessage for connection %d, message type %v", id, t) + logger.WithFields(logrus.Fields{ + "connID": id, + "messageType": fmt.Sprintf("%v", t), + }).Debugf("convertToMessage") } - var m Message v := reflect.New(t) - //logger.Debugf("Giving %d bytes to the decoder", len(msg)) used, err := deserializeMessage(msg, v) if err != nil { - return nil, err + logger.Critical().WithError(err).WithFields(logrus.Fields{ + "connID": id, + "messageType": fmt.Sprintf("%v", t), + }).Warning("deserializeMessage failed") + return nil, ErrDisconnectMalformedMessage } if used != len(msg) { - return nil, errors.New("Data buffer was not completely decoded") + logger.WithError(ErrDisconnectMessageDecodeUnderflow).WithFields(logrus.Fields{ + "connID": id, + "messageType": fmt.Sprintf("%v", t), + }).Warning() + return nil, ErrDisconnectMessageDecodeUnderflow } - m, succ = (v.Interface()).(Message) - if !succ { + m, ok := (v.Interface()).(Message) + if !ok { // This occurs only when the user registers an interface that does not // match the Message interface. They should have known about this // earlier via a call to VerifyMessages @@ -95,8 +123,7 @@ func EncodeMessage(msg Message) []byte { t := reflect.ValueOf(msg).Elem().Type() msgID, succ := MessageIDMap[t] if !succ { - txt := "Attempted to serialize message struct not in MessageIdMap: %v" - logger.Panicf(txt, msg) + logger.Panicf("Attempted to serialize message struct not in MessageIdMap: %v", msg) } bMsg := encoder.Serialize(msg) @@ -110,8 +137,7 @@ func EncodeMessage(msg Message) []byte { } // Sends []byte over a net.Conn -var sendByteMessage = func(conn net.Conn, msg []byte, - timeout time.Duration) error { +var sendByteMessage = func(conn net.Conn, msg []byte, timeout time.Duration) error { deadline := time.Time{} if timeout != 0 { deadline = time.Now().Add(timeout) @@ -120,7 +146,9 @@ var sendByteMessage = func(conn net.Conn, msg []byte, return err } if _, err := conn.Write(msg); err != nil { - return err + return &WriteError{ + Err: err, + } } return nil } diff --git a/src/daemon/gnet/dispatcher_test.go b/src/daemon/gnet/dispatcher_test.go index 1ca0a69c3f..425cecc72c 100644 --- a/src/daemon/gnet/dispatcher_test.go +++ b/src/daemon/gnet/dispatcher_test.go @@ -8,9 +8,7 @@ import ( "testing" "time" - "github.com/stretchr/testify/assert" - - "github.com/skycoin/skycoin/src/cipher/encoder" + "github.com/stretchr/testify/require" ) var ( @@ -21,6 +19,23 @@ func resetHandler() { sendByteMessage = _sendByteMessage } +func TestMsgIDStringSafe(t *testing.T) { + var id [4]byte + require.Equal(t, "\\x00\\x00\\x00\\x00", msgIDStringSafe(id)) + + id = [4]byte{'F', 'O', 'O', 'B'} + + require.Equal(t, "FOOB", msgIDStringSafe(id)) + + id = [4]byte{200, 2, '\n', '\t'} + + require.Equal(t, "\\xc8\\x02\\n\\t", msgIDStringSafe(id)) + + id = [4]byte{'\'', '\\', ' ', '"'} + + require.Equal(t, "'\\\\ \\\"", msgIDStringSafe(id)) +} + func TestConvertToMessage(t *testing.T) { EraseMessages() resetHandler() @@ -31,24 +46,24 @@ func TestConvertToMessage(t *testing.T) { b = append(b, BytePrefix[:]...) b = append(b, byte(7)) m, err := convertToMessage(c.ID, b, testing.Verbose()) - assert.Nil(t, err) - assert.NotNil(t, m) + require.NoError(t, err) + require.NotNil(t, m) if m == nil { t.Fatalf("ConvertToMessage failed") } bm := m.(*ByteMessage) - assert.Equal(t, bm.X, byte(7)) + require.Equal(t, bm.X, byte(7)) } -func TestConvertToMessageNoMessageId(t *testing.T) { +func TestConvertToMessageNoMessageID(t *testing.T) { EraseMessages() resetHandler() c := &Connection{} b := []byte{} m, err := convertToMessage(c.ID, b, testing.Verbose()) - assert.Nil(t, m) - assert.NotNil(t, err) - assert.Equal(t, err.Error(), "Not enough data to read msg id") + require.Nil(t, m) + require.Error(t, err) + require.Equal(t, ErrDisconnectTruncatedMessageID, err) } func TestConvertToMessageUnknownMessage(t *testing.T) { @@ -57,9 +72,9 @@ func TestConvertToMessageUnknownMessage(t *testing.T) { c := &Connection{} b := MessagePrefix{'C', 'C', 'C', 'C'} m, err := convertToMessage(c.ID, b[:], testing.Verbose()) - assert.NotNil(t, err) - assert.Equal(t, err.Error(), "Unknown message CCCC received") - assert.Nil(t, m) + require.Error(t, err) + require.Equal(t, ErrDisconnectUnknownMessage, err) + require.Nil(t, m) } func TestConvertToMessageBadDeserialize(t *testing.T) { @@ -72,15 +87,15 @@ func TestConvertToMessageBadDeserialize(t *testing.T) { // Test with too many bytes b := append(DummyPrefix[:], []byte{0, 1, 1, 1}...) m, err := convertToMessage(c.ID, b, testing.Verbose()) - assert.NotNil(t, err) - assert.Nil(t, m) + require.Error(t, err) + require.Nil(t, m) // Test with not enough bytes b = append([]byte{}, BytePrefix[:]...) m, err = convertToMessage(c.ID, b, testing.Verbose()) - assert.NotNil(t, err) - assert.Equal(t, encoder.ErrBufferUnderflow, err) - assert.Nil(t, m) + require.Error(t, err) + require.Equal(t, ErrDisconnectMalformedMessage, err) + require.Nil(t, m) } func TestConvertToMessageNotMessage(t *testing.T) { @@ -89,7 +104,7 @@ func TestConvertToMessageNotMessage(t *testing.T) { RegisterMessage(NothingPrefix, Nothing{}) // don't verify messages c := &Connection{} - assert.Panics(t, func() { + require.Panics(t, func() { _, _ = convertToMessage(c.ID, NothingPrefix[:], testing.Verbose()) // nolint: errcheck }) } @@ -101,8 +116,8 @@ func TestDeserializeMessageTrapsPanic(t *testing.T) { m := PointerMessage{Ptr: &p} b := []byte{4, 4, 4, 4, 4, 4, 4, 4} _, err := deserializeMessage(b, reflect.ValueOf(m)) - assert.NotNil(t, err) - assert.Equal(t, err.Error(), "DeserializeRawToValue value must be a ptr, is struct") + require.Error(t, err) + require.Equal(t, err.Error(), "DeserializeRawToValue value must be a ptr, is struct") } func TestEncodeMessage(t *testing.T) { @@ -112,13 +127,13 @@ func TestEncodeMessage(t *testing.T) { VerifyMessages() m := NewByteMessage(7) b := EncodeMessage(m) - assert.True(t, bytes.Equal(b, []byte{5, 0, 0, 0, 'B', 'Y', 'T', 'E', 7})) + require.True(t, bytes.Equal(b, []byte{5, 0, 0, 0, 'B', 'Y', 'T', 'E', 7})) } func TestEncodeMessageUnknownMessage(t *testing.T) { resetHandler() EraseMessages() - assert.Panics(t, func() { EncodeMessage(&DummyMessage{}) }) + require.Panics(t, func() { EncodeMessage(&DummyMessage{}) }) } func TestSendByteMessage(t *testing.T) { @@ -126,9 +141,9 @@ func TestSendByteMessage(t *testing.T) { b := []byte{1} c := NewCaptureConn() err := sendByteMessage(c, b, 0) - assert.Nil(t, err) - assert.True(t, bytes.Equal(c.(*CaptureConn).Wrote, b)) - assert.True(t, c.(*CaptureConn).WriteDeadlineSet) + require.NoError(t, err) + require.True(t, bytes.Equal(c.(*CaptureConn).Wrote, b)) + require.True(t, c.(*CaptureConn).WriteDeadlineSet) } func TestSendByteMessageWithTimeout(t *testing.T) { @@ -136,23 +151,23 @@ func TestSendByteMessageWithTimeout(t *testing.T) { b := []byte{1} c := NewCaptureConn() err := sendByteMessage(c, b, time.Minute) - assert.Nil(t, err) - assert.True(t, bytes.Equal(c.(*CaptureConn).Wrote, b)) - assert.True(t, c.(*CaptureConn).WriteDeadlineSet) + require.NoError(t, err) + require.True(t, bytes.Equal(c.(*CaptureConn).Wrote, b)) + require.True(t, c.(*CaptureConn).WriteDeadlineSet) } func TestSendByteMessageWriteFailed(t *testing.T) { resetHandler() c := &FailingWriteConn{} err := sendByteMessage(c, nil, 0) - assert.NotNil(t, err) + require.Error(t, err) } func TestSendByteMessageWriteDeadlineFailed(t *testing.T) { resetHandler() c := &FailingWriteDeadlineConn{} err := sendByteMessage(c, nil, 0) - assert.NotNil(t, err) + require.Error(t, err) } func TestSendMessage(t *testing.T) { @@ -163,11 +178,11 @@ func TestSendMessage(t *testing.T) { m := NewByteMessage(7) sendByteMessage = func(conn net.Conn, msg []byte, tm time.Duration) error { expect := []byte{5, 0, 0, 0, 'B', 'Y', 'T', 'E', 7} - assert.True(t, bytes.Equal(msg, expect)) + require.True(t, bytes.Equal(msg, expect)) return nil } err := sendMessage(nil, m, 0) - assert.Nil(t, err) + require.NoError(t, err) } /* Helpers */ diff --git a/src/daemon/gnet/message.go b/src/daemon/gnet/message.go index fd144b127e..32eb130f69 100644 --- a/src/daemon/gnet/message.go +++ b/src/daemon/gnet/message.go @@ -24,65 +24,16 @@ func MessagePrefixFromString(prefix string) MessagePrefix { return p } -/* - Need to use bytes type - - need to get rid of interface message type - - need to store abstract function pointer - - need to invoke the abstract message pointer - -Operations -- store a function signature (variable?) -- store a function -- - -*/ - -/* -Message Type needs to embody multiple types of struct data -- each type must have a response function -- the second parameter of each response function is different for each type -*/ - -/* -func Call(m map[string]interface{}, name string, params ... interface{}) (result []reflect.Value, err error) { -Ā Ā Ā Ā f = reflect.ValueOf(m[name]) -Ā Ā Ā Ā if len(params) != f.Type().NumIn() { -Ā Ā Ā Ā Ā Ā Ā Ā err = errors.New("The number of params is not adapted.") -Ā Ā Ā Ā Ā Ā Ā Ā return -Ā Ā Ā Ā } -Ā Ā Ā Ā in := make([]reflect.Value, len(params)) -Ā Ā Ā Ā for k, param := range params { -Ā Ā Ā Ā Ā Ā Ā Ā in[k] = reflect.ValueOf(param) -Ā Ā Ā Ā } -Ā Ā Ā Ā result = f[name].Call(in) -Ā Ā Ā Ā return -} -Call(funcs, "foo") -Call(funcs, "bar", 1, 2, 3) - -func foobar() { - // bla...bla...bla... -} -funcs := map[string]func() {"foobar":foobar} -funcs["foobar"]() - -*/ - // Message message interface type Message interface { - // State is user-defined application state that is attached to the - // Dispatcher. - // Return a non-nil error from handle only if you've disconnected the - // client. You don't have to return the DisconnectReason but that may - // be the most convenient. If error is not nil, event buffer processing - // is aborted. + // State is user-defined application state that is attached to the Dispatcher. + // If a non-nil error is returned, the connection will be disconnected. Handle(context *MessageContext, state interface{}) error } // MessageContext message context type MessageContext struct { - // Conn *Connection // connection message was received from - ConnID int // connection message was received from + ConnID uint64 // connection message was received from Addr string } @@ -107,8 +58,7 @@ func RegisterMessage(prefix MessagePrefix, msg interface{}) { copy(id[:], prefix[:]) _, exists := MessageIDReverseMap[id] if exists { - logger.Panicf("Attempted to register message prefix %s twice", - string(id[:])) + logger.Panicf("Attempted to register message prefix %s twice", string(id[:])) } _, exists = MessageIDMap[t] if exists { diff --git a/src/daemon/gnet/pool.go b/src/daemon/gnet/pool.go index 9ee946297c..8b870ce1a6 100644 --- a/src/daemon/gnet/pool.go +++ b/src/daemon/gnet/pool.go @@ -15,6 +15,8 @@ import ( "io" + "github.com/sirupsen/logrus" + "github.com/skycoin/skycoin/src/cipher/encoder" "github.com/skycoin/skycoin/src/daemon/strand" "github.com/skycoin/skycoin/src/util/elapse" @@ -29,48 +31,81 @@ const ( readLoopDurationThreshold = 10 * time.Second sendInMsgChanDurationThreshold = 5 * time.Second sendLoopDurationThreshold = 500 * time.Millisecond - defaultMaxDefaultConnNum = 1 ) var ( - // ErrDisconnectReadFailed also includes a remote closed socket - ErrDisconnectReadFailed DisconnectReason = errors.New("Read failed") - // ErrDisconnectWriteFailed write faile - ErrDisconnectWriteFailed DisconnectReason = errors.New("Write failed") // ErrDisconnectSetReadDeadlineFailed set read deadline failed - ErrDisconnectSetReadDeadlineFailed = errors.New("SetReadDeadline failed") + ErrDisconnectSetReadDeadlineFailed DisconnectReason = errors.New("SetReadDeadline failed") // ErrDisconnectInvalidMessageLength invalid message length ErrDisconnectInvalidMessageLength DisconnectReason = errors.New("Invalid message length") // ErrDisconnectMalformedMessage malformed message ErrDisconnectMalformedMessage DisconnectReason = errors.New("Malformed message body") - // ErrDisconnectUnknownMessage unknow message + // ErrDisconnectUnknownMessage unknown message ErrDisconnectUnknownMessage DisconnectReason = errors.New("Unknown message ID") - // ErrDisconnectUnexpectedError unexpected error - ErrDisconnectUnexpectedError DisconnectReason = errors.New("Unexpected error encountered") + // ErrDisconnectShutdown shutting down the client + ErrDisconnectShutdown DisconnectReason = errors.New("Shutdown") + // ErrDisconnectMessageDecodeUnderflow message data did not fully decode to a message object + ErrDisconnectMessageDecodeUnderflow DisconnectReason = errors.New("Message data did not fully decode to a message object") + // ErrDisconnectTruncatedMessageID message data was too short to contain a message ID + ErrDisconnectTruncatedMessageID DisconnectReason = errors.New("Message data was too short to contain a message ID") + // ErrConnectionPoolClosed error message indicates the connection pool is closed ErrConnectionPoolClosed = errors.New("Connection pool is closed") // ErrWriteQueueFull write queue is full ErrWriteQueueFull = errors.New("Write queue full") // ErrNoReachableConnections when broadcasting a message, no connections were available to send a message to ErrNoReachableConnections = errors.New("All pool connections are unreachable at this time") + // ErrNoMatchingConnections when broadcasting a message, no connections were found for the provided addresses + ErrNoMatchingConnections = errors.New("No connections found for broadcast addresses") // ErrPoolEmpty when broadcasting a message, the connection pool was empty - ErrPoolEmpty = errors.New("Connection pool is empty") + ErrPoolEmpty = errors.New("Connection pool is empty after filtering connections") + // ErrConnectionExists connection exists + ErrConnectionExists = errors.New("Connection exists") + // ErrMaxConnectionsReached max connection reached + ErrMaxConnectionsReached = errors.New("Max connections reached") + // ErrMaxOutgoingConnectionsReached max outgoing connections reached + ErrMaxOutgoingConnectionsReached = errors.New("Max outgoing connections reached") + // ErrMaxOutgoingDefaultConnectionsReached max outgoing default connections reached + ErrMaxOutgoingDefaultConnectionsReached = errors.New("Max outgoing default connections reached") + // ErrNoAddresses no addresses were provided to BroadcastMessage + ErrNoAddresses = errors.New("No addresses provided") + // Logger logger = logging.MustGetLogger("gnet") ) +// ReadError connection read error +type ReadError struct { + Err error +} + +func (e ReadError) Error() string { + return fmt.Sprintf("read failed: %v", e.Err) +} + +// WriteError connection read error +type WriteError struct { + Err error +} + +func (e WriteError) Error() string { + return fmt.Sprintf("write failed: %v", e.Err) +} + // Config gnet config type Config struct { // Address to listen on. Leave empty for arbitrary assignment Address string // Port to listen on. Set to 0 for arbitrary assignment Port uint16 - // Connection limits + // Maximum total connections MaxConnections int - // Messages greater than length are rejected and the sender disconnected - MaxMessageLength int + // Maximum outgoing connections + MaxOutgoingConnections int // Maximum allowed default outgoing connection number MaxDefaultPeerOutgoingConnections int + // Messages greater than length are rejected and the sender disconnected + MaxMessageLength int // Timeout is the timeout for dialing new connections. Use a // timeout of 0 to ignore timeout. DialTimeout time.Duration @@ -89,6 +124,8 @@ type Config struct { DisconnectCallback DisconnectCallback // Triggered on client connect ConnectCallback ConnectCallback + // Triggered on client connect failure + ConnectFailureCallback ConnectFailureCallback // Print debug logs DebugPrint bool // Default "trusted" peers @@ -104,7 +141,7 @@ func NewConfig() Config { Port: 0, MaxConnections: 128, MaxMessageLength: 256 * 1024, - MaxDefaultPeerOutgoingConnections: defaultMaxDefaultConnNum, + MaxDefaultPeerOutgoingConnections: 1, DialTimeout: time.Second * 30, ReadTimeout: time.Second * 30, WriteTimeout: time.Second * 30, @@ -125,7 +162,7 @@ const ( // Connection is stored by the ConnectionPool type Connection struct { // Key in ConnectionPool.Pool - ID int + ID uint64 // TCP connection Conn net.Conn // Message buffer @@ -142,7 +179,7 @@ type Connection struct { } // NewConnection creates a new Connection tied to a ConnectionPool -func NewConnection(pool *ConnectionPool, id int, conn net.Conn, writeQueueSize int, solicited bool) *Connection { +func NewConnection(pool *ConnectionPool, id uint64, conn net.Conn, writeQueueSize int, solicited bool) *Connection { return &Connection{ ID: id, Conn: conn, @@ -174,10 +211,13 @@ func (conn *Connection) Close() error { } // DisconnectCallback triggered on client disconnect -type DisconnectCallback func(addr string, reason DisconnectReason) +type DisconnectCallback func(addr string, id uint64, reason DisconnectReason) // ConnectCallback triggered on client connect -type ConnectCallback func(addr string, solicited bool) +type ConnectCallback func(addr string, id uint64, solicited bool) + +// ConnectFailureCallback trigger on client connect failure +type ConnectFailureCallback func(addr string, solicited bool, err error) // ConnectionPool connection pool type ConnectionPool struct { @@ -186,15 +226,17 @@ type ConnectionPool struct { // Channel for async message sending SendResults chan SendResult // All connections, indexed by ConnId - pool map[int]*Connection + pool map[uint64]*Connection // All connections, indexed by address addresses map[string]*Connection // connected default peer connections - defaultConnections map[string]struct{} + defaultOutgoingConnections map[string]struct{} + // connected outgoing connections + outgoingConnections map[string]struct{} // User-defined state to be passed into message handlers messageState interface{} // Connection ID counter - connID int + connID uint64 // Listening connection listener net.Listener listenerLock sync.Mutex @@ -216,16 +258,17 @@ func NewConnectionPool(c Config, state interface{}) *ConnectionPool { } pool := &ConnectionPool{ - Config: c, - pool: make(map[int]*Connection), - addresses: make(map[string]*Connection), - defaultConnections: make(map[string]struct{}), - SendResults: make(chan SendResult, c.SendResultsSize), - messageState: state, - quit: make(chan struct{}), - done: make(chan struct{}), - strandDone: make(chan struct{}), - reqC: make(chan strand.Request), + Config: c, + pool: make(map[uint64]*Connection), + addresses: make(map[string]*Connection), + defaultOutgoingConnections: make(map[string]struct{}), + outgoingConnections: make(map[string]struct{}), + SendResults: make(chan SendResult, c.SendResultsSize), + messageState: state, + quit: make(chan struct{}), + done: make(chan struct{}), + strandDone: make(chan struct{}), + reqC: make(chan strand.Request), } return pool @@ -277,7 +320,10 @@ loop: go func() { defer pool.wg.Done() if err := pool.handleConnection(conn, false); err != nil { - logger.Errorf("pool.handleConnection error: %v", err) + logger.WithFields(logrus.Fields{ + "addr": conn.RemoteAddr(), + "outgoing": false, + }).WithError(err).Error("pool.handleConnection") } }() } @@ -301,7 +347,7 @@ func (pool *ConnectionPool) processStrand() { return case req := <-pool.reqC: if err := req.Func(); err != nil { - logger.Errorf("req.Func %s failed: %v", req.Name, err) + logger.WithField("operation", req.Name).WithError(err).Error("strand req.Func failed") } } } @@ -355,91 +401,128 @@ func (pool *ConnectionPool) strand(name string, f func() error) error { return strand.Strand(logger, pool.reqC, name, f, pool.quit, ErrConnectionPoolClosed) } -// NewConnection creates a new Connection around a net.Conn. Trying to make a connection -// to an address that is already connected will failed. -// Returns nil, nil when max default connection limit hit -func (pool *ConnectionPool) NewConnection(conn net.Conn, solicited bool) (*Connection, error) { - a := conn.RemoteAddr().String() - var nc *Connection - if err := pool.strand("NewConnection", func() error { - if _, ok := pool.addresses[a]; ok { - return fmt.Errorf("Already connected to %s", a) +// ListeningAddress returns address, on which the ConnectionPool +// listening on. It returns nil, and error if the ConnectionPool +// is not listening +func (pool *ConnectionPool) ListeningAddress() (net.Addr, error) { + if pool.listener == nil { + return nil, errors.New("Not listening, call StartListen first") + } + return pool.listener.Addr(), nil +} + +func (pool *ConnectionPool) canConnect(a string, solicited bool) error { + if pool.isConnExist(a) { + return ErrConnectionExists + } + + if pool.isMaxConnectionsReached() { + return ErrMaxConnectionsReached + } + + if solicited { + if pool.isMaxOutgoingConnectionsReached() { + return ErrMaxOutgoingConnectionsReached } if _, ok := pool.Config.defaultConnections[a]; ok { - if pool.isMaxDefaultConnectionsReached() && solicited { - return nil + if pool.isMaxOutgoingDefaultConnectionsReached() { + return ErrMaxOutgoingDefaultConnectionsReached } - - pool.defaultConnections[a] = struct{}{} - l := len(pool.defaultConnections) - logger.Debugf("%d/%d default connections in use", l, pool.Config.MaxDefaultPeerOutgoingConnections) } + } - pool.connID++ - nc = NewConnection(pool, pool.connID, conn, pool.Config.ConnectionWriteQueueSize, solicited) + return nil +} - pool.pool[nc.ID] = nc - pool.addresses[a] = nc - return nil - }); err != nil { +// newConnection creates a new Connection around a net.Conn. Trying to make a connection +// to an address that is already connected will failed. +func (pool *ConnectionPool) newConnection(conn net.Conn, solicited bool) (*Connection, error) { + a := conn.RemoteAddr().String() + + if err := pool.canConnect(a, solicited); err != nil { return nil, err } - return nc, nil -} + if solicited { + pool.outgoingConnections[a] = struct{}{} -// ListeningAddress returns address, on which the ConnectionPool -// listening on. It returns nil, and error if the ConnectionPool -// is not listening -func (pool *ConnectionPool) ListeningAddress() (net.Addr, error) { - if pool.listener == nil { - return nil, errors.New("Not listening, call StartListen first") + if _, ok := pool.Config.defaultConnections[a]; ok { + pool.defaultOutgoingConnections[a] = struct{}{} + l := len(pool.defaultOutgoingConnections) + logger.WithField("addr", a).Debugf("%d/%d outgoing default connections in use", l, pool.Config.MaxDefaultPeerOutgoingConnections) + } } - return pool.listener.Addr(), nil + + // ID must start at 1; in case connID overflows back to 0, force it to 1 + pool.connID++ + if pool.connID == 0 { + pool.connID = 1 + } + + nc := NewConnection(pool, pool.connID, conn, pool.Config.ConnectionWriteQueueSize, solicited) + + pool.pool[nc.ID] = nc + pool.addresses[a] = nc + + return nc, nil } // Creates a Connection and begins its read and write loop func (pool *ConnectionPool) handleConnection(conn net.Conn, solicited bool) error { - defer logger.Debugf("Connection %s closed", conn.RemoteAddr()) + defer logger.WithField("addr", conn.RemoteAddr()).Debug("Connection closed") addr := conn.RemoteAddr().String() c, err := func() (c *Connection, err error) { + // TODO -- when limits in newConnection() are reached, should we allow the peer + // to be added anyway, so that we can disconnect it normally and send a disconnect packet? + // It would have to allowed to complete the handshake, otherwise the DISC packet will be ignored + // Or we would have to permit a DISC packet before an INTR + // But the read/write loop would still need to be started + // A ConnectEvent would need to be triggered, or else the DisconnectEvent gnet ID will not match the + // pending connection's zero gnet ID. defer func() { if err != nil { if closeErr := conn.Close(); closeErr != nil { - logger.Errorf("conn.Close() %s error: %v", addr, closeErr) + logger.WithError(closeErr).WithField("addr", addr).Error("handleConnection conn.Close") } } }() - exist, err := pool.IsConnExist(addr) - if err != nil { - return - } - if exist { - err = fmt.Errorf("Connection %s already exists", addr) - return - } + err = pool.strand("handleConnection", func() error { + var err error + c, err = pool.newConnection(conn, solicited) + if err != nil { + return err + } + + if pool.Config.ConnectCallback != nil { + pool.Config.ConnectCallback(c.Addr(), c.ID, solicited) + } + + return nil + }) - return pool.NewConnection(conn, solicited) + return }() + // TODO -- this error is not fully propagated back to a caller of Connect() so the daemon state + // can get stuck in pending if err != nil { + logger.WithError(err).WithField("addr", conn.RemoteAddr()).Debug("handleConnection: newConnection failed") + if pool.Config.ConnectFailureCallback != nil { + pool.Config.ConnectFailureCallback(addr, solicited, err) + } return err } - // c is nil if max default connection limit is reached - if c == nil { - return nil - } + msgC := make(chan []byte, 32) - if pool.Config.ConnectCallback != nil { - pool.Config.ConnectCallback(c.Addr(), solicited) + type methodErr struct { + method string + err error } - - msgC := make(chan []byte, 32) - errC := make(chan error, 3) + errC := make(chan methodErr, 3) var wg sync.WaitGroup wg.Add(1) @@ -447,7 +530,10 @@ func (pool *ConnectionPool) handleConnection(conn net.Conn, solicited bool) erro go func() { defer wg.Done() if err := pool.readLoop(c, msgC, qc); err != nil { - errC <- err + errC <- methodErr{ + method: "readLoop", + err: err, + } } }() @@ -455,7 +541,10 @@ func (pool *ConnectionPool) handleConnection(conn net.Conn, solicited bool) erro go func() { defer wg.Done() if err := pool.sendLoop(c, pool.Config.WriteTimeout, qc); err != nil { - errC <- err + errC <- methodErr{ + method: "sendLoop", + err: err, + } } }() @@ -468,7 +557,10 @@ func (pool *ConnectionPool) handleConnection(conn net.Conn, solicited bool) erro for msg := range msgC { elapser.Register(fmt.Sprintf("pool.receiveMessage address=%s", addr)) if err := pool.receiveMessage(c, msg); err != nil { - errC <- err + errC <- methodErr{ + method: "receiveMessage", + err: err, + } return } elapser.CheckForDone() @@ -478,13 +570,22 @@ func (pool *ConnectionPool) handleConnection(conn net.Conn, solicited bool) erro select { case <-pool.quit: if err := conn.Close(); err != nil { - logger.Errorf("conn.Close() %s error: %v", addr, err) + logger.WithError(err).WithField("addr", addr).Error("conn.Close") } - case err = <-errC: - if err := pool.Disconnect(c.Addr(), err); err != nil { - logger.Errorf("Disconnect %s failed: %v", addr, err) - } else { - logger.Debugf("Disconnected from %s", addr) + case mErr := <-errC: + err = mErr.err + logger.WithError(mErr.err).WithFields(logrus.Fields{ + "addr": addr, + "method": mErr.method, + }).Error("handleConnection failure") + + // This Disconnect does not send a DISC packet because it is inside gnet. + // A DISC packet is not useful at this point, because the error is most likely + // that the connection is unreachable. + // However, it may also be that the connection sent data that could not be deserialized + // to a message. + if err := pool.Disconnect(c.Addr(), mErr.err); err != nil { + logger.WithError(err).WithField("addr", addr).Error("Disconnect") } } close(qc) @@ -507,7 +608,7 @@ func (pool *ConnectionPool) readLoop(conn *Connection, msgChan chan []byte, qc c defer sendInMsgChanElapser.CheckForDone() for { - elapser.Register(fmt.Sprintf("readLoop address=%s", conn.Addr())) + elapser.Register(fmt.Sprintf("readLoop addr=%s", conn.Addr())) deadline := time.Time{} if pool.Config.ReadTimeout != 0 { deadline = time.Now().Add(pool.Config.ReadTimeout) @@ -574,7 +675,7 @@ func (pool *ConnectionPool) sendLoop(conn *Connection, timeout time.Duration, qc // since no further action in this block will happen after the write. if err == nil { if err := pool.updateLastSent(conn.Addr(), Now()); err != nil { - logger.Warningf("updateLastSent(%s) failed", conn.Addr()) + logger.WithField("addr", conn.Addr()).WithError(err).Warning("updateLastSent failed") } } @@ -584,7 +685,7 @@ func (pool *ConnectionPool) sendLoop(conn *Connection, timeout time.Duration, qc return nil case pool.SendResults <- sr: default: - logger.Warningf("SendResults queue full address=%s", conn.Addr()) + logger.WithField("addr", conn.Addr()).Warning("SendResults queue full") } if err != nil { @@ -597,17 +698,15 @@ func (pool *ConnectionPool) sendLoop(conn *Connection, timeout time.Duration, qc func readData(reader io.Reader, buf []byte) ([]byte, error) { c, err := reader.Read(buf) if err != nil { - return nil, fmt.Errorf("read data failed: %v", err) + return nil, &ReadError{ + Err: err, + } } if c == 0 { return nil, nil } data := make([]byte, c) - n := copy(data, buf) - if n != c { - // I don't believe this can ever occur - return nil, errors.New("Failed to copy all the bytes") - } + copy(data, buf) return data, nil } @@ -615,7 +714,6 @@ func readData(reader io.Reader, buf []byte) ([]byte, error) { func decodeData(buf *bytes.Buffer, maxMsgLength int) ([][]byte, error) { dataArray := [][]byte{} for buf.Len() > messageLengthSize { - //logger.Debug("There is data in the buffer, extracting") prefix := buf.Bytes()[:messageLengthSize] // decode message length tmpLength := uint32(0) @@ -629,14 +727,13 @@ func decodeData(buf *bytes.Buffer, maxMsgLength int) ([][]byte, error) { } length := int(tmpLength) - // logger.Debugf("Length is %d", length) + // Disconnect if we received an invalid length. if length < messagePrefixLength || length > maxMsgLength { return [][]byte{}, ErrDisconnectInvalidMessageLength } if buf.Len()-messageLengthSize < length { - // logger.Debug("Skipping, not enough data to read this") return [][]byte{}, nil } @@ -652,55 +749,22 @@ func decodeData(buf *bytes.Buffer, maxMsgLength int) ([][]byte, error) { return dataArray, nil } -// IsConnExist check if the connection of address does exist -func (pool *ConnectionPool) IsConnExist(addr string) (bool, error) { - var exist bool - if err := pool.strand("IsConnExist", func() error { - if _, ok := pool.addresses[addr]; ok { - exist = true - } - return nil - }); err != nil { - return false, fmt.Errorf("Check connection existence failed: %v ", err) - } - - return exist, nil -} - -// IsDefaultConnection returns if the addr is a default connection -func (pool *ConnectionPool) IsDefaultConnection(addr string) bool { - _, ok := pool.Config.defaultConnections[addr] +// isConnExist check if the connection of address does exist +func (pool *ConnectionPool) isConnExist(addr string) bool { + _, ok := pool.addresses[addr] return ok } -// IsMaxDefaultConnectionsReached returns whether the max default connection number was reached. -func (pool *ConnectionPool) IsMaxDefaultConnectionsReached() (bool, error) { - var reached bool - if err := pool.strand("IsMaxDefaultConnectionsReached", func() error { - reached = pool.isMaxDefaultConnectionsReached() - return nil - }); err != nil { - return false, err - } - - return reached, nil +func (pool *ConnectionPool) isMaxConnectionsReached() bool { + return len(pool.pool) >= pool.Config.MaxConnections } -func (pool *ConnectionPool) isMaxDefaultConnectionsReached() bool { - return len(pool.defaultConnections) >= pool.Config.MaxDefaultPeerOutgoingConnections +func (pool *ConnectionPool) isMaxOutgoingConnectionsReached() bool { + return len(pool.outgoingConnections) >= pool.Config.MaxOutgoingConnections } -// DefaultConnectionsInUse returns the default connection in use -func (pool *ConnectionPool) DefaultConnectionsInUse() (int, error) { - var use int - if err := pool.strand("GetDefaultConnectionsInUse", func() error { - use = len(pool.defaultConnections) - return nil - }); err != nil { - return 0, err - } - - return use, nil +func (pool *ConnectionPool) isMaxOutgoingDefaultConnectionsReached() bool { + return len(pool.defaultOutgoingConnections) >= pool.Config.MaxDefaultPeerOutgoingConnections } func (pool *ConnectionPool) updateLastSent(addr string, t time.Time) error { @@ -740,31 +804,13 @@ func (pool *ConnectionPool) GetConnection(addr string) (*Connection, error) { // Connect to an address func (pool *ConnectionPool) Connect(address string) error { - exist, err := pool.IsConnExist(address) - if err != nil { - return err - } - - if exist { - return nil - } - - var hitMaxDefaultConnNum bool - // Checks if it's one of the default connection - if err := pool.strand("Check default connection", func() error { - if _, ok := pool.Config.defaultConnections[address]; ok { - hitMaxDefaultConnNum = pool.isMaxDefaultConnectionsReached() - } - return nil + if err := pool.strand("canConnect", func() error { + return pool.canConnect(address, true) }); err != nil { return err } - if hitMaxDefaultConnNum { - return nil - } - - logger.Debugf("Making TCP Connection to %s", address) + logger.WithField("addr", address).Debugf("Making TCP connection") conn, err := net.DialTimeout("tcp", address, pool.Config.DialTimeout) if err != nil { return err @@ -774,59 +820,79 @@ func (pool *ConnectionPool) Connect(address string) error { go func() { defer pool.wg.Done() if err := pool.handleConnection(conn, true); err != nil { - logger.Errorf("pool.handleConnection error: %v", err) + logger.WithFields(logrus.Fields{ + "addr": conn.RemoteAddr(), + "outgoing": true, + }).WithError(err).Error("pool.handleConnection") } }() return nil } -// Disconnect removes a connection from the pool by address, and passes a Disconnection to -// the DisconnectCallback +// Disconnect removes a connection from the pool by address and invokes DisconnectCallback func (pool *ConnectionPool) Disconnect(addr string, r DisconnectReason) error { - if err := pool.strand("Disconnect", func() error { - exist := pool.disconnect(addr) + return pool.strand("Disconnect", func() error { + logger.WithFields(logrus.Fields{ + "addr": addr, + "reason": r, + }).Debug("Disconnecting") // checks if the address is default node address + isDefaultOutgoingConn := false if _, ok := pool.Config.defaultConnections[addr]; ok { - l := len(pool.defaultConnections) + if _, ok := pool.outgoingConnections[addr]; ok { + isDefaultOutgoingConn = true + } + } + + conn := pool.disconnect(addr, r) + + if conn == nil { + return errors.New("Disconnect: connection does not exist") + } + + if isDefaultOutgoingConn { + l := len(pool.defaultOutgoingConnections) logger.Debugf("%d/%d default connections in use", l, pool.Config.MaxDefaultPeerOutgoingConnections) } - if pool.Config.DisconnectCallback != nil && exist { - pool.Config.DisconnectCallback(addr, r) + if pool.Config.DisconnectCallback != nil { + pool.Config.DisconnectCallback(addr, conn.ID, r) } return nil - }); err != nil { - return err - } - - return nil + }) } -func (pool *ConnectionPool) disconnect(addr string) bool { +func (pool *ConnectionPool) disconnect(addr string, r DisconnectReason) *Connection { conn, ok := pool.addresses[addr] if !ok { - return false + return nil + } + + fields := logrus.Fields{ + "addr": addr, + "id": conn.ID, } delete(pool.pool, conn.ID) delete(pool.addresses, addr) - delete(pool.defaultConnections, addr) + delete(pool.defaultOutgoingConnections, addr) + delete(pool.outgoingConnections, addr) if err := conn.Close(); err != nil { - logger.Errorf("conn.Close() error address=%s: %v", addr, err) - } else { - logger.Debugf("Disconnected from %s", addr) + logger.WithError(err).WithFields(fields).Error("conn.Close") } - return true + logger.WithFields(fields).WithField("reason", r).Debug("Closed connection and removed from pool") + + return conn } // disconnectAll disconnects all connections. Only safe to call in Shutdown() func (pool *ConnectionPool) disconnectAll() { for _, conn := range pool.pool { addr := conn.Addr() - pool.disconnect(addr) + pool.disconnect(addr, ErrDisconnectShutdown) } } @@ -853,28 +919,10 @@ func (pool *ConnectionPool) Size() (l int, err error) { return } -// OutgoingConnectionsNum returns the number of outgoing connections -func (pool *ConnectionPool) OutgoingConnectionsNum() (int, error) { - var n int - if err := pool.strand("OutgoingSize", func() error { - for _, p := range pool.pool { - if p.Solicited { - n++ - } - } - return nil - }); err != nil { - return 0, err - } - - return n, nil -} - -// SendMessage sends a Message to a Connection and pushes the result onto the -// SendResults channel. +// SendMessage sends a Message to a Connection func (pool *ConnectionPool) SendMessage(addr string, msg Message) error { if pool.Config.DebugPrint { - logger.Debugf("Send, Msg Type: %s", reflect.TypeOf(msg)) + logger.WithField("msgType", reflect.TypeOf(msg)).Debug("SendMessage") } return pool.strand("SendMessage", func() error { @@ -882,7 +930,7 @@ func (pool *ConnectionPool) SendMessage(addr string, msg Message) error { select { case conn.WriteQueue <- msg: default: - logger.Critical().Infof("Write queue full for address %s", addr) + logger.Critical().WithField("addr", addr).Info("Write queue full") return ErrWriteQueueFull } } else { @@ -892,37 +940,58 @@ func (pool *ConnectionPool) SendMessage(addr string, msg Message) error { }) } -// BroadcastMessage sends a Message to all connections in the Pool. -func (pool *ConnectionPool) BroadcastMessage(msg Message) error { +// BroadcastMessage sends a Message to all connections specified in addrs. +// If a connection does not exist for a given address, it is skipped. +// If no messages were written to any connection, an error is returned. +// Returns the number of connections that the message was queued for sending to. +// Note that actual sending can still fail later, if the connection drops before the message is sent. +func (pool *ConnectionPool) BroadcastMessage(msg Message, addrs []string) (int, error) { if pool.Config.DebugPrint { - logger.Debugf("Broadcast, Msg Type: %s", reflect.TypeOf(msg)) + logger.WithField("msgType", reflect.TypeOf(msg)).Debug("BroadcastMessage") } - fullWriteQueue := []string{} + if len(addrs) == 0 { + return 0, ErrNoAddresses + } + + sentTo := 0 + if err := pool.strand("BroadcastMessage", func() error { if len(pool.pool) == 0 { return ErrPoolEmpty } - for _, conn := range pool.pool { - select { - case conn.WriteQueue <- msg: - default: - logger.Critical().Infof("Write queue full for address %s", conn.Addr()) - fullWriteQueue = append(fullWriteQueue, conn.Addr()) + fullWriteQueue := 0 + foundConns := 0 + + for _, addr := range addrs { + if conn, ok := pool.addresses[addr]; ok { + foundConns++ + select { + case conn.WriteQueue <- msg: + default: + logger.Critical().WithField("addr", conn.Addr()).Info("Write queue full") + fullWriteQueue++ + } } } - if len(fullWriteQueue) == len(pool.pool) { + if foundConns == 0 { + return ErrNoMatchingConnections + } + + if fullWriteQueue == foundConns { return ErrNoReachableConnections } + sentTo = foundConns - fullWriteQueue + return nil }); err != nil { - return err + return 0, err } - return nil + return sentTo, nil } // Unpacks incoming bytes to a Message and calls the message handler. If @@ -930,7 +999,6 @@ func (pool *ConnectionPool) BroadcastMessage(msg Message) error { // first return value. Otherwise, error will be nil and DisconnectReason will // be the value returned from the message handler. func (pool *ConnectionPool) receiveMessage(c *Connection, msg []byte) error { - m, err := convertToMessage(c.ID, msg, pool.Config.DebugPrint) if err != nil { return err @@ -965,11 +1033,11 @@ func (pool *ConnectionPool) SendPings(rate time.Duration, msg Message) error { return nil } -// ClearStaleConnections removes connections that have not sent a message in too long -func (pool *ConnectionPool) ClearStaleConnections(idleLimit time.Duration, reason DisconnectReason) error { +// GetStaleConnections returns connections that have been idle for longer than idleLimit +func (pool *ConnectionPool) GetStaleConnections(idleLimit time.Duration) ([]string, error) { now := Now() - idleConns := []string{} - if err := pool.strand("ClearStaleConnections", func() error { + var idleConns []string + if err := pool.strand("GetStaleConnections", func() error { for _, conn := range pool.pool { if conn.LastReceived.Add(idleLimit).Before(now) { idleConns = append(idleConns, conn.Addr()) @@ -977,15 +1045,10 @@ func (pool *ConnectionPool) ClearStaleConnections(idleLimit time.Duration, reaso } return nil }); err != nil { - return err + return nil, err } - for _, a := range idleConns { - if err := pool.Disconnect(a, reason); err != nil { - logger.WithError(err).WithField("addr", a).Warning("Error in disconnecting from stale connection") - } - } - return nil + return idleConns, nil } // Now returns the current UTC time diff --git a/src/daemon/gnet/pool_test.go b/src/daemon/gnet/pool_test.go index c60449bd5a..c2086707de 100644 --- a/src/daemon/gnet/pool_test.go +++ b/src/daemon/gnet/pool_test.go @@ -34,6 +34,9 @@ func newTestConfig() Config { cfg := NewConfig() cfg.Port = uint16(port) cfg.Address = address + cfg.MaxOutgoingConnections = 8 + cfg.MaxConnections = 8 + cfg.MaxDefaultPeerOutgoingConnections = 8 return cfg } @@ -43,14 +46,14 @@ func TestNewConnectionPool(t *testing.T) { cfg.DialTimeout = time.Duration(777) p := NewConnectionPool(cfg, nil) - require.Equal(t, p.Config, cfg) - require.Equal(t, p.Config.Port, cfg.Port) - require.Equal(t, p.Config.Address, cfg.Address) + require.Equal(t, cfg, p.Config) + require.Equal(t, cfg.Port, p.Config.Port) + require.Equal(t, cfg.Address, p.Config.Address) require.NotNil(t, p.pool) - require.Equal(t, len(p.pool), 0) + require.Equal(t, 0, len(p.pool)) require.NotNil(t, p.addresses) - require.Equal(t, len(p.addresses), 0) - require.Equal(t, p.connID, 0) + require.Equal(t, 0, len(p.addresses)) + require.Equal(t, uint64(0), p.connID) } func TestNewConnection(t *testing.T) { @@ -72,13 +75,13 @@ func TestNewConnection(t *testing.T) { err = p.strand("", func() error { c := p.addresses[conn.LocalAddr().String()] - require.Equal(t, p.pool[p.connID], c) - require.Equal(t, p.connID, 1) + require.Equal(t, c, p.pool[p.connID]) + require.Equal(t, uint64(1), p.connID) require.Equal(t, c.Addr(), conn.LocalAddr().String()) - require.Equal(t, cap(c.WriteQueue), cfg.ConnectionWriteQueueSize) + require.Equal(t, cfg.ConnectionWriteQueueSize, cap(c.WriteQueue)) require.NotNil(t, c.Buffer) - require.Equal(t, c.Buffer.Len(), 0) - require.Equal(t, c.ConnectionPool, p) + require.Equal(t, 0, c.Buffer.Len()) + require.Equal(t, p, c.ConnectionPool) require.False(t, c.LastSent.IsZero()) require.False(t, c.LastReceived.IsZero()) return nil @@ -94,7 +97,7 @@ func TestNewConnectionAlreadyConnected(t *testing.T) { p := NewConnectionPool(cfg, nil) cc := make(chan *Connection, 1) - p.Config.ConnectCallback = func(addr string, solicited bool) { + p.Config.ConnectCallback = func(addr string, id uint64, solicited bool) { require.False(t, solicited) cc <- p.pool[1] } @@ -117,9 +120,9 @@ func TestNewConnectionAlreadyConnected(t *testing.T) { require.NotNil(t, ac) require.Equal(t, c.ID, ac.ID) - _, err = p.NewConnection(c.Conn, true) + _, err = p.newConnection(c.Conn, true) require.Error(t, err) - require.True(t, strings.HasPrefix(err.Error(), "Already connected to")) + require.Equal(t, ErrConnectionExists, err) p.Shutdown() <-q @@ -131,7 +134,7 @@ func TestAcceptConnections(t *testing.T) { cc := make(chan *Connection, 1) var wasSolicited *bool - p.Config.ConnectCallback = func(addr string, solicited bool) { + p.Config.ConnectCallback = func(addr string, id uint64, solicited bool) { wasSolicited = &solicited require.False(t, solicited) cc <- p.pool[1] @@ -220,7 +223,7 @@ func TestHandleConnection(t *testing.T) { // Unsolicited cc := make(chan *Connection, 1) var wasSolicited *bool - p.Config.ConnectCallback = func(address string, solicited bool) { + p.Config.ConnectCallback = func(addr string, id uint64, solicited bool) { wasSolicited = &solicited cc <- p.pool[1] } @@ -236,21 +239,32 @@ func TestHandleConnection(t *testing.T) { conn, err := net.Dial("tcp", addr) require.NoError(t, err) - c := <-cc + var c *Connection + waitTimeout := time.Second * 3 + select { + case c = <-cc: + case <-time.After(waitTimeout): + t.Fatal("Timed out waiting for connection") + } require.NotNil(t, c) - exist, err := p.IsConnExist(conn.LocalAddr().String()) + var exist bool + err = p.strand("isConnExist", func() error { + exist = p.isConnExist(conn.LocalAddr().String()) + return nil + }) require.NoError(t, err) require.True(t, exist) - delete(p.addresses, conn.LocalAddr().String()) - delete(p.pool, 1) + dc := p.disconnect(conn.LocalAddr().String(), ErrDisconnectUnknownMessage) + require.NotNil(t, dc) require.NotNil(t, wasSolicited) require.False(t, *wasSolicited) // Solicited - p.Config.ConnectCallback = func(address string, s bool) { + wasSolicited = nil + p.Config.ConnectCallback = func(addr string, id uint64, s bool) { wasSolicited = &s cc <- p.pool[2] } @@ -258,28 +272,35 @@ func TestHandleConnection(t *testing.T) { done := make(chan struct{}) go func() { defer close(done) - p.handleConnection(conn, true) // nolint: errcheck + err = p.handleConnection(conn, true) + require.NotEqual(t, ErrConnectionExists, err) + require.NotEqual(t, ErrMaxConnectionsReached, err) + require.NotEqual(t, ErrMaxOutgoingConnectionsReached, err) + require.NotEqual(t, ErrMaxOutgoingDefaultConnectionsReached, err) }() - c = <-cc + c = nil + select { + case c = <-cc: + case <-time.After(waitTimeout): + t.Fatal("Timed out waiting for connection") + } require.NotNil(t, c) require.Equal(t, addr, c.Addr()) - require.NotNil(t, wasSolicited) - require.True(t, *wasSolicited) - - exist, err = p.IsConnExist(conn.RemoteAddr().String()) - require.NoError(t, err) - require.True(t, exist) - - require.Equal(t, len(p.addresses), 1) - require.Equal(t, len(p.pool), 1) - p.Shutdown() - <-done + select { + case <-done: + case <-time.After(waitTimeout): + t.Fatal("Timed out waiting for done") + } - <-q + select { + case <-q: + case <-time.After(waitTimeout): + t.Fatal("Timed out waiting for quit") + } } func TestConnect(t *testing.T) { @@ -300,7 +321,7 @@ func TestConnect(t *testing.T) { // If already connected, should return same connection err = p.Connect(addr) - require.NoError(t, err) + require.Equal(t, ErrConnectionExists, err) wait() delete(p.addresses, addr) @@ -351,7 +372,7 @@ func TestDisconnect(t *testing.T) { // Setup a callback to capture the connection pointer so we can get the address cc := make(chan *Connection, 1) - p.Config.ConnectCallback = func(addr string, solicited bool) { + p.Config.ConnectCallback = func(addr string, id uint64, solicited bool) { cc <- p.pool[1] } @@ -377,7 +398,7 @@ func TestDisconnect(t *testing.T) { require.NoError(t, err) err = p.strand("", func() error { - p.Config.DisconnectCallback = func(addr string, reason DisconnectReason) { + p.Config.DisconnectCallback = func(addr string, id uint64, reason DisconnectReason) { require.Equal(t, cAddr, addr) } return nil @@ -388,7 +409,7 @@ func TestDisconnect(t *testing.T) { require.NoError(t, err) err = p.strand("", func() error { - p.Config.DisconnectCallback = func(addr string, reason DisconnectReason) { + p.Config.DisconnectCallback = func(addr string, id uint64, reason DisconnectReason) { t.Fatal("disconnect unknown connection should not see this") } return nil @@ -396,7 +417,7 @@ func TestDisconnect(t *testing.T) { require.NoError(t, err) err = p.Disconnect("", nil) - require.NoError(t, err) + require.Equal(t, errors.New("Disconnect: connection does not exist"), err) p.Shutdown() <-q @@ -475,14 +496,14 @@ func TestGetConnections(t *testing.T) { require.NoError(t, err) require.Equal(t, len(conns), 3) - m := make(map[int]*Connection, 3) + m := make(map[uint64]*Connection, 3) for i, c := range conns { m[c.ID] = &conns[i] } require.Equal(t, len(m), 3) for i := 1; i <= 3; i++ { - require.Equal(t, m[i], p.pool[i]) + require.Equal(t, m[uint64(i)], p.pool[uint64(i)]) } p.Shutdown() @@ -494,7 +515,7 @@ func TestConnectionReadLoopReadError(t *testing.T) { p := NewConnectionPool(cfg, nil) cc := make(chan *Connection, 1) - p.Config.ConnectCallback = func(addr string, solicited bool) { + p.Config.ConnectCallback = func(addr string, id uint64, solicited bool) { cc <- p.addresses[addr] } @@ -507,12 +528,12 @@ func TestConnectionReadLoopReadError(t *testing.T) { wait() - readDataErr := errors.New("read data failed: failed") + readDataErr := "read failed: failed" disconnectCalled := make(chan struct{}) - p.Config.DisconnectCallback = func(addr string, reason DisconnectReason) { - require.Equal(t, readDataErr, reason) - close(disconnectCalled) + p.Config.DisconnectCallback = func(addr string, id uint64, reason DisconnectReason) { + defer close(disconnectCalled) + require.Equal(t, readDataErr, reason.Error()) } // 1: @@ -521,7 +542,7 @@ func TestConnectionReadLoopReadError(t *testing.T) { reconn := NewReadErrorConn() go func() { err := p.handleConnection(reconn, false) - require.Equal(t, readDataErr, err) + require.Equal(t, readDataErr, err.Error()) }() <-cc @@ -531,7 +552,11 @@ func TestConnectionReadLoopReadError(t *testing.T) { require.True(t, reconn.(*ReadErrorConn).GetReadDeadlineSet() != time.Time{}) reconn.Close() - <-disconnectCalled + select { + case <-disconnectCalled: + case <-time.After(time.Second * 10): + t.Fatal("wait for disconnectCalled timed out") + } p.Shutdown() <-q @@ -542,7 +567,7 @@ func TestConnectionReadLoopSetReadDeadlineFailed(t *testing.T) { p := NewConnectionPool(cfg, nil) cc := make(chan *Connection, 1) - p.Config.ConnectCallback = func(addr string, solicited bool) { + p.Config.ConnectCallback = func(addr string, id uint64, solicited bool) { cc <- p.addresses[addr] } @@ -558,9 +583,9 @@ func TestConnectionReadLoopSetReadDeadlineFailed(t *testing.T) { // 2: // Use a mock net.Conn that fails on SetReadDeadline disconnectCalled := make(chan struct{}) - p.Config.DisconnectCallback = func(addr string, reason DisconnectReason) { + p.Config.DisconnectCallback = func(addr string, id uint64, reason DisconnectReason) { + defer close(disconnectCalled) require.Equal(t, ErrDisconnectSetReadDeadlineFailed, reason) - close(disconnectCalled) } rdfconn := &ReadDeadlineFailedConn{} @@ -585,7 +610,7 @@ func TestConnectionReadLoopInvalidMessageLength(t *testing.T) { p := NewConnectionPool(cfg, nil) cc := make(chan *Connection, 1) - p.Config.ConnectCallback = func(addr string, solicited bool) { + p.Config.ConnectCallback = func(addr string, id uint64, solicited bool) { cc <- p.addresses[addr] } @@ -602,9 +627,9 @@ func TestConnectionReadLoopInvalidMessageLength(t *testing.T) { // Use a mock net.Conn that returns some bytes on Read // Look for these bytes copied into the eventChannel disconnectCalled := make(chan struct{}) - p.Config.DisconnectCallback = func(addr string, reason DisconnectReason) { + p.Config.DisconnectCallback = func(addr string, id uint64, reason DisconnectReason) { + defer close(disconnectCalled) require.Equal(t, ErrDisconnectInvalidMessageLength, reason) - close(disconnectCalled) } raconn := newReadAlwaysConn() @@ -631,7 +656,7 @@ func TestConnectionReadLoopTerminates(t *testing.T) { p := NewConnectionPool(cfg, nil) cc := make(chan *Connection, 1) - p.Config.ConnectCallback = func(addr string, solicited bool) { + p.Config.ConnectCallback = func(addr string, id uint64, solicited bool) { cc <- p.addresses[addr] } @@ -644,18 +669,18 @@ func TestConnectionReadLoopTerminates(t *testing.T) { wait() - readDataErr := errors.New("read data failed: done") + readDataErr := "read failed: done" // 4: Use a mock net.Conn that successfully returns 0 bytes when read rnconn := newReadNothingConn() disconnectCalled := make(chan struct{}) - p.Config.DisconnectCallback = func(addr string, reason DisconnectReason) { - require.Equal(t, readDataErr, reason) - close(disconnectCalled) + p.Config.DisconnectCallback = func(addr string, id uint64, reason DisconnectReason) { + defer close(disconnectCalled) + require.Equal(t, readDataErr, reason.Error()) } go func() { err := p.handleConnection(rnconn, false) - require.Equal(t, readDataErr, err) + require.Equal(t, readDataErr, err.Error()) }() <-cc @@ -682,11 +707,11 @@ func TestProcessConnectionBuffers(t *testing.T) { // Setup a callback to capture the connection pointer so we can get the address cc := make(chan *Connection, 1) - p.Config.ConnectCallback = func(addr string, solicited bool) { + p.Config.ConnectCallback = func(addr string, id uint64, solicited bool) { cc <- p.addresses[addr] } - p.Config.DisconnectCallback = func(addr string, reason DisconnectReason) { + p.Config.DisconnectCallback = func(addr string, id uint64, reason DisconnectReason) { t.Fatalf("Unexpected disconnect address=%s reason=%v", addr, reason) } @@ -723,9 +748,9 @@ func TestProcessConnectionBuffers(t *testing.T) { t.Logf("Pushing multiple messages, first one causing an error") disconnectCalled := make(chan struct{}) - p.Config.DisconnectCallback = func(addr string, reason DisconnectReason) { + p.Config.DisconnectCallback = func(addr string, id uint64, reason DisconnectReason) { + defer close(disconnectCalled) require.Equal(t, reason, ErrErrorMessageHandler) - close(disconnectCalled) } _, err = conn.Write([]byte{4, 0, 0, 0, 'E', 'R', 'R', 0x00}) @@ -737,7 +762,7 @@ func TestProcessConnectionBuffers(t *testing.T) { t.Fatal("disconnect did not happen, would block") } - p.Config.DisconnectCallback = func(addr string, reason DisconnectReason) { + p.Config.DisconnectCallback = func(addr string, id uint64, reason DisconnectReason) { fmt.Println(reason) t.Fatal("should not see this") } @@ -754,12 +779,12 @@ func TestProcessConnectionBuffers(t *testing.T) { require.NotNil(t, c) disconnectCalled = make(chan struct{}) - p.Config.DisconnectCallback = func(addr string, reason DisconnectReason) { + p.Config.DisconnectCallback = func(addr string, id uint64, reason DisconnectReason) { + defer close(disconnectCalled) require.Equal(t, c.Addr(), addr) require.Equal(t, reason, ErrDisconnectInvalidMessageLength) require.Nil(t, p.pool[1]) require.Nil(t, p.pool[2]) - close(disconnectCalled) } // Sending a length of < messagePrefixLength should cause a disconnect @@ -784,9 +809,9 @@ func TestProcessConnectionBuffers(t *testing.T) { t.Logf("Pushing message with too large length") p.Config.MaxMessageLength = 4 disconnectCalled = make(chan struct{}) - p.Config.DisconnectCallback = func(addr string, r DisconnectReason) { + p.Config.DisconnectCallback = func(addr string, id uint64, r DisconnectReason) { + defer close(disconnectCalled) require.Equal(t, ErrDisconnectInvalidMessageLength, r) - close(disconnectCalled) } _, err = conn.Write([]byte{5, 0, 0, 0, 'B', 'Y', 'T', 'E'}) @@ -817,12 +842,12 @@ func TestConnectionWriteLoop(t *testing.T) { // Setup a callback to capture the connection pointer so we can get the address cc := make(chan *Connection, 1) - p.Config.ConnectCallback = func(addr string, solicited bool) { + p.Config.ConnectCallback = func(addr string, id uint64, solicited bool) { cc <- p.pool[1] } disconnectErr := make(chan DisconnectReason, 1) - p.Config.DisconnectCallback = func(addr string, reason DisconnectReason) { + p.Config.DisconnectCallback = func(addr string, id uint64, reason DisconnectReason) { fmt.Printf("DisconnectCallback called, address=%s reason=%v\n", addr, reason) disconnectErr <- reason } @@ -917,7 +942,7 @@ func TestPoolSendMessageOK(t *testing.T) { // Setup a callback to capture the connection pointer so we can get the address cc := make(chan *Connection, 1) - p.Config.ConnectCallback = func(addr string, solicited bool) { + p.Config.ConnectCallback = func(addr string, id uint64, solicited bool) { cc <- p.pool[1] } @@ -955,7 +980,7 @@ func TestPoolSendMessageWriteQueueFull(t *testing.T) { // Setup a callback to capture the connection pointer so we can get the address cc := make(chan *Connection, 1) - p.Config.ConnectCallback = func(addr string, solicited bool) { + p.Config.ConnectCallback = func(addr string, id uint64, solicited bool) { cc <- p.pool[1] } @@ -1013,7 +1038,7 @@ func TestPoolBroadcastMessage(t *testing.T) { ready := make(chan struct{}) var i int var counterLock sync.Mutex - p.Config.ConnectCallback = func(addr string, solicited bool) { + p.Config.ConnectCallback = func(addr string, id uint64, solicited bool) { counterLock.Lock() defer counterLock.Unlock() i++ @@ -1048,9 +1073,26 @@ func TestPoolBroadcastMessage(t *testing.T) { <-ready + var addrs []string + err = p.strand("addresses", func() error { + for a := range p.addresses { + addrs = append(addrs, a) + } + return nil + }) + require.NoError(t, err) + require.NotEmpty(t, addrs) + m := NewByteMessage(88) - err = p.BroadcastMessage(m) + n, err := p.BroadcastMessage(m, addrs) require.NoError(t, err) + require.Equal(t, 2, n) + + _, err = p.BroadcastMessage(m, []string{}) + require.Equal(t, ErrNoAddresses, err) + + _, err = p.BroadcastMessage(m, []string{"1.1.1.1"}) + require.Equal(t, ErrNoMatchingConnections, err) // Spam the connections with so much data that their write queue overflows, // which will cause ErrNoReachableConnections @@ -1062,7 +1104,7 @@ func TestPoolBroadcastMessage(t *testing.T) { for i := 0; i < attempts; i++ { go func() { defer wg.Done() - err := p.BroadcastMessage(m) + _, err := p.BroadcastMessage(m, addrs) if err == ErrNoReachableConnections { once.Do(func() { gotErr = true diff --git a/src/daemon/heights.go b/src/daemon/heights.go deleted file mode 100644 index abd208eea0..0000000000 --- a/src/daemon/heights.go +++ /dev/null @@ -1,93 +0,0 @@ -package daemon - -import ( - "sort" - "strings" - "sync" -) - -// PeerBlockchainHeight is a peer's IP address with their reported blockchain height -type PeerBlockchainHeight struct { - Address string - Height uint64 -} - -// peerBlockchainHeights tracks reported blockchain heights of peers -type peerBlockchainHeights struct { - // Peer-reported blockchain height. Use to estimate download progress - heights map[string]uint64 - sync.Mutex -} - -// newPeerBlockchainHeights creates a peerBlockchainHeights -func newPeerBlockchainHeights() *peerBlockchainHeights { - return &peerBlockchainHeights{ - heights: make(map[string]uint64), - } -} - -// Remove removes a connection from the records -func (p *peerBlockchainHeights) Remove(addr string) { - p.Lock() - defer p.Unlock() - - delete(p.heights, addr) -} - -// Record saves a peer-reported blockchain height -func (p *peerBlockchainHeights) Record(addr string, height uint64) { - p.Lock() - defer p.Unlock() - - p.heights[addr] = height -} - -// Estimate returns the blockchain length estimated from peer reports. -// The highest height reported amongst all peers, and including the node itself, -// is returned. -func (p *peerBlockchainHeights) Estimate(headSeq uint64) uint64 { - p.Lock() - defer p.Unlock() - - for _, seq := range p.heights { - if headSeq < seq { - headSeq = seq - } - } - - return headSeq -} - -// Get returns the height for a given address -func (p *peerBlockchainHeights) Get(addr string) (uint64, bool) { - p.Lock() - defer p.Unlock() - - height, ok := p.heights[addr] - return height, ok -} - -// All returns recorded peers' blockchain heights as an array. -// The array is sorted by address as strings. -func (p *peerBlockchainHeights) All() []PeerBlockchainHeight { - p.Lock() - defer p.Unlock() - - if len(p.heights) == 0 { - return []PeerBlockchainHeight{} - } - - peerHeights := make([]PeerBlockchainHeight, 0, len(p.heights)) - for addr, height := range p.heights { - peerHeights = append(peerHeights, PeerBlockchainHeight{ - Address: addr, - Height: height, - }) - } - - sort.Slice(peerHeights, func(i, j int) bool { - return strings.Compare(peerHeights[i].Address, peerHeights[j].Address) < 0 - }) - - return peerHeights -} diff --git a/src/daemon/heights_test.go b/src/daemon/heights_test.go deleted file mode 100644 index f3acd1dc7b..0000000000 --- a/src/daemon/heights_test.go +++ /dev/null @@ -1,82 +0,0 @@ -package daemon - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestPeerBlockchainHeights(t *testing.T) { - p := newPeerBlockchainHeights() - - addr1 := "127.0.0.1:1234" - addr2 := "127.0.0.1:5678" - addr3 := "127.0.0.1:9999" - - require.Empty(t, p.heights) - p.Remove(addr1) - require.Empty(t, p.heights) - - e := p.Estimate(1) - require.Equal(t, uint64(1), e) - - e = p.Estimate(13) - require.Equal(t, uint64(13), e) - - p.Record(addr1, 10) - require.Len(t, p.heights, 1) - - records := p.All() - require.Len(t, records, 1) - require.Equal(t, PeerBlockchainHeight{ - Address: addr1, - Height: 10, - }, records[0]) - - p.Record(addr1, 11) - require.Len(t, p.heights, 1) - - records = p.All() - require.Len(t, records, 1) - require.Equal(t, PeerBlockchainHeight{ - Address: addr1, - Height: 11, - }, records[0]) - - e = p.Estimate(1) - require.Equal(t, uint64(11), e) - - e = p.Estimate(13) - require.Equal(t, uint64(13), e) - - p.Record(addr2, 12) - p.Record(addr3, 12) - require.Len(t, p.heights, 3) - - records = p.All() - require.Len(t, records, 3) - require.Equal(t, []PeerBlockchainHeight{ - { - Address: addr1, - Height: 11, - }, - { - Address: addr2, - Height: 12, - }, - { - Address: addr3, - Height: 12, - }, - }, records) - - e = p.Estimate(1) - require.Equal(t, uint64(12), e) - - e = p.Estimate(13) - require.Equal(t, uint64(13), e) - - p.Record(addr3, 24) - e = p.Estimate(13) - require.Equal(t, uint64(24), e) -} diff --git a/src/daemon/messages.go b/src/daemon/messages.go index 0dcde846fa..bead69d2eb 100644 --- a/src/daemon/messages.go +++ b/src/daemon/messages.go @@ -4,16 +4,18 @@ import ( "encoding/binary" "errors" "fmt" - "math/rand" "net" "strings" - "time" + + "github.com/sirupsen/logrus" "github.com/skycoin/skycoin/src/cipher" + "github.com/skycoin/skycoin/src/cipher/encoder" "github.com/skycoin/skycoin/src/coin" "github.com/skycoin/skycoin/src/daemon/gnet" "github.com/skycoin/skycoin/src/daemon/pex" "github.com/skycoin/skycoin/src/util/iputil" + "github.com/skycoin/skycoin/src/util/useragent" ) // Message represent a packet to be serialized over the network by @@ -23,7 +25,7 @@ import ( // DaemonLoop(). // Message do this by caching the gnet.MessageContext received in Handle() // and placing itself on the messageEvent channel. -// When the message is retrieved from the messageEvent channel, its Process() +// When the message is retrieved from the messageEvent channel, its process() // method is called. // MessageConfig config contains a gnet.Message's 4byte prefix and a @@ -55,6 +57,7 @@ func getMessageConfigs() []MessageConfig { NewMessageConfig("GETT", GetTxnsMessage{}), NewMessageConfig("GIVT", GiveTxnsMessage{}), NewMessageConfig("ANNT", AnnounceTxnsMessage{}), + NewMessageConfig("DISC", DisconnectMessage{}), } } @@ -82,15 +85,12 @@ func (msc *MessagesConfig) Register() { // Messages messages struct type Messages struct { Config MessagesConfig - // Magic value for detecting self-connection - Mirror uint32 } // NewMessages creates Messages func NewMessages(c MessagesConfig) *Messages { return &Messages{ Config: c, - Mirror: rand.New(rand.NewSource(time.Now().UTC().UnixNano())).Uint32(), } } @@ -127,18 +127,16 @@ func (ipa IPAddr) String() string { return fmt.Sprintf("%s:%d", net.IP(ipb).String(), ipa.Port) } -// AsyncMessage messages that perform an action when received must implement this interface. -// Process() is called after the message is pulled off of messageEvent channel. +// asyncMessage messages that perform an action when received must implement this interface. +// process() is called after the message is pulled off of messageEvent channel. // Messages should place themselves on the messageEvent channel in their // Handle() method required by gnet. -type AsyncMessage interface { - Process(d Daemoner) +type asyncMessage interface { + process(d daemoner) } // GetPeersMessage sent to request peers type GetPeersMessage struct { - // c *gnet.MessageContext `enc:"-"` - // connID int `enc:"-"` addr string `enc:"-"` } @@ -149,26 +147,18 @@ func NewGetPeersMessage() *GetPeersMessage { // Handle handles message func (gpm *GetPeersMessage) Handle(mc *gnet.MessageContext, daemon interface{}) error { - // self.connID = mc.ConnID gpm.addr = mc.Addr - return daemon.(Daemoner).RecordMessageEvent(gpm, mc) + return daemon.(daemoner).recordMessageEvent(gpm, mc) } -// Process Notifies the Pex instance that peers were requested -func (gpm *GetPeersMessage) Process(d Daemoner) { - if d.PexConfig().Disabled { +// process Notifies the Pex instance that peers were requested +func (gpm *GetPeersMessage) process(d daemoner) { + if d.pexConfig().Disabled { return } - peers := d.RandomExchangeable(d.PexConfig().ReplyCount) - if len(peers) == 0 { - logger.Debug("We have no peers to send in reply") - return - } - - m := NewGivePeersMessage(peers) - if err := d.SendMessage(gpm.addr, m); err != nil { - logger.Errorf("Send GivePeersMessage to %s failed: %v", gpm.addr, err) + if err := d.sendRandomPeers(gpm.addr); err != nil { + logger.WithField("addr", gpm.addr).WithError(err).Error("SendRandomPeers failed") } } @@ -184,8 +174,7 @@ func NewGivePeersMessage(peers []pex.Peer) *GivePeersMessage { for _, ps := range peers { ipaddr, err := NewIPAddr(ps.Addr) if err != nil { - logger.Warningf("GivePeersMessage skipping address %s", ps.Addr) - logger.Warning(err.Error()) + logger.WithError(err).WithField("addr", ps.Addr).Warning("GivePeersMessage skipping invalid address") continue } ipaddrs = append(ipaddrs, ipaddr) @@ -207,182 +196,225 @@ func (gpm *GivePeersMessage) GetPeers() []string { // Handle handle message func (gpm *GivePeersMessage) Handle(mc *gnet.MessageContext, daemon interface{}) error { gpm.c = mc - return daemon.(Daemoner).RecordMessageEvent(gpm, mc) + return daemon.(daemoner).recordMessageEvent(gpm, mc) } -// Process Notifies the Pex instance that peers were received -func (gpm *GivePeersMessage) Process(d Daemoner) { - if d.PexConfig().Disabled { +// process Notifies the Pex instance that peers were received +func (gpm *GivePeersMessage) process(d daemoner) { + if d.pexConfig().Disabled { return } + peers := gpm.GetPeers() - logger.Debugf("Got these peers via PEX: %s", strings.Join(peers, ", ")) - d.AddPeers(peers) + if len(peers) == 0 { + return + } + + // Cap the number of peers printed in the log to prevent log spam abuse + peersToFmt := peers + if len(peersToFmt) > 30 { + peersToFmt = peersToFmt[:30] + } + peersStr := strings.Join(peersToFmt, ", ") + if len(peers) != len(peersToFmt) { + peersStr += fmt.Sprintf(" and %d more", len(peers)-len(peersToFmt)) + } + + logger.WithFields(logrus.Fields{ + "addr": gpm.c.Addr, + "gnetID": gpm.c.ConnID, + "peers": peersStr, + "count": len(peers), + }).Debug("Received peers via PEX") + + d.addPeers(peers) } -// IntroductionMessage jan IntroductionMessage is sent on first connect by both parties +// IntroductionMessage is sent on first connect by both parties type IntroductionMessage struct { - // Mirror is a random value generated on client startup that is used - // to identify self-connections + c *gnet.MessageContext `enc:"-"` + + // Mirror is a random value generated on client startup that is used to identify self-connections Mirror uint32 - // Port is the port that this client is listening on - Port uint16 - // Our client version - Version int32 - c *gnet.MessageContext `enc:"-"` - // We validate the message in Handle() and cache the result for Process() - valid bool `enc:"-"` // skip it during encoding - // Pubkey is the blockchain pubkey - Pubkey []byte `enc:",omitempty"` + // ListenPort is the port that this client is listening on + ListenPort uint16 + // Protocol version + ProtocolVersion int32 + + // Extra is extra bytes added to the struct to accommodate multiple versions of this packet. + // Currently it contains the blockchain pubkey and user agent but will accept a client that does not provide it. + // If any of this data is provided, it must include a valid blockchain pubkey and a valid user agent string (maxlen=256). + // Contents of extra: + // ExtraByte uint32 // length prefix of []byte + // Pubkey cipher.Pubkey // blockchain pubkey + // UserAgent string `enc:",maxlen=256"` + Extra []byte `enc:",omitempty"` } // NewIntroductionMessage creates introduction message -func NewIntroductionMessage(mirror uint32, version int32, port uint16, pubkey []byte) *IntroductionMessage { +func NewIntroductionMessage(mirror uint32, version int32, port uint16, pubkey cipher.PubKey, userAgent string) *IntroductionMessage { + if len(userAgent) > useragent.MaxLen { + logger.WithFields(logrus.Fields{ + "userAgent": userAgent, + "maxLen": useragent.MaxLen, + }).Panic("user agent exceeds max len") + } + if userAgent == "" { + logger.Panic("user agent is required") + } + + userAgentSerialized := encoder.SerializeString(userAgent) + + extra := make([]byte, len(pubkey)+len(userAgentSerialized)) + + copy(extra[:len(pubkey)], pubkey[:]) + copy(extra[len(pubkey):], userAgentSerialized) + return &IntroductionMessage{ - Mirror: mirror, - Version: version, - Port: port, - Pubkey: pubkey, + Mirror: mirror, + ProtocolVersion: version, + ListenPort: port, + Extra: extra, } } -// Handle Responds to an gnet.Pool event. We implement Handle() here because we -// need to control the DisconnectReason sent back to gnet. We still implement -// Process(), where we do modifications that are not threadsafe +// Handle records message event in daemon func (intro *IntroductionMessage) Handle(mc *gnet.MessageContext, daemon interface{}) error { - d := daemon.(Daemoner) - - err := func() error { - // Disconnect if this is a self connection (we have the same mirror value) - if intro.Mirror == d.Mirror() { - logger.Infof("Remote mirror value %v matches ours", intro.Mirror) - if err := d.Disconnect(mc.Addr, ErrDisconnectSelf); err != nil { - logger.WithError(err).WithField("addr", mc.Addr).Warning("Disconnect") - } - return ErrDisconnectSelf - } + intro.c = mc + return daemon.(daemoner).recordMessageEvent(intro, mc) +} - // Disconnect if not running the same version - if intro.Version != d.DaemonConfig().Version { - logger.Infof("%s has different version %d. Disconnecting.", - mc.Addr, intro.Version) - if err := d.Disconnect(mc.Addr, ErrDisconnectInvalidVersion); err != nil { - logger.WithError(err).WithField("addr", mc.Addr).Warning("Disconnect") - } - return ErrDisconnectInvalidVersion - } +// process an event queued by Handle() +func (intro *IntroductionMessage) process(d daemoner) { + addr := intro.c.Addr - logger.Infof("%s verified for version %d", mc.Addr, intro.Version) - - // v25 Checks the blockchain pubkey, would accept message with no Pubkey - // v26 would check the blockchain pubkey and reject if not matched or not provided - if len(intro.Pubkey) > 0 { - var bcPubKey cipher.PubKey - if len(intro.Pubkey) < len(bcPubKey) { - logger.Infof("Extra data length does not meet the minimum requirement") - if err := d.Disconnect(mc.Addr, ErrDisconnectInvalidExtraData); err != nil { - logger.WithError(err).WithField("addr", mc.Addr).Warning("Disconnect") - } - return ErrDisconnectInvalidExtraData - } - copy(bcPubKey[:], intro.Pubkey[:len(bcPubKey)]) - - if d.BlockchainPubkey() != bcPubKey { - logger.Infof("Blockchain pubkey does not match, local: %s, remote: %s", d.BlockchainPubkey().Hex(), bcPubKey.Hex()) - if err := d.Disconnect(mc.Addr, ErrDisconnectBlockchainPubkeyNotMatched); err != nil { - logger.WithError(err).WithField("addr", mc.Addr).Warning("Disconnect") - } - return ErrDisconnectBlockchainPubkeyNotMatched - } - } + fields := logrus.Fields{ + "addr": addr, + "gnetID": intro.c.ConnID, + "listenPort": intro.ListenPort, + } - // only solicited connection can be added to exchange peer list, cause accepted - // connection may not have incomming port. - ip, port, err := iputil.SplitAddr(mc.Addr) - if err != nil { - // This should never happen, but the program should still work if it - // does. - logger.Errorf("Invalid Addr() for connection: %s", mc.Addr) - if err := d.Disconnect(mc.Addr, ErrDisconnectOtherError); err != nil { - logger.WithError(err).WithField("addr", mc.Addr).Warning("Disconnect") - } - return ErrDisconnectOtherError + logger.WithFields(fields).Debug("IntroductionMessage.process") + + userAgent, err := intro.verify(d) + if err != nil { + if err := d.Disconnect(addr, err); err != nil { + logger.WithError(err).WithFields(fields).Warning("Disconnect") } + return + } - // Checks if the introduction message is from outgoing connection. - // It's outgoing connection if port == intro.Port, as the incoming - // connection's port is a random port, it's different from the port - // in introduction message. - if port == intro.Port { - if err := d.SetHasIncomingPort(mc.Addr); err != nil { - logger.Errorf("Failed to set peer has incoming port status, %v", err) - } - } else { - if err := d.AddPeer(fmt.Sprintf("%s:%d", ip, intro.Port)); err != nil { - logger.Errorf("Failed to add peer: %v", err) + if _, err := d.connectionIntroduced(addr, intro.c.ConnID, intro, userAgent); err != nil { + logger.WithError(err).WithFields(fields).Warning("connectionIntroduced failed") + var reason gnet.DisconnectReason + switch err { + // It is hypothetically possible that a message would get processed after + // a disconnect event for a given connection. + // In this case, drop the packet. + // Do not perform a disconnect, since this would operate on the new connection. + // This should be prevented by an earlier check in daemon.onMessageEvent() + case ErrConnectionGnetIDMismatch, ErrConnectionStateNotConnected, ErrConnectionAlreadyIntroduced: + logger.Critical().WithError(err).WithFields(fields).Warning("IntroductionMessage.process connection state out of order") + return + case ErrConnectionNotExist: + return + case ErrConnectionIPMirrorExists: + reason = ErrDisconnectConnectedTwice + case pex.ErrPeerlistFull: + reason = ErrDisconnectPeerlistFull + // Send more peers before disconnecting + logger.WithFields(fields).Debug("Sending peers before disconnecting due to peer list full") + if err := d.sendRandomPeers(addr); err != nil { + logger.WithError(err).WithFields(fields).Warning("sendRandomPeers failed") } + default: + reason = ErrDisconnectUnexpectedError } - // Disconnect if connected twice to the same peer (judging by ip:mirror) - knownPort, exists := d.GetMirrorPort(mc.Addr, intro.Mirror) - if exists { - logger.Infof("%s is already connected on port %d", mc.Addr, knownPort) - if err := d.Disconnect(mc.Addr, ErrDisconnectConnectedTwice); err != nil { - logger.WithError(err).WithField("addr", mc.Addr).Warning("Disconnect") - } - return ErrDisconnectConnectedTwice + if err := d.Disconnect(addr, reason); err != nil { + logger.WithError(err).WithFields(fields).Warning("Disconnect") } - return nil - }() - intro.valid = (err == nil) - intro.c = mc + return + } - if err != nil { - d.IncreaseRetryTimes(mc.Addr) - d.RemoveFromExpectingIntroductions(mc.Addr) - return err + // Request blocks immediately after they're confirmed + if err := d.requestBlocksFromAddr(addr); err != nil { + logger.WithError(err).WithFields(fields).Warning("requestBlocksFromAddr") + } else { + logger.WithFields(fields).Debug("Requested blocks") } - err = d.RecordMessageEvent(intro, mc) - d.ResetRetryTimes(mc.Addr) - return err + // Announce unconfirmed txns + if err := d.announceAllTxns(); err != nil { + logger.WithError(err).Warning("announceAllTxns failed") + } } -// Process an event queued by Handle() -func (intro *IntroductionMessage) Process(d Daemoner) { - d.RemoveFromExpectingIntroductions(intro.c.Addr) - if !intro.valid { - return +func (intro *IntroductionMessage) verify(d daemoner) (*useragent.Data, error) { + addr := intro.c.Addr + + fields := logrus.Fields{ + "addr": addr, + "gnetID": intro.c.ConnID, } - // Add the remote peer with their chosen listening port - a := intro.c.Addr - // Record their listener, to avoid double connections - err := d.RecordConnectionMirror(a, intro.Mirror) - if err != nil { - // This should never happen, but the program should not allow itself - // to be corrupted in case it does - logger.Errorf("Invalid port for connection %s", a) - if err := d.Disconnect(intro.c.Addr, ErrDisconnectOtherError); err != nil { - logger.WithError(err).WithField("addr", intro.c.Addr).Warning("Disconnect") - } - return + dc := d.daemonConfig() + + // Disconnect if this is a self connection (we have the same mirror value) + if intro.Mirror == dc.Mirror { + logger.WithFields(fields).WithField("mirror", intro.Mirror).Info("Remote mirror value matches ours") + return nil, ErrDisconnectSelf } - // Request blocks immediately after they're confirmed - err = d.RequestBlocksFromAddr(intro.c.Addr) - if err == nil { - logger.Debugf("Successfully requested blocks from %s", intro.c.Addr) - } else { - logger.Warning(err) + // Disconnect if peer version is not within the supported range + if intro.ProtocolVersion < dc.MinProtocolVersion { + logger.WithFields(fields).WithFields(logrus.Fields{ + "protocolVersion": intro.ProtocolVersion, + "minProtocolVersion": dc.MinProtocolVersion, + }).Info("protocol version below minimum supported protocol version") + return nil, ErrDisconnectVersionNotSupported } - // Announce unconfirmed txns - if err := d.AnnounceAllTxns(); err != nil { - logger.WithError(err).Warning("AnnounceAllTxns failed") + logger.WithFields(fields).WithField("protocolVersion", intro.ProtocolVersion).Debug("Peer protocol version accepted") + + // v24 does not send blockchain pubkey or user agent + // v25 sends blockchain pubkey and user agent + // v24 and v25 check the blockchain pubkey and user agent, would accept message with no Pubkey and user agent + // v26 would check the blockchain pubkey and reject if not matched or not provided, and parses a user agent + var userAgentData useragent.Data + if len(intro.Extra) > 0 { + var bcPubKey cipher.PubKey + if len(intro.Extra) < len(bcPubKey) { + logger.WithFields(fields).Warning("Extra data length does not meet the minimum requirement") + return nil, ErrDisconnectInvalidExtraData + } + copy(bcPubKey[:], intro.Extra[:len(bcPubKey)]) + + if dc.BlockchainPubkey != bcPubKey { + logger.WithFields(fields).WithFields(logrus.Fields{ + "pubkey": bcPubKey.Hex(), + "daemonPubkey": dc.BlockchainPubkey.Hex(), + }).Warning("Blockchain pubkey does not match") + return nil, ErrDisconnectBlockchainPubkeyNotMatched + } + + userAgentSerialized := intro.Extra[len(bcPubKey):] + userAgent, _, err := encoder.DeserializeString(userAgentSerialized, useragent.MaxLen) + if err != nil { + logger.WithError(err).WithFields(fields).Warning("Extra data user agent string could not be deserialized") + return nil, ErrDisconnectInvalidExtraData + } + + userAgentData, err = useragent.Parse(useragent.Sanitize(userAgent)) + if err != nil { + logger.WithError(err).WithFields(fields).WithField("userAgent", userAgent).Warning("User agent is invalid") + return nil, ErrDisconnectInvalidUserAgent + } } + + return &userAgentData, nil } // PingMessage Sent to keep a connection alive. A PongMessage is sent in reply. @@ -393,16 +425,21 @@ type PingMessage struct { // Handle implements the Messager interface func (ping *PingMessage) Handle(mc *gnet.MessageContext, daemon interface{}) error { ping.c = mc - return daemon.(Daemoner).RecordMessageEvent(ping, mc) + return daemon.(daemoner).recordMessageEvent(ping, mc) } -// Process Sends a PongMessage to the sender of PingMessage -func (ping *PingMessage) Process(d Daemoner) { - if d.DaemonConfig().LogPings { - logger.Debugf("Reply to ping from %s", ping.c.Addr) +// process Sends a PongMessage to the sender of PingMessage +func (ping *PingMessage) process(d daemoner) { + fields := logrus.Fields{ + "addr": ping.c.Addr, + "gnetID": ping.c.ConnID, } - if err := d.SendMessage(ping.c.Addr, &PongMessage{}); err != nil { - logger.Errorf("Send PongMessage to %s failed: %v", ping.c.Addr, err) + + if d.daemonConfig().LogPings { + logger.WithFields(fields).Debug("Replying to ping") + } + if err := d.sendMessage(ping.c.Addr, &PongMessage{}); err != nil { + logger.WithFields(fields).WithError(err).Error("Send PongMessage failed") } } @@ -415,11 +452,55 @@ func (pong *PongMessage) Handle(mc *gnet.MessageContext, daemon interface{}) err // There is nothing to do; gnet updates Connection.LastMessage internally // when this is received if daemon.(*Daemon).Config.LogPings { - logger.Debugf("Received pong from %s", mc.Addr) + logger.WithFields(logrus.Fields{ + "addr": mc.Addr, + "gnetID": mc.ConnID, + }).Debug("Received pong") } return nil } +// DisconnectMessage sent to a peer before disconnecting, indicating the reason for disconnect +type DisconnectMessage struct { + c *gnet.MessageContext `enc:"-"` + reason gnet.DisconnectReason `enc:"-"` + + // Error code + ReasonCode uint16 + + // Reserved for future use + Reserved []byte +} + +// NewDisconnectMessage creates message sent to reject previously received message +func NewDisconnectMessage(reason gnet.DisconnectReason) *DisconnectMessage { + return &DisconnectMessage{ + reason: reason, + ReasonCode: DisconnectReasonToCode(reason), + Reserved: nil, + } +} + +// Handle an event queued by Handle() +func (dm *DisconnectMessage) Handle(mc *gnet.MessageContext, daemon interface{}) error { + dm.c = mc + return daemon.(daemoner).recordMessageEvent(dm, mc) +} + +// process disconnect message by reflexively disconnecting +func (dm *DisconnectMessage) process(d daemoner) { + logger.WithFields(logrus.Fields{ + "addr": dm.c.Addr, + "gnetID": dm.c.ConnID, + "code": dm.ReasonCode, + "reason": DisconnectCodeToReason(dm.ReasonCode), + }).Infof("DisconnectMessage received") + + if err := d.disconnectNow(dm.c.Addr, ErrDisconnectReceivedDisconnect); err != nil { + logger.WithError(err).WithField("addr", dm.c.Addr).Warning("disconnectNow") + } +} + // GetBlocksMessage sent to request blocks since LastBlock type GetBlocksMessage struct { LastBlock uint64 @@ -436,25 +517,28 @@ func NewGetBlocksMessage(lastBlock uint64, requestedBlocks uint64) *GetBlocksMes } // Handle handles message -func (gbm *GetBlocksMessage) Handle(mc *gnet.MessageContext, - daemon interface{}) error { +func (gbm *GetBlocksMessage) Handle(mc *gnet.MessageContext, daemon interface{}) error { gbm.c = mc - return daemon.(Daemoner).RecordMessageEvent(gbm, mc) + return daemon.(daemoner).recordMessageEvent(gbm, mc) } -// Process should send number to be requested, with request -func (gbm *GetBlocksMessage) Process(d Daemoner) { - // TODO -- we need the sig to be sent with the block, but only the master - // can sign blocks. Thus the sig needs to be stored with the block. - if d.DaemonConfig().DisableNetworking { +// process should send number to be requested, with request +func (gbm *GetBlocksMessage) process(d daemoner) { + if d.daemonConfig().DisableNetworking { return } + + fields := logrus.Fields{ + "addr": gbm.c.Addr, + "gnetID": gbm.c.ConnID, + } + // Record this as this peer's highest block - d.RecordPeerHeight(gbm.c.Addr, gbm.LastBlock) + d.recordPeerHeight(gbm.c.Addr, gbm.c.ConnID, gbm.LastBlock) // Fetch and return signed blocks since LastBlock - blocks, err := d.GetSignedBlocksSince(gbm.LastBlock, gbm.RequestedBlocks) + blocks, err := d.getSignedBlocksSince(gbm.LastBlock, gbm.RequestedBlocks) if err != nil { - logger.Infof("Get signed blocks failed: %v", err) + logger.WithError(err).Error("Get signed blocks failed") return } @@ -462,17 +546,17 @@ func (gbm *GetBlocksMessage) Process(d Daemoner) { return } - logger.Debugf("Got %d blocks since %d", len(blocks), gbm.LastBlock) + logger.WithFields(fields).Debugf("Got %d blocks since %d", len(blocks), gbm.LastBlock) m := NewGiveBlocksMessage(blocks) - if err := d.SendMessage(gbm.c.Addr, m); err != nil { - logger.Errorf("Send GiveBlocksMessage to %s failed: %v", gbm.c.Addr, err) + if err := d.sendMessage(gbm.c.Addr, m); err != nil { + logger.WithFields(fields).WithError(err).Error("Send GiveBlocksMessage failed") } } // GiveBlocksMessage sent in response to GetBlocksMessage, or unsolicited type GiveBlocksMessage struct { - Blocks []coin.SignedBlock + Blocks []coin.SignedBlock `enc:",maxlen=128"` c *gnet.MessageContext `enc:"-"` } @@ -484,14 +568,14 @@ func NewGiveBlocksMessage(blocks []coin.SignedBlock) *GiveBlocksMessage { } // Handle handle message -func (gbm *GiveBlocksMessage) Handle(mc *gnet.MessageContext, daemon interface{}) error { - gbm.c = mc - return daemon.(Daemoner).RecordMessageEvent(gbm, mc) +func (m *GiveBlocksMessage) Handle(mc *gnet.MessageContext, daemon interface{}) error { + m.c = mc + return daemon.(daemoner).recordMessageEvent(m, mc) } -// Process process message -func (gbm *GiveBlocksMessage) Process(d Daemoner) { - if d.DaemonConfig().DisableNetworking { +// process process message +func (m *GiveBlocksMessage) process(d daemoner) { + if d.daemonConfig().DisableNetworking { logger.Critical().Info("Visor disabled, ignoring GiveBlocksMessage") return } @@ -500,9 +584,9 @@ func (gbm *GiveBlocksMessage) Process(d Daemoner) { // It is not necessary that the blocks be executed together in a single transaction. processed := 0 - maxSeq, ok, err := d.HeadBkSeq() + maxSeq, ok, err := d.headBkSeq() if err != nil { - logger.WithError(err).Error("visor.HeadBkSeq failed") + logger.WithError(err).Error("d.headBkSeq failed") return } if !ok { @@ -510,7 +594,7 @@ func (gbm *GiveBlocksMessage) Process(d Daemoner) { return } - for _, b := range gbm.Blocks { + for _, b := range m.Blocks { // To minimize waste when receiving multiple responses from peers // we only break out of the loop if the block itself is invalid. // E.g. if we request 20 blocks since 0 from 2 peers, and one peer @@ -521,12 +605,12 @@ func (gbm *GiveBlocksMessage) Process(d Daemoner) { continue } - err := d.ExecuteSignedBlock(b) + err := d.executeSignedBlock(b) if err == nil { - logger.Critical().Infof("Added new block %d", b.Block.Head.BkSeq) + logger.Critical().WithField("seq", b.Block.Head.BkSeq).Info("Added new block") processed++ } else { - logger.Critical().Errorf("Failed to execute received block %d: %v", b.Block.Head.BkSeq, err) + logger.Critical().WithError(err).WithField("seq", b.Block.Head.BkSeq).Error("Failed to execute received block") // Blocks must be received in order, so if one fails its assumed // the rest are failing break @@ -536,9 +620,9 @@ func (gbm *GiveBlocksMessage) Process(d Daemoner) { return } - headBkSeq, ok, err := d.HeadBkSeq() + headBkSeq, ok, err := d.headBkSeq() if err != nil { - logger.WithError(err).Error("visor.HeadBkSeq failed") + logger.WithError(err).Error("d.headBkSeq failed") return } if !ok { @@ -553,14 +637,14 @@ func (gbm *GiveBlocksMessage) Process(d Daemoner) { } // Announce our new blocks to peers - m1 := NewAnnounceBlocksMessage(headBkSeq) - if err := d.BroadcastMessage(m1); err != nil { + abm := NewAnnounceBlocksMessage(headBkSeq) + if _, err := d.broadcastMessage(abm); err != nil { logger.WithError(err).Warning("Broadcast AnnounceBlocksMessage failed") } // Request more blocks - m2 := NewGetBlocksMessage(headBkSeq, d.DaemonConfig().BlocksResponseCount) - if err := d.BroadcastMessage(m2); err != nil { + gbm := NewGetBlocksMessage(headBkSeq, d.daemonConfig().BlocksResponseCount) + if _, err := d.broadcastMessage(gbm); err != nil { logger.WithError(err).Warning("Broadcast GetBlocksMessage failed") } } @@ -582,18 +666,23 @@ func NewAnnounceBlocksMessage(seq uint64) *AnnounceBlocksMessage { // Handle handles message func (abm *AnnounceBlocksMessage) Handle(mc *gnet.MessageContext, daemon interface{}) error { abm.c = mc - return daemon.(Daemoner).RecordMessageEvent(abm, mc) + return daemon.(daemoner).recordMessageEvent(abm, mc) } -// Process process message -func (abm *AnnounceBlocksMessage) Process(d Daemoner) { - if d.DaemonConfig().DisableNetworking { +// process process message +func (abm *AnnounceBlocksMessage) process(d daemoner) { + if d.daemonConfig().DisableNetworking { return } - headBkSeq, ok, err := d.HeadBkSeq() + fields := logrus.Fields{ + "addr": abm.c.Addr, + "gnetID": abm.c.ConnID, + } + + headBkSeq, ok, err := d.headBkSeq() if err != nil { - logger.WithError(err).Error("AnnounceBlocksMessage Visor.HeadBkSeq failed") + logger.WithError(err).Error("AnnounceBlocksMessage d.headBkSeq failed") return } if !ok { @@ -607,9 +696,9 @@ func (abm *AnnounceBlocksMessage) Process(d Daemoner) { // TODO: Should this be block get request for current sequence? // If client is not caught up, won't attempt to get block - m := NewGetBlocksMessage(headBkSeq, d.DaemonConfig().BlocksResponseCount) - if err := d.SendMessage(abm.c.Addr, m); err != nil { - logger.Errorf("Send GetBlocksMessage to %s failed: %v", abm.c.Addr, err) + m := NewGetBlocksMessage(headBkSeq, d.daemonConfig().BlocksResponseCount) + if err := d.sendMessage(abm.c.Addr, m); err != nil { + logger.WithError(err).WithFields(fields).Error("Send GetBlocksMessage") } } @@ -620,7 +709,7 @@ type SendingTxnsMessage interface { // AnnounceTxnsMessage tells a peer that we have these transactions type AnnounceTxnsMessage struct { - Transactions []cipher.SHA256 + Transactions []cipher.SHA256 `enc:",maxlen=256"` c *gnet.MessageContext `enc:"-"` } @@ -639,18 +728,23 @@ func (atm *AnnounceTxnsMessage) GetFiltered() []cipher.SHA256 { // Handle handle message func (atm *AnnounceTxnsMessage) Handle(mc *gnet.MessageContext, daemon interface{}) error { atm.c = mc - return daemon.(Daemoner).RecordMessageEvent(atm, mc) + return daemon.(daemoner).recordMessageEvent(atm, mc) } -// Process process message -func (atm *AnnounceTxnsMessage) Process(d Daemoner) { - if d.DaemonConfig().DisableNetworking { +// process process message +func (atm *AnnounceTxnsMessage) process(d daemoner) { + if d.daemonConfig().DisableNetworking { return } - unknown, err := d.GetUnconfirmedUnknown(atm.Transactions) + fields := logrus.Fields{ + "addr": atm.c.Addr, + "gnetID": atm.c.ConnID, + } + + unknown, err := d.filterKnownUnconfirmed(atm.Transactions) if err != nil { - logger.WithError(err).Error("AnnounceTxnsMessage Visor.GetUnconfirmedUnknown failed") + logger.WithError(err).Error("AnnounceTxnsMessage d.filterKnownUnconfirmed failed") return } @@ -659,8 +753,8 @@ func (atm *AnnounceTxnsMessage) Process(d Daemoner) { } m := NewGetTxnsMessage(unknown) - if err := d.SendMessage(atm.c.Addr, m); err != nil { - logger.Errorf("Send GetTxnsMessage to %s failed: %v", atm.c.Addr, err) + if err := d.sendMessage(atm.c.Addr, m); err != nil { + logger.WithFields(fields).WithError(err).Error("Send GetTxnsMessage failed") } } @@ -680,19 +774,24 @@ func NewGetTxnsMessage(txns []cipher.SHA256) *GetTxnsMessage { // Handle handle message func (gtm *GetTxnsMessage) Handle(mc *gnet.MessageContext, daemon interface{}) error { gtm.c = mc - return daemon.(Daemoner).RecordMessageEvent(gtm, mc) + return daemon.(daemoner).recordMessageEvent(gtm, mc) } -// Process process message -func (gtm *GetTxnsMessage) Process(d Daemoner) { - if d.DaemonConfig().DisableNetworking { +// process process message +func (gtm *GetTxnsMessage) process(d daemoner) { + if d.daemonConfig().DisableNetworking { return } + fields := logrus.Fields{ + "addr": gtm.c.Addr, + "gnetID": gtm.c.ConnID, + } + // Locate all txns from the unconfirmed pool - known, err := d.GetUnconfirmedKnown(gtm.Transactions) + known, err := d.getKnownUnconfirmed(gtm.Transactions) if err != nil { - logger.WithError(err).Error("GetTxnsMessage Visor.GetUnconfirmedKnown failed") + logger.WithError(err).Error("GetTxnsMessage d.getKnownUnconfirmed failed") return } if len(known) == 0 { @@ -701,19 +800,19 @@ func (gtm *GetTxnsMessage) Process(d Daemoner) { // Reply to sender with GiveTxnsMessage m := NewGiveTxnsMessage(known) - if err := d.SendMessage(gtm.c.Addr, m); err != nil { - logger.Errorf("Send GiveTxnsMessage to %s failed: %v", gtm.c.Addr, err) + if err := d.sendMessage(gtm.c.Addr, m); err != nil { + logger.WithError(err).WithFields(fields).Error("Send GiveTxnsMessage") } } // GiveTxnsMessage tells the transaction of given hashes type GiveTxnsMessage struct { - Transactions coin.Transactions + Transactions []coin.Transaction `enc:",maxlen=256"` c *gnet.MessageContext `enc:"-"` } // NewGiveTxnsMessage creates GiveTxnsMessage -func NewGiveTxnsMessage(txns coin.Transactions) *GiveTxnsMessage { +func NewGiveTxnsMessage(txns []coin.Transaction) *GiveTxnsMessage { return &GiveTxnsMessage{ Transactions: txns, } @@ -721,18 +820,18 @@ func NewGiveTxnsMessage(txns coin.Transactions) *GiveTxnsMessage { // GetFiltered returns transactions hashes func (gtm *GiveTxnsMessage) GetFiltered() []cipher.SHA256 { - return gtm.Transactions.Hashes() + return coin.Transactions(gtm.Transactions).Hashes() } // Handle handle message func (gtm *GiveTxnsMessage) Handle(mc *gnet.MessageContext, daemon interface{}) error { gtm.c = mc - return daemon.(Daemoner).RecordMessageEvent(gtm, mc) + return daemon.(daemoner).recordMessageEvent(gtm, mc) } -// Process process message -func (gtm *GiveTxnsMessage) Process(d Daemoner) { - if d.DaemonConfig().DisableNetworking { +// process process message +func (gtm *GiveTxnsMessage) process(d daemoner) { + if d.daemonConfig().DisableNetworking { return } @@ -742,27 +841,30 @@ func (gtm *GiveTxnsMessage) Process(d Daemoner) { // Only announce transactions that are new to us, so that peers can't spam relays // It is not necessary to inject all of the transactions inside a database transaction, // since each is independent - known, softErr, err := d.InjectTransaction(txn) + known, softErr, err := d.injectTransaction(txn) if err != nil { - logger.Warningf("Failed to record transaction %s: %v", txn.Hash().Hex(), err) + logger.WithError(err).WithField("txid", txn.Hash().Hex()).Warning("Failed to record transaction") continue } else if softErr != nil { - logger.Warningf("Transaction soft violation: %v", err) - continue + logger.WithError(err).WithField("txid", txn.Hash().Hex()).Warning("Transaction soft violation") + // Allow soft txn violations to rebroadcast } else if known { - logger.Warningf("Duplicate Transaction: %s", txn.Hash().Hex()) + logger.WithField("txid", txn.Hash().Hex()).Debug("Duplicate transaction") continue } hashes = append(hashes, txn.Hash()) } + if len(hashes) == 0 { + return + } + // Announce these transactions to peers - if len(hashes) != 0 { - logger.Debugf("Announce %d transactions", len(hashes)) - m := NewAnnounceTxnsMessage(hashes) - if err := d.BroadcastMessage(m); err != nil { - logger.WithError(err).Warning("Broadcast AnnounceTxnsMessage failed") - } + m := NewAnnounceTxnsMessage(hashes) + if n, err := d.broadcastMessage(m); err != nil { + logger.WithError(err).Warning("Broadcast AnnounceTxnsMessage failed") + } else { + logger.Debugf("Announced %d transactions to %d peers", len(hashes), n) } } diff --git a/src/daemon/messages_benchmark_test.go b/src/daemon/messages_benchmark_test.go index c3a066360d..58821035b7 100644 --- a/src/daemon/messages_benchmark_test.go +++ b/src/daemon/messages_benchmark_test.go @@ -64,10 +64,10 @@ func BenchmarkSerializeGivePeersMessage(b *testing.B) { var introPubKey = cipher.MustPubKeyFromHex("03cd7dfcd8c3452d1bb5d9d9e34dd95d6848cb9f66c2aad127b60578f4be7498f2") var introMessageObj = IntroductionMessage{ - Mirror: 1234, - Port: 5678, - Version: 1, - Pubkey: introPubKey[:], + Mirror: 1234, + ListenPort: 5678, + ProtocolVersion: 1, + Extra: introPubKey[:], } func BenchmarkDeserializeRawIntroductionMessage(b *testing.B) { diff --git a/src/daemon/messages_example_test.go b/src/daemon/messages_example_test.go index 274de6d175..08410dd753 100644 --- a/src/daemon/messages_example_test.go +++ b/src/daemon/messages_example_test.go @@ -216,7 +216,7 @@ func (mai *MessagesAnnotationsIterator) Next() (Annotation, bool) { if i < mai.MaxField { f = t.Field(i) if f.Type.Kind() == reflect.Slice { - if _, omitempty := encoder.ParseTag(f.Tag.Get("enc")); omitempty { + if encoder.TagOmitempty(f.Tag.Get("enc")) { if i == mai.MaxField-1 { vF = v.Field(i) if vF.Len() == 0 { @@ -416,7 +416,10 @@ func ExampleOmitEmptySliceTestStruct() { func ExampleIntroductionMessage() { defer gnet.EraseMessages() setupMsgEncoding() - var message = NewIntroductionMessage(1234, 5, 7890, nil) + + pk := cipher.MustPubKeyFromHex("0328c576d3f420e7682058a981173a4b374c7cc5ff55bf394d3cf57059bbe6456a") + + var message = NewIntroductionMessage(1234, 5, 7890, pk, "skycoin:0.24.1") fmt.Println("IntroductionMessage:") var mai = NewMessagesAnnotationsIterator(message) w := bufio.NewWriter(os.Stdout) @@ -425,13 +428,65 @@ func ExampleIntroductionMessage() { fmt.Println(err) } // Output: - // IntroductionMessage: - // 0x0000 | 0e 00 00 00 ....................................... Length + // IntroductionMessage: + // 0x0000 | 45 00 00 00 ....................................... Length // 0x0004 | 49 4e 54 52 ....................................... Prefix // 0x0008 | d2 04 00 00 ....................................... Mirror - // 0x000c | d2 1e ............................................. Port - // 0x000e | 05 00 00 00 ....................................... Version - // 0x0012 | + // 0x000c | d2 1e ............................................. ListenPort + // 0x000e | 05 00 00 00 ....................................... ProtocolVersion + // 0x0012 | 33 00 00 00 ....................................... Extra length + // 0x0016 | 03 ................................................ Extra[0] + // 0x0017 | 28 ................................................ Extra[1] + // 0x0018 | c5 ................................................ Extra[2] + // 0x0019 | 76 ................................................ Extra[3] + // 0x001a | d3 ................................................ Extra[4] + // 0x001b | f4 ................................................ Extra[5] + // 0x001c | 20 ................................................ Extra[6] + // 0x001d | e7 ................................................ Extra[7] + // 0x001e | 68 ................................................ Extra[8] + // 0x001f | 20 ................................................ Extra[9] + // 0x0020 | 58 ................................................ Extra[10] + // 0x0021 | a9 ................................................ Extra[11] + // 0x0022 | 81 ................................................ Extra[12] + // 0x0023 | 17 ................................................ Extra[13] + // 0x0024 | 3a ................................................ Extra[14] + // 0x0025 | 4b ................................................ Extra[15] + // 0x0026 | 37 ................................................ Extra[16] + // 0x0027 | 4c ................................................ Extra[17] + // 0x0028 | 7c ................................................ Extra[18] + // 0x0029 | c5 ................................................ Extra[19] + // 0x002a | ff ................................................ Extra[20] + // 0x002b | 55 ................................................ Extra[21] + // 0x002c | bf ................................................ Extra[22] + // 0x002d | 39 ................................................ Extra[23] + // 0x002e | 4d ................................................ Extra[24] + // 0x002f | 3c ................................................ Extra[25] + // 0x0030 | f5 ................................................ Extra[26] + // 0x0031 | 70 ................................................ Extra[27] + // 0x0032 | 59 ................................................ Extra[28] + // 0x0033 | bb ................................................ Extra[29] + // 0x0034 | e6 ................................................ Extra[30] + // 0x0035 | 45 ................................................ Extra[31] + // 0x0036 | 6a ................................................ Extra[32] + // 0x0037 | 0e ................................................ Extra[33] + // 0x0038 | 00 ................................................ Extra[34] + // 0x0039 | 00 ................................................ Extra[35] + // 0x003a | 00 ................................................ Extra[36] + // 0x003b | 73 ................................................ Extra[37] + // 0x003c | 6b ................................................ Extra[38] + // 0x003d | 79 ................................................ Extra[39] + // 0x003e | 63 ................................................ Extra[40] + // 0x003f | 6f ................................................ Extra[41] + // 0x0040 | 69 ................................................ Extra[42] + // 0x0041 | 6e ................................................ Extra[43] + // 0x0042 | 3a ................................................ Extra[44] + // 0x0043 | 30 ................................................ Extra[45] + // 0x0044 | 2e ................................................ Extra[46] + // 0x0045 | 32 ................................................ Extra[47] + // 0x0046 | 34 ................................................ Extra[48] + // 0x0047 | 2e ................................................ Extra[49] + // 0x0048 | 31 ................................................ Extra[50] + // 0x0049 | } func ExampleGetPeersMessage() { @@ -757,3 +812,28 @@ func ExampleAnnounceTxnsMessage() { // 0x003c | 5c bf 72 f9 8c c6 a6 2c 72 97 23 cb c0 75 0d 3b ... Transactions[1] // 0x004c | } + +func ExampleDisconnectMessage() { + defer gnet.EraseMessages() + setupMsgEncoding() + + message := NewDisconnectMessage(ErrDisconnectIdle) + fmt.Println("DisconnectMessage:") + var mai = NewMessagesAnnotationsIterator(message) + w := bufio.NewWriter(os.Stdout) + err := NewFromIterator(gnet.EncodeMessage(message), &mai, w) + if err != nil { + fmt.Println(err) + } + // DisconnectMessage: + // 0x0000 | 31 00 00 00 ....................................... Length + // 0x0004 | 52 4a 43 54 ....................................... Prefix + // 0x0008 | 49 4e 54 52 ....................................... TargetPrefix + // 0x000c | 13 00 00 00 ....................................... ErrorCode + // 0x0010 | 1d 00 00 00 45 78 61 6d 70 6c 65 52 65 6a 65 63 + // 0x0020 | 74 57 69 74 68 50 65 65 72 73 4d 65 73 73 61 67 + // 0x0030 | 65 ................................................ Reason + // 0x0031 | 00 00 00 00 ....................................... Reserved length + // 0x0035 | + +} diff --git a/src/daemon/messages_test.go b/src/daemon/messages_test.go index cece758c85..299917b5df 100644 --- a/src/daemon/messages_test.go +++ b/src/daemon/messages_test.go @@ -6,12 +6,14 @@ import ( "path/filepath" "testing" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/skycoin/skycoin/src/cipher" "github.com/skycoin/skycoin/src/cipher/encoder" "github.com/skycoin/skycoin/src/coin" "github.com/skycoin/skycoin/src/daemon/gnet" + "github.com/skycoin/skycoin/src/util/useragent" ) func TestIntroductionMessage(t *testing.T) { @@ -21,262 +23,281 @@ func TestIntroductionMessage(t *testing.T) { pubkey, _ := cipher.GenerateKeyPair() pubkey2, _ := cipher.GenerateKeyPair() - type mirrorPortResult struct { - port uint16 - exist bool - } - type daemonMockValue struct { - version uint32 - mirror uint32 - isDefaultConnection bool - isMaxConnectionsReached bool - isMaxConnectionsReachedErr error - setHasIncomingPortErr error - getMirrorPortResult mirrorPortResult - recordMessageEventErr error - pubkey cipher.PubKey - disconnectReason gnet.DisconnectReason - disconnectErr error - addPeerArg string - addPeerErr error + protocolVersion uint32 + minProtocolVersion uint32 + mirror uint32 + recordMessageEventErr error + pubkey cipher.PubKey + disconnectReason gnet.DisconnectReason + disconnectErr error + connectionIntroduced *connection + connectionIntroducedErr error + requestBlocksFromAddrErr error + announceAllTxnsErr error } tt := []struct { name string addr string + gnetID uint64 + doProcess bool mockValue daemonMockValue intro *IntroductionMessage - err error }{ { name: "INTR message without extra bytes", addr: "121.121.121.121:6000", mockValue: daemonMockValue{ - mirror: 10000, - version: 1, - getMirrorPortResult: mirrorPortResult{ - exist: false, + mirror: 10000, + protocolVersion: 1, + connectionIntroduced: &connection{ + Addr: "121.121.121.121:6000", + ConnectionDetails: ConnectionDetails{ + ListenPort: 6000, + Outgoing: true, + }, }, }, intro: &IntroductionMessage{ - Mirror: 10001, - Port: 6000, - Version: 1, - valid: true, + Mirror: 10001, + ListenPort: 6000, + ProtocolVersion: 1, }, - err: nil, }, { - name: "INTR message with pubkey", + name: "INTR message with pubkey but empty user agent", addr: "121.121.121.121:6000", mockValue: daemonMockValue{ - mirror: 10000, - version: 1, - getMirrorPortResult: mirrorPortResult{ - exist: false, - }, - pubkey: pubkey, + mirror: 10000, + protocolVersion: 1, + pubkey: pubkey, + disconnectReason: ErrDisconnectInvalidUserAgent, }, intro: &IntroductionMessage{ - Mirror: 10001, - Port: 6000, - Version: 1, - valid: true, - Pubkey: pubkey[:], + Mirror: 10001, + ListenPort: 6000, + ProtocolVersion: 1, + Extra: append(pubkey[:], []byte{0, 0, 0, 0}...), }, - err: nil, }, { - name: "INTR message with pubkey", + name: "INTR message with pubkey and user agent", addr: "121.121.121.121:6000", mockValue: daemonMockValue{ - mirror: 10000, - version: 1, - getMirrorPortResult: mirrorPortResult{ - exist: false, + mirror: 10000, + protocolVersion: 1, + pubkey: pubkey, + connectionIntroduced: &connection{ + Addr: "121.121.121.121:6000", + ConnectionDetails: ConnectionDetails{ + ListenPort: 6000, + UserAgent: useragent.Data{ + Coin: "skycoin", + Version: "0.24.1", + }, + }, }, - pubkey: pubkey, }, intro: &IntroductionMessage{ - Mirror: 10001, - Port: 6000, - Version: 1, - valid: true, - Pubkey: pubkey[:], + Mirror: 10001, + ListenPort: 6000, + ProtocolVersion: 1, + Extra: append(pubkey[:], encoder.SerializeString("skycoin:0.24.1")...), }, - err: nil, }, { - name: "INTR message with pubkey and additional data", + name: "INTR message with pubkey, user agent and additional data", addr: "121.121.121.121:6000", mockValue: daemonMockValue{ - mirror: 10000, - version: 1, - getMirrorPortResult: mirrorPortResult{ - exist: false, + mirror: 10000, + protocolVersion: 1, + pubkey: pubkey, + connectionIntroduced: &connection{ + Addr: "121.121.121.121:6000", + ConnectionDetails: ConnectionDetails{ + ListenPort: 6000, + UserAgent: useragent.Data{ + Coin: "skycoin", + Version: "0.24.1", + }, + }, }, - pubkey: pubkey, }, intro: &IntroductionMessage{ - Mirror: 10001, - Port: 6000, - Version: 1, - valid: true, - Pubkey: append(pubkey[:], []byte("additional data")...), + Mirror: 10001, + ListenPort: 6000, + ProtocolVersion: 1, + Extra: append(append(pubkey[:], encoder.SerializeString("skycoin:0.24.1")...), []byte("additional data")...), }, - err: nil, }, { name: "INTR message with different pubkey", addr: "121.121.121.121:6000", mockValue: daemonMockValue{ - mirror: 10000, - version: 1, - getMirrorPortResult: mirrorPortResult{ - exist: false, - }, + mirror: 10000, + protocolVersion: 1, pubkey: pubkey, disconnectReason: ErrDisconnectBlockchainPubkeyNotMatched, }, intro: &IntroductionMessage{ - Mirror: 10001, - Port: 6000, - Version: 1, - valid: true, - Pubkey: pubkey2[:], + Mirror: 10001, + ListenPort: 6000, + ProtocolVersion: 1, + Extra: append(pubkey2[:], encoder.SerializeString("skycoin:0.24.1")...), }, - err: ErrDisconnectBlockchainPubkeyNotMatched, }, { name: "INTR message with invalid pubkey", addr: "121.121.121.121:6000", mockValue: daemonMockValue{ - mirror: 10000, - version: 1, - getMirrorPortResult: mirrorPortResult{ - exist: false, - }, + mirror: 10000, + protocolVersion: 1, pubkey: pubkey, disconnectReason: ErrDisconnectInvalidExtraData, }, intro: &IntroductionMessage{ - Mirror: 10001, - Port: 6000, - Version: 1, - valid: true, - Pubkey: []byte("invalid extra data"), + Mirror: 10001, + ListenPort: 6000, + ProtocolVersion: 1, + Extra: []byte("invalid extra data"), }, - err: ErrDisconnectInvalidExtraData, }, { - name: "Disconnect self connection", + name: "INTR message with pubkey, malformed user agent bytes", + addr: "121.121.121.121:6000", mockValue: daemonMockValue{ mirror: 10000, - disconnectReason: ErrDisconnectSelf, + protocolVersion: 1, + pubkey: pubkey, + disconnectReason: ErrDisconnectInvalidExtraData, }, intro: &IntroductionMessage{ - Mirror: 10000, + Mirror: 10001, + ListenPort: 6000, + ProtocolVersion: 1, + Extra: append(pubkey[:], []byte{1, 2, 3}...), }, - err: ErrDisconnectSelf, }, { - name: "Invalid version", + name: "INTR message with pubkey, invalid user agent after parsing", + addr: "121.121.121.121:6000", mockValue: daemonMockValue{ mirror: 10000, - version: 1, - disconnectReason: ErrDisconnectInvalidVersion, + protocolVersion: 1, + pubkey: pubkey, + disconnectReason: ErrDisconnectInvalidUserAgent, }, intro: &IntroductionMessage{ - Mirror: 10001, - Version: 0, + Mirror: 10001, + ListenPort: 6000, + ProtocolVersion: 1, + Extra: append(pubkey[:], encoder.SerializeString("skycoin:0241")...), }, - err: ErrDisconnectInvalidVersion, }, { - name: "Invalid address", - addr: "121.121.121.121", + name: "Disconnect self connection", + addr: "12.12.12.12:6000", mockValue: daemonMockValue{ mirror: 10000, - version: 1, - disconnectReason: ErrDisconnectOtherError, - pubkey: pubkey, + disconnectReason: ErrDisconnectSelf, }, intro: &IntroductionMessage{ - Mirror: 10001, - Version: 1, - Port: 6000, + Mirror: 10000, }, - err: ErrDisconnectOtherError, }, { - name: "incomming connection", + name: "ProtocolVersion below minimum supported version", + mockValue: daemonMockValue{ + mirror: 10000, + protocolVersion: 1, + minProtocolVersion: 2, + disconnectReason: ErrDisconnectVersionNotSupported, + }, + intro: &IntroductionMessage{ + Mirror: 10001, + ProtocolVersion: 0, + }, + }, + { + name: "incoming connection", addr: "121.121.121.121:12345", mockValue: daemonMockValue{ - mirror: 10000, - version: 1, - isDefaultConnection: true, - isMaxConnectionsReached: true, - getMirrorPortResult: mirrorPortResult{ - exist: false, + mirror: 10000, + protocolVersion: 1, + pubkey: pubkey, + connectionIntroduced: &connection{ + Addr: "121.121.121.121:12345", + ConnectionDetails: ConnectionDetails{ + ListenPort: 6000, + UserAgent: useragent.Data{ + Coin: "skycoin", + Version: "0.24.1", + Remark: "foo", + }, + }, }, - pubkey: pubkey, - addPeerArg: "121.121.121.121:6000", - addPeerErr: nil, }, intro: &IntroductionMessage{ - Mirror: 10001, - Version: 1, - Port: 6000, - valid: true, + Mirror: 10001, + ProtocolVersion: 1, + ListenPort: 6000, }, }, { - name: "Connect twice", - addr: "121.121.121.121:6000", + name: "Connect twice", + addr: "121.121.121.121:6000", + gnetID: 2, + doProcess: true, mockValue: daemonMockValue{ - mirror: 10000, - version: 1, - isDefaultConnection: true, - getMirrorPortResult: mirrorPortResult{ - exist: true, - }, - pubkey: pubkey, - addPeerArg: "121.121.121.121:6000", - addPeerErr: nil, - disconnectReason: ErrDisconnectConnectedTwice, + mirror: 10000, + protocolVersion: 1, + pubkey: pubkey, + disconnectReason: ErrDisconnectConnectedTwice, + connectionIntroducedErr: ErrConnectionIPMirrorExists, + connectionIntroduced: &connection{}, }, intro: &IntroductionMessage{ - Mirror: 10001, - Version: 1, - Port: 6000, + Mirror: 10001, + ProtocolVersion: 1, + ListenPort: 6000, }, - err: ErrDisconnectConnectedTwice, }, } for _, tc := range tt { t.Run(tc.name, func(t *testing.T) { - mc := &gnet.MessageContext{Addr: tc.addr} + mc := &gnet.MessageContext{ + Addr: tc.addr, + ConnID: tc.gnetID, + } tc.intro.c = mc - d := &MockDaemoner{} - d.On("DaemonConfig").Return(DaemonConfig{Version: int32(tc.mockValue.version)}) - d.On("Mirror").Return(tc.mockValue.mirror) - d.On("IsDefaultConnection", tc.addr).Return(tc.mockValue.isDefaultConnection) - d.On("SetHasIncomingPort", tc.addr).Return(tc.mockValue.setHasIncomingPortErr) - d.On("GetMirrorPort", tc.addr, tc.intro.Mirror).Return(tc.mockValue.getMirrorPortResult.port, tc.mockValue.getMirrorPortResult.exist) - d.On("RecordMessageEvent", tc.intro, mc).Return(tc.mockValue.recordMessageEventErr) - d.On("ResetRetryTimes", tc.addr) - d.On("BlockchainPubkey").Return(tc.mockValue.pubkey) + d := &mockDaemoner{} + d.On("daemonConfig").Return(DaemonConfig{ + ProtocolVersion: int32(tc.mockValue.protocolVersion), + MinProtocolVersion: int32(tc.mockValue.minProtocolVersion), + UserAgent: useragent.Data{ + Coin: "skycoin", + Version: "0.24.1", + }, + Mirror: tc.mockValue.mirror, + BlockchainPubkey: tc.mockValue.pubkey, + }) + d.On("recordMessageEvent", tc.intro, mc).Return(tc.mockValue.recordMessageEventErr) d.On("Disconnect", tc.addr, tc.mockValue.disconnectReason).Return(tc.mockValue.disconnectErr) - d.On("IncreaseRetryTimes", tc.addr) - d.On("RemoveFromExpectingIntroductions", tc.addr) - d.On("IsMaxDefaultConnectionsReached").Return(tc.mockValue.isMaxConnectionsReached, tc.mockValue.isMaxConnectionsReachedErr) - d.On("AddPeer", tc.mockValue.addPeerArg).Return(tc.mockValue.addPeerErr) + d.On("connectionIntroduced", tc.addr, tc.gnetID, tc.intro, mock.Anything).Return(tc.mockValue.connectionIntroduced, tc.mockValue.connectionIntroducedErr) + d.On("requestBlocksFromAddr", tc.addr).Return(tc.mockValue.requestBlocksFromAddrErr) + d.On("announceAllTxns").Return(tc.mockValue.announceAllTxnsErr) err := tc.intro.Handle(mc, d) - require.Equal(t, tc.err, err) + require.NoError(t, err) + + tc.intro.process(d) + + if tc.mockValue.disconnectReason != nil { + d.AssertCalled(t, "Disconnect", tc.addr, tc.mockValue.disconnectReason) + } }) } } @@ -295,19 +316,19 @@ func TestMessageEncodeDecode(t *testing.T) { goldenFile: "intro-msg.golden", obj: &IntroductionMessage{}, msg: &IntroductionMessage{ - Mirror: 99998888, - Port: 8888, - Version: 12341234, + Mirror: 99998888, + ListenPort: 8888, + ProtocolVersion: 12341234, }, }, { goldenFile: "intro-msg-pubkey.golden", obj: &IntroductionMessage{}, msg: &IntroductionMessage{ - Mirror: 99998888, - Port: 8888, - Version: 12341234, - Pubkey: introPubKey[:], + Mirror: 99998888, + ListenPort: 8888, + ProtocolVersion: 12341234, + Extra: introPubKey[:], }, }, { diff --git a/src/daemon/mock_daemoner_test.go b/src/daemon/mock_daemoner_test.go index bb54805a00..2047c65e7d 100644 --- a/src/daemon/mock_daemoner_test.go +++ b/src/daemon/mock_daemoner_test.go @@ -7,29 +7,16 @@ import coin "github.com/skycoin/skycoin/src/coin" import gnet "github.com/skycoin/skycoin/src/daemon/gnet" import mock "github.com/stretchr/testify/mock" import pex "github.com/skycoin/skycoin/src/daemon/pex" +import useragent "github.com/skycoin/skycoin/src/util/useragent" import visor "github.com/skycoin/skycoin/src/visor" -// MockDaemoner is an autogenerated mock type for the Daemoner type -type MockDaemoner struct { +// mockDaemoner is an autogenerated mock type for the daemoner type +type mockDaemoner struct { mock.Mock } -// AddPeer provides a mock function with given fields: addr -func (_m *MockDaemoner) AddPeer(addr string) error { - ret := _m.Called(addr) - - var r0 error - if rf, ok := ret.Get(0).(func(string) error); ok { - r0 = rf(addr) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// AddPeers provides a mock function with given fields: addrs -func (_m *MockDaemoner) AddPeers(addrs []string) int { +// addPeers provides a mock function with given fields: addrs +func (_m *mockDaemoner) addPeers(addrs []string) int { ret := _m.Called(addrs) var r0 int @@ -42,8 +29,8 @@ func (_m *MockDaemoner) AddPeers(addrs []string) int { return r0 } -// AnnounceAllTxns provides a mock function with given fields: -func (_m *MockDaemoner) AnnounceAllTxns() error { +// announceAllTxns provides a mock function with given fields: +func (_m *mockDaemoner) announceAllTxns() error { ret := _m.Called() var r0 error @@ -56,38 +43,52 @@ func (_m *MockDaemoner) AnnounceAllTxns() error { return r0 } -// BlockchainPubkey provides a mock function with given fields: -func (_m *MockDaemoner) BlockchainPubkey() cipher.PubKey { - ret := _m.Called() +// broadcastMessage provides a mock function with given fields: msg +func (_m *mockDaemoner) broadcastMessage(msg gnet.Message) (int, error) { + ret := _m.Called(msg) - var r0 cipher.PubKey - if rf, ok := ret.Get(0).(func() cipher.PubKey); ok { - r0 = rf() + var r0 int + if rf, ok := ret.Get(0).(func(gnet.Message) int); ok { + r0 = rf(msg) } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(cipher.PubKey) - } + r0 = ret.Get(0).(int) } - return r0 + var r1 error + if rf, ok := ret.Get(1).(func(gnet.Message) error); ok { + r1 = rf(msg) + } else { + r1 = ret.Error(1) + } + + return r0, r1 } -// BroadcastMessage provides a mock function with given fields: msg -func (_m *MockDaemoner) BroadcastMessage(msg gnet.Message) error { - ret := _m.Called(msg) +// connectionIntroduced provides a mock function with given fields: addr, gnetID, m, userAgent +func (_m *mockDaemoner) connectionIntroduced(addr string, gnetID uint64, m *IntroductionMessage, userAgent *useragent.Data) (*connection, error) { + ret := _m.Called(addr, gnetID, m, userAgent) - var r0 error - if rf, ok := ret.Get(0).(func(gnet.Message) error); ok { - r0 = rf(msg) + var r0 *connection + if rf, ok := ret.Get(0).(func(string, uint64, *IntroductionMessage, *useragent.Data) *connection); ok { + r0 = rf(addr, gnetID, m, userAgent) } else { - r0 = ret.Error(0) + if ret.Get(0) != nil { + r0 = ret.Get(0).(*connection) + } } - return r0 + var r1 error + if rf, ok := ret.Get(1).(func(string, uint64, *IntroductionMessage, *useragent.Data) error); ok { + r1 = rf(addr, gnetID, m, userAgent) + } else { + r1 = ret.Error(1) + } + + return r0, r1 } -// DaemonConfig provides a mock function with given fields: -func (_m *MockDaemoner) DaemonConfig() DaemonConfig { +// daemonConfig provides a mock function with given fields: +func (_m *mockDaemoner) daemonConfig() DaemonConfig { ret := _m.Called() var r0 DaemonConfig @@ -100,8 +101,8 @@ func (_m *MockDaemoner) DaemonConfig() DaemonConfig { return r0 } -// Disconnect provides a mock function with given fields: addr, r -func (_m *MockDaemoner) Disconnect(addr string, r gnet.DisconnectReason) error { +// disconnectNow provides a mock function with given fields: addr, r +func (_m *mockDaemoner) disconnectNow(addr string, r gnet.DisconnectReason) error { ret := _m.Called(addr, r) var r0 error @@ -114,8 +115,8 @@ func (_m *MockDaemoner) Disconnect(addr string, r gnet.DisconnectReason) error { return r0 } -// ExecuteSignedBlock provides a mock function with given fields: b -func (_m *MockDaemoner) ExecuteSignedBlock(b coin.SignedBlock) error { +// executeSignedBlock provides a mock function with given fields: b +func (_m *mockDaemoner) executeSignedBlock(b coin.SignedBlock) error { ret := _m.Called(b) var r0 error @@ -128,43 +129,22 @@ func (_m *MockDaemoner) ExecuteSignedBlock(b coin.SignedBlock) error { return r0 } -// GetMirrorPort provides a mock function with given fields: addr, mirror -func (_m *MockDaemoner) GetMirrorPort(addr string, mirror uint32) (uint16, bool) { - ret := _m.Called(addr, mirror) - - var r0 uint16 - if rf, ok := ret.Get(0).(func(string, uint32) uint16); ok { - r0 = rf(addr, mirror) - } else { - r0 = ret.Get(0).(uint16) - } - - var r1 bool - if rf, ok := ret.Get(1).(func(string, uint32) bool); ok { - r1 = rf(addr, mirror) - } else { - r1 = ret.Get(1).(bool) - } - - return r0, r1 -} - -// GetSignedBlocksSince provides a mock function with given fields: seq, count -func (_m *MockDaemoner) GetSignedBlocksSince(seq uint64, count uint64) ([]coin.SignedBlock, error) { - ret := _m.Called(seq, count) +// filterKnownUnconfirmed provides a mock function with given fields: txns +func (_m *mockDaemoner) filterKnownUnconfirmed(txns []cipher.SHA256) ([]cipher.SHA256, error) { + ret := _m.Called(txns) - var r0 []coin.SignedBlock - if rf, ok := ret.Get(0).(func(uint64, uint64) []coin.SignedBlock); ok { - r0 = rf(seq, count) + var r0 []cipher.SHA256 + if rf, ok := ret.Get(0).(func([]cipher.SHA256) []cipher.SHA256); ok { + r0 = rf(txns) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).([]coin.SignedBlock) + r0 = ret.Get(0).([]cipher.SHA256) } } var r1 error - if rf, ok := ret.Get(1).(func(uint64, uint64) error); ok { - r1 = rf(seq, count) + if rf, ok := ret.Get(1).(func([]cipher.SHA256) error); ok { + r1 = rf(txns) } else { r1 = ret.Error(1) } @@ -172,8 +152,8 @@ func (_m *MockDaemoner) GetSignedBlocksSince(seq uint64, count uint64) ([]coin.S return r0, r1 } -// GetUnconfirmedKnown provides a mock function with given fields: txns -func (_m *MockDaemoner) GetUnconfirmedKnown(txns []cipher.SHA256) (coin.Transactions, error) { +// getKnownUnconfirmed provides a mock function with given fields: txns +func (_m *mockDaemoner) getKnownUnconfirmed(txns []cipher.SHA256) (coin.Transactions, error) { ret := _m.Called(txns) var r0 coin.Transactions @@ -195,22 +175,22 @@ func (_m *MockDaemoner) GetUnconfirmedKnown(txns []cipher.SHA256) (coin.Transact return r0, r1 } -// GetUnconfirmedUnknown provides a mock function with given fields: txns -func (_m *MockDaemoner) GetUnconfirmedUnknown(txns []cipher.SHA256) ([]cipher.SHA256, error) { - ret := _m.Called(txns) +// getSignedBlocksSince provides a mock function with given fields: seq, count +func (_m *mockDaemoner) getSignedBlocksSince(seq uint64, count uint64) ([]coin.SignedBlock, error) { + ret := _m.Called(seq, count) - var r0 []cipher.SHA256 - if rf, ok := ret.Get(0).(func([]cipher.SHA256) []cipher.SHA256); ok { - r0 = rf(txns) + var r0 []coin.SignedBlock + if rf, ok := ret.Get(0).(func(uint64, uint64) []coin.SignedBlock); ok { + r0 = rf(seq, count) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).([]cipher.SHA256) + r0 = ret.Get(0).([]coin.SignedBlock) } } var r1 error - if rf, ok := ret.Get(1).(func([]cipher.SHA256) error); ok { - r1 = rf(txns) + if rf, ok := ret.Get(1).(func(uint64, uint64) error); ok { + r1 = rf(seq, count) } else { r1 = ret.Error(1) } @@ -218,8 +198,8 @@ func (_m *MockDaemoner) GetUnconfirmedUnknown(txns []cipher.SHA256) ([]cipher.SH return r0, r1 } -// HeadBkSeq provides a mock function with given fields: -func (_m *MockDaemoner) HeadBkSeq() (uint64, bool, error) { +// headBkSeq provides a mock function with given fields: +func (_m *mockDaemoner) headBkSeq() (uint64, bool, error) { ret := _m.Called() var r0 uint64 @@ -246,13 +226,8 @@ func (_m *MockDaemoner) HeadBkSeq() (uint64, bool, error) { return r0, r1, r2 } -// IncreaseRetryTimes provides a mock function with given fields: addr -func (_m *MockDaemoner) IncreaseRetryTimes(addr string) { - _m.Called(addr) -} - -// InjectTransaction provides a mock function with given fields: txn -func (_m *MockDaemoner) InjectTransaction(txn coin.Transaction) (bool, *visor.ErrTxnViolatesSoftConstraint, error) { +// injectTransaction provides a mock function with given fields: txn +func (_m *mockDaemoner) injectTransaction(txn coin.Transaction) (bool, *visor.ErrTxnViolatesSoftConstraint, error) { ret := _m.Called(txn) var r0 bool @@ -281,57 +256,8 @@ func (_m *MockDaemoner) InjectTransaction(txn coin.Transaction) (bool, *visor.Er return r0, r1, r2 } -// IsDefaultConnection provides a mock function with given fields: addr -func (_m *MockDaemoner) IsDefaultConnection(addr string) bool { - ret := _m.Called(addr) - - var r0 bool - if rf, ok := ret.Get(0).(func(string) bool); ok { - r0 = rf(addr) - } else { - r0 = ret.Get(0).(bool) - } - - return r0 -} - -// IsMaxDefaultConnectionsReached provides a mock function with given fields: -func (_m *MockDaemoner) IsMaxDefaultConnectionsReached() (bool, error) { - ret := _m.Called() - - var r0 bool - if rf, ok := ret.Get(0).(func() bool); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(bool) - } - - var r1 error - if rf, ok := ret.Get(1).(func() error); ok { - r1 = rf() - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Mirror provides a mock function with given fields: -func (_m *MockDaemoner) Mirror() uint32 { - ret := _m.Called() - - var r0 uint32 - if rf, ok := ret.Get(0).(func() uint32); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(uint32) - } - - return r0 -} - -// PexConfig provides a mock function with given fields: -func (_m *MockDaemoner) PexConfig() pex.Config { +// pexConfig provides a mock function with given fields: +func (_m *mockDaemoner) pexConfig() pex.Config { ret := _m.Called() var r0 pex.Config @@ -344,42 +270,12 @@ func (_m *MockDaemoner) PexConfig() pex.Config { return r0 } -// RandomExchangeable provides a mock function with given fields: n -func (_m *MockDaemoner) RandomExchangeable(n int) pex.Peers { - ret := _m.Called(n) - - var r0 pex.Peers - if rf, ok := ret.Get(0).(func(int) pex.Peers); ok { - r0 = rf(n) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(pex.Peers) - } - } - - return r0 -} - -// RecordConnectionMirror provides a mock function with given fields: addr, mirror -func (_m *MockDaemoner) RecordConnectionMirror(addr string, mirror uint32) error { - ret := _m.Called(addr, mirror) - - var r0 error - if rf, ok := ret.Get(0).(func(string, uint32) error); ok { - r0 = rf(addr, mirror) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// RecordMessageEvent provides a mock function with given fields: m, c -func (_m *MockDaemoner) RecordMessageEvent(m AsyncMessage, c *gnet.MessageContext) error { +// recordMessageEvent provides a mock function with given fields: m, c +func (_m *mockDaemoner) recordMessageEvent(m asyncMessage, c *gnet.MessageContext) error { ret := _m.Called(m, c) var r0 error - if rf, ok := ret.Get(0).(func(AsyncMessage, *gnet.MessageContext) error); ok { + if rf, ok := ret.Get(0).(func(asyncMessage, *gnet.MessageContext) error); ok { r0 = rf(m, c) } else { r0 = ret.Error(0) @@ -388,18 +284,13 @@ func (_m *MockDaemoner) RecordMessageEvent(m AsyncMessage, c *gnet.MessageContex return r0 } -// RecordPeerHeight provides a mock function with given fields: addr, height -func (_m *MockDaemoner) RecordPeerHeight(addr string, height uint64) { - _m.Called(addr, height) +// recordPeerHeight provides a mock function with given fields: addr, gnetID, height +func (_m *mockDaemoner) recordPeerHeight(addr string, gnetID uint64, height uint64) { + _m.Called(addr, gnetID, height) } -// RemoveFromExpectingIntroductions provides a mock function with given fields: addr -func (_m *MockDaemoner) RemoveFromExpectingIntroductions(addr string) { - _m.Called(addr) -} - -// RequestBlocksFromAddr provides a mock function with given fields: addr -func (_m *MockDaemoner) RequestBlocksFromAddr(addr string) error { +// requestBlocksFromAddr provides a mock function with given fields: addr +func (_m *mockDaemoner) requestBlocksFromAddr(addr string) error { ret := _m.Called(addr) var r0 error @@ -412,13 +303,8 @@ func (_m *MockDaemoner) RequestBlocksFromAddr(addr string) error { return r0 } -// ResetRetryTimes provides a mock function with given fields: addr -func (_m *MockDaemoner) ResetRetryTimes(addr string) { - _m.Called(addr) -} - -// SendMessage provides a mock function with given fields: addr, msg -func (_m *MockDaemoner) SendMessage(addr string, msg gnet.Message) error { +// sendMessage provides a mock function with given fields: addr, msg +func (_m *mockDaemoner) sendMessage(addr string, msg gnet.Message) error { ret := _m.Called(addr, msg) var r0 error @@ -431,8 +317,8 @@ func (_m *MockDaemoner) SendMessage(addr string, msg gnet.Message) error { return r0 } -// SetHasIncomingPort provides a mock function with given fields: addr -func (_m *MockDaemoner) SetHasIncomingPort(addr string) error { +// sendRandomPeers provides a mock function with given fields: addr +func (_m *mockDaemoner) sendRandomPeers(addr string) error { ret := _m.Called(addr) var r0 error @@ -444,3 +330,17 @@ func (_m *MockDaemoner) SetHasIncomingPort(addr string) error { return r0 } + +// Disconnect provides a mock function with given fields: addr, r +func (_m *mockDaemoner) Disconnect(addr string, r gnet.DisconnectReason) error { + ret := _m.Called(addr, r) + + var r0 error + if rf, ok := ret.Get(0).(func(string, gnet.DisconnectReason) error); ok { + r0 = rf(addr, r) + } else { + r0 = ret.Error(0) + } + + return r0 +} diff --git a/src/daemon/pex/peerlist.go b/src/daemon/pex/peerlist.go index 4d2cb1609a..97588189bf 100644 --- a/src/daemon/pex/peerlist.go +++ b/src/daemon/pex/peerlist.go @@ -8,7 +8,10 @@ import ( "os" "time" + "github.com/sirupsen/logrus" + "github.com/skycoin/skycoin/src/util/file" + "github.com/skycoin/skycoin/src/util/useragent" ) // Peers peer list @@ -51,26 +54,33 @@ func loadCachedPeersFile(path string) (map[string]*Peer, error) { } if err != nil { + logger.WithField("path", path).WithError(err).Error("Failed to load peers file") return nil, err } peers := make(map[string]*Peer, len(peersJSON)) for addr, peerJSON := range peersJSON { + fields := logrus.Fields{ + "addr": addr, + "path": path, + } + a, err := validateAddress(addr, true) if err != nil { - logger.Errorf("Invalid address in peers JSON file %s: %v", addr, err) + logger.WithError(err).WithFields(fields).Error("Invalid address in peers JSON file") continue } peer, err := newPeerFromJSON(peerJSON) if err != nil { - logger.Errorf("newPeerFromJSON failed: %v", err) + logger.WithError(err).WithFields(fields).Error("newPeerFromJSON failed") continue } if a != peer.Addr { - logger.Errorf("Address key %s does not match Peer.Addr %s", a, peer.Addr) + fields["peerAddr"] = peer.Addr + logger.WithFields(fields).Error("Address key does not match Peer.Addr") continue } @@ -108,6 +118,12 @@ func (pl *peerlist) addPeers(addrs []string) { } } +func (pl *peerlist) seen(addr string) { + if p, ok := pl.peers[addr]; ok && p != nil { + p.Seen() + } +} + // getCanTryPeers returns all peers that are triable(retried times blew exponential backoff times) // and are able to pass the filters. func (pl *peerlist) getCanTryPeers(flts []Filter) Peers { @@ -165,12 +181,8 @@ func canTry(p Peer) bool { return p.CanTry() } -func zeroRetryTimes(p Peer) bool { - return p.RetryTimes == 0 -} - // isExchangeable filters exchangeable peers -var isExchangeable = []Filter{hasIncomingPort, isPublic, zeroRetryTimes} +var isExchangeable = []Filter{hasIncomingPort, isPublic} // removePeer removes peer func (pl *peerlist) removePeer(addr string) { @@ -204,7 +216,7 @@ func (pl *peerlist) setAllUntrusted() { } } -// setHasIncomingPort updates whether the peer is valid and has public incoming port +// setHasIncomingPort marks the peer's port as being publicly accessible func (pl *peerlist) setHasIncomingPort(addr string, hasIncomingPort bool) error { if p, ok := pl.peers[addr]; ok { p.HasIncomingPort = hasIncomingPort @@ -215,13 +227,24 @@ func (pl *peerlist) setHasIncomingPort(addr string, hasIncomingPort bool) error return fmt.Errorf("set peer.HasIncomingPort failed: %v does not exist in peer list", addr) } +// setUserAgent sets a peer's user agent +func (pl *peerlist) setUserAgent(addr string, userAgent useragent.Data) error { + if p, ok := pl.peers[addr]; ok { + p.UserAgent = userAgent + p.Seen() + return nil + } + + return fmt.Errorf("set peer.UserAgent failed: %v does not exist in peer list", addr) +} + // len returns number of peers func (pl *peerlist) len() int { return len(pl.peers) } -// getPeerByAddr returns peer of given address -func (pl *peerlist) getPeerByAddr(addr string) (Peer, bool) { +// getPeer returns peer for a given address +func (pl *peerlist) getPeer(addr string) (Peer, bool) { p, ok := pl.peers[addr] if ok { return *p, true @@ -229,12 +252,12 @@ func (pl *peerlist) getPeerByAddr(addr string) (Peer, bool) { return Peer{}, false } -// ClearOld removes public peers that haven't been seen in timeAgo seconds +// clearOld removes public, untrusted peers that haven't been seen in timeAgo seconds func (pl *peerlist) clearOld(timeAgo time.Duration) { t := time.Now().UTC() for addr, peer := range pl.peers { lastSeen := time.Unix(peer.LastSeen, 0) - if !peer.Private && t.Sub(lastSeen) > timeAgo { + if !peer.Private && !peer.Trusted && t.Sub(lastSeen) > timeAgo { delete(pl.peers, addr) } } @@ -247,15 +270,16 @@ func (pl *peerlist) random(count int, flts []Filter) Peers { if len(keys) == 0 { return Peers{} } + max := count - if count == 0 || count > len(keys) { + if max == 0 || max > len(keys) { max = len(keys) } - ps := make(Peers, 0) + ps := make(Peers, max) perm := rand.Perm(len(keys)) - for _, i := range perm[:max] { - ps = append(ps, *pl.peers[keys[i]]) + for i, j := range perm[:max] { + ps[i] = *pl.peers[keys[j]] } return ps } @@ -301,6 +325,27 @@ func (pl *peerlist) resetAllRetryTimes() { } } +func (pl *peerlist) findOldestUntrustedPeer() *Peer { + var oldest *Peer + + for _, p := range pl.peers { + if p.Trusted || p.Private { + continue + } + + if oldest == nil || p.LastSeen < oldest.LastSeen { + oldest = p + } + } + + if oldest != nil { + p := *oldest + return &p + } + + return nil +} + // PeerJSON is for saving and loading peers to disk. Some fields are strange, // to be backwards compatible due to variable name changes type PeerJSON struct { @@ -312,6 +357,7 @@ type PeerJSON struct { Trusted bool // Whether this peer is trusted HasIncomePort *bool `json:"HasIncomePort,omitempty"` // Whether this peer has incoming port [DEPRECATED] HasIncomingPort *bool // Whether this peer has incoming port + UserAgent useragent.Data } // newPeerJSON returns a PeerJSON from a Peer @@ -322,6 +368,7 @@ func newPeerJSON(p Peer) PeerJSON { Private: p.Private, Trusted: p.Trusted, HasIncomingPort: &p.HasIncomingPort, + UserAgent: p.UserAgent, } } @@ -365,5 +412,6 @@ func newPeerFromJSON(p PeerJSON) (*Peer, error) { Private: p.Private, Trusted: p.Trusted, HasIncomingPort: hasIncomingPort, + UserAgent: p.UserAgent, }, nil } diff --git a/src/daemon/pex/peerlist_test.go b/src/daemon/pex/peerlist_test.go index 58d8c7d336..a02d83beb2 100644 --- a/src/daemon/pex/peerlist_test.go +++ b/src/daemon/pex/peerlist_test.go @@ -330,6 +330,92 @@ func TestPeerListSetTrusted(t *testing.T) { } } +func TestPeerlistFindOldestUntrustedPeer(t *testing.T) { + peer1 := Peer{ + Addr: "1.1.1.1:6060", + LastSeen: time.Now().UTC().Unix() - 60*60*24*2, + } + peer2 := Peer{ + Addr: "2.2.2.2:6060", + LastSeen: time.Now().UTC().Unix() - 60*60*24*7, + } + peer3 := Peer{ + Addr: "3.3.3.3:6060", + LastSeen: time.Now().UTC().Unix() - 60, + } + trustedPeer := Peer{ + Addr: "4.4.4.4:6060", + LastSeen: time.Now().UTC().Unix() - 60*60*24*30, + Trusted: true, + } + privatePeer := Peer{ + Addr: "5.5.5.5:6060", + LastSeen: time.Now().UTC().Unix() - 60*60*24*30, + Private: true, + } + + cases := []struct { + name string + initPeers []Peer + expect *Peer + }{ + { + name: "empty peerlist", + initPeers: []Peer{}, + expect: nil, + }, + + { + name: "no untrusted public peers", + initPeers: []Peer{ + trustedPeer, + privatePeer, + }, + expect: nil, + }, + + { + name: "one peer", + initPeers: []Peer{ + peer1, + }, + expect: &peer1, + }, + + { + name: "3 peers ignore trusted", + initPeers: []Peer{ + peer1, + trustedPeer, + peer2, + peer3, + }, + expect: &peer2, + }, + + { + name: "3 peers ignore private", + initPeers: []Peer{ + peer1, + privatePeer, + peer2, + peer3, + }, + expect: &peer2, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + pl := newPeerlist() + pl.setPeers(tc.initPeers) + + p := pl.findOldestUntrustedPeer() + require.Equal(t, tc.expect, p) + }) + } +} + func TestPeerlistClearOld(t *testing.T) { tt := []struct { name string diff --git a/src/daemon/pex/pex.go b/src/daemon/pex/pex.go index f17c9458de..c794f1bf7c 100644 --- a/src/daemon/pex/pex.go +++ b/src/daemon/pex/pex.go @@ -18,8 +18,10 @@ import ( "time" "github.com/cenkalti/backoff" + "github.com/sirupsen/logrus" "github.com/skycoin/skycoin/src/util/logging" + "github.com/skycoin/skycoin/src/util/useragent" ) //TODO: @@ -96,12 +98,13 @@ func validateAddress(ipPort string, allowLocalhost bool) (string, error) { // Peer represents a known peer type Peer struct { - Addr string // An address of the form ip:port - LastSeen int64 // Unix timestamp when this peer was last seen - Private bool // Whether it should omitted from public requests - Trusted bool // Whether this peer is trusted - HasIncomingPort bool // Whether this peer has accessible public port - RetryTimes int `json:"-"` // records the retry times + Addr string // An address of the form ip:port + LastSeen int64 // Unix timestamp when this peer was last seen + Private bool // Whether it should omitted from public requests + Trusted bool // Whether this peer is trusted + HasIncomingPort bool // Whether this peer has accessible public port + UserAgent useragent.Data // Peer's last reported user agent + RetryTimes int `json:"-"` // records the retry times } // NewPeer returns a *Peer initialized by an address string of the form ip:port @@ -123,13 +126,15 @@ func (peer *Peer) Seen() { // IncreaseRetryTimes adds the retry times func (peer *Peer) IncreaseRetryTimes() { peer.RetryTimes++ - logger.Debugf("Increase retry times of %v: %v", peer.Addr, peer.RetryTimes) + logger.WithFields(logrus.Fields{ + "addr": peer.Addr, + "retryTimes": peer.RetryTimes, + }).Debug("Increase retry times") } // ResetRetryTimes resets the retry time func (peer *Peer) ResetRetryTimes() { peer.RetryTimes = 0 - logger.Debugf("Reset retry times of %v", peer.Addr) } // CanTry returns whether this peer is tryable base on the exponential backoff algorithm @@ -191,7 +196,7 @@ type Config struct { func NewConfig() Config { return Config{ DataDirectory: "./", - Max: 1000, + Max: 65535, Expiration: time.Hour * 24 * 7, CullRate: time.Minute * 10, ClearOldRate: time.Minute * 10, @@ -254,7 +259,7 @@ func New(cfg Config) (*Pex, error) { // Add custom peers if cfg.CustomPeersFile != "" { if err := pex.loadCustom(cfg.CustomPeersFile); err != nil { - logger.Critical().WithError(err).Errorf("Failed to load custom peers from %s", cfg.CustomPeersFile) + logger.Critical().WithError(err).WithField("file", cfg.CustomPeersFile).Error("Failed to load custom peers file") return nil, err } } @@ -268,7 +273,7 @@ func New(cfg Config) (*Pex, error) { if pex.Config.DownloadPeerList { go func() { if err := pex.downloadPeers(); err != nil { - logger.Errorf("Failed to download peers list: %v", err) + logger.WithError(err).Error("Failed to download peers list") } }() } @@ -286,7 +291,7 @@ func (px *Pex) Run() error { // Save the peerlist logger.Info("Save peerlist") if err := px.save(); err != nil { - logger.Errorf("Save peers failed: %v", err) + logger.WithError(err).Error("Save peerlist failed") } }() @@ -297,9 +302,11 @@ func (px *Pex) Run() error { case <-clearOldTicker.C: // Remove peers we haven't seen in a while if !px.Config.Disabled && !px.Config.NetworkDisabled { - px.Lock() - px.peerlist.clearOld(px.Config.Expiration) - px.Unlock() + func() { + px.Lock() + defer px.Unlock() + px.peerlist.clearOld(px.Config.Expiration) + }() } case <-px.quit: return nil @@ -318,15 +325,15 @@ func (px *Pex) Shutdown() { func (px *Pex) downloadPeers() error { body, err := backoffDownloadText(px.Config.PeerListURL) if err != nil { - logger.Errorf("Failed to download peers from %s. err: %s", px.Config.PeerListURL, err.Error()) + logger.WithError(err).WithField("url", px.Config.PeerListURL).Error("Failed to download peers") return err } peers := parseRemotePeerList(body) - logger.Infof("Downloaded peers list from %s, got %d peers", px.Config.PeerListURL, len(peers)) + logger.WithField("url", px.Config.PeerListURL).Infof("Downloaded peers list, got %d peers", len(peers)) n := px.AddPeers(peers) - logger.Infof("Added %d/%d peers from downloaded peers list", n, len(peers)) + logger.WithField("url", px.Config.PeerListURL).Infof("Added %d/%d peers from downloaded peers list", n, len(peers)) return nil } @@ -362,7 +369,7 @@ func (px *Pex) loadCache() error { var validPeers []Peer for addr, p := range peers { if _, err := validateAddress(addr, px.Config.AllowLocalhost); err != nil { - logger.Errorf("Invalid peer address: %v", err) + logger.WithError(err).Error("Invalid peer address") continue } @@ -413,25 +420,41 @@ func (px *Pex) save() error { } // AddPeer adds a peer to the peer list, given an address. If the peer list is -// full, PeerlistFullError is returned +// full, it will try to remove an old peer to make room. +// If no room can be made, ErrPeerlistFull is returned func (px *Pex) AddPeer(addr string) error { px.Lock() defer px.Unlock() cleanAddr, err := validateAddress(addr, px.Config.AllowLocalhost) if err != nil { - logger.Errorf("Invalid address %s: %v", addr, err) + logger.WithError(err).WithField("addr", addr).Error("Invalid address") return ErrInvalidAddress } - if !px.peerlist.hasPeer(cleanAddr) { - if px.Config.Max > 0 && px.peerlist.len() >= px.Config.Max { + if px.peerlist.hasPeer(cleanAddr) { + px.peerlist.seen(cleanAddr) + return nil + } + + if px.isFull() { + oldestPeer := px.peerlist.findOldestUntrustedPeer() + if oldestPeer == nil || time.Now().UTC().Unix()-oldestPeer.LastSeen < 60*60*24 { return ErrPeerlistFull } - px.peerlist.addPeer(cleanAddr) + px.peerlist.removePeer(oldestPeer.Addr) + + if px.isFull() { + // This can happen if the node is run with a peers.json file that has more peers + // than the max peerlist size, then the peers.json file isn't truncated to the max peerlist size. + // It is not an error. + // The max is a soft limit; exceeding the max will not crash the program. + logger.Critical().Error("AddPeer: after removing the worst peer, the peerlist was still full") + } } + px.peerlist.addPeer(cleanAddr) return nil } @@ -452,7 +475,7 @@ func (px *Pex) AddPeers(addrs []string) int { for _, addr := range addrs { a, err := validateAddress(addr, px.Config.AllowLocalhost) if err != nil { - logger.Infof("Add peers sees an invalid address %s: %v", addr, err) + logger.WithField("addr", addr).WithError(err).Info("Add peers sees an invalid address") continue } validAddrs = append(validAddrs, a) @@ -482,7 +505,7 @@ func (px *Pex) SetPrivate(addr string, private bool) error { cleanAddr, err := validateAddress(addr, px.Config.AllowLocalhost) if err != nil { - logger.Errorf("Invalid address %s: %v", addr, err) + logger.WithError(err).WithField("addr", addr).Error("Invalid address") return ErrInvalidAddress } @@ -496,7 +519,7 @@ func (px *Pex) SetTrusted(addr string) error { cleanAddr, err := validateAddress(addr, px.Config.AllowLocalhost) if err != nil { - logger.Errorf("Invalid address %s: %v", addr, err) + logger.WithError(err).WithField("addr", addr).Error("Invalid address") return ErrInvalidAddress } @@ -518,13 +541,33 @@ func (px *Pex) SetHasIncomingPort(addr string, hasPublicPort bool) error { cleanAddr, err := validateAddress(addr, px.Config.AllowLocalhost) if err != nil { - logger.Errorf("Invalid address %s: %v", addr, err) + logger.WithError(err).WithField("addr", addr).Error("Invalid address") return ErrInvalidAddress } return px.peerlist.setHasIncomingPort(cleanAddr, hasPublicPort) } +// SetUserAgent sets the peer's user agent +func (px *Pex) SetUserAgent(addr string, userAgent useragent.Data) error { + px.Lock() + defer px.Unlock() + + if !userAgent.Empty() { + if _, err := userAgent.Build(); err != nil { + return err + } + } + + cleanAddr, err := validateAddress(addr, px.Config.AllowLocalhost) + if err != nil { + logger.WithError(err).WithField("addr", addr).Error("Invalid address") + return ErrInvalidAddress + } + + return px.peerlist.setUserAgent(cleanAddr, userAgent) +} + // RemovePeer removes peer func (px *Pex) RemovePeer(addr string) { px.Lock() @@ -532,11 +575,11 @@ func (px *Pex) RemovePeer(addr string) { px.peerlist.removePeer(addr) } -// GetPeerByAddr returns peer of given address -func (px *Pex) GetPeerByAddr(addr string) (Peer, bool) { +// GetPeer returns peer of given address +func (px *Pex) GetPeer(addr string) (Peer, bool) { px.RLock() defer px.RUnlock() - return px.peerlist.getPeerByAddr(addr) + return px.peerlist.getPeer(addr) } // Trusted returns trusted peers @@ -560,11 +603,13 @@ func (px *Pex) TrustedPublic() Peers { return px.peerlist.getCanTryPeers([]Filter{isPublic, isTrusted}) } -// RandomPublic returns N random public peers +// RandomPublic returns N random public untrusted peers func (px *Pex) RandomPublic(n int) Peers { px.RLock() defer px.RUnlock() - return px.peerlist.random(n, []Filter{isPublic}) + return px.peerlist.random(n, []Filter{func(p Peer) bool { + return !p.Private + }}) } // RandomExchangeable returns N random exchangeable peers @@ -599,6 +644,10 @@ func (px *Pex) ResetAllRetryTimes() { func (px *Pex) IsFull() bool { px.RLock() defer px.RUnlock() + return px.isFull() +} + +func (px *Pex) isFull() bool { return px.Config.Max > 0 && px.peerlist.len() >= px.Config.Max } @@ -626,22 +675,22 @@ func backoffDownloadText(url string) (string, error) { b := backoff.NewExponentialBackOff() notify := func(err error, wait time.Duration) { - logger.Errorf("waiting %v to retry downloadText, error: %v", wait, err) + logger.WithError(err).WithField("waitTime", wait).Error("waiting to retry downloadText") } operation := func() error { - logger.Infof("Trying to download peers list from %s", url) + logger.WithField("url", url).Info("Trying to download peers list") var err error body, err = downloadText(url) return err } if err := backoff.RetryNotify(operation, b, notify); err != nil { - logger.Infof("Gave up dowloading peers list from %s: %v", url, err) + logger.WithField("url", url).WithError(err).Info("Gave up dowloading peers list") return "", err } - logger.Infof("Peers list downloaded from %s", url) + logger.WithField("url", url).Info("Peers list downloaded") return body, nil } diff --git a/src/daemon/pex/pex_test.go b/src/daemon/pex/pex_test.go index 79c1e2597c..2ae724aa28 100644 --- a/src/daemon/pex/pex_test.go +++ b/src/daemon/pex/pex_test.go @@ -377,40 +377,118 @@ func TestPexLoadPeers(t *testing.T) { } func TestPexAddPeer(t *testing.T) { + now := time.Now().UTC().Unix() + tt := []struct { - name string - peers []string - max int - peer string - err error + name string + peers []Peer + max int + finalLen int + peer string + err error + removedPeer string + check func(px *Pex) }{ { - "ok", - testPeers[:1], - 2, - testPeers[1], - nil, + name: "ok", + peers: []Peer{ + { + Addr: testPeers[0], + LastSeen: now, + }, + }, + max: 2, + peer: testPeers[1], + err: nil, + finalLen: 2, }, { - "invalid peer", - testPeers[:1], - 2, - wrongPortPeer, - ErrInvalidAddress, + name: "invalid peer", + peers: []Peer{ + { + Addr: testPeers[0], + LastSeen: now, + }, + }, + max: 2, + peer: wrongPortPeer, + err: ErrInvalidAddress, + finalLen: 1, }, { - "reach max", - testPeers[:2], - 2, - testPeers[3], - ErrPeerlistFull, + name: "peer known", + peers: []Peer{ + { + Addr: testPeers[0], + LastSeen: now, + }, + { + Addr: testPeers[1], + LastSeen: now - 60, + }, + }, + max: 2, + peer: testPeers[1], + err: nil, + finalLen: 2, + check: func(px *Pex) { + p := px.peerlist.peers[testPeers[1]] + require.NotNil(t, p) + // Peer should have been marked as seen + require.True(t, p.LastSeen > now-60) + }, }, { - "no max", - testPeers[:2], - 0, - testPeers[3], - nil, + name: "reach max", + peers: []Peer{ + { + Addr: testPeers[0], + LastSeen: now, + }, + { + Addr: testPeers[1], + LastSeen: now, + }, + }, + max: 2, + peer: testPeers[3], + err: ErrPeerlistFull, + finalLen: 2, + }, + { + name: "reach max but kicked old peer", + peers: []Peer{ + { + Addr: testPeers[0], + LastSeen: now, + }, + { + Addr: testPeers[1], + LastSeen: now - 60*60*24*2, + }, + }, + max: 2, + peer: testPeers[3], + err: nil, + finalLen: 2, + removedPeer: testPeers[1], + }, + { + name: "no max", + peers: []Peer{ + { + Addr: testPeers[0], + LastSeen: now, + }, + { + Addr: testPeers[1], + LastSeen: now, + }, + }, + max: 0, + peer: testPeers[3], + err: nil, + finalLen: 3, }, } @@ -424,14 +502,22 @@ func TestPexAddPeer(t *testing.T) { cfg := NewConfig() cfg.Max = tc.max cfg.DataDirectory = dir - cfg.DefaultConnections = tc.peers + cfg.DefaultConnections = []string{} // create px instance and load peers px, err := New(cfg) require.NoError(t, err) + px.peerlist.setPeers(tc.peers) + err = px.AddPeer(tc.peer) require.Equal(t, tc.err, err) + require.Equal(t, tc.finalLen, len(px.peerlist.peers)) + + if tc.check != nil { + tc.check(px) + } + if err != nil { return } @@ -439,6 +525,11 @@ func TestPexAddPeer(t *testing.T) { // check if the peer is in the peer list _, ok := px.peerlist.peers[tc.peer] require.True(t, ok) + + if tc.removedPeer != "" { + _, ok := px.peerlist.peers[tc.removedPeer] + require.False(t, ok) + } }) } } @@ -588,94 +679,81 @@ func TestPexRandomExchangeable(t *testing.T) { expectPeers []Peer }{ { - "n=0 exchangeable=0", - []Peer{ + name: "n=0 exchangeable=0", + peers: []Peer{ Peer{Addr: testPeers[0], Private: true, HasIncomingPort: true}, Peer{Addr: testPeers[1], Private: true, HasIncomingPort: true}, Peer{Addr: testPeers[2], Private: true, HasIncomingPort: true}, }, - 0, - 0, - []Peer{}, + n: 0, + expectN: 0, + expectPeers: []Peer{}, }, { - "n=0 exchangeable=1", - []Peer{ + name: "n=0 exchangeable=1", + peers: []Peer{ Peer{Addr: testPeers[0], Private: false, HasIncomingPort: true}, Peer{Addr: testPeers[1], Private: true, HasIncomingPort: true}, Peer{Addr: testPeers[2], Private: true, HasIncomingPort: true}, }, - 0, - 1, - []Peer{ + n: 0, + expectN: 1, + expectPeers: []Peer{ Peer{Addr: testPeers[0], Private: false, HasIncomingPort: true}, }, }, { - "n=0 exchangeable=2", - []Peer{ + name: "n=0 exchangeable=2", + peers: []Peer{ Peer{Addr: testPeers[0], Private: false, HasIncomingPort: true}, Peer{Addr: testPeers[1], Private: false, HasIncomingPort: true}, Peer{Addr: testPeers[2], Private: true, HasIncomingPort: true}, }, - 0, - 2, - []Peer{ + n: 0, + expectN: 2, + expectPeers: []Peer{ Peer{Addr: testPeers[0], Private: false, HasIncomingPort: true}, Peer{Addr: testPeers[1], Private: false, HasIncomingPort: true}, }, }, { - "n=1 exchangeable=0", - []Peer{ + name: "n=1 exchangeable=0", + peers: []Peer{ Peer{Addr: testPeers[0], Private: true, HasIncomingPort: true}, Peer{Addr: testPeers[1], Private: true, HasIncomingPort: true}, Peer{Addr: testPeers[2], Private: true, HasIncomingPort: true}, }, - 1, - 0, - []Peer{}, + n: 1, + expectN: 0, + expectPeers: []Peer{}, }, { - "n=1 exchangeable=1", - []Peer{ + name: "n=1 exchangeable=1", + peers: []Peer{ Peer{Addr: testPeers[0], Private: false, HasIncomingPort: true}, Peer{Addr: testPeers[1], Private: false, HasIncomingPort: false}, Peer{Addr: testPeers[2], Private: true, HasIncomingPort: true}, }, - 1, - 1, - []Peer{ + n: 1, + expectN: 1, + expectPeers: []Peer{ Peer{Addr: testPeers[0], Private: false, HasIncomingPort: true}, }, }, { - "n=1 exchangeable=2", - []Peer{ + name: "n=1 exchangeable=2", + peers: []Peer{ Peer{Addr: testPeers[0], Private: false, HasIncomingPort: true}, Peer{Addr: testPeers[1], Private: false, HasIncomingPort: true}, Peer{Addr: testPeers[2], Private: true, HasIncomingPort: true}, }, - 1, - 1, - []Peer{ + n: 1, + expectN: 1, + expectPeers: []Peer{ Peer{Addr: testPeers[0], Private: false, HasIncomingPort: true}, Peer{Addr: testPeers[1], Private: false, HasIncomingPort: true}, }, }, - { - "n=2 exchangeable=1", - []Peer{ - Peer{Addr: testPeers[0], Private: false, HasIncomingPort: true}, - Peer{Addr: testPeers[1], Private: false, HasIncomingPort: true, RetryTimes: 1}, - Peer{Addr: testPeers[2], Private: true, HasIncomingPort: true}, - }, - 2, - 1, - []Peer{ - Peer{Addr: testPeers[0], Private: false, HasIncomingPort: true}, - }, - }, } for _, tc := range tt { @@ -1368,7 +1446,7 @@ func TestPexGetPeerByAddr(t *testing.T) { pex.peerlist.setPeers(tc.initPeers) - p, ok := pex.GetPeerByAddr(tc.addr) + p, ok := pex.GetPeer(tc.addr) require.Equal(t, tc.find, ok) if ok { require.Equal(t, tc.peer, p) diff --git a/src/daemon/pool.go b/src/daemon/pool.go index 93b330f3e9..0cc50e1e64 100644 --- a/src/daemon/pool.go +++ b/src/daemon/pool.go @@ -22,9 +22,11 @@ type PoolConfig struct { ClearStaleRate time.Duration // Buffer size for gnet.ConnectionPool's network Read events EventChannelSize int - // Maximum number of connections to maintain + // Maximum number of connections MaxConnections int - // Maximum number of connections to peers in the DefaultConnections list to maintain + // Maximum number of outgoing connections + MaxOutgoingConnections int + // Maximum number of outgoing connections to peers in the DefaultConnections list to maintain MaxDefaultPeerOutgoingConnections int // Default "trusted" peers DefaultConnections []string @@ -46,6 +48,7 @@ func NewPoolConfig() PoolConfig { ClearStaleRate: 1 * time.Second, EventChannelSize: 4096, MaxConnections: 128, + MaxOutgoingConnections: 8, MaxDefaultPeerOutgoingConnections: 1, } } @@ -64,7 +67,9 @@ func NewPool(cfg PoolConfig, d *Daemon) *Pool { gnetCfg.Address = cfg.address gnetCfg.ConnectCallback = d.onGnetConnect gnetCfg.DisconnectCallback = d.onGnetDisconnect + gnetCfg.ConnectFailureCallback = d.onGnetConnectFailure gnetCfg.MaxConnections = cfg.MaxConnections + gnetCfg.MaxOutgoingConnections = cfg.MaxOutgoingConnections gnetCfg.MaxDefaultPeerOutgoingConnections = cfg.MaxDefaultPeerOutgoingConnections gnetCfg.DefaultConnections = cfg.DefaultConnections @@ -93,16 +98,14 @@ func (pool *Pool) RunOffline() error { return pool.Pool.RunOffline() } -// Send a ping if our last message sent was over pingRate ago +// sendPings send a ping if our last message sent was over pingRate ago func (pool *Pool) sendPings() { if err := pool.Pool.SendPings(pool.Config.PingRate, &PingMessage{}); err != nil { logger.WithError(err).Error("sendPings failed") } } -// Removes connections that have not sent a message in too long -func (pool *Pool) clearStaleConnections() { - if err := pool.Pool.ClearStaleConnections(pool.Config.IdleLimit, ErrDisconnectIdle); err != nil { - logger.WithError(err).Error("clearStaleConnections failed") - } +// getStaleConnections returns connections that have been idle for longer than idleLimit +func (pool *Pool) getStaleConnections() ([]string, error) { + return pool.Pool.GetStaleConnections(pool.Config.IdleLimit) } diff --git a/src/daemon/storage.go b/src/daemon/storage.go deleted file mode 100644 index 2d66bffcf1..0000000000 --- a/src/daemon/storage.go +++ /dev/null @@ -1,289 +0,0 @@ -package daemon - -import ( - "sync" - "time" - - "github.com/skycoin/skycoin/src/daemon/pex" -) - -// ExpectIntroductions records connections that are expecting introduction msg. -type ExpectIntroductions struct { - value map[string]time.Time - sync.Mutex -} - -// CullMatchFunc function for checking if the connection need to be culled -type CullMatchFunc func(addr string, t time.Time) (bool, error) - -// NewExpectIntroductions creates a ExpectIntroduction instance -func NewExpectIntroductions() *ExpectIntroductions { - return &ExpectIntroductions{ - value: make(map[string]time.Time), - } -} - -// Add adds expecting introduction connection -func (s *ExpectIntroductions) Add(addr string, tm time.Time) { - s.Lock() - defer s.Unlock() - s.value[addr] = tm -} - -// Remove removes connection -func (s *ExpectIntroductions) Remove(addr string) { - s.Lock() - defer s.Unlock() - delete(s.value, addr) -} - -// CullInvalidConns cull connections that match the matchFunc -func (s *ExpectIntroductions) CullInvalidConns(f CullMatchFunc) ([]string, error) { - s.Lock() - defer s.Unlock() - - var addrs []string - for addr, t := range s.value { - ok, err := f(addr, t) - if err != nil { - return nil, err - } - - if ok { - addrs = append(addrs, addr) - delete(s.value, addr) - } - } - - return addrs, nil -} - -// Get returns the time of speicific address -func (s *ExpectIntroductions) Get(addr string) (time.Time, bool) { - s.Lock() - defer s.Unlock() - t, ok := s.value[addr] - return t, ok -} - -// ConnectionMirrors records mirror for connection -type ConnectionMirrors struct { - value map[string]uint32 - sync.Mutex -} - -// NewConnectionMirrors create ConnectionMirrors instance. -func NewConnectionMirrors() *ConnectionMirrors { - return &ConnectionMirrors{ - value: make(map[string]uint32), - } -} - -// Add adds connection mirror -func (s *ConnectionMirrors) Add(addr string, mirror uint32) { - s.Lock() - defer s.Unlock() - s.value[addr] = mirror -} - -// Get returns the mirror of connection -func (s *ConnectionMirrors) Get(addr string) (uint32, bool) { - s.Lock() - defer s.Unlock() - v, ok := s.value[addr] - return v, ok -} - -// Remove remove connection mirror -func (s *ConnectionMirrors) Remove(addr string) { - s.Lock() - defer s.Unlock() - delete(s.value, addr) -} - -// OutgoingConnections records the outgoing connections -type OutgoingConnections struct { - value map[string]struct{} - sync.Mutex -} - -// NewOutgoingConnections create OutgoingConnection instance -func NewOutgoingConnections(max int) *OutgoingConnections { - return &OutgoingConnections{ - value: make(map[string]struct{}, max), - } -} - -// Add records connection -func (s *OutgoingConnections) Add(addr string) { - s.Lock() - defer s.Unlock() - s.value[addr] = struct{}{} -} - -// Remove remove connection -func (s *OutgoingConnections) Remove(addr string) { - s.Lock() - defer s.Unlock() - delete(s.value, addr) -} - -// Get returns if connection is outgoing -func (s *OutgoingConnections) Get(addr string) bool { - s.Lock() - defer s.Unlock() - _, ok := s.value[addr] - return ok -} - -// Len returns the outgoing connections count -func (s *OutgoingConnections) Len() int { - s.Lock() - defer s.Unlock() - return len(s.value) -} - -// PendingConnections records pending connection peers -type PendingConnections struct { - value map[string]pex.Peer - sync.Mutex -} - -// NewPendingConnections creates new PendingConnections instance -func NewPendingConnections(maxConn int) *PendingConnections { - return &PendingConnections{ - value: make(map[string]pex.Peer, maxConn), - } -} - -// Add adds pending connection -func (s *PendingConnections) Add(peer pex.Peer) { - s.Lock() - defer s.Unlock() - s.value[peer.Addr] = peer -} - -// Get returns pending connections -func (s *PendingConnections) Get(addr string) (pex.Peer, bool) { - s.Lock() - defer s.Unlock() - v, ok := s.value[addr] - return v, ok -} - -// Remove removes pending connection -func (s *PendingConnections) Remove(addr string) { - s.Lock() - defer s.Unlock() - delete(s.value, addr) -} - -// Len returns pending connection number -func (s *PendingConnections) Len() int { - s.Lock() - defer s.Unlock() - return len(s.value) -} - -// MirrorConnections records mirror connections -type MirrorConnections struct { - value map[uint32]map[string]uint16 - sync.Mutex -} - -// NewMirrorConnections create mirror connection instance -func NewMirrorConnections() *MirrorConnections { - return &MirrorConnections{ - value: make(map[uint32]map[string]uint16), - } -} - -// Add adds mirror connection -func (s *MirrorConnections) Add(mirror uint32, ip string, port uint16) { - s.Lock() - defer s.Unlock() - - if m, ok := s.value[mirror]; ok { - m[ip] = port - return - } - - m := make(map[string]uint16) - m[ip] = port - s.value[mirror] = m -} - -// Get returns ip port of specific mirror -func (s *MirrorConnections) Get(mirror uint32, ip string) (uint16, bool) { - s.Lock() - defer s.Unlock() - - m, ok := s.value[mirror] - if ok { - port, exist := m[ip] - return port, exist - } - - return 0, false -} - -// Remove removes port of ip for specific mirror -func (s *MirrorConnections) Remove(mirror uint32, ip string) { - s.Lock() - defer s.Unlock() - - m, ok := s.value[mirror] - if ok { - delete(m, ip) - } -} - -// IPCount records connection number from the same base ip -type IPCount struct { - value map[string]int - sync.Mutex -} - -// NewIPCount returns IPCount instance -func NewIPCount() *IPCount { - return &IPCount{ - value: make(map[string]int), - } -} - -// Increase increases one for specific ip -func (s *IPCount) Increase(ip string) { - s.Lock() - defer s.Unlock() - - if c, ok := s.value[ip]; ok { - c++ - s.value[ip] = c - return - } - - s.value[ip] = 1 -} - -// Decrease decreases one for specific ip -func (s *IPCount) Decrease(ip string) { - s.Lock() - defer s.Unlock() - - if c, ok := s.value[ip]; ok { - if c <= 1 { - delete(s.value, ip) - return - } - c-- - s.value[ip] = c - } -} - -// Get return ip count -func (s *IPCount) Get(ip string) (int, bool) { - s.Lock() - defer s.Unlock() - v, ok := s.value[ip] - return v, ok -} diff --git a/src/daemon/storage_test.go b/src/daemon/storage_test.go deleted file mode 100644 index ecd99d702b..0000000000 --- a/src/daemon/storage_test.go +++ /dev/null @@ -1,232 +0,0 @@ -package daemon - -import ( - "sync" - "testing" - "time" - - "github.com/stretchr/testify/require" - - "github.com/skycoin/skycoin/src/daemon/pex" -) - -func TestExpectIntroductions(t *testing.T) { - ei := NewExpectIntroductions() - - _, ok := ei.Get("foo") - require.False(t, ok) - - tm := time.Now() - ei.Add("foo", tm) - tm2, ok := ei.Get("foo") - require.True(t, ok) - require.Equal(t, tm, tm2) - - ei.Remove("foo") - _, ok = ei.Get("foo") - require.False(t, ok) -} - -func TestExpectIntroductionsCullInvalidConnections(t *testing.T) { - ei := NewExpectIntroductions() - now := time.Now().UTC() - ei.Add("a", now) - ei.Add("b", now.Add(1)) - ei.Add("c", now.Add(2)) - - wg := sync.WaitGroup{} - vc := make(chan string, 3) - - wg.Add(2) - go func() { - defer wg.Done() - as, err := ei.CullInvalidConns(func(addr string, tm time.Time) (bool, error) { - if addr == "a" || addr == "b" { - return true, nil - } - return false, nil - }) - require.NoError(t, err) - - for _, s := range as { - vc <- s - } - }() - - go func() { - defer wg.Done() - as, err := ei.CullInvalidConns(func(addr string, tm time.Time) (bool, error) { - if addr == "c" { - return true, nil - } - return false, nil - }) - - require.NoError(t, err) - - for _, s := range as { - vc <- s - } - }() - - wg.Wait() - require.Equal(t, 3, len(vc)) - - _, ok := ei.Get("a") - require.False(t, ok) - _, ok = ei.Get("b") - require.False(t, ok) - _, ok = ei.Get("c") - require.False(t, ok) -} - -func TestConnectionMirrors(t *testing.T) { - cm := NewConnectionMirrors() - - _, ok := cm.Get("foo") - require.False(t, ok) - - cm.Add("foo", 10) - c, ok := cm.Get("foo") - require.True(t, ok) - require.Equal(t, uint32(10), c) - - cm.Remove("foo") - _, ok = cm.Get("foo") - require.False(t, ok) -} - -func TestOutgoingConnections(t *testing.T) { - oc := NewOutgoingConnections(3) - - n := oc.Len() - require.Equal(t, 0, n) - - ok := oc.Get("foo") - require.False(t, ok) - - oc.Add("foo") - ok = oc.Get("foo") - require.True(t, ok) - - n = oc.Len() - require.Equal(t, 1, n) - - oc.Add("foo") - ok = oc.Get("foo") - require.True(t, ok) - - n = oc.Len() - require.Equal(t, 1, n) - - oc.Add("foo2") - ok = oc.Get("foo2") - require.True(t, ok) - - n = oc.Len() - require.Equal(t, 2, n) - - oc.Remove("foo") - ok = oc.Get("foo") - require.False(t, ok) - - n = oc.Len() - require.Equal(t, 1, n) -} - -func TestPendingConns(t *testing.T) { - pc := NewPendingConnections(3) - - n := pc.Len() - require.Equal(t, 0, n) - - _, ok := pc.Get("foo") - require.False(t, ok) - - pc.Add(pex.Peer{Addr: "foo"}) - p, ok := pc.Get("foo") - require.Equal(t, pex.Peer{Addr: "foo"}, p) - require.True(t, ok) - - n = pc.Len() - require.Equal(t, 1, n) - - pc.Add(pex.Peer{Addr: "foo"}) - p, ok = pc.Get("foo") - require.Equal(t, pex.Peer{Addr: "foo"}, p) - require.True(t, ok) - - n = pc.Len() - require.Equal(t, 1, n) - - pc.Add(pex.Peer{Addr: "foo2"}) - p, ok = pc.Get("foo2") - require.Equal(t, pex.Peer{Addr: "foo2"}, p) - require.True(t, ok) - - n = pc.Len() - require.Equal(t, 2, n) -} - -func TestMirrorConnections(t *testing.T) { - mc := NewMirrorConnections() - - _, ok := mc.Get(99, "foo") - require.False(t, ok) - - mc.Add(99, "foo", 10) - c, ok := mc.Get(99, "foo") - require.True(t, ok) - require.Equal(t, uint16(10), c) - - mc.Remove(99, "foo") - _, ok = mc.Get(99, "foo") - require.False(t, ok) - - mc.Add(99, "foo2", 10) - c, ok = mc.Get(99, "foo2") - require.True(t, ok) - require.Equal(t, uint16(10), c) - - mc.Add(99, "foo", 10) - c, ok = mc.Get(99, "foo") - require.True(t, ok) - require.Equal(t, uint16(10), c) - - mc.Remove(99, "foo2") - _, ok = mc.Get(99, "foo2") - require.False(t, ok) - - _, ok = mc.Get(99, "foo") - require.True(t, ok) -} - -func TestIPCount(t *testing.T) { - ic := NewIPCount() - - _, ok := ic.Get("foo") - require.False(t, ok) - - ic.Increase("foo") - n, ok := ic.Get("foo") - require.Equal(t, 1, n) - require.True(t, ok) - - for i := 0; i < 3; i++ { - ic.Decrease("foo") - n, ok = ic.Get("foo") - require.Equal(t, 0, n) - require.False(t, ok) - } - - for i := 0; i < 5; i++ { - ic.Increase("foo") - n, ok := ic.Get("foo") - require.Equal(t, i+1, n) - require.True(t, ok) - } - - n, ok = ic.Get("foo") - require.Equal(t, 5, n) - require.True(t, ok) -} diff --git a/src/daemon/strand/strand.go b/src/daemon/strand/strand.go index 21882736aa..629960f2b4 100644 --- a/src/daemon/strand/strand.go +++ b/src/daemon/strand/strand.go @@ -9,6 +9,8 @@ package strand import ( "time" + "github.com/sirupsen/logrus" + "github.com/skycoin/skycoin/src/util/logging" ) @@ -38,7 +40,7 @@ type Request struct { // channel closes. func Strand(logger *logging.Logger, c chan Request, name string, f func() error, quit chan struct{}, quitErr error) error { if Debug { - logger.Debugf("Strand precall %s", name) + logger.WithField("operation", name).Debug("Strand precall") } done := make(chan struct{}) @@ -69,32 +71,36 @@ func Strand(logger *logging.Logger, c chan Request, name string, f func() error, case <-done: return case <-t.C: - logger.Warningf("%s is taking longer than %s", name, threshold) + logger.WithFields(logrus.Fields{ + "operation": name, + "threshold": threshold, + }).Warning("Strand operation exceeded threshold") threshold *= 10 t.Reset(threshold) } t1 := time.Now() - logger.Infof("ELAPSED: %s", t1.Sub(t0)) + logger.WithField("elapsed", t1.Sub(t0)).Info() } }() if Debug { - logger.Debugf("Stranding %s", name) + logger.WithField("operation", name).Debug("Stranding") } err = f() - // Log the error here so that the Request channel consumer doesn't need to - if err != nil { - logger.Errorf("%s error: %v", name, err) - } - // Notify us if the function call took too long elapsed := time.Since(t) if elapsed > logDurationThreshold { - logger.Warningf("%s took %s", name, elapsed) + logger.WithFields(logrus.Fields{ + "operation": name, + "elapsed": elapsed, + }).Warning() } else if Debug { - logger.Debugf("%s took %s", name, elapsed) + logger.WithFields(logrus.Fields{ + "operation": name, + "elapsed": elapsed, + }).Debug() } return err diff --git a/src/gui/static/src/app/components/layout/password-dialog/password-dialog.component.html b/src/gui/static/src/app/components/layout/password-dialog/password-dialog.component.html index 2cf211c33c..f8855d956d 100644 --- a/src/gui/static/src/app/components/layout/password-dialog/password-dialog.component.html +++ b/src/gui/static/src/app/components/layout/password-dialog/password-dialog.component.html @@ -1,5 +1,5 @@ -

{{ data.description }}

+

{{ data.description | translate }}

diff --git a/src/gui/static/src/app/components/layout/password-dialog/password-dialog.component.scss b/src/gui/static/src/app/components/layout/password-dialog/password-dialog.component.scss index 2dacc419b6..ec05c939ff 100644 --- a/src/gui/static/src/app/components/layout/password-dialog/password-dialog.component.scss +++ b/src/gui/static/src/app/components/layout/password-dialog/password-dialog.component.scss @@ -11,6 +11,10 @@ line-height: 1.5; } +.-warning { + color: $red; +} + .link { font-size: 13px; width: 100%; diff --git a/src/gui/static/src/app/components/layout/password-dialog/password-dialog.component.ts b/src/gui/static/src/app/components/layout/password-dialog/password-dialog.component.ts index 484ff20165..004468abd3 100644 --- a/src/gui/static/src/app/components/layout/password-dialog/password-dialog.component.ts +++ b/src/gui/static/src/app/components/layout/password-dialog/password-dialog.component.ts @@ -31,6 +31,7 @@ export class PasswordDialogComponent implements OnInit, OnDestroy { this.data = Object.assign({ confirm: false, description: null, + warning: false, title: null, wallet: null, }, data || {}); diff --git a/src/gui/static/src/app/components/pages/send-skycoin/send-form-advanced/send-form-advanced.component.html b/src/gui/static/src/app/components/pages/send-skycoin/send-form-advanced/send-form-advanced.component.html index 775fd64d32..7ec7ecb5f8 100644 --- a/src/gui/static/src/app/components/pages/send-skycoin/send-form-advanced/send-form-advanced.component.html +++ b/src/gui/static/src/app/components/pages/send-skycoin/send-form-advanced/send-form-advanced.component.html @@ -3,6 +3,7 @@
+ diff --git a/src/gui/static/src/app/components/pages/wallets/wallet-detail/wallet-detail.component.ts b/src/gui/static/src/app/components/pages/wallets/wallet-detail/wallet-detail.component.ts index 547349af2a..ec92d72139 100644 --- a/src/gui/static/src/app/components/pages/wallets/wallet-detail/wallet-detail.component.ts +++ b/src/gui/static/src/app/components/pages/wallets/wallet-detail/wallet-detail.component.ts @@ -7,7 +7,6 @@ import { QrCodeComponent } from '../../../layout/qr-code/qr-code.component'; import { PasswordDialogComponent } from '../../../layout/password-dialog/password-dialog.component'; import { MatSnackBar } from '@angular/material'; import { showSnackbarError } from '../../../../utils/errors'; -import { TranslateService } from '@ngx-translate/core'; import { NumberOfAddressesComponent } from '../number-of-addresses/number-of-addresses'; @Component({ @@ -15,25 +14,17 @@ import { NumberOfAddressesComponent } from '../number-of-addresses/number-of-add templateUrl: './wallet-detail.component.html', styleUrls: ['./wallet-detail.component.scss'], }) -export class WalletDetailComponent implements OnInit, OnDestroy { +export class WalletDetailComponent implements OnDestroy { @Input() wallet: Wallet; - private encryptionWarning: string; private HowManyAddresses: number; constructor( private dialog: MatDialog, private walletService: WalletService, private snackbar: MatSnackBar, - private translateService: TranslateService, ) { } - ngOnInit() { - this.translateService.get('wallet.new.encrypt-warning').subscribe(msg => { - this.encryptionWarning = msg; - }); - } - ngOnDestroy() { this.snackbar.dismiss(); } @@ -71,8 +62,10 @@ export class WalletDetailComponent implements OnInit, OnDestroy { }; if (!this.wallet.encrypted) { - config.data['description'] = this.encryptionWarning; + config.data['description'] = 'wallet.new.encrypt-warning'; } else { + config.data['description'] = 'wallet.decrypt-warning'; + config.data['warning'] = true; config.data['wallet'] = this.wallet; } diff --git a/src/gui/static/src/app/services/api.service.ts b/src/gui/static/src/app/services/api.service.ts index 813d0507ab..e6b1e2d97e 100644 --- a/src/gui/static/src/app/services/api.service.ts +++ b/src/gui/static/src/app/services/api.service.ts @@ -7,6 +7,7 @@ import 'rxjs/add/operator/catch'; import 'rxjs/add/operator/map'; import { TranslateService } from '@ngx-translate/core'; import { BigNumber } from 'bignumber.js'; +import { BehaviorSubject } from 'rxjs/BehaviorSubject'; import { Address, GetWalletsResponseEntry, GetWalletsResponseWallet, NormalTransaction, @@ -16,23 +17,26 @@ import { @Injectable() export class ApiService { private url = environment.nodeUrl; + private gettingCsrf: BehaviorSubject = new BehaviorSubject(false); constructor( private http: Http, private translate: TranslateService, ) { } - getExplorerAddress(address: Address): Observable { - return this.get('explorer/address', {address: address.address}) + getTransactions(addresses: Address[]): Observable { + const formattedAddresses = addresses.map(a => a.address).join(','); + + return this.post('transactions', {addrs: formattedAddresses, verbose: true}) .map(transactions => transactions.map(transaction => ({ addresses: [], balance: new BigNumber(0), block: transaction.status.block_seq, confirmed: transaction.status.confirmed, - timestamp: transaction.timestamp, - txid: transaction.txid, - inputs: transaction.inputs, - outputs: transaction.outputs, + timestamp: transaction.txn.timestamp, + txid: transaction.txn.txid, + inputs: transaction.txn.inputs, + outputs: transaction.txn.outputs, }))); } @@ -123,7 +127,21 @@ export class ApiService { } getCsrf() { - return this.get('csrf').map(response => response.csrf_token); + return this.gettingCsrf.filter(response => !response).first().flatMap(() => { + this.gettingCsrf.next(true); + + return this.get('csrf') + .map(response => { + setTimeout(() => this.gettingCsrf.next(false)); + + return response.csrf_token; + }) + .catch((error: any) => { + setTimeout(() => this.gettingCsrf.next(false)); + + return error; + }); + }); } post(url, params = {}, options: any = {}, useV2 = false) { diff --git a/src/gui/static/src/app/services/wallet.service.ts b/src/gui/static/src/app/services/wallet.service.ts index 3fec83a874..94901ab3f8 100644 --- a/src/gui/static/src/app/services/wallet.service.ts +++ b/src/gui/static/src/app/services/wallet.service.ts @@ -75,7 +75,7 @@ export class WalletService { return this.addressesAsString() .first() .filter(addresses => !!addresses) - .flatMap(addresses => this.apiService.get('outputs', {addrs: addresses})); + .flatMap(addresses => this.apiService.post('outputs', {addrs: addresses})); } outputsWithWallets(): Observable { @@ -198,17 +198,9 @@ export class WalletService { return this.allAddresses().first().flatMap(addresses => { this.addresses = addresses; - return Observable.forkJoin(addresses.map(address => this.apiService.getExplorerAddress(address))); + return this.apiService.getTransactions(addresses); }).map(transactions => { - return [] - .concat.apply([], transactions) - .reduce((array, item) => { - if (!array.find(trans => trans.txid === item.txid)) { - array.push(item); - } - - return array; - }, []) + return transactions .sort((a, b) => b.timestamp - a.timestamp) .map(transaction => { const outgoing = this.addresses.some(address => { diff --git a/src/gui/static/src/assets/i18n/en.json b/src/gui/static/src/assets/i18n/en.json index a6473fb6e0..12e7f91259 100644 --- a/src/gui/static/src/assets/i18n/en.json +++ b/src/gui/static/src/assets/i18n/en.json @@ -96,6 +96,7 @@ "hide-empty": "Hide Empty", "encrypt": "Encrypt Wallet", "decrypt": "Decrypt Wallet", + "decrypt-warning": "Warning: for security reasons, it is not recommended to keep the wallets unencrypted. Caution is advised.", "edit": "Edit Wallet", "add": "Add Wallet", "load": "Load Wallet", @@ -169,7 +170,8 @@ "send-button": "Send", "back-button": "Back", "simple": "Simple", - "advanced": "Advanced" + "advanced": "Advanced", + "select-wallet": "Select Wallet" }, "reset": { diff --git a/src/gui/static/src/current-skycoin.json b/src/gui/static/src/current-skycoin.json index 381e17e55d..400f1dcc6f 100644 --- a/src/gui/static/src/current-skycoin.json +++ b/src/gui/static/src/current-skycoin.json @@ -1 +1 @@ -versionData='{ "version": "0.24.1" }'; +versionData='{ "version": "0.25.0-rc1" }'; diff --git a/src/params/distribution.go b/src/params/distribution.go new file mode 100644 index 0000000000..5e13a410a2 --- /dev/null +++ b/src/params/distribution.go @@ -0,0 +1,41 @@ +package params + +// GetDistributionAddresses returns a copy of the hardcoded distribution addresses array. +// Each address has 1,000,000 coins. There are 100 addresses. +func GetDistributionAddresses() []string { + addrs := make([]string, len(distributionAddresses)) + for i := range distributionAddresses { + addrs[i] = distributionAddresses[i] + } + return addrs +} + +// GetUnlockedDistributionAddresses returns distribution addresses that are unlocked, i.e. they have spendable outputs +func GetUnlockedDistributionAddresses() []string { + // The first InitialUnlockedCount (25) addresses are unlocked by default. + // Subsequent addresses will be unlocked at a rate of UnlockAddressRate (5) per year, + // after the InitialUnlockedCount (25) addresses have no remaining balance. + // The unlock timer will be enabled manually once the + // InitialUnlockedCount (25) addresses are distributed. + + // NOTE: To have automatic unlocking, transaction verification would have + // to be handled in visor rather than in coin.Transactions.Visor(), because + // the coin package is agnostic to the state of the blockchain and cannot reference it. + // Instead of automatic unlocking, we can hardcode the timestamp at which the first 30% + // is distributed, then compute the unlocked addresses easily here. + + addrs := make([]string, InitialUnlockedCount) + copy(addrs[:], distributionAddresses[:InitialUnlockedCount]) + return addrs +} + +// GetLockedDistributionAddresses returns distribution addresses that are locked, i.e. they have unspendable outputs +func GetLockedDistributionAddresses() []string { + // TODO -- once we reach 30% distribution, we can hardcode the + // initial timestamp for releasing more coins + addrs := make([]string, DistributionAddressesTotal-InitialUnlockedCount) + for i := range distributionAddresses[InitialUnlockedCount:] { + addrs[i] = distributionAddresses[InitialUnlockedCount+uint64(i)] + } + return addrs +} diff --git a/src/params/distribution_test.go b/src/params/distribution_test.go new file mode 100644 index 0000000000..c7997c7bdd --- /dev/null +++ b/src/params/distribution_test.go @@ -0,0 +1,57 @@ +package params + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestDistributionAddressArrays(t *testing.T) { + require.Len(t, GetDistributionAddresses(), 100) + + // At the time of this writing, there should be 25 addresses in the + // unlocked pool and 75 in the locked pool. + require.Len(t, GetUnlockedDistributionAddresses(), 25) + require.Len(t, GetLockedDistributionAddresses(), 75) + + all := GetDistributionAddresses() + allMap := make(map[string]struct{}) + for _, a := range all { + // Check no duplicate address in distribution addresses + _, ok := allMap[a] + require.False(t, ok) + allMap[a] = struct{}{} + } + + unlocked := GetUnlockedDistributionAddresses() + unlockedMap := make(map[string]struct{}) + for _, a := range unlocked { + // Check no duplicate address in unlocked addresses + _, ok := unlockedMap[a] + require.False(t, ok) + + // Check unlocked address in set of all addresses + _, ok = allMap[a] + require.True(t, ok) + + unlockedMap[a] = struct{}{} + } + + locked := GetLockedDistributionAddresses() + lockedMap := make(map[string]struct{}) + for _, a := range locked { + // Check no duplicate address in locked addresses + _, ok := lockedMap[a] + require.False(t, ok) + + // Check locked address in set of all addresses + _, ok = allMap[a] + require.True(t, ok) + + // Check locked address not in unlocked addresses + _, ok = unlockedMap[a] + require.False(t, ok) + + lockedMap[a] = struct{}{} + } +} diff --git a/src/params/droplet.go b/src/params/droplet.go new file mode 100644 index 0000000000..fe06ffc8ba --- /dev/null +++ b/src/params/droplet.go @@ -0,0 +1,43 @@ +package params + +import ( + "errors" + + "github.com/skycoin/skycoin/src/util/droplet" +) + +var ( + // ErrInvalidDecimals is returned by DropletPrecisionCheck if a coin amount has an invalid number of decimal places + ErrInvalidDecimals = errors.New("invalid amount, too many decimal places") + + // maxDropletDivisor represents the modulus divisor when checking droplet precision rules. + // It is computed from MaxDropletPrecision in init() + maxDropletDivisor uint64 +) + +// MaxDropletDivisor represents the modulus divisor when checking droplet precision rules. +func MaxDropletDivisor() uint64 { + // The value is wrapped in a getter to make it immutable to external packages + return maxDropletDivisor +} + +// DropletPrecisionCheck checks if an amount of coins is valid given decimal place restrictions +func DropletPrecisionCheck(amount uint64) error { + if amount%maxDropletDivisor != 0 { + return ErrInvalidDecimals + } + return nil +} + +func calculateDivisor(precision uint64) uint64 { + if precision > droplet.Exponent { + panic("precision must be <= droplet.Exponent") + } + + n := droplet.Exponent - precision + var i uint64 = 1 + for k := uint64(0); k < n; k++ { + i = i * 10 + } + return i +} diff --git a/src/params/droplet_test.go b/src/params/droplet_test.go new file mode 100644 index 0000000000..7e63e56923 --- /dev/null +++ b/src/params/droplet_test.go @@ -0,0 +1,37 @@ +package params + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + _require "github.com/skycoin/skycoin/src/testutil/require" +) + +func TestCalculateDivisor(t *testing.T) { + cases := []struct { + precision uint64 + divisor uint64 + }{ + {0, 1e6}, + {1, 1e5}, + {2, 1e4}, + {3, 1e3}, + {4, 1e2}, + {5, 1e1}, + {6, 1}, + } + + for _, tc := range cases { + name := fmt.Sprintf("calculateDivisor(%d)=%d", tc.precision, tc.divisor) + t.Run(name, func(t *testing.T) { + divisor := calculateDivisor(tc.precision) + require.Equal(t, tc.divisor, divisor, "%d != %d", tc.divisor, divisor) + }) + } + + _require.PanicsWithLogMessage(t, "precision must be <= droplet.Exponent", func() { + calculateDivisor(7) + }) +} diff --git a/src/params/init.go b/src/params/init.go new file mode 100644 index 0000000000..39954f3d68 --- /dev/null +++ b/src/params/init.go @@ -0,0 +1,79 @@ +package params + +import ( + "fmt" + "os" + "strconv" +) + +func init() { + loadCoinHourBurnFactor() + loadMaxUserTransactionSize() + + // Compute maxDropletDivisor from precision + maxDropletDivisor = calculateDivisor(MaxDropletPrecision) + + sanityCheck() +} + +func sanityCheck() { + if UserBurnFactor <= 1 { + panic("UserBurnFactor must be > 1") + } + + if MaxUserTransactionSize <= 0 { + panic("MaxUserTransactionSize must be > 0") + } + + if InitialUnlockedCount > DistributionAddressesTotal { + panic("unlocked addresses > total distribution addresses") + } + + if uint64(len(distributionAddresses)) != DistributionAddressesTotal { + panic("available distribution addresses > total allowed distribution addresses") + } + + if DistributionAddressInitialBalance*DistributionAddressesTotal > MaxCoinSupply { + panic("total balance in distribution addresses > max coin supply") + } + + if MaxCoinSupply%DistributionAddressesTotal != 0 { + panic("MaxCoinSupply should be perfectly divisible by DistributionAddressesTotal") + } +} + +func loadCoinHourBurnFactor() { + xs := os.Getenv("USER_BURN_FACTOR") + if xs == "" { + return + } + + x, err := strconv.ParseUint(xs, 10, 64) + if err != nil { + panic(fmt.Sprintf("Invalid USER_BURN_FACTOR %q: %v", xs, err)) + } + + if x <= 1 { + panic("USER_BURN_FACTOR must be > 1") + } + + UserBurnFactor = x +} + +func loadMaxUserTransactionSize() { + xs := os.Getenv("MAX_USER_TXN_SIZE") + if xs == "" { + return + } + + x, err := strconv.ParseInt(xs, 10, 32) + if err != nil { + panic(fmt.Sprintf("Invalid MAX_USER_TXN_SIZE %q: %v", xs, err)) + } + + if x <= 0 { + panic("MAX_USER_TXN_SIZE must be > 0") + } + + MaxUserTransactionSize = int(x) +} diff --git a/src/visor/parameters.go b/src/params/params.go similarity index 90% rename from src/visor/parameters.go rename to src/params/params.go index c3ca8d3c99..92dbd2646a 100644 --- a/src/visor/parameters.go +++ b/src/params/params.go @@ -1,4 +1,4 @@ -package visor +package params /* CODE GENERATED AUTOMATICALLY WITH FIBER COIN CREATOR @@ -20,12 +20,22 @@ const ( // Once the InitialUnlockedCount is exhausted, // UnlockAddressRate addresses will be unlocked per UnlockTimeInterval UnlockTimeInterval uint64 = 31536000 // in seconds +) + +var ( + // UserBurnFactor inverse fraction of coinhours that must be burned (can be overridden with `USER_BURN_FACTOR` env var), + // used when creating a transaction + UserBurnFactor uint64 = 2 + + // MaxUserTransactionSize is the maximum size of a user-created transaction + MaxUserTransactionSize = 32768 // in bytes + // MaxDropletPrecision represents the decimal precision of droplets MaxDropletPrecision uint64 = 3 - //DefaultMaxBlockSize is max block size - DefaultMaxBlockSize int = 32768 // in bytes ) +// distributionAddresses are addresses that received coins from the genesis address in the first block, +// used to calculate current and max supply and do distribution timelocking var distributionAddresses = [DistributionAddressesTotal]string{ "R6aHqKWSQfvpdo2fGSrq4F1RYXkBWR9HHJ", "2EYM4WFHe4Dgz6kjAdUkM6Etep7ruz2ia6h", diff --git a/src/readable/network.go b/src/readable/network.go index b2e09a5b6f..9101dd8614 100644 --- a/src/readable/network.go +++ b/src/readable/network.go @@ -2,35 +2,53 @@ package readable import ( "github.com/skycoin/skycoin/src/daemon" + "github.com/skycoin/skycoin/src/util/useragent" ) // Connection a connection's state within the daemon type Connection struct { - ID int `json:"id"` - Addr string `json:"address"` - LastSent int64 `json:"last_sent"` - LastReceived int64 `json:"last_received"` - // Whether the connection is from us to them (true, outgoing), - // or from them to us (false, incoming) - Outgoing bool `json:"outgoing"` - // Whether the client has identified their version, mirror etc - Introduced bool `json:"introduced"` - Mirror uint32 `json:"mirror"` - ListenPort uint16 `json:"listen_port"` - Height uint64 `json:"height"` + GnetID uint64 `json:"id"` + Addr string `json:"address"` + LastSent int64 `json:"last_sent"` + LastReceived int64 `json:"last_received"` + ConnectedAt int64 `json:"connected_at"` + Outgoing bool `json:"outgoing"` + State daemon.ConnectionState `json:"state"` + Mirror uint32 `json:"mirror"` + ListenPort uint16 `json:"listen_port"` + Height uint64 `json:"height"` + UserAgent useragent.Data `json:"user_agent"` + IsTrustedPeer bool `json:"is_trusted_peer"` } // NewConnection copies daemon.Connection to a struct with json tags func NewConnection(c *daemon.Connection) Connection { + var lastSent int64 + var lastReceived int64 + var connectedAt int64 + + if !c.Gnet.LastSent.IsZero() { + lastSent = c.Gnet.LastSent.Unix() + } + if !c.Gnet.LastReceived.IsZero() { + lastReceived = c.Gnet.LastReceived.Unix() + } + if !c.ConnectedAt.IsZero() { + connectedAt = c.ConnectedAt.Unix() + } + return Connection{ - ID: c.ID, - Addr: c.Addr, - LastSent: c.LastSent, - LastReceived: c.LastReceived, - Outgoing: c.Outgoing, - Introduced: c.Introduced, - Mirror: c.Mirror, - ListenPort: c.ListenPort, - Height: c.Height, + GnetID: c.Gnet.ID, + Addr: c.Addr, + LastSent: lastSent, + LastReceived: lastReceived, + ConnectedAt: connectedAt, + Outgoing: c.Outgoing, + State: c.State, + Mirror: c.Mirror, + ListenPort: c.ListenPort, + Height: c.Height, + UserAgent: c.UserAgent, + IsTrustedPeer: c.Pex.Trusted, } } diff --git a/src/skycoin/config.go b/src/skycoin/config.go index 05269a1edf..7a9523909d 100644 --- a/src/skycoin/config.go +++ b/src/skycoin/config.go @@ -14,8 +14,10 @@ import ( "github.com/skycoin/skycoin/src/api" "github.com/skycoin/skycoin/src/cipher" + "github.com/skycoin/skycoin/src/params" "github.com/skycoin/skycoin/src/readable" "github.com/skycoin/skycoin/src/util/file" + "github.com/skycoin/skycoin/src/util/useragent" "github.com/skycoin/skycoin/src/wallet" ) @@ -31,6 +33,9 @@ type Config struct { // NodeConfig records the node's configuration type NodeConfig struct { + // Name of the coin + CoinName string + // Disable peer exchange DisablePEX bool // Download peer list @@ -70,6 +75,8 @@ type NodeConfig struct { Address string // gnet uses this for TCP incoming and outgoing Port int + // MaxConnections is the maximum number of total connections allowed + MaxConnections int // Maximum outgoing connections to maintain MaxOutgoingConnections int // Maximum default outgoing connections @@ -112,9 +119,14 @@ type NodeConfig struct { // GUI directory contains assets for the HTML interface GUIDirectory string - ReadTimeout time.Duration - WriteTimeout time.Duration - IdleTimeout time.Duration + // Timeouts for the HTTP listener + HTTPReadTimeout time.Duration + HTTPWriteTimeout time.Duration + HTTPIdleTimeout time.Duration + + // Remark to include in user agent sent in the wire protocol introduction + UserAgentRemark string + userAgent useragent.Data // Logging ColorLog bool @@ -128,6 +140,15 @@ type NodeConfig struct { // Reset the database if integrity checks fail, and continue running ResetCorruptDB bool + // Maximum size of blocks in bytes to apply when creating blocks + MaxBlockSize int + // Maximum size of a transaction in bytes to apply to unconfirmed txns (received over the network, or when refreshing the pool) + MaxUnconfirmedTransactionSize int + // Coin hour burn factor to apply to unconfirmed txns (received over the network, or when refreshing the pool) + UnconfirmedBurnFactor uint64 + // Coin hour burn factor to apply when creating blocks + CreateBlockBurnFactor uint64 + // Wallets // Defaults to ${DataDirectory}/wallets/ WalletDirectory string @@ -139,7 +160,7 @@ type NodeConfig struct { // Load custom peers from disk CustomPeersFile string - RunMaster bool + RunBlockPublisher bool /* Developer options */ @@ -176,6 +197,7 @@ type NodeConfig struct { // NewNodeConfig returns a new node config instance func NewNodeConfig(mode string, node NodeParameters) NodeConfig { nodeConfig := NodeConfig{ + CoinName: node.CoinName, GenesisSignatureStr: node.GenesisSignatureStr, GenesisAddressStr: node.GenesisAddressStr, GenesisCoinVolume: node.GenesisCoinVolume, @@ -206,9 +228,11 @@ func NewNodeConfig(mode string, node NodeParameters) NodeConfig { Address: "", //gnet uses this for TCP incoming and outgoing Port: node.Port, - // MaxOutgoingConnections is the maximum outgoing connections allowed. + // MaxConnections is the maximum number of total connections allowed + MaxConnections: 128, + // MaxOutgoingConnections is the maximum outgoing connections allowed MaxOutgoingConnections: 8, - // MaxDefaultOutgoingConnections is the maximum default outgoing connections allowed. + // MaxDefaultOutgoingConnections is the maximum default outgoing connections allowed MaxDefaultPeerOutgoingConnections: 1, DownloadPeerList: true, PeerListURL: node.PeerListURL, @@ -224,7 +248,7 @@ func NewNodeConfig(mode string, node NodeParameters) NodeConfig { WebInterfaceCert: "", WebInterfaceKey: "", WebInterfaceHTTPS: false, - EnabledAPISets: api.EndpointsRead + "," + api.EndpointsTransaction, + EnabledAPISets: strings.Join([]string{api.EndpointsRead, api.EndpointsTransaction}, ","), DisabledAPISets: "", EnableAllAPISets: false, @@ -244,19 +268,23 @@ func NewNodeConfig(mode string, node NodeParameters) NodeConfig { VerifyDB: false, ResetCorruptDB: false, + // Blockchain/transaction validation + MaxUnconfirmedTransactionSize: params.MaxUserTransactionSize, + MaxBlockSize: params.MaxUserTransactionSize, + UnconfirmedBurnFactor: params.UserBurnFactor, + CreateBlockBurnFactor: params.UserBurnFactor, + // Wallets WalletDirectory: "", WalletCryptoType: string(wallet.CryptoTypeScryptChacha20poly1305), // Timeout settings for http.Server // https://blog.cloudflare.com/the-complete-guide-to-golang-net-http-timeouts/ - ReadTimeout: time.Second * 10, - WriteTimeout: time.Second * 60, - IdleTimeout: time.Second * 120, + HTTPReadTimeout: time.Second * 10, + HTTPWriteTimeout: time.Second * 60, + HTTPIdleTimeout: time.Second * 120, - // Centralized network configuration - RunMaster: false, - /* Developer options */ + RunBlockPublisher: false, // Enable cpu profiling ProfileCPU: false, @@ -276,7 +304,8 @@ func (c *Config) postProcess() error { if help { flag.Usage() fmt.Println("Additional environment variables:") - fmt.Println("* COINHOUR_BURN_FACTOR - Set the coin hour burn factor required for transactions. Must be > 1.") + fmt.Println("* USER_BURN_FACTOR - Set the coin hour burn factor required for user-created transactions. Must be > 1.") + fmt.Println("* MAX_USER_TXN_SIZE - Set the maximum transaction size (in bytes) allowed for user-created transactions. Must be > 183.") os.Exit(0) } @@ -331,11 +360,23 @@ func (c *Config) postProcess() error { c.Node.DBPath = replaceHome(c.Node.DBPath, home) } - if c.Node.RunMaster { - // Run in arbitrating mode if the node is master + if c.Node.RunBlockPublisher { + // Run in arbitrating mode if the node is block publisher c.Node.Arbitrating = true } + userAgentData := useragent.Data{ + Coin: c.Node.CoinName, + Version: c.Build.Version, + Remark: c.Node.UserAgentRemark, + } + + if _, err := userAgentData.Build(); err != nil { + return err + } + + c.Node.userAgent = userAgentData + apiSets, err := buildAPISets(c.Node) if err != nil { return err @@ -365,6 +406,38 @@ func (c *Config) postProcess() error { return errors.New("Web interface auth enabled but HTTPS is not enabled. Use -web-interface-plaintext-auth=true if this is desired") } + if c.Node.MaxOutgoingConnections > c.Node.MaxConnections { + return errors.New("-max-outgoing-connections cannot be higher than -max-connections") + } + + if c.Node.MaxBlockSize <= 0 { + return errors.New("-block-size must be > 0") + } + if c.Node.MaxBlockSize < params.MaxUserTransactionSize { + return fmt.Errorf("-max-block-size must be >= params.MaxUserTransactionSize (%d)", params.MaxUserTransactionSize) + } + + if c.Node.MaxUnconfirmedTransactionSize <= 0 { + return errors.New("-unconfirmed-txn-size must be > 0") + } + if c.Node.MaxUnconfirmedTransactionSize < params.MaxUserTransactionSize { + return fmt.Errorf("-unconfirmed-txn-size must be >= params.MaxUserTransactionSize (%d)", params.MaxUserTransactionSize) + } + + if c.Node.UnconfirmedBurnFactor < 2 { + return errors.New("-unconfirmed-burn-factor must be >= 2") + } + if c.Node.UnconfirmedBurnFactor < params.UserBurnFactor { + return fmt.Errorf("-unconfirmed-burn-factor must be >= params.UserBurnFactor (%d)", params.UserBurnFactor) + } + + if c.Node.CreateBlockBurnFactor < 2 { + return errors.New("-create-block-burn-factor must be >= 2") + } + if c.Node.CreateBlockBurnFactor < params.UserBurnFactor { + return fmt.Errorf("-create-block-burn-factor must be >= params.UserBurnFactor (%d)", params.UserBurnFactor) + } + return nil } @@ -390,6 +463,8 @@ func buildAPISets(c NodeConfig) (map[string]struct{}, error) { api.EndpointsStatus, api.EndpointsWallet, api.EndpointsTransaction, + api.EndpointsPrometheus, + api.EndpointsNetCtrl, // Do not include insecure or deprecated API sets, they must always // be explicitly enabled through -enable-api-sets } @@ -455,9 +530,21 @@ func (c *NodeConfig) RegisterFlags() { flag.StringVar(&c.WebInterfaceKey, "web-interface-key", c.WebInterfaceKey, "skycoind.key file for web interface HTTPS. If not provided, will autogenerate or use skycoind.key in -data-directory") flag.BoolVar(&c.WebInterfaceHTTPS, "web-interface-https", c.WebInterfaceHTTPS, "enable HTTPS for web interface") flag.StringVar(&c.HostWhitelist, "host-whitelist", c.HostWhitelist, "Hostnames to whitelist in the Host header check. Only applies when the web interface is bound to localhost.") - flag.StringVar(&c.EnabledAPISets, "enable-api-sets", c.EnabledAPISets, "enable API set. Options are READ, STATUS, WALLET, INSECURE_WALLET_SEED, DEPRECATED_WALLET_SPEND. Multiple values should be separated by comma") - flag.StringVar(&c.DisabledAPISets, "disable-api-sets", c.DisabledAPISets, "disable API set. Options are READ, STATUS, WALLET, INSECURE_WALLET_SEED, DEPRECATED_WALLET_SPEND. Multiple values should be separated by comma") + + allAPISets := []string{ + api.EndpointsRead, + api.EndpointsStatus, + api.EndpointsWallet, + api.EndpointsTransaction, + api.EndpointsPrometheus, + api.EndpointsNetCtrl, + api.EndpointsInsecureWalletSeed, + api.EndpointsDeprecatedWalletSpend, + } + flag.StringVar(&c.EnabledAPISets, "enable-api-sets", c.EnabledAPISets, fmt.Sprintf("enable API set. Options are %s. Multiple values should be separated by comma", strings.Join(allAPISets, ", "))) + flag.StringVar(&c.DisabledAPISets, "disable-api-sets", c.DisabledAPISets, fmt.Sprintf("disable API set. Options are %s. Multiple values should be separated by comma", strings.Join(allAPISets, ", "))) flag.BoolVar(&c.EnableAllAPISets, "enable-all-api-sets", c.EnableAllAPISets, "enable all API sets, except for deprecated or insecure sets. This option is applied before -disable-api-sets.") + flag.StringVar(&c.WebInterfaceUsername, "web-interface-username", c.WebInterfaceUsername, "username for the web interface") flag.StringVar(&c.WebInterfacePassword, "web-interface-password", c.WebInterfacePassword, "password for the web interface") flag.BoolVar(&c.WebInterfacePlaintextAuth, "web-interface-plaintext-auth", c.WebInterfacePlaintextAuth, "allow web interface auth without https") @@ -485,20 +572,26 @@ func (c *NodeConfig) RegisterFlags() { flag.BoolVar(&c.DisableDefaultPeers, "disable-default-peers", c.DisableDefaultPeers, "disable the hardcoded default peers") flag.StringVar(&c.CustomPeersFile, "custom-peers-file", c.CustomPeersFile, "load custom peers from a newline separate list of ip:port in a file. Note that this is different from the peers.json file in the data directory") - // Key Configuration Data - flag.BoolVar(&c.RunMaster, "master", c.RunMaster, "run the daemon as blockchain master server") + flag.StringVar(&c.UserAgentRemark, "user-agent-remark", c.UserAgentRemark, "additional remark to include in the user agent sent over the wire protocol") + + flag.IntVar(&c.MaxUnconfirmedTransactionSize, "unconfirmed-txn-size", c.MaxUnconfirmedTransactionSize, "maximum size of an unconfirmed transaction") + flag.IntVar(&c.MaxBlockSize, "block-size", c.MaxBlockSize, "maximum size of a block") + flag.Uint64Var(&c.UnconfirmedBurnFactor, "burn-factor-unconfirmed", c.UnconfirmedBurnFactor, "coinhour burn factor applied to unconfirmed transactions") + flag.Uint64Var(&c.CreateBlockBurnFactor, "burn-factor-create-block", c.CreateBlockBurnFactor, "coinhour burn factor applied when creating blocks") - flag.StringVar(&c.BlockchainPubkeyStr, "master-public-key", c.BlockchainPubkeyStr, "public key of the master chain") - flag.StringVar(&c.BlockchainSeckeyStr, "master-secret-key", c.BlockchainSeckeyStr, "secret key, set for master") + flag.BoolVar(&c.RunBlockPublisher, "block-publisher", c.RunBlockPublisher, "run the daemon as a block publisher") + flag.StringVar(&c.BlockchainPubkeyStr, "blockchain-public-key", c.BlockchainPubkeyStr, "public key of the blockchain") + flag.StringVar(&c.BlockchainSeckeyStr, "blockchain-secret-key", c.BlockchainSeckeyStr, "secret key of the blockchain") flag.StringVar(&c.GenesisAddressStr, "genesis-address", c.GenesisAddressStr, "genesis address") flag.StringVar(&c.GenesisSignatureStr, "genesis-signature", c.GenesisSignatureStr, "genesis block signature") flag.Uint64Var(&c.GenesisTimestamp, "genesis-timestamp", c.GenesisTimestamp, "genesis block timestamp") flag.StringVar(&c.WalletDirectory, "wallet-dir", c.WalletDirectory, "location of the wallet files. Defaults to ~/.skycoin/wallet/") - flag.IntVar(&c.MaxOutgoingConnections, "max-outgoing-connections", c.MaxOutgoingConnections, "The maximum outgoing connections allowed") + flag.IntVar(&c.MaxConnections, "max-connections", c.MaxConnections, "Maximum number of total connections allowed") + flag.IntVar(&c.MaxOutgoingConnections, "max-outgoing-connections", c.MaxOutgoingConnections, "Maximum number of outgoing connections allowed") flag.IntVar(&c.MaxDefaultPeerOutgoingConnections, "max-default-peer-outgoing-connections", c.MaxDefaultPeerOutgoingConnections, "The maximum default peer outgoing connections allowed") - flag.IntVar(&c.PeerlistSize, "peerlist-size", c.PeerlistSize, "The peer list size") + flag.IntVar(&c.PeerlistSize, "peerlist-size", c.PeerlistSize, "Max number of peers to track in peerlist") flag.DurationVar(&c.OutgoingConnectionsRate, "connection-rate", c.OutgoingConnectionsRate, "How often to make an outgoing connection") flag.BoolVar(&c.LocalhostOnly, "localhost-only", c.LocalhostOnly, "Run on localhost and only connect to localhost peers") flag.BoolVar(&c.Arbitrating, "arbitrating", c.Arbitrating, "Run node in arbitrating mode") @@ -514,7 +607,7 @@ func (c *NodeConfig) applyConfigMode(configMode string) { case "": case "STANDALONE_CLIENT": c.EnableAllAPISets = true - c.EnabledAPISets = "INSECURE_WALLET_SEED" + c.EnabledAPISets = api.EndpointsInsecureWalletSeed c.EnableGUI = true c.LaunchBrowser = true c.DisableCSRF = false diff --git a/src/skycoin/parameters.go b/src/skycoin/parameters.go index 3f388223cb..4d65f56e75 100644 --- a/src/skycoin/parameters.go +++ b/src/skycoin/parameters.go @@ -9,56 +9,74 @@ import ( // Parameters records fiber coin parameters type Parameters struct { - Node NodeParameters `mapstructure:"node"` - Visor VisorParameters `mapstructure:"visor"` + Node NodeParameters `mapstructure:"node"` + Params ParamsParameters `mapstructure:"params"` } -// NodeParameters records the node's configurable parameters +// NodeParameters configures the default CLI options for the skycoin node. +// These parameters are loaded via cmd/skycoin/skycoin.go into src/skycoin/skycoin.go. type NodeParameters struct { - PeerListURL string `mapstructure:"peer_list_url"` - Port int `mapstructure:"port"` - WebInterfacePort int `mapstructure:"web_interface_port"` - GenesisSignatureStr string `mapstructure:"genesis_signature_str"` - GenesisAddressStr string `mapstructure:"genesis_address_str"` - BlockchainPubkeyStr string `mapstructure:"blockchain_pubkey_str"` - BlockchainSeckeyStr string `mapstructure:"blockchain_seckey_str"` - GenesisTimestamp uint64 `mapstructure:"genesis_timestamp"` - GenesisCoinVolume uint64 `mapstructure:"genesis_coin_volume"` - DefaultConnections []string `mapstructure:"default_connections"` - - DataDirectory string - ProfileCPUFile string + // Port is the default port that the wire protocol communicates over + Port int `mapstructure:"port"` + // WebInterfacePort is the default port that the web/gui interface serves on + WebInterfacePort int `mapstructure:"web_interface_port"` + // GenesisSignatureStr is a hex-encoded signature of the genesis block input + GenesisSignatureStr string `mapstructure:"genesis_signature_str"` + // GenesisAddressStr is the skycoin address that the genesis coins were sent to in the genesis block + GenesisAddressStr string `mapstructure:"genesis_address_str"` + // BlockchainPubkeyStr is a hex-encoded public key used to validate published blocks + BlockchainPubkeyStr string `mapstructure:"blockchain_pubkey_str"` + // BlockchainSeckey is a hex-encoded secret key required for block publishing. + // It must correspond to BlockchainPubkeyStr + BlockchainSeckeyStr string `mapstructure:"blockchain_seckey_str"` + // GenesisTimestamp is the timestamp of the genesis block + GenesisTimestamp uint64 `mapstructure:"genesis_timestamp"` + // GenesisCoinVolume is the total number of coins in the genesis block + GenesisCoinVolume uint64 `mapstructure:"genesis_coin_volume"` + // DefaultConnections are the default "trusted" connections a node will try to connect to for bootstrapping + DefaultConnections []string `mapstructure:"default_connections"` + // PeerlistURL is a URL pointing to a newline-separated list of ip:ports that are used for bootstrapping (but they are not "trusted") + PeerListURL string `mapstructure:"peer_list_url"` + // UnconfirmedBurnFactor is the burn factor to apply when verifying unconfirmed transactions + UnconfirmedBurnFactor uint64 `mapstructure:"unconfirmed_burn_factor"` + // CreateBlockBurnFactor is the burn factor to apply to transactions when publishing blocks + CreateBlockBurnFactor uint64 `mapstructure:"create_block_burn_factor"` + // MaxBlockSize is the maximum size of blocks when publishing blocks + MaxBlockSize int `mapstructure:"max_block_size"` + // MaxUnconfirmedTransactionSize is the maximum size of an unconfirmed transaction + MaxUnconfirmedTransactionSize int `mapstructure:"max_unconfirmed_transaction_size"` + + // These fields are set by cmd/newcoin and are not configured in the fiber.toml file + CoinName string + DataDirectory string } -// VisorParameters are the parameters used to generate parameters.go in visor -type VisorParameters struct { +// ParamsParameters are the parameters used to generate params/params.go. +// These parameters are exposed in an importable package `params` because they +// may need to be imported by libraries that would not know the node's configured CLI options. +type ParamsParameters struct { // MaxCoinSupply is the maximum supply of coins MaxCoinSupply uint64 `mapstructure:"max_coin_supply"` - // DistributionAddressesTotal is the number of distribution addresses DistributionAddressesTotal uint64 `mapstructure:"distribution_addresses_total"` - // DistributionAddressInitialBalance is the initial balance of each distribution address DistributionAddressInitialBalance uint64 - // InitialUnlockedCount is the initial number of unlocked addresses InitialUnlockedCount uint64 `mapstructure:"initial_unlocked_count"` - // UnlockAddressRate is the number of addresses to unlock per unlock time interval UnlockAddressRate uint64 `mapstructure:"unlock_address_rate"` - - // UnlockTimeInterval is the distribution address unlock time interval, measured in seconds - // Once the InitialUnlockedCount is exhausted, - // UnlockAddressRate addresses will be unlocked per UnlockTimeInterval + // UnlockTimeInterval is the distribution address unlock time interval, measured in seconds. + // Once the InitialUnlockedCount is exhausted, UnlockAddressRate addresses will be unlocked per UnlockTimeInterval UnlockTimeInterval uint64 `mapstructure:"unlock_time_interval"` - // MaxDropletPrecision represents the decimal precision of droplets MaxDropletPrecision uint64 `mapstructure:"max_droplet_precision"` - - //DefaultMaxBlockSize is max block size - DefaultMaxBlockSize int `mapstructure:"default_max_block_size"` - + // MaxUserTransactionSize is max size of a user-created transaction (typically equal to the max size of a block) + MaxUserTransactionSize int `mapstructure:"max_user_transaction_size"` + // DistributionAddresses are addresses that received coins from the genesis address in the first block, + // used to calculate current and max supply and do distribution timelocking DistributionAddresses []string `mapstructure:"distribution_addresses"` + // UserBurnFactor inverse fraction of coinhours that must be burned, this value is used when creating transactions + UserBurnFactor uint64 `mapstructure:"user_burn_factor"` } // NewParameters loads blockchain config parameters from a config file @@ -103,17 +121,22 @@ func setDefaults() { viper.SetDefault("node.genesis_coin_volume", 100e12) viper.SetDefault("node.port", 6000) viper.SetDefault("node.web_interface_port", 6420) + viper.SetDefault("node.max_block_size", 32*1024) + viper.SetDefault("node.max_unconfirmed_transaction_size", 32*1024) + viper.SetDefault("node.unconfirmed_burn_factor", 2) + viper.SetDefault("node.create_block_burn_factor", 2) // build defaults viper.SetDefault("build.commit", "") viper.SetDefault("build.branch", "") - // visor parameter defaults - viper.SetDefault("visor.max_coin_supply", 1e8) - viper.SetDefault("visor.distribution_addresses_total", 100) - viper.SetDefault("visor.initial_unlocked_count", 25) - viper.SetDefault("visor.unlock_address_rate", 5) - viper.SetDefault("visor.unlock_time_interval", 60*60*24*365) - viper.SetDefault("visor.max_droplet_precision", 3) - viper.SetDefault("visor.default_max_block_size", 32*1024) + // params defaults + viper.SetDefault("params.max_coin_supply", 1e8) + viper.SetDefault("params.distribution_addresses_total", 100) + viper.SetDefault("params.initial_unlocked_count", 25) + viper.SetDefault("params.unlock_address_rate", 5) + viper.SetDefault("params.unlock_time_interval", 60*60*24*365) + viper.SetDefault("params.max_droplet_precision", 3) + viper.SetDefault("params.user_burn_factor", 2) + viper.SetDefault("params.max_user_transaction_size", 32*1024) } diff --git a/src/skycoin/parameters_test.go b/src/skycoin/parameters_test.go index 0d8a86738c..66280d2e2d 100644 --- a/src/skycoin/parameters_test.go +++ b/src/skycoin/parameters_test.go @@ -27,18 +27,23 @@ func TestNewParameters(t *testing.T) { "172.104.85.6:6000", "139.162.7.132:6000", }, - Port: 6000, - PeerListURL: "https://downloads.skycoin.net/blockchain/peers.txt", - WebInterfacePort: 6420, + Port: 6000, + PeerListURL: "https://downloads.skycoin.net/blockchain/peers.txt", + WebInterfacePort: 6420, + UnconfirmedBurnFactor: 10, + CreateBlockBurnFactor: 9, + MaxBlockSize: 1111, + MaxUnconfirmedTransactionSize: 777, }, - Visor: VisorParameters{ + Params: ParamsParameters{ MaxCoinSupply: 1e8, DistributionAddressesTotal: 100, InitialUnlockedCount: 25, UnlockAddressRate: 5, UnlockTimeInterval: 60 * 60 * 24 * 365, MaxDropletPrecision: 3, - DefaultMaxBlockSize: 32 * 1024, + UserBurnFactor: 3, + MaxUserTransactionSize: 999, }, }, coinConfig) } diff --git a/src/skycoin/skycoin.go b/src/skycoin/skycoin.go index 96dc18f202..daccb604f5 100644 --- a/src/skycoin/skycoin.go +++ b/src/skycoin/skycoin.go @@ -22,10 +22,10 @@ import ( "github.com/skycoin/skycoin/src/cipher" "github.com/skycoin/skycoin/src/coin" "github.com/skycoin/skycoin/src/daemon" + "github.com/skycoin/skycoin/src/params" "github.com/skycoin/skycoin/src/readable" "github.com/skycoin/skycoin/src/util/apputil" "github.com/skycoin/skycoin/src/util/certutil" - "github.com/skycoin/skycoin/src/util/fee" "github.com/skycoin/skycoin/src/util/logging" "github.com/skycoin/skycoin/src/visor" "github.com/skycoin/skycoin/src/visor/dbutil" @@ -201,7 +201,8 @@ func (c *Coin) Run() error { } } - c.logger.Infof("Coinhour burn factor is %d", fee.BurnFactor) + c.logger.Infof("Coinhour burn factor for creating transactions is %d", params.UserBurnFactor) + c.logger.Infof("Max user transaction size is %d", params.MaxUserTransactionSize) d, err = daemon.NewDaemon(dconf, db) if err != nil { @@ -364,20 +365,27 @@ func (c *Coin) ConfigureDaemon() daemon.Config { dc.Daemon.Port = c.config.Node.Port dc.Daemon.Address = c.config.Node.Address dc.Daemon.LocalhostOnly = c.config.Node.LocalhostOnly - dc.Daemon.OutgoingMax = c.config.Node.MaxOutgoingConnections + dc.Daemon.MaxConnections = c.config.Node.MaxConnections + dc.Daemon.MaxOutgoingConnections = c.config.Node.MaxOutgoingConnections dc.Daemon.DataDirectory = c.config.Node.DataDirectory dc.Daemon.LogPings = !c.config.Node.DisablePingPong dc.Daemon.BlockchainPubkey = c.config.Node.blockchainPubkey + dc.Daemon.UserAgent = c.config.Node.userAgent if c.config.Node.OutgoingConnectionsRate == 0 { c.config.Node.OutgoingConnectionsRate = time.Millisecond } dc.Daemon.OutgoingRate = c.config.Node.OutgoingConnectionsRate - dc.Visor.IsMaster = c.config.Node.RunMaster + dc.Visor.IsBlockPublisher = c.config.Node.RunBlockPublisher dc.Visor.BlockchainPubkey = c.config.Node.blockchainPubkey dc.Visor.BlockchainSeckey = c.config.Node.blockchainSeckey + dc.Visor.MaxBlockSize = c.config.Node.MaxBlockSize + dc.Visor.MaxUnconfirmedTransactionSize = c.config.Node.MaxUnconfirmedTransactionSize + dc.Visor.UnconfirmedBurnFactor = c.config.Node.UnconfirmedBurnFactor + dc.Visor.CreateBlockBurnFactor = c.config.Node.CreateBlockBurnFactor + dc.Visor.GenesisAddress = c.config.Node.genesisAddress dc.Visor.GenesisSignature = c.config.Node.genesisSignature dc.Visor.GenesisTimestamp = c.config.Node.GenesisTimestamp @@ -410,15 +418,19 @@ func (c *Coin) createGUI(d *daemon.Daemon, host string) (*api.Server, error) { EnableJSON20RPC: c.config.Node.RPCInterface, EnableGUI: c.config.Node.EnableGUI, EnableUnversionedAPI: c.config.Node.EnableUnversionedAPI, - ReadTimeout: c.config.Node.ReadTimeout, - WriteTimeout: c.config.Node.WriteTimeout, - IdleTimeout: c.config.Node.IdleTimeout, + ReadTimeout: c.config.Node.HTTPReadTimeout, + WriteTimeout: c.config.Node.HTTPWriteTimeout, + IdleTimeout: c.config.Node.HTTPIdleTimeout, EnabledAPISets: c.config.Node.enabledAPISets, HostWhitelist: c.config.Node.hostWhitelist, - BuildInfo: readable.BuildInfo{ - Version: c.config.Build.Version, - Commit: c.config.Build.Commit, - Branch: c.config.Build.Branch, + Health: api.HealthConfig{ + BuildInfo: readable.BuildInfo{ + Version: c.config.Build.Version, + Commit: c.config.Build.Commit, + Branch: c.config.Build.Branch, + }, + CoinName: c.config.Node.CoinName, + DaemonUserAgent: c.config.Node.userAgent, }, Username: c.config.Node.WebInterfaceUsername, Password: c.config.Node.WebInterfacePassword, @@ -530,20 +542,20 @@ func InitTransaction(UxID string, genesisSecKey cipher.SecKey) coin.Transaction output := cipher.MustSHA256FromHex(UxID) tx.PushInput(output) - addrs := visor.GetDistributionAddresses() + addrs := params.GetDistributionAddresses() if len(addrs) != 100 { log.Panic("Should have 100 distribution addresses") } // 1 million per address, measured in droplets - if visor.DistributionAddressInitialBalance != 1e6 { - log.Panic("visor.DistributionAddressInitialBalance expected to be 1e6*1e6") + if params.DistributionAddressInitialBalance != 1e6 { + log.Panic("params.DistributionAddressInitialBalance expected to be 1e6*1e6") } for i := range addrs { addr := cipher.MustDecodeBase58Address(addrs[i]) - tx.PushOutput(addr, visor.DistributionAddressInitialBalance*1e6, 1) + tx.PushOutput(addr, params.DistributionAddressInitialBalance*1e6, 1) } seckeys := make([]cipher.SecKey, 1) diff --git a/src/skycoin/skycoin_test.go b/src/skycoin/skycoin_test.go index 835e92aaf0..0fc6042f9d 100644 --- a/src/skycoin/skycoin_test.go +++ b/src/skycoin/skycoin_test.go @@ -68,7 +68,7 @@ func getCoinName() string { func versionUpgradeWaitTimeout(t *testing.T) time.Duration { x := os.Getenv("VERSION_UPGRADE_TEST_WAIT_TIMEOUT") if x == "" { - return time.Second * 3 + return time.Second * 5 } d, err := time.ParseDuration(x) diff --git a/src/skycoin/testdata/test.fiber.toml b/src/skycoin/testdata/test.fiber.toml index cc993ad3ee..2c4396e8ed 100644 --- a/src/skycoin/testdata/test.fiber.toml +++ b/src/skycoin/testdata/test.fiber.toml @@ -18,3 +18,11 @@ default_connections = [ ] launch_browser = true peer_list_url = "https://downloads.skycoin.net/blockchain/peers.txt" +unconfirmed_burn_factor = 10 +create_block_burn_factor = 9 +max_block_size = 1111 +max_unconfirmed_transaction_size = 777 + +[params] +user_burn_factor = 3 +max_user_transaction_size = 999 diff --git a/src/util/droplet/droplet_test.go b/src/util/droplet/droplet_test.go index 44ff0cb7c0..332f72f779 100644 --- a/src/util/droplet/droplet_test.go +++ b/src/util/droplet/droplet_test.go @@ -150,6 +150,14 @@ func TestFromString(t *testing.T) { s: "1.0000001", e: ErrTooManyDecimals, }, + { + s: "1.0000000", // 7 decimal places, but they are 0s + n: 1e6, + }, + { + s: "1.000001000", + n: 1e6 + 1e0, + }, } for _, tcc := range cases { diff --git a/src/util/fee/fee.go b/src/util/fee/fee.go index fed4703a0a..798dd4d604 100644 --- a/src/util/fee/fee.go +++ b/src/util/fee/fee.go @@ -5,36 +5,10 @@ package fee import ( "errors" - "fmt" - "os" - "strconv" "github.com/skycoin/skycoin/src/coin" ) -var ( - // BurnFactor inverse fraction of coinhours that must be burned (can be overridden with COINHOUR_BURN_FACTOR env var) - BurnFactor uint64 = 2 -) - -func init() { - xs := os.Getenv("COINHOUR_BURN_FACTOR") - if xs == "" { - return - } - - x, err := strconv.ParseUint(xs, 10, 64) - if err != nil { - panic(fmt.Sprintf("Invalid COINHOUR_BURN_FACTOR %q: %v", xs, err)) - } - - if x <= 1 { - panic(fmt.Sprintf("BurnFactor must be > 1")) - } - - BurnFactor = x -} - var ( // ErrTxnNoFee is returned if a transaction has no coinhour fee ErrTxnNoFee = errors.New("Transaction has zero coinhour fee") @@ -47,21 +21,21 @@ var ( ) // VerifyTransactionFee performs additional transaction verification at the unconfirmed pool level. -// This checks tunable parameters that should prevent the transaction from +// This checks tunable params that should prevent the transaction from // entering the blockchain, but cannot be done at the blockchain level because // they may be changed. -func VerifyTransactionFee(t *coin.Transaction, fee uint64) error { +func VerifyTransactionFee(t *coin.Transaction, fee, burnFactor uint64) error { hours, err := t.OutputHours() if err != nil { return err } - return VerifyTransactionFeeForHours(hours, fee) + return VerifyTransactionFeeForHours(hours, fee, burnFactor) } // VerifyTransactionFeeForHours verifies the fee given fee and hours, // where hours is the number of hours in a transaction's outputs, // and hours+fee is the number of hours in a transaction's inputs -func VerifyTransactionFeeForHours(hours, fee uint64) error { +func VerifyTransactionFeeForHours(hours, fee, burnFactor uint64) error { // Require non-zero coinhour fee if fee == 0 { return ErrTxnNoFee @@ -74,7 +48,7 @@ func VerifyTransactionFeeForHours(hours, fee uint64) error { } // Calculate the required fee - requiredFee := RequiredFee(total) + requiredFee := RequiredFee(total, burnFactor) // Ensure that the required fee is met if fee < requiredFee { @@ -85,10 +59,10 @@ func VerifyTransactionFeeForHours(hours, fee uint64) error { } // RequiredFee returns the coinhours fee required for an amount of hours -// The required fee is calculated as hours/BurnFactor, rounded up. -func RequiredFee(hours uint64) uint64 { - feeHours := hours / BurnFactor - if hours%BurnFactor != 0 { +// The required fee is calculated as hours/burnFactor, rounded up. +func RequiredFee(hours, burnFactor uint64) uint64 { + feeHours := hours / burnFactor + if hours%burnFactor != 0 { feeHours++ } @@ -96,8 +70,8 @@ func RequiredFee(hours uint64) uint64 { } // RemainingHours returns the amount of coinhours leftover after paying the fee for the input. -func RemainingHours(hours uint64) uint64 { - fee := RequiredFee(hours) +func RemainingHours(hours, burnFactor uint64) uint64 { + fee := RequiredFee(hours, burnFactor) return hours - fee } diff --git a/src/util/fee/fee_test.go b/src/util/fee/fee_test.go index d97f6cf319..010b994900 100644 --- a/src/util/fee/fee_test.go +++ b/src/util/fee/fee_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/require" "github.com/skycoin/skycoin/src/coin" + "github.com/skycoin/skycoin/src/params" "github.com/skycoin/skycoin/src/testutil" ) @@ -99,19 +100,17 @@ var burnFactor10VerifyTxnFeeTestCases = []verifyTxnFeeTestCase{ } func TestVerifyTransactionFee(t *testing.T) { - originalBurnFactor := BurnFactor - emptyTxn := &coin.Transaction{} hours, err := emptyTxn.OutputHours() require.NoError(t, err) require.Equal(t, uint64(0), hours) // A txn with no outputs hours and no coinhours burn fee is valid - err = VerifyTransactionFee(emptyTxn, 0) + err = VerifyTransactionFee(emptyTxn, 0, 2) testutil.RequireError(t, err, ErrTxnNoFee.Error()) // A txn with no outputs hours but with a coinhours burn fee is valid - err = VerifyTransactionFee(emptyTxn, 100) + err = VerifyTransactionFee(emptyTxn, 100, 2) require.NoError(t, err) txn := &coin.Transaction{} @@ -127,31 +126,31 @@ func TestVerifyTransactionFee(t *testing.T) { require.Equal(t, uint64(4e6), hours) // A txn with insufficient net coinhours burn fee is invalid - err = VerifyTransactionFee(txn, 0) + err = VerifyTransactionFee(txn, 0, 2) testutil.RequireError(t, err, ErrTxnNoFee.Error()) - err = VerifyTransactionFee(txn, 1) + err = VerifyTransactionFee(txn, 1, 2) testutil.RequireError(t, err, ErrTxnInsufficientFee.Error()) // A txn with sufficient net coinhours burn fee is valid hours, err = txn.OutputHours() require.NoError(t, err) - err = VerifyTransactionFee(txn, hours) + err = VerifyTransactionFee(txn, hours, 2) require.NoError(t, err) hours, err = txn.OutputHours() require.NoError(t, err) - err = VerifyTransactionFee(txn, hours*10) + err = VerifyTransactionFee(txn, hours*10, 2) require.NoError(t, err) // fee + hours overflows - err = VerifyTransactionFee(txn, math.MaxUint64-3e6) + err = VerifyTransactionFee(txn, math.MaxUint64-3e6, 2) testutil.RequireError(t, err, "Hours and fee overflow") // txn has overflowing output hours txn.Out = append(txn.Out, coin.TransactionOutput{ Hours: math.MaxUint64 - 1e6 - 3e6 + 1, }) - err = VerifyTransactionFee(txn, 10) + err = VerifyTransactionFee(txn, 10, 2) testutil.RequireError(t, err, "Transaction output hours overflow") cases := []struct { @@ -165,31 +164,26 @@ func TestVerifyTransactionFee(t *testing.T) { tested := false for _, tcc := range cases { - if tcc.burnFactor == BurnFactor { + if tcc.burnFactor == params.UserBurnFactor { tested = true } for _, tc := range tcc.cases { name := fmt.Sprintf("burnFactor=%d input=%d output=%d", tcc.burnFactor, tc.inputHours, tc.outputHours) t.Run(name, func(t *testing.T) { - BurnFactor = tcc.burnFactor - defer func() { - BurnFactor = originalBurnFactor - }() - txn := &coin.Transaction{} txn.Out = append(txn.Out, coin.TransactionOutput{ Hours: tc.outputHours, }) require.True(t, tc.inputHours >= tc.outputHours) - err := VerifyTransactionFee(txn, tc.inputHours-tc.outputHours) + err := VerifyTransactionFee(txn, tc.inputHours-tc.outputHours, tcc.burnFactor) require.Equal(t, tc.err, err) }) } } - require.True(t, tested, "configured BurnFactor=%d has not been tested", BurnFactor) + require.True(t, tested, "configured params.UserBurnFactor=%d has not been tested", params.UserBurnFactor) } type requiredFeeTestCase struct { @@ -252,8 +246,6 @@ var burnFactor10RequiredFeeTestCases = []requiredFeeTestCase{ } func TestRequiredFee(t *testing.T) { - originalBurnFactor := BurnFactor - cases := []struct { burnFactor uint64 cases []requiredFeeTestCase @@ -265,28 +257,23 @@ func TestRequiredFee(t *testing.T) { tested := false for _, tcc := range cases { - if tcc.burnFactor == BurnFactor { + if tcc.burnFactor == params.UserBurnFactor { tested = true } for _, tc := range tcc.cases { name := fmt.Sprintf("burnFactor=%d hours=%d fee=%d", tcc.burnFactor, tc.hours, tc.fee) t.Run(name, func(t *testing.T) { - BurnFactor = tcc.burnFactor - defer func() { - BurnFactor = originalBurnFactor - }() - - fee := RequiredFee(tc.hours) + fee := RequiredFee(tc.hours, tcc.burnFactor) require.Equal(t, tc.fee, fee) - remainingHours := RemainingHours(tc.hours) + remainingHours := RemainingHours(tc.hours, tcc.burnFactor) require.Equal(t, tc.hours-fee, remainingHours) }) } } - require.True(t, tested, "configured BurnFactor=%d has not been tested", BurnFactor) + require.True(t, tested, "configured params.UserBurnFactor=%d has not been tested", params.UserBurnFactor) } func TestTransactionFee(t *testing.T) { diff --git a/src/util/iputil/iputil.go b/src/util/iputil/iputil.go index 6b492a00ef..1dccbd9cd0 100644 --- a/src/util/iputil/iputil.go +++ b/src/util/iputil/iputil.go @@ -5,11 +5,19 @@ package iputil import ( "errors" - "fmt" "net" "strconv" ) +var ( + // ErrMissingIP IP missing from ip:port string + ErrMissingIP = errors.New("IP missing from ip:port address") + // ErrInvalidPort port invalid in ip:port string + ErrInvalidPort = errors.New("Port invalid in ip:port address") + // ErrNoLocalIP no localhost IP found in system net interfaces + ErrNoLocalIP = errors.New("No local IP found") +) + // LocalhostIP returns the address for localhost on the machine func LocalhostIP() (string, error) { tt, err := net.Interfaces() @@ -27,7 +35,7 @@ func LocalhostIP() (string, error) { } } } - return "", errors.New("No local IP found") + return "", ErrNoLocalIP } // IsLocalhost returns true if addr is a localhost address @@ -46,12 +54,12 @@ func SplitAddr(addr string) (string, uint16, error) { } if ip == "" { - return "", 0, fmt.Errorf("IP missing from %s", addr) + return "", 0, ErrMissingIP } port64, err := strconv.ParseUint(port, 10, 16) if err != nil { - return "", 0, fmt.Errorf("Invalid port in %s", addr) + return "", 0, ErrInvalidPort } return ip, uint16(port64), nil diff --git a/src/util/iputil/iputil_test.go b/src/util/iputil/iputil_test.go index bc2280e49b..cc437c72d0 100644 --- a/src/util/iputil/iputil_test.go +++ b/src/util/iputil/iputil_test.go @@ -1,7 +1,6 @@ package iputil import ( - "fmt" "net" "testing" @@ -73,15 +72,15 @@ func TestSplitAddr(t *testing.T) { }, { input: "0.0.0.0:", - err: fmt.Errorf("Invalid port in %s", "0.0.0.0:"), + err: ErrInvalidPort, }, { input: "0.0.0.0:x", - err: fmt.Errorf("Invalid port in %s", "0.0.0.0:x"), + err: ErrInvalidPort, }, { input: ":9999", - err: fmt.Errorf("IP missing from %s", ":9999"), + err: ErrMissingIP, }, { input: "127.0.0.1", @@ -102,7 +101,7 @@ func TestSplitAddr(t *testing.T) { }, { input: "[::]:x", - err: fmt.Errorf("Invalid port in %s", "[::]:x"), + err: ErrInvalidPort, }, } diff --git a/src/util/useragent/useragent.go b/src/util/useragent/useragent.go new file mode 100644 index 0000000000..b338aed3fe --- /dev/null +++ b/src/util/useragent/useragent.go @@ -0,0 +1,222 @@ +// Package useragent implements methods for managing Skycoin user agents. +// +// A skycoin user agent has the following format: +// +// `$NAME:$VERSION[$GIT_HASH]($REMARK)` +// +// `$NAME` and `$VERSION` are required. +// +// * `$NAME` is the coin or application's name, e.g. `Skycoin`. It can contain the following characters: `A-Za-z0-9\-_+`. +// * `$VERSION` must be a valid [semver](http://semver.org/) version, e.g. `1.2.3` or `1.2.3-rc1`. +// Semver has the option of including build metadata such as the git commit hash, but this is not included by the default client. +// * `$REMARK` is optional. If not present, the enclosing brackets `()` should be omitted. +// It can contain the following characters: `A-Za-z0-9\-_+;:!$%,.=?~ ` (includes the space character). +package useragent + +import ( + "encoding/json" + "errors" + "fmt" + "regexp" + "strings" + + "github.com/blang/semver" +) + +const ( + // IllegalChars are printable ascii characters forbidden from a user agent string. All other ascii or bytes are also forbidden. + IllegalChars = `<>&"'#@|{}` + "`" + // MaxLen the maximum length of a user agent + MaxLen = 256 + + // NamePattern is the regex pattern for the name portion of the user agent + NamePattern = `[A-Za-z0-9\-_+]+` + // VersionPattern is the regex pattern for the version portion of the user agent + VersionPattern = `[0-9]+\.[0-9]+\.[0-9][A-Za-z0-9\-.+]*` + // RemarkPattern is the regex pattern for the remark portion of the user agent + RemarkPattern = `[A-Za-z0-9\-_+;:!$%,.=?~ ]+` + + // Pattern is the regex pattern for the user agent in entirety + Pattern = `^(` + NamePattern + `):(` + VersionPattern + `)(\(` + RemarkPattern + `\))?$` +) + +var ( + illegalCharsSanitizeRe *regexp.Regexp + illegalCharsCheckRe *regexp.Regexp + re *regexp.Regexp + + // ErrIllegalChars user agent contains illegal characters + ErrIllegalChars = errors.New("User agent has invalid character(s)") + // ErrTooLong user agent exceeds a certain max length + ErrTooLong = errors.New("User agent is too long") + // ErrMalformed user agent does not match the user agent pattern + ErrMalformed = errors.New("User agent is malformed") + // ErrEmpty user agent is an empty string + ErrEmpty = errors.New("User agent is an empty string") +) + +func init() { + illegalCharsSanitizeRe = regexp.MustCompile(fmt.Sprintf("([^[:print:]]|[%s])+", IllegalChars)) + illegalCharsCheckRe = regexp.MustCompile(fmt.Sprintf("[^[:print:]]|[%s]", IllegalChars)) + re = regexp.MustCompile(Pattern) +} + +// Data holds parsed user agent data +type Data struct { + Coin string + Version string + Remark string +} + +// Empty returns true if the Data is empty +func (d Data) Empty() bool { + return d == (Data{}) +} + +// Build builds a user agent string. Returns an error if the user agent would be invalid. +func (d Data) Build() (string, error) { + if d.Coin == "" { + return "", errors.New("missing coin name") + } + if d.Version == "" { + return "", errors.New("missing version") + } + + _, err := semver.Parse(d.Version) + if err != nil { + return "", err + } + + s := d.build() + + if err := validate(s); err != nil { + return "", err + } + + d2, err := Parse(s) + if err != nil { + return "", fmt.Errorf("Built a user agent that fails to parse: %q %v", s, err) + } + + if d2 != d { + return "", errors.New("Built a user agent that does not parse to the original format") + } + + return s, nil +} + +// MustBuild calls Build and panics on error +func (d Data) MustBuild() string { + s, err := d.Build() + if err != nil { + panic(err) + } + return s +} + +func (d Data) build() string { + if d.Coin == "" || d.Version == "" { + return "" + } + + remark := d.Remark + if remark != "" { + remark = fmt.Sprintf("(%s)", remark) + } + + return fmt.Sprintf("%s:%s%s", d.Coin, d.Version, remark) +} + +// MarshalJSON marshals Data as JSON +func (d Data) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, d.build())), nil +} + +// UnmarshalJSON unmarshals []byte to Data +func (d *Data) UnmarshalJSON(v []byte) error { + var s string + if err := json.Unmarshal(v, &s); err != nil { + return err + } + + if s == "" { + return nil + } + + parsed, err := Parse(s) + if err != nil { + return err + } + + *d = parsed + return nil +} + +// Parse parses a user agent string to Data +func Parse(userAgent string) (Data, error) { + if len(userAgent) == 0 { + return Data{}, ErrEmpty + } + + if err := validate(userAgent); err != nil { + return Data{}, err + } + + subs := re.FindAllStringSubmatch(userAgent, -1) + + if len(subs) == 0 { + return Data{}, ErrMalformed + } + + m := subs[0] + + if m[0] != userAgent { + // This should not occur since the pattern has ^$ boundaries applied, but just in case + return Data{}, errors.New("User agent did not match pattern completely") + } + + coin := m[1] + version := m[2] + remark := m[3] + + if _, err := semver.Parse(version); err != nil { + return Data{}, fmt.Errorf("User agent version is not a valid semver: %v", err) + } + + remark = strings.TrimPrefix(remark, "(") + remark = strings.TrimSuffix(remark, ")") + + return Data{ + Coin: coin, + Version: version, + Remark: remark, + }, nil +} + +// MustParse parses and panics on error +func MustParse(userAgent string) Data { + d, err := Parse(userAgent) + if err != nil { + panic(err) + } + + return d +} + +// validate validates a user agent string. The user agent must not contain illegal characters. +func validate(userAgent string) error { + if len(userAgent) > MaxLen { + return ErrTooLong + } + + if illegalCharsCheckRe.MatchString(userAgent) { + return ErrIllegalChars + } + + return nil +} + +// Sanitize removes illegal characters from a user agent string +func Sanitize(userAgent string) string { + return illegalCharsSanitizeRe.ReplaceAllLiteralString(userAgent, "") +} diff --git a/src/util/useragent/useragent_test.go b/src/util/useragent/useragent_test.go new file mode 100644 index 0000000000..7eb24087f7 --- /dev/null +++ b/src/util/useragent/useragent_test.go @@ -0,0 +1,250 @@ +package useragent + +import ( + "encoding/json" + "errors" + "fmt" + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestDataBuild(t *testing.T) { + cases := []struct { + name string + data Data + userAgent string + err error + }{ + { + name: "without remark", + userAgent: "Skycoin:0.24.1", + data: Data{ + Coin: "Skycoin", + Version: "0.24.1", + }, + }, + { + name: "with remark", + userAgent: "Skycoin:0.24.1(remark; foo)", + data: Data{ + Coin: "Skycoin", + Version: "0.24.1", + Remark: "remark; foo", + }, + }, + { + name: "invalid characters in coin", + data: Data{ + Coin: "foo<>", + Version: "0.24.1", + }, + err: ErrIllegalChars, + }, + { + name: "invalid characters in version", + data: Data{ + Coin: "foo", + Version: "<0.24.1", + }, + err: errors.New(`Invalid character(s) found in major number "<0"`), + }, + { + name: "invalid characters in remark", + data: Data{ + Coin: "foo", + Version: "0.24.1", + Remark: "<>", + }, + err: ErrIllegalChars, + }, + { + name: "missing coin", + data: Data{ + Version: "0.24.1", + }, + err: errors.New("missing coin name"), + }, + { + name: "missing version", + data: Data{ + Coin: "Skycoin", + }, + err: errors.New("missing version"), + }, + { + name: "version is not valid semver", + data: Data{ + Coin: "Skycoin", + Version: "0.24", + }, + err: errors.New("No Major.Minor.Patch elements found"), + }, + { + name: "invalid remark", + data: Data{ + Coin: "skycoin", + Version: "0.24.1", + Remark: "\t", + }, + err: ErrIllegalChars, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + userAgent, err := tc.data.Build() + + if tc.err != nil { + require.Equal(t, tc.err, err) + return + } + + require.NoError(t, err) + require.Equal(t, tc.userAgent, userAgent) + }) + } +} + +func TestParse(t *testing.T) { + cases := []struct { + name string + userAgent string + data Data + err error + }{ + { + name: "too long", + userAgent: fmt.Sprintf("skycoin:0.24.1[abcdefg](%s)", strings.Repeat("a", 245)), + err: ErrTooLong, + }, + { + name: "no tab chars allowed", + userAgent: "skycoin:0.24.1(\t)", + err: ErrIllegalChars, + }, + { + name: "no newlines allowed", + userAgent: "skycoin:0.24.1(\n)", + err: ErrIllegalChars, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + d, err := Parse(tc.userAgent) + if tc.err != nil { + require.Equal(t, tc.err, err) + return + } + + require.NoError(t, err) + require.Equal(t, tc.data, d) + }) + } +} + +func TestDataJSON(t *testing.T) { + d := Data{} + + x, err := json.Marshal(d) + require.NoError(t, err) + require.Equal(t, `""`, string(x)) + + d.Coin = "skycoin" + d.Version = "0.25.0-rc1" + + x, err = json.Marshal(d) + require.NoError(t, err) + require.Equal(t, `"skycoin:0.25.0-rc1"`, string(x)) + + var e Data + err = json.Unmarshal([]byte(x), &e) + require.NoError(t, err) + require.Equal(t, d, e) + + d.Remark = "foo; bar" + + x, err = json.Marshal(d) + require.NoError(t, err) + require.Equal(t, `"skycoin:0.25.0-rc1(foo; bar)"`, string(x)) + + e = Data{} + err = json.Unmarshal([]byte(x), &e) + require.NoError(t, err) + require.Equal(t, d, e) + + // Fails, does not parse to a string + err = json.Unmarshal([]byte("{}"), &e) + require.Error(t, err) + + // OK, empty string + e = Data{} + err = json.Unmarshal([]byte(`""`), &e) + require.NoError(t, err) + require.Equal(t, Data{}, e) + + // Fails, does not parse + err = json.Unmarshal([]byte(`"skycoin:0.24.1(<>)"`), &e) + require.Equal(t, ErrIllegalChars, err) +} + +func TestSanitize(t *testing.T) { + for i := 0; i < len(IllegalChars); i++ { + x := "t" + IllegalChars[i:i+1] + t.Run(x, func(t *testing.T) { + require.Equal(t, "t", Sanitize(x)) + }) + } + + for i := 0; i < 256; i++ { + j := byte(i) + if j >= ' ' || j <= '~' { + continue + } + + v := []byte{'t', j} + + t.Run(fmt.Sprintf("%q", j), func(t *testing.T) { + require.Equal(t, "t", Sanitize(string(v))) + }) + } + + z := "dog\t\t\t\ncat\x01t\xE3\xE4t" + require.Equal(t, "dogcattt", Sanitize(z)) + + // Should not have anything stripped + x := "Skycoin:0.24.1(foo; bar)" + require.Equal(t, x, Sanitize(x)) +} + +func TestEmpty(t *testing.T) { + var d Data + require.True(t, d.Empty()) + + d.Coin = "skycoin" + d.Version = "0.24.1" + require.False(t, d.Empty()) +} + +func TestMustParse(t *testing.T) { + d := MustParse("skycoin:0.25.0") + require.Equal(t, Data{ + Coin: "skycoin", + Version: "0.25.0", + }, d) + + require.Panics(t, func() { + MustParse("foo") // nolint: errcheck + }) +} + +func TestMustBuild(t *testing.T) { + d := Data{ + Version: "0", + } + require.Panics(t, func() { + d.MustBuild() // nolint: errcheck + }) +} diff --git a/src/visor/blockchain.go b/src/visor/blockchain.go index 08729322ae..1568aa4ead 100644 --- a/src/visor/blockchain.go +++ b/src/visor/blockchain.go @@ -103,7 +103,7 @@ func CreateBuckets(db *dbutil.DB) error { // BlockchainConfig configures Blockchain options type BlockchainConfig struct { - // Arbitrating mode: if in arbitrating mode, when master node execute blocks, + // Arbitrating mode: if in arbitrating mode, when block publishing node execute blocks, // the invalid transaction will be skipped and continue the next; otherwise, // node will throw the error and return. Arbitrating bool @@ -397,7 +397,7 @@ func (bc Blockchain) VerifySingleTxnHardConstraints(tx *dbutil.Tx, txn coin.Tran // VerifySingleTxnSoftHardConstraints checks that the transaction does not violate hard or soft constraints, // for transactions that are not included in a block. // Hard constraints are checked before soft constraints. -func (bc Blockchain) VerifySingleTxnSoftHardConstraints(tx *dbutil.Tx, txn coin.Transaction, maxSize int) error { +func (bc Blockchain) VerifySingleTxnSoftHardConstraints(tx *dbutil.Tx, txn coin.Transaction, maxSize int, burnFactor uint64) error { // NOTE: Unspent().GetArray() returns an error if not all txn.In can be found // This prevents double spends uxIn, err := bc.Unspent().GetArray(tx, txn.In) @@ -415,7 +415,7 @@ func (bc Blockchain) VerifySingleTxnSoftHardConstraints(tx *dbutil.Tx, txn coin. return err } - return VerifySingleTxnSoftConstraints(txn, head.Time(), uxIn, maxSize) + return VerifySingleTxnSoftConstraints(txn, head.Time(), uxIn, maxSize, burnFactor) } func (bc Blockchain) verifySingleTxnHardConstraints(tx *dbutil.Tx, txn coin.Transaction, head *coin.SignedBlock, uxIn coin.UxArray) error { @@ -554,7 +554,9 @@ func (bc Blockchain) processTransactions(tx *dbutil.Tx, txs coin.Transactions) ( // signature indices and duplicate spends within itself if err := bc.VerifyBlockTxnConstraints(tx, txn); err != nil { switch err.(type) { - case ErrTxnViolatesHardConstraint, ErrTxnViolatesSoftConstraint: + case ErrTxnViolatesSoftConstraint: + logger.WithError(err).Panic("bc.VerifyBlockTxnConstraints should not return a ErrTxnViolatesSoftConstraint error") + case ErrTxnViolatesHardConstraint: if bc.cfg.Arbitrating { skip[i] = struct{}{} continue @@ -633,7 +635,7 @@ func (bc Blockchain) processTransactions(tx *dbutil.Tx, txs coin.Transactions) ( // amongst different txns. Duplicate transactions are // caught earlier, when duplicate expected outputs are // checked for, and will not trigger this. - return nil, errors.New("Duplicate transaction") + return nil, errors.New("Unexpected duplicate transaction") } } for a := range s.In { @@ -683,9 +685,9 @@ func (bc Blockchain) TransactionFee(tx *dbutil.Tx, headTime uint64) coin.FeeCalc // VerifySignature checks that BlockSigs state correspond with coin.Blockchain state // and that all signatures are valid. func (bc *Blockchain) VerifySignature(block *coin.SignedBlock) error { - err := cipher.VerifySignature(bc.cfg.Pubkey, block.Sig, block.HashHeader()) + err := block.VerifySignature(bc.cfg.Pubkey) if err != nil { - logger.Errorf("Signature verification failed: %v", err) + logger.Errorf("Blockchain signature verification failed for block %d: %v", block.Head.BkSeq, err) } return err } @@ -711,7 +713,7 @@ func (bc *Blockchain) WalkChain(workers int, f func(*dbutil.Tx, *coin.SignedBloc if err := bc.db.View("WalkChain verify blocks", func(tx *dbutil.Tx) error { for b := range signedBlockC { if err := f(tx, b); err != nil { - // if err := cipher.VerifySignature(bc.cfg.Pubkey, sh.sig, sh.hash); err != nil { + // if err := cipher.VerifyPubKeySignedHash(bc.cfg.Pubkey, sh.sig, sh.hash); err != nil { // logger.Errorf("Signature verification failed: %v", err) select { case errC <- err: diff --git a/src/visor/blockchain_verify_test.go b/src/visor/blockchain_verify_test.go index 6a7b698b84..7e631b8a5e 100644 --- a/src/visor/blockchain_verify_test.go +++ b/src/visor/blockchain_verify_test.go @@ -10,6 +10,7 @@ import ( "github.com/skycoin/skycoin/src/cipher" "github.com/skycoin/skycoin/src/coin" + "github.com/skycoin/skycoin/src/params" "github.com/skycoin/skycoin/src/testutil" "github.com/skycoin/skycoin/src/visor/blockdb" "github.com/skycoin/skycoin/src/visor/dbutil" @@ -149,7 +150,7 @@ func makeTransactionForChain(t *testing.T, tx *dbutil.Tx, bc *Blockchain, ux coi require.Equal(t, len(txn.Sigs), 1) - err = cipher.ChkSig(ux.Body.Address, cipher.AddSHA256(txn.HashInner(), txn.In[0]), txn.Sigs[0]) + err = cipher.VerifyAddressSignedHash(ux.Body.Address, txn.Sigs[0], cipher.AddSHA256(txn.HashInner(), txn.In[0])) require.NoError(t, err) txn.UpdateHeader() @@ -337,20 +338,20 @@ func TestVerifyTransactionSoftHardConstraints(t *testing.T) { toAddr := testutil.MakeAddress() coins := uint64(10e6) - verifySingleTxnSoftHardConstraints := func(txn coin.Transaction, maxBlockSize int) error { + verifySingleTxnSoftHardConstraints := func(txn coin.Transaction, maxBlockSize int, burnFactor uint64) error { return db.View("", func(tx *dbutil.Tx) error { - return bc.VerifySingleTxnSoftHardConstraints(tx, txn, maxBlockSize) + return bc.VerifySingleTxnSoftHardConstraints(tx, txn, maxBlockSize, burnFactor) }) } // create normal spending txn uxs := coin.CreateUnspents(gb.Head, gb.Body.Transactions[0]) txn := makeSpendTx(t, uxs, []cipher.SecKey{genSecret}, toAddr, coins) - err = verifySingleTxnSoftHardConstraints(txn, DefaultMaxBlockSize) + err = verifySingleTxnSoftHardConstraints(txn, params.MaxUserTransactionSize, params.UserBurnFactor) require.NoError(t, err) // Transaction size exceeds maxSize - err = verifySingleTxnSoftHardConstraints(txn, txn.Size()-1) + err = verifySingleTxnSoftHardConstraints(txn, txn.Size()-1, params.UserBurnFactor) requireSoftViolation(t, "Transaction size bigger than max block size", err) // Invalid transaction fee @@ -360,12 +361,12 @@ func TestVerifyTransactionSoftHardConstraints(t *testing.T) { hours += ux.Body.Hours } txn = makeSpendTxWithHoursBurned(t, uxs, []cipher.SecKey{genSecret}, toAddr, coins, 0) - err = verifySingleTxnSoftHardConstraints(txn, DefaultMaxBlockSize) + err = verifySingleTxnSoftHardConstraints(txn, params.MaxUserTransactionSize, params.UserBurnFactor) requireSoftViolation(t, "Transaction has zero coinhour fee", err) // Invalid transaction fee, part 2 txn = makeSpendTxWithHoursBurned(t, uxs, []cipher.SecKey{genSecret}, toAddr, coins, 1) - err = verifySingleTxnSoftHardConstraints(txn, DefaultMaxBlockSize) + err = verifySingleTxnSoftHardConstraints(txn, params.MaxUserTransactionSize, params.UserBurnFactor) requireSoftViolation(t, "Transaction coinhour fee minimum not met", err) // Transaction locking is tested by TestVerifyTransactionIsLocked @@ -373,7 +374,7 @@ func TestVerifyTransactionSoftHardConstraints(t *testing.T) { // Test invalid header hash originInnerHash := txn.InnerHash txn.InnerHash = cipher.SHA256{} - err = verifySingleTxnSoftHardConstraints(txn, DefaultMaxBlockSize) + err = verifySingleTxnSoftHardConstraints(txn, params.MaxUserTransactionSize, params.UserBurnFactor) requireHardViolation(t, "InnerHash does not match computed hash", err) // Set back the originInnerHash @@ -400,7 +401,7 @@ func TestVerifyTransactionSoftHardConstraints(t *testing.T) { require.NoError(t, err) // A UxOut does not exist, it was already spent - err = verifySingleTxnSoftHardConstraints(txn, DefaultMaxBlockSize) + err = verifySingleTxnSoftHardConstraints(txn, params.MaxUserTransactionSize, params.UserBurnFactor) expectedErr := NewErrTxnViolatesHardConstraint(blockdb.NewErrUnspentNotExist(txn.In[0].Hex())) require.Equal(t, expectedErr, err) @@ -409,21 +410,21 @@ func TestVerifyTransactionSoftHardConstraints(t *testing.T) { _, key := cipher.GenerateKeyPair() toAddr2 := testutil.MakeAddress() tx2 := makeSpendTx(t, uxs, []cipher.SecKey{key, key}, toAddr2, 5e6) - err = verifySingleTxnSoftHardConstraints(tx2, DefaultMaxBlockSize) + err = verifySingleTxnSoftHardConstraints(tx2, params.MaxUserTransactionSize, params.UserBurnFactor) requireHardViolation(t, "Signature not valid for output being spent", err) // Create lost coin transaction uxs2 := coin.CreateUnspents(b.Head, txn) toAddr3 := testutil.MakeAddress() lostCoinTx := makeLostCoinTx(coin.UxArray{uxs2[1]}, []cipher.SecKey{genSecret}, toAddr3, 10e5) - err = verifySingleTxnSoftHardConstraints(lostCoinTx, DefaultMaxBlockSize) + err = verifySingleTxnSoftHardConstraints(lostCoinTx, params.MaxUserTransactionSize, params.UserBurnFactor) requireHardViolation(t, "Transactions may not destroy coins", err) // Create transaction with duplicate UxOuts uxs = coin.CreateUnspents(b.Head, txn) toAddr4 := testutil.MakeAddress() dupUxOutTx := makeDuplicateUxOutTx(coin.UxArray{uxs[0]}, []cipher.SecKey{genSecret}, toAddr4, 1e6) - err = verifySingleTxnSoftHardConstraints(dupUxOutTx, DefaultMaxBlockSize) + err = verifySingleTxnSoftHardConstraints(dupUxOutTx, params.MaxUserTransactionSize, params.UserBurnFactor) requireHardViolation(t, "Duplicate output in transaction", err) } @@ -474,7 +475,7 @@ func TestVerifyTxnFeeCoinHoursAdditionFails(t *testing.T) { testutil.RequireError(t, coinHoursErr, "UxOut.CoinHours addition of earned coin hours overflow") // VerifySingleTxnSoftConstraints should fail on this, when trying to calculate the TransactionFee - err = VerifySingleTxnSoftConstraints(txn, head.Time()+1e6, uxIn, DefaultMaxBlockSize) + err = VerifySingleTxnSoftConstraints(txn, head.Time()+1e6, uxIn, params.MaxUserTransactionSize, params.UserBurnFactor) testutil.RequireError(t, err, NewErrTxnViolatesSoftConstraint(coinHoursErr).Error()) // VerifySingleTxnHardConstraints should fail on this, when performing the extra check of @@ -486,7 +487,7 @@ func TestVerifyTxnFeeCoinHoursAdditionFails(t *testing.T) { } func TestVerifyTransactionIsLocked(t *testing.T) { - for _, addr := range GetLockedDistributionAddresses() { + for _, addr := range params.GetLockedDistributionAddresses() { t.Run(fmt.Sprintf("IsLocked: %s", addr), func(t *testing.T) { testVerifyTransactionAddressLocking(t, addr, errors.New("Transaction has locked address inputs")) }) @@ -494,7 +495,7 @@ func TestVerifyTransactionIsLocked(t *testing.T) { } func TestVerifyTransactionIsUnlocked(t *testing.T) { - for _, addr := range GetUnlockedDistributionAddresses() { + for _, addr := range params.GetUnlockedDistributionAddresses() { t.Run(fmt.Sprintf("IsUnlocked: %s", addr), func(t *testing.T) { testVerifyTransactionAddressLocking(t, addr, nil) }) @@ -552,7 +553,7 @@ func testVerifyTransactionAddressLocking(t *testing.T, toAddr string, expectedEr }) require.NoError(t, err) - err = VerifySingleTxnSoftConstraints(txn, head.Time(), uxIn, DefaultMaxBlockSize) + err = VerifySingleTxnSoftConstraints(txn, head.Time(), uxIn, params.MaxUserTransactionSize, params.UserBurnFactor) if expectedErr == nil { require.NoError(t, err) } else { diff --git a/src/visor/blockdb/blocksigs.go b/src/visor/blockdb/blocksigs.go index efac4e010d..eebd108cb1 100644 --- a/src/visor/blockdb/blocksigs.go +++ b/src/visor/blockdb/blocksigs.go @@ -12,14 +12,14 @@ var ( ) // blockSigs manages known blockSigs as received. -// TODO -- support out of order blocks. This requires a change to the +// TODO -- support out of order blocks. This requires a change to the // message protocol to support ranges similar to bitcoin's locator hashes. // We also need to keep track of whether a block has been executed so that // as continuity is established we can execute chains of blocks. // TODO -- Since we will need to hold blocks that cannot be verified // immediately against the blockchain, we need to be able to hold multiple -// blockSigs per BkSeq, or use hashes as keys. For now, this is not a -// problem assuming the signed blocks created from master are valid blocks, +// blockSigs per BkSeq, or use hashes as keys. For now, this is not a +// problem assuming the signed blocks created by a block publisher are valid blocks, // because we can check the signature independently of the blockchain. type blockSigs struct{} diff --git a/src/visor/daemon_visor_test.go b/src/visor/daemon_visor_test.go index ead9e576ca..1fddbbb119 100644 --- a/src/visor/daemon_visor_test.go +++ b/src/visor/daemon_visor_test.go @@ -25,7 +25,7 @@ import ( ) func setupSimpleVisor(t *testing.T, db *dbutil.DB, bc *Blockchain) *Visor { - cfg := NewVisorConfig() + cfg := NewConfig() cfg.DBPath = db.Path() pool, err := NewUnconfirmedTransactionPool(db) @@ -60,7 +60,7 @@ func TestVerifyTransactionInvalidFee(t *testing.T) { // Setup a minimal visor v := setupSimpleVisor(t, db, bc) - _, softErr, err := v.InjectTransaction(txn) + _, softErr, err := v.InjectForeignTransaction(txn) require.NoError(t, err) require.NotNil(t, softErr) require.Equal(t, NewErrTxnViolatesSoftConstraint(fee.ErrTxnNoFee), *softErr) @@ -90,7 +90,7 @@ func TestVerifyTransactionInvalidSignature(t *testing.T) { // Setup a minimal visor v := setupSimpleVisor(t, db, bc) - _, softErr, err := v.InjectTransaction(txn) + _, softErr, err := v.InjectForeignTransaction(txn) require.Nil(t, softErr) testutil.RequireError(t, err, NewErrTxnViolatesHardConstraint(errors.New("Invalid number of signatures")).Error()) } @@ -120,7 +120,7 @@ func TestInjectValidTransaction(t *testing.T) { require.Len(t, txns, 0) // Call injectTransaction - _, softErr, err := v.InjectTransaction(txn) + _, softErr, err := v.InjectForeignTransaction(txn) require.Nil(t, softErr) require.NoError(t, err) @@ -156,7 +156,7 @@ func TestInjectTransactionSoftViolationNoFee(t *testing.T) { require.Len(t, txns, 0) // Call injectTransaction - _, softErr, err := v.InjectTransaction(txn) + _, softErr, err := v.InjectForeignTransaction(txn) require.NoError(t, err) require.NotNil(t, softErr) require.Equal(t, NewErrTxnViolatesSoftConstraint(fee.ErrTxnNoFee), *softErr) diff --git a/src/visor/distribution.go b/src/visor/distribution.go index f0817ff377..593815267e 100644 --- a/src/visor/distribution.go +++ b/src/visor/distribution.go @@ -2,60 +2,12 @@ package visor import ( "github.com/skycoin/skycoin/src/coin" + "github.com/skycoin/skycoin/src/params" ) -// Note: parameters.go contains many constants used in this file -// they are the ones generated from the fiber config file. - -func init() { - if MaxCoinSupply%DistributionAddressesTotal != 0 { - panic("MaxCoinSupply should be perfectly divisible by DistributionAddressesTotal") - } -} - -// GetDistributionAddresses returns a copy of the hardcoded distribution addresses array. -// Each address has 1,000,000 coins. There are 100 addresses. -func GetDistributionAddresses() []string { - addrs := make([]string, len(distributionAddresses)) - for i := range distributionAddresses { - addrs[i] = distributionAddresses[i] - } - return addrs -} - -// GetUnlockedDistributionAddresses returns distribution addresses that are unlocked, i.e. they have spendable outputs -func GetUnlockedDistributionAddresses() []string { - // The first InitialUnlockedCount (25) addresses are unlocked by default. - // Subsequent addresses will be unlocked at a rate of UnlockAddressRate (5) per year, - // after the InitialUnlockedCount (25) addresses have no remaining balance. - // The unlock timer will be enabled manually once the - // InitialUnlockedCount (25) addresses are distributed. - - // NOTE: To have automatic unlocking, transaction verification would have - // to be handled in visor rather than in coin.Transactions.Visor(), because - // the coin package is agnostic to the state of the blockchain and cannot reference it. - // Instead of automatic unlocking, we can hardcode the timestamp at which the first 30% - // is distributed, then compute the unlocked addresses easily here. - - addrs := make([]string, InitialUnlockedCount) - copy(addrs[:], distributionAddresses[:InitialUnlockedCount]) - return addrs -} - -// GetLockedDistributionAddresses returns distribution addresses that are locked, i.e. they have unspendable outputs -func GetLockedDistributionAddresses() []string { - // TODO -- once we reach 30% distribution, we can hardcode the - // initial timestamp for releasing more coins - addrs := make([]string, DistributionAddressesTotal-InitialUnlockedCount) - for i := range distributionAddresses[InitialUnlockedCount:] { - addrs[i] = distributionAddresses[InitialUnlockedCount+uint64(i)] - } - return addrs -} - // TransactionIsLocked returns true if the transaction spends locked outputs func TransactionIsLocked(inUxs coin.UxArray) bool { - lockedAddrs := GetLockedDistributionAddresses() + lockedAddrs := params.GetLockedDistributionAddresses() lockedAddrsMap := make(map[string]struct{}) for _, a := range lockedAddrs { lockedAddrsMap[a] = struct{}{} diff --git a/src/visor/distribution_test.go b/src/visor/distribution_test.go index 28b111dea8..19b15f20f3 100644 --- a/src/visor/distribution_test.go +++ b/src/visor/distribution_test.go @@ -7,58 +7,9 @@ import ( "github.com/skycoin/skycoin/src/cipher" "github.com/skycoin/skycoin/src/coin" + "github.com/skycoin/skycoin/src/params" ) -func TestDistributionAddressArrays(t *testing.T) { - require.Len(t, GetDistributionAddresses(), 100) - - // At the time of this writing, there should be 25 addresses in the - // unlocked pool and 75 in the locked pool. - require.Len(t, GetUnlockedDistributionAddresses(), 25) - require.Len(t, GetLockedDistributionAddresses(), 75) - - all := GetDistributionAddresses() - allMap := make(map[string]struct{}) - for _, a := range all { - // Check no duplicate address in distribution addresses - _, ok := allMap[a] - require.False(t, ok) - allMap[a] = struct{}{} - } - - unlocked := GetUnlockedDistributionAddresses() - unlockedMap := make(map[string]struct{}) - for _, a := range unlocked { - // Check no duplicate address in unlocked addresses - _, ok := unlockedMap[a] - require.False(t, ok) - - // Check unlocked address in set of all addresses - _, ok = allMap[a] - require.True(t, ok) - - unlockedMap[a] = struct{}{} - } - - locked := GetLockedDistributionAddresses() - lockedMap := make(map[string]struct{}) - for _, a := range locked { - // Check no duplicate address in locked addresses - _, ok := lockedMap[a] - require.False(t, ok) - - // Check locked address in set of all addresses - _, ok = allMap[a] - require.True(t, ok) - - // Check locked address not in unlocked addresses - _, ok = unlockedMap[a] - require.False(t, ok) - - lockedMap[a] = struct{}{} - } -} - func TestTransactionIsLocked(t *testing.T) { test := func(addrStr string, expectedIsLocked bool) { addr := cipher.MustDecodeBase58Address(addrStr) @@ -74,11 +25,11 @@ func TestTransactionIsLocked(t *testing.T) { require.Equal(t, expectedIsLocked, isLocked) } - for _, a := range GetLockedDistributionAddresses() { + for _, a := range params.GetLockedDistributionAddresses() { test(a, true) } - for _, a := range GetUnlockedDistributionAddresses() { + for _, a := range params.GetUnlockedDistributionAddresses() { test(a, false) } diff --git a/src/visor/mock_blockchainer_test.go b/src/visor/mock_blockchainer_test.go index 392e549024..fb11740a52 100644 --- a/src/visor/mock_blockchainer_test.go +++ b/src/visor/mock_blockchainer_test.go @@ -341,13 +341,13 @@ func (_m *MockBlockchainer) VerifySingleTxnHardConstraints(tx *dbutil.Tx, txn co return r0 } -// VerifySingleTxnSoftHardConstraints provides a mock function with given fields: tx, txn, maxSize -func (_m *MockBlockchainer) VerifySingleTxnSoftHardConstraints(tx *dbutil.Tx, txn coin.Transaction, maxSize int) error { - ret := _m.Called(tx, txn, maxSize) +// VerifySingleTxnSoftHardConstraints provides a mock function with given fields: tx, txn, maxSize, burnFactor +func (_m *MockBlockchainer) VerifySingleTxnSoftHardConstraints(tx *dbutil.Tx, txn coin.Transaction, maxSize int, burnFactor uint64) error { + ret := _m.Called(tx, txn, maxSize, burnFactor) var r0 error - if rf, ok := ret.Get(0).(func(*dbutil.Tx, coin.Transaction, int) error); ok { - r0 = rf(tx, txn, maxSize) + if rf, ok := ret.Get(0).(func(*dbutil.Tx, coin.Transaction, int, uint64) error); ok { + r0 = rf(tx, txn, maxSize, burnFactor) } else { r0 = ret.Error(0) } diff --git a/src/visor/mock_unconfirmed_txn_pooler_test.go b/src/visor/mock_unconfirmed_transaction_pooler_test.go similarity index 69% rename from src/visor/mock_unconfirmed_txn_pooler_test.go rename to src/visor/mock_unconfirmed_transaction_pooler_test.go index caeb16e018..4dd4079a14 100644 --- a/src/visor/mock_unconfirmed_txn_pooler_test.go +++ b/src/visor/mock_unconfirmed_transaction_pooler_test.go @@ -7,13 +7,13 @@ import coin "github.com/skycoin/skycoin/src/coin" import dbutil "github.com/skycoin/skycoin/src/visor/dbutil" import mock "github.com/stretchr/testify/mock" -// MockUnconfirmedTxnPooler is an autogenerated mock type for the UnconfirmedTxnPooler type -type MockUnconfirmedTxnPooler struct { +// MockUnconfirmedTransactionPooler is an autogenerated mock type for the UnconfirmedTransactionPooler type +type MockUnconfirmedTransactionPooler struct { mock.Mock } // AllRawTransactions provides a mock function with given fields: tx -func (_m *MockUnconfirmedTxnPooler) AllRawTransactions(tx *dbutil.Tx) (coin.Transactions, error) { +func (_m *MockUnconfirmedTransactionPooler) AllRawTransactions(tx *dbutil.Tx) (coin.Transactions, error) { ret := _m.Called(tx) var r0 coin.Transactions @@ -35,8 +35,31 @@ func (_m *MockUnconfirmedTxnPooler) AllRawTransactions(tx *dbutil.Tx) (coin.Tran return r0, r1 } +// FilterKnown provides a mock function with given fields: tx, txns +func (_m *MockUnconfirmedTransactionPooler) FilterKnown(tx *dbutil.Tx, txns []cipher.SHA256) ([]cipher.SHA256, error) { + ret := _m.Called(tx, txns) + + var r0 []cipher.SHA256 + if rf, ok := ret.Get(0).(func(*dbutil.Tx, []cipher.SHA256) []cipher.SHA256); ok { + r0 = rf(tx, txns) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]cipher.SHA256) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(*dbutil.Tx, []cipher.SHA256) error); ok { + r1 = rf(tx, txns) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // ForEach provides a mock function with given fields: tx, f -func (_m *MockUnconfirmedTxnPooler) ForEach(tx *dbutil.Tx, f func(cipher.SHA256, UnconfirmedTransaction) error) error { +func (_m *MockUnconfirmedTransactionPooler) ForEach(tx *dbutil.Tx, f func(cipher.SHA256, UnconfirmedTransaction) error) error { ret := _m.Called(tx, f) var r0 error @@ -50,7 +73,7 @@ func (_m *MockUnconfirmedTxnPooler) ForEach(tx *dbutil.Tx, f func(cipher.SHA256, } // Get provides a mock function with given fields: tx, hash -func (_m *MockUnconfirmedTxnPooler) Get(tx *dbutil.Tx, hash cipher.SHA256) (*UnconfirmedTransaction, error) { +func (_m *MockUnconfirmedTransactionPooler) Get(tx *dbutil.Tx, hash cipher.SHA256) (*UnconfirmedTransaction, error) { ret := _m.Called(tx, hash) var r0 *UnconfirmedTransaction @@ -73,7 +96,7 @@ func (_m *MockUnconfirmedTxnPooler) Get(tx *dbutil.Tx, hash cipher.SHA256) (*Unc } // GetFiltered provides a mock function with given fields: tx, filter -func (_m *MockUnconfirmedTxnPooler) GetFiltered(tx *dbutil.Tx, filter func(UnconfirmedTransaction) bool) ([]UnconfirmedTransaction, error) { +func (_m *MockUnconfirmedTransactionPooler) GetFiltered(tx *dbutil.Tx, filter func(UnconfirmedTransaction) bool) ([]UnconfirmedTransaction, error) { ret := _m.Called(tx, filter) var r0 []UnconfirmedTransaction @@ -96,7 +119,7 @@ func (_m *MockUnconfirmedTxnPooler) GetFiltered(tx *dbutil.Tx, filter func(Uncon } // GetHashes provides a mock function with given fields: tx, filter -func (_m *MockUnconfirmedTxnPooler) GetHashes(tx *dbutil.Tx, filter func(UnconfirmedTransaction) bool) ([]cipher.SHA256, error) { +func (_m *MockUnconfirmedTransactionPooler) GetHashes(tx *dbutil.Tx, filter func(UnconfirmedTransaction) bool) ([]cipher.SHA256, error) { ret := _m.Called(tx, filter) var r0 []cipher.SHA256 @@ -119,7 +142,7 @@ func (_m *MockUnconfirmedTxnPooler) GetHashes(tx *dbutil.Tx, filter func(Unconfi } // GetIncomingOutputs provides a mock function with given fields: tx, bh -func (_m *MockUnconfirmedTxnPooler) GetIncomingOutputs(tx *dbutil.Tx, bh coin.BlockHeader) (coin.UxArray, error) { +func (_m *MockUnconfirmedTransactionPooler) GetIncomingOutputs(tx *dbutil.Tx, bh coin.BlockHeader) (coin.UxArray, error) { ret := _m.Called(tx, bh) var r0 coin.UxArray @@ -142,7 +165,7 @@ func (_m *MockUnconfirmedTxnPooler) GetIncomingOutputs(tx *dbutil.Tx, bh coin.Bl } // GetKnown provides a mock function with given fields: tx, txns -func (_m *MockUnconfirmedTxnPooler) GetKnown(tx *dbutil.Tx, txns []cipher.SHA256) (coin.Transactions, error) { +func (_m *MockUnconfirmedTransactionPooler) GetKnown(tx *dbutil.Tx, txns []cipher.SHA256) (coin.Transactions, error) { ret := _m.Called(tx, txns) var r0 coin.Transactions @@ -164,31 +187,8 @@ func (_m *MockUnconfirmedTxnPooler) GetKnown(tx *dbutil.Tx, txns []cipher.SHA256 return r0, r1 } -// GetUnknown provides a mock function with given fields: tx, txns -func (_m *MockUnconfirmedTxnPooler) GetUnknown(tx *dbutil.Tx, txns []cipher.SHA256) ([]cipher.SHA256, error) { - ret := _m.Called(tx, txns) - - var r0 []cipher.SHA256 - if rf, ok := ret.Get(0).(func(*dbutil.Tx, []cipher.SHA256) []cipher.SHA256); ok { - r0 = rf(tx, txns) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]cipher.SHA256) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(*dbutil.Tx, []cipher.SHA256) error); ok { - r1 = rf(tx, txns) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // GetUnspentsOfAddr provides a mock function with given fields: tx, addr -func (_m *MockUnconfirmedTxnPooler) GetUnspentsOfAddr(tx *dbutil.Tx, addr cipher.Address) (coin.UxArray, error) { +func (_m *MockUnconfirmedTransactionPooler) GetUnspentsOfAddr(tx *dbutil.Tx, addr cipher.Address) (coin.UxArray, error) { ret := _m.Called(tx, addr) var r0 coin.UxArray @@ -210,20 +210,20 @@ func (_m *MockUnconfirmedTxnPooler) GetUnspentsOfAddr(tx *dbutil.Tx, addr cipher return r0, r1 } -// InjectTransaction provides a mock function with given fields: tx, bc, t, maxSize -func (_m *MockUnconfirmedTxnPooler) InjectTransaction(tx *dbutil.Tx, bc Blockchainer, t coin.Transaction, maxSize int) (bool, *ErrTxnViolatesSoftConstraint, error) { - ret := _m.Called(tx, bc, t, maxSize) +// InjectTransaction provides a mock function with given fields: tx, bc, t, maxSize, burnFactor +func (_m *MockUnconfirmedTransactionPooler) InjectTransaction(tx *dbutil.Tx, bc Blockchainer, t coin.Transaction, maxSize int, burnFactor uint64) (bool, *ErrTxnViolatesSoftConstraint, error) { + ret := _m.Called(tx, bc, t, maxSize, burnFactor) var r0 bool - if rf, ok := ret.Get(0).(func(*dbutil.Tx, Blockchainer, coin.Transaction, int) bool); ok { - r0 = rf(tx, bc, t, maxSize) + if rf, ok := ret.Get(0).(func(*dbutil.Tx, Blockchainer, coin.Transaction, int, uint64) bool); ok { + r0 = rf(tx, bc, t, maxSize, burnFactor) } else { r0 = ret.Get(0).(bool) } var r1 *ErrTxnViolatesSoftConstraint - if rf, ok := ret.Get(1).(func(*dbutil.Tx, Blockchainer, coin.Transaction, int) *ErrTxnViolatesSoftConstraint); ok { - r1 = rf(tx, bc, t, maxSize) + if rf, ok := ret.Get(1).(func(*dbutil.Tx, Blockchainer, coin.Transaction, int, uint64) *ErrTxnViolatesSoftConstraint); ok { + r1 = rf(tx, bc, t, maxSize, burnFactor) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*ErrTxnViolatesSoftConstraint) @@ -231,8 +231,8 @@ func (_m *MockUnconfirmedTxnPooler) InjectTransaction(tx *dbutil.Tx, bc Blockcha } var r2 error - if rf, ok := ret.Get(2).(func(*dbutil.Tx, Blockchainer, coin.Transaction, int) error); ok { - r2 = rf(tx, bc, t, maxSize) + if rf, ok := ret.Get(2).(func(*dbutil.Tx, Blockchainer, coin.Transaction, int, uint64) error); ok { + r2 = rf(tx, bc, t, maxSize, burnFactor) } else { r2 = ret.Error(2) } @@ -241,7 +241,7 @@ func (_m *MockUnconfirmedTxnPooler) InjectTransaction(tx *dbutil.Tx, bc Blockcha } // Len provides a mock function with given fields: tx -func (_m *MockUnconfirmedTxnPooler) Len(tx *dbutil.Tx) (uint64, error) { +func (_m *MockUnconfirmedTransactionPooler) Len(tx *dbutil.Tx) (uint64, error) { ret := _m.Called(tx) var r0 uint64 @@ -262,7 +262,7 @@ func (_m *MockUnconfirmedTxnPooler) Len(tx *dbutil.Tx) (uint64, error) { } // RecvOfAddresses provides a mock function with given fields: tx, bh, addrs -func (_m *MockUnconfirmedTxnPooler) RecvOfAddresses(tx *dbutil.Tx, bh coin.BlockHeader, addrs []cipher.Address) (coin.AddressUxOuts, error) { +func (_m *MockUnconfirmedTransactionPooler) RecvOfAddresses(tx *dbutil.Tx, bh coin.BlockHeader, addrs []cipher.Address) (coin.AddressUxOuts, error) { ret := _m.Called(tx, bh, addrs) var r0 coin.AddressUxOuts @@ -284,13 +284,13 @@ func (_m *MockUnconfirmedTxnPooler) RecvOfAddresses(tx *dbutil.Tx, bh coin.Block return r0, r1 } -// Refresh provides a mock function with given fields: tx, bc, maxBlockSize -func (_m *MockUnconfirmedTxnPooler) Refresh(tx *dbutil.Tx, bc Blockchainer, maxBlockSize int) ([]cipher.SHA256, error) { - ret := _m.Called(tx, bc, maxBlockSize) +// Refresh provides a mock function with given fields: tx, bc, maxBlockSize, burnFactor +func (_m *MockUnconfirmedTransactionPooler) Refresh(tx *dbutil.Tx, bc Blockchainer, maxBlockSize int, burnFactor uint64) ([]cipher.SHA256, error) { + ret := _m.Called(tx, bc, maxBlockSize, burnFactor) var r0 []cipher.SHA256 - if rf, ok := ret.Get(0).(func(*dbutil.Tx, Blockchainer, int) []cipher.SHA256); ok { - r0 = rf(tx, bc, maxBlockSize) + if rf, ok := ret.Get(0).(func(*dbutil.Tx, Blockchainer, int, uint64) []cipher.SHA256); ok { + r0 = rf(tx, bc, maxBlockSize, burnFactor) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]cipher.SHA256) @@ -298,8 +298,8 @@ func (_m *MockUnconfirmedTxnPooler) Refresh(tx *dbutil.Tx, bc Blockchainer, maxB } var r1 error - if rf, ok := ret.Get(1).(func(*dbutil.Tx, Blockchainer, int) error); ok { - r1 = rf(tx, bc, maxBlockSize) + if rf, ok := ret.Get(1).(func(*dbutil.Tx, Blockchainer, int, uint64) error); ok { + r1 = rf(tx, bc, maxBlockSize, burnFactor) } else { r1 = ret.Error(1) } @@ -308,7 +308,7 @@ func (_m *MockUnconfirmedTxnPooler) Refresh(tx *dbutil.Tx, bc Blockchainer, maxB } // RemoveInvalid provides a mock function with given fields: tx, bc -func (_m *MockUnconfirmedTxnPooler) RemoveInvalid(tx *dbutil.Tx, bc Blockchainer) ([]cipher.SHA256, error) { +func (_m *MockUnconfirmedTransactionPooler) RemoveInvalid(tx *dbutil.Tx, bc Blockchainer) ([]cipher.SHA256, error) { ret := _m.Called(tx, bc) var r0 []cipher.SHA256 @@ -331,7 +331,7 @@ func (_m *MockUnconfirmedTxnPooler) RemoveInvalid(tx *dbutil.Tx, bc Blockchainer } // RemoveTransactions provides a mock function with given fields: tx, txns -func (_m *MockUnconfirmedTxnPooler) RemoveTransactions(tx *dbutil.Tx, txns []cipher.SHA256) error { +func (_m *MockUnconfirmedTransactionPooler) RemoveTransactions(tx *dbutil.Tx, txns []cipher.SHA256) error { ret := _m.Called(tx, txns) var r0 error @@ -345,7 +345,7 @@ func (_m *MockUnconfirmedTxnPooler) RemoveTransactions(tx *dbutil.Tx, txns []cip } // SetTransactionsAnnounced provides a mock function with given fields: tx, hashes -func (_m *MockUnconfirmedTxnPooler) SetTransactionsAnnounced(tx *dbutil.Tx, hashes map[cipher.SHA256]int64) error { +func (_m *MockUnconfirmedTransactionPooler) SetTransactionsAnnounced(tx *dbutil.Tx, hashes map[cipher.SHA256]int64) error { ret := _m.Called(tx, hashes) var r0 error diff --git a/src/visor/unconfirmed.go b/src/visor/unconfirmed.go index e8e750ce16..d3af6cd9c1 100644 --- a/src/visor/unconfirmed.go +++ b/src/visor/unconfirmed.go @@ -199,10 +199,10 @@ func (utp *UnconfirmedTransactionPool) SetTransactionsAnnounced(tx *dbutil.Tx, h // existed in the pool. // If the transaction violates hard constraints, it is rejected. // Soft constraints violations mark a txn as invalid, but the txn is inserted. The soft violation is returned. -func (utp *UnconfirmedTransactionPool) InjectTransaction(tx *dbutil.Tx, bc Blockchainer, txn coin.Transaction, maxSize int) (bool, *ErrTxnViolatesSoftConstraint, error) { +func (utp *UnconfirmedTransactionPool) InjectTransaction(tx *dbutil.Tx, bc Blockchainer, txn coin.Transaction, maxSize int, burnFactor uint64) (bool, *ErrTxnViolatesSoftConstraint, error) { var isValid int8 = 1 var softErr *ErrTxnViolatesSoftConstraint - if err := bc.VerifySingleTxnSoftHardConstraints(tx, txn, maxSize); err != nil { + if err := bc.VerifySingleTxnSoftHardConstraints(tx, txn, maxSize, burnFactor); err != nil { logger.Warningf("bc.VerifySingleTxnSoftHardConstraints failedĀ for txn %s: %v", txn.TxIDHex(), err) switch err.(type) { case ErrTxnViolatesSoftConstraint: @@ -301,7 +301,7 @@ func (utp *UnconfirmedTransactionPool) RemoveTransactions(tx *dbutil.Tx, txHashe // Refresh checks all unconfirmed txns against the blockchain. // If the transaction becomes invalid it is marked invalid. // If the transaction becomes valid it is marked valid and is returned to the caller. -func (utp *UnconfirmedTransactionPool) Refresh(tx *dbutil.Tx, bc Blockchainer, maxBlockSize int) ([]cipher.SHA256, error) { +func (utp *UnconfirmedTransactionPool) Refresh(tx *dbutil.Tx, bc Blockchainer, maxBlockSize int, burnFactor uint64) ([]cipher.SHA256, error) { utxns, err := utp.txns.getAll(tx) if err != nil { return nil, err @@ -313,7 +313,7 @@ func (utp *UnconfirmedTransactionPool) Refresh(tx *dbutil.Tx, bc Blockchainer, m for _, utxn := range utxns { utxn.Checked = now.UnixNano() - err := bc.VerifySingleTxnSoftHardConstraints(tx, utxn.Transaction, maxBlockSize) + err := bc.VerifySingleTxnSoftHardConstraints(tx, utxn.Transaction, maxBlockSize, burnFactor) switch err.(type) { case ErrTxnViolatesSoftConstraint, ErrTxnViolatesHardConstraint: @@ -365,8 +365,8 @@ func (utp *UnconfirmedTransactionPool) RemoveInvalid(tx *dbutil.Tx, bc Blockchai return removeUtxns, nil } -// GetUnknown returns txn hashes with known ones removed -func (utp *UnconfirmedTransactionPool) GetUnknown(tx *dbutil.Tx, txns []cipher.SHA256) ([]cipher.SHA256, error) { +// FilterKnown returns txn hashes with known ones removed +func (utp *UnconfirmedTransactionPool) FilterKnown(tx *dbutil.Tx, txns []cipher.SHA256) ([]cipher.SHA256, error) { var unknown []cipher.SHA256 for _, h := range txns { @@ -380,7 +380,7 @@ func (utp *UnconfirmedTransactionPool) GetUnknown(tx *dbutil.Tx, txns []cipher.S return unknown, nil } -// GetKnown returns all known coin.Transactions from the pool, given hashes to select +// GetKnown returns all known transactions from the pool, given hashes to select func (utp *UnconfirmedTransactionPool) GetKnown(tx *dbutil.Tx, txns []cipher.SHA256) (coin.Transactions, error) { var known coin.Transactions diff --git a/src/visor/verify.go b/src/visor/verify.go index 0d3f9d5e86..12c555b595 100644 --- a/src/visor/verify.go +++ b/src/visor/verify.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/skycoin/skycoin/src/coin" + "github.com/skycoin/skycoin/src/params" "github.com/skycoin/skycoin/src/util/fee" ) @@ -138,15 +139,15 @@ func (e ErrTxnViolatesUserConstraint) Error() string { // * That the transaction burn enough coin hours (the fee) // * That if that transaction does not spend from a locked distribution address // * That the transaction does not create outputs with a higher decimal precision than is allowed -func VerifySingleTxnSoftConstraints(txn coin.Transaction, headTime uint64, uxIn coin.UxArray, maxSize int) error { - if err := verifyTxnSoftConstraints(txn, headTime, uxIn, maxSize); err != nil { +func VerifySingleTxnSoftConstraints(txn coin.Transaction, headTime uint64, uxIn coin.UxArray, maxSize int, burnFactor uint64) error { + if err := verifyTxnSoftConstraints(txn, headTime, uxIn, maxSize, burnFactor); err != nil { return NewErrTxnViolatesSoftConstraint(err) } return nil } -func verifyTxnSoftConstraints(txn coin.Transaction, headTime uint64, uxIn coin.UxArray, maxSize int) error { +func verifyTxnSoftConstraints(txn coin.Transaction, headTime uint64, uxIn coin.UxArray, maxSize int, burnFactor uint64) error { if txn.Size() > maxSize { return errTxnExceedsMaxBlockSize } @@ -156,7 +157,7 @@ func verifyTxnSoftConstraints(txn coin.Transaction, headTime uint64, uxIn coin.U return err } - if err := fee.VerifyTransactionFee(&txn, f); err != nil { + if err := fee.VerifyTransactionFee(&txn, f, burnFactor); err != nil { return err } @@ -166,7 +167,7 @@ func verifyTxnSoftConstraints(txn coin.Transaction, headTime uint64, uxIn coin.U // Reject transactions that do not conform to decimal restrictions for _, o := range txn.Out { - if err := DropletPrecisionCheck(o.Coins); err != nil { + if err := params.DropletPrecisionCheck(o.Coins); err != nil { return err } } diff --git a/src/visor/visor.go b/src/visor/visor.go index 94b7b6c0f8..60c09818ee 100644 --- a/src/visor/visor.go +++ b/src/visor/visor.go @@ -21,7 +21,7 @@ import ( "github.com/skycoin/skycoin/src/cipher" "github.com/skycoin/skycoin/src/coin" - "github.com/skycoin/skycoin/src/util/droplet" + "github.com/skycoin/skycoin/src/params" "github.com/skycoin/skycoin/src/util/logging" "github.com/skycoin/skycoin/src/util/timeutil" "github.com/skycoin/skycoin/src/visor/blockdb" @@ -32,76 +32,29 @@ import ( var ( logger = logging.MustGetLogger("visor") - - // errInvalidDecimals is returned by DropletPrecisionCheck if a coin amount has an invalid number of decimal places - errInvalidDecimals = errors.New("invalid amount, too many decimal places") - - // maxDropletDivisor represents the modulus divisor when checking droplet precision rules. - // It is computed from MaxDropletPrecision in init() - maxDropletDivisor uint64 ) -// MaxDropletDivisor represents the modulus divisor when checking droplet precision rules. -func MaxDropletDivisor() uint64 { - // The value is wrapped in a getter to make it immutable to external packages - return maxDropletDivisor -} - -func init() { - sanityCheck() - // Compute maxDropletDivisor from precision - maxDropletDivisor = calculateDivisor(MaxDropletPrecision) -} - -func sanityCheck() { - if InitialUnlockedCount > DistributionAddressesTotal { - logger.Panic("unlocked addresses > total distribution addresses") - } - - if uint64(len(distributionAddresses)) != DistributionAddressesTotal { - logger.Panic("available distribution addresses > total allowed distribution addresses") - } - - if DistributionAddressInitialBalance*DistributionAddressesTotal > MaxCoinSupply { - logger.Panic("total balance in distribution addresses > max coin supply") - } -} - -func calculateDivisor(precision uint64) uint64 { - if precision > droplet.Exponent { - logger.Panic("precision must be <= droplet.Exponent") - } - - n := droplet.Exponent - precision - var i uint64 = 1 - for k := uint64(0); k < n; k++ { - i = i * 10 - } - return i -} - -// DropletPrecisionCheck checks if an amount of coins is valid given decimal place restrictions -func DropletPrecisionCheck(amount uint64) error { - if amount%maxDropletDivisor != 0 { - return errInvalidDecimals - } - return nil -} - // Config configuration parameters for the Visor type Config struct { - // Is this the master blockchain - IsMaster bool + // Is this a block publishing node + IsBlockPublisher bool - //Public key of blockchain authority + // Public key of the blockchain BlockchainPubkey cipher.PubKey - //Secret key of blockchain authority (if master) + // Secret key of the blockchain (required if block publisher) BlockchainSeckey cipher.SecKey - // Maximum size of a block, in bytes. + // Maximum size of a txn, in bytes for unconfirmed transactions + MaxUnconfirmedTransactionSize int + // Maximum size of a block, in bytes for creating blocks MaxBlockSize int + // Burn factor to apply to unconfirmed transactions (received over the network, or when refreshing the pool) + UnconfirmedBurnFactor uint64 + // Burn factor to apply when creating blocks + CreateBlockBurnFactor uint64 + // Where the blockchain is saved BlockchainFile string // Where the block signatures are saved @@ -129,17 +82,18 @@ type Config struct { WalletCryptoType wallet.CryptoType } -// NewVisorConfig put cap on block size, not on transactions/block -//Skycoin transactions are smaller than Bitcoin transactions so skycoin has -//a higher transactions per second for the same block size -func NewVisorConfig() Config { +// NewConfig creates Config +func NewConfig() Config { c := Config{ - IsMaster: false, + IsBlockPublisher: false, BlockchainPubkey: cipher.PubKey{}, BlockchainSeckey: cipher.SecKey{}, - MaxBlockSize: DefaultMaxBlockSize, + MaxUnconfirmedTransactionSize: params.MaxUserTransactionSize, + MaxBlockSize: params.MaxUserTransactionSize, + UnconfirmedBurnFactor: params.UserBurnFactor, + CreateBlockBurnFactor: params.UserBurnFactor, GenesisAddress: cipher.Address{}, GenesisSignature: cipher.Sig{}, @@ -152,19 +106,38 @@ func NewVisorConfig() Config { // Verify verifies the configuration func (c Config) Verify() error { - if c.IsMaster { + if c.IsBlockPublisher { if c.BlockchainPubkey != cipher.MustPubKeyFromSecKey(c.BlockchainSeckey) { - return errors.New("Cannot run in master: invalid seckey for pubkey") + return errors.New("Cannot run as block publisher: invalid seckey for pubkey") } } + if c.UnconfirmedBurnFactor < params.UserBurnFactor { + return fmt.Errorf("UnconfirmedBurnFactor must be >= params.UserBurnFactor (%d)", params.UserBurnFactor) + } + + if c.CreateBlockBurnFactor < params.UserBurnFactor { + return fmt.Errorf("CreateBlockBurnFactor must be >= params.UserBurnFactor (%d)", params.UserBurnFactor) + } + + if c.MaxBlockSize <= 0 { + return errors.New("MaxBlockSize must be > 0") + } + + if c.MaxUnconfirmedTransactionSize <= 0 { + return errors.New("UnconfirmedBlockMaxBlockSize must be > 0") + } + if params.MaxUserTransactionSize > c.MaxBlockSize { + return fmt.Errorf("params.MaxUserTransactionSize must be < MaxBlockSize (%d)", c.MaxBlockSize) + } + return nil } //go:generate go install //go:generate mockery -name Historyer -case underscore -inpkg -testonly //go:generate mockery -name Blockchainer -case underscore -inpkg -testonly -//go:generate mockery -name UnconfirmedTxnPooler -case underscore -inpkg -testonly +//go:generate mockery -name UnconfirmedTransactionPooler -case underscore -inpkg -testonly // Historyer is the interface that provides methods for accessing history data that are parsed from blockchain. type Historyer interface { @@ -196,20 +169,20 @@ type Blockchainer interface { ExecuteBlock(tx *dbutil.Tx, sb *coin.SignedBlock) error VerifyBlockTxnConstraints(tx *dbutil.Tx, txn coin.Transaction) error VerifySingleTxnHardConstraints(tx *dbutil.Tx, txn coin.Transaction) error - VerifySingleTxnSoftHardConstraints(tx *dbutil.Tx, txn coin.Transaction, maxSize int) error + VerifySingleTxnSoftHardConstraints(tx *dbutil.Tx, txn coin.Transaction, maxSize int, burnFactor uint64) error TransactionFee(tx *dbutil.Tx, hours uint64) coin.FeeCalculator } -// UnconfirmedTxnPooler is the interface that provides methods for +// UnconfirmedTransactionPooler is the interface that provides methods for // accessing the unconfirmed transaction pool -type UnconfirmedTxnPooler interface { +type UnconfirmedTransactionPooler interface { SetTransactionsAnnounced(tx *dbutil.Tx, hashes map[cipher.SHA256]int64) error - InjectTransaction(tx *dbutil.Tx, bc Blockchainer, t coin.Transaction, maxSize int) (bool, *ErrTxnViolatesSoftConstraint, error) + InjectTransaction(tx *dbutil.Tx, bc Blockchainer, t coin.Transaction, maxSize int, burnFactor uint64) (bool, *ErrTxnViolatesSoftConstraint, error) AllRawTransactions(tx *dbutil.Tx) (coin.Transactions, error) RemoveTransactions(tx *dbutil.Tx, txns []cipher.SHA256) error - Refresh(tx *dbutil.Tx, bc Blockchainer, maxBlockSize int) ([]cipher.SHA256, error) + Refresh(tx *dbutil.Tx, bc Blockchainer, maxBlockSize int, burnFactor uint64) ([]cipher.SHA256, error) RemoveInvalid(tx *dbutil.Tx, bc Blockchainer) ([]cipher.SHA256, error) - GetUnknown(tx *dbutil.Tx, txns []cipher.SHA256) ([]cipher.SHA256, error) + FilterKnown(tx *dbutil.Tx, txns []cipher.SHA256) ([]cipher.SHA256, error) GetKnown(tx *dbutil.Tx, txns []cipher.SHA256) (coin.Transactions, error) RecvOfAddresses(tx *dbutil.Tx, bh coin.BlockHeader, addrs []cipher.Address) (coin.AddressUxOuts, error) GetIncomingOutputs(tx *dbutil.Tx, bh coin.BlockHeader) (coin.UxArray, error) @@ -221,11 +194,11 @@ type UnconfirmedTxnPooler interface { Len(tx *dbutil.Tx) (uint64, error) } -// Visor manages the Blockchain as both a Master and a Normal +// Visor manages the blockchain type Visor struct { Config Config DB *dbutil.DB - Unconfirmed UnconfirmedTxnPooler + Unconfirmed UnconfirmedTransactionPooler Blockchain Blockchainer Wallets *wallet.Service StartedAt time.Time @@ -236,14 +209,19 @@ type Visor struct { // NewVisor creates a Visor for managing the blockchain database func NewVisor(c Config, db *dbutil.DB) (*Visor, error) { logger.Info("Creating new visor") - if c.IsMaster { - logger.Info("Visor is master") + if c.IsBlockPublisher { + logger.Info("Visor running in block publisher mode") } if err := c.Verify(); err != nil { return nil, err } + logger.Infof("Max block size is %d", c.MaxBlockSize) + logger.Infof("Max unconfirmed transaction size is %d", c.MaxUnconfirmedTransactionSize) + logger.Infof("Coinhour burn factor for verifying unconfirmed transactions is %d", c.UnconfirmedBurnFactor) + logger.Infof("Coinhour burn factor for transactions when creating blocks is %d", c.CreateBlockBurnFactor) + // Loads wallet wltServConfig := wallet.Config{ WalletDir: c.WalletDirectory, @@ -410,7 +388,7 @@ func (vs *Visor) maybeCreateGenesisBlock(tx *dbutil.Tx) error { var sb coin.SignedBlock // record the signature of genesis block - if vs.Config.IsMaster { + if vs.Config.IsBlockPublisher { sb = vs.signBlock(*b) logger.Infof("Genesis block signature=%s", sb.Sig.Hex()) } else { @@ -438,7 +416,7 @@ func (vs *Visor) RefreshUnconfirmed() ([]cipher.SHA256, error) { var hashes []cipher.SHA256 if err := vs.DB.Update("RefreshUnconfirmed", func(tx *dbutil.Tx) error { var err error - hashes, err = vs.Unconfirmed.Refresh(tx, vs.Blockchain, vs.Config.MaxBlockSize) + hashes, err = vs.Unconfirmed.Refresh(tx, vs.Blockchain, vs.Config.MaxUnconfirmedTransactionSize, vs.Config.UnconfirmedBurnFactor) return err }); err != nil { return nil, err @@ -465,8 +443,8 @@ func (vs *Visor) RemoveInvalidUnconfirmed() ([]cipher.SHA256, error) { // CreateBlock creates a SignedBlock from pending transactions func (vs *Visor) createBlock(tx *dbutil.Tx, when uint64) (coin.SignedBlock, error) { - if !vs.Config.IsMaster { - logger.Panic("Only master chain can create blocks") + if !vs.Config.IsBlockPublisher { + logger.Panic("Only a block publisher node can create blocks") } // Gather all unconfirmed transactions @@ -484,7 +462,7 @@ func (vs *Visor) createBlock(tx *dbutil.Tx, when uint64) (coin.SignedBlock, erro // Filter transactions that violate all constraints var filteredTxns coin.Transactions for _, txn := range txns { - if err := vs.Blockchain.VerifySingleTxnSoftHardConstraints(tx, txn, vs.Config.MaxBlockSize); err != nil { + if err := vs.Blockchain.VerifySingleTxnSoftHardConstraints(tx, txn, vs.Config.MaxBlockSize, vs.Config.CreateBlockBurnFactor); err != nil { switch err.(type) { case ErrTxnViolatesHardConstraint, ErrTxnViolatesSoftConstraint: logger.Warningf("Transaction %s violates constraints: %v", txn.TxIDHex(), err) @@ -552,7 +530,7 @@ func (vs *Visor) CreateAndExecuteBlock() (coin.SignedBlock, error) { } // ExecuteSignedBlock adds a block to the blockchain, or returns error. -// Blocks must be executed in sequence, and be signed by the master server +// Blocks must be executed in sequence, and be signed by a block publisher node func (vs *Visor) ExecuteSignedBlock(b coin.SignedBlock) error { return vs.DB.Update("ExecuteSignedBlock", func(tx *dbutil.Tx) error { return vs.executeSignedBlock(tx, b) @@ -560,7 +538,7 @@ func (vs *Visor) ExecuteSignedBlock(b coin.SignedBlock) error { } // executeSignedBlock adds a block to the blockchain, or returns error. -// Blocks must be executed in sequence, and be signed by the master server +// Blocks must be executed in sequence, and be signed by a block publisher node func (vs *Visor) executeSignedBlock(tx *dbutil.Tx, b coin.SignedBlock) error { if err := b.VerifySignature(vs.Config.BlockchainPubkey); err != nil { return err @@ -584,10 +562,10 @@ func (vs *Visor) executeSignedBlock(tx *dbutil.Tx, b coin.SignedBlock) error { return vs.history.ParseBlock(tx, b.Block) } -// signBlock signs a block for master. Will panic if anything is invalid +// signBlock signs a block for a block publisher node. Will panic if anything is invalid func (vs *Visor) signBlock(b coin.Block) coin.SignedBlock { - if !vs.Config.IsMaster { - logger.Panic("Only master chain can sign blocks") + if !vs.Config.IsBlockPublisher { + logger.Panic("Only a block publisher node can sign blocks") } sig := cipher.MustSignHash(b.HashHeader(), vs.Config.BlockchainSeckey) @@ -915,18 +893,19 @@ func (vs *Visor) getBlocksVerbose(tx *dbutil.Tx, getBlocks func(*dbutil.Tx) ([]c return blocks, inputs, nil } -// InjectTransaction records a coin.Transaction to the UnconfirmedTransactionPool if the txn is not +// InjectForeignTransaction records a coin.Transaction to the UnconfirmedTransactionPool if the txn is not // already in the blockchain. // The bool return value is whether or not the transaction was already in the pool. // If the transaction violates hard constraints, it is rejected, and error will not be nil. // If the transaction only violates soft constraints, it is still injected, and the soft constraint violation is returned. -func (vs *Visor) InjectTransaction(txn coin.Transaction) (bool, *ErrTxnViolatesSoftConstraint, error) { +// This method is intended for transactions received over the network. +func (vs *Visor) InjectForeignTransaction(txn coin.Transaction) (bool, *ErrTxnViolatesSoftConstraint, error) { var known bool var softErr *ErrTxnViolatesSoftConstraint - if err := vs.DB.Update("InjectTransaction", func(tx *dbutil.Tx) error { + if err := vs.DB.Update("InjectForeignTransaction", func(tx *dbutil.Tx) error { var err error - known, softErr, err = vs.Unconfirmed.InjectTransaction(tx, vs.Blockchain, txn, vs.Config.MaxBlockSize) + known, softErr, err = vs.Unconfirmed.InjectTransaction(tx, vs.Blockchain, txn, vs.Config.MaxUnconfirmedTransactionSize, vs.Config.UnconfirmedBurnFactor) return err }); err != nil { return false, nil, err @@ -935,16 +914,16 @@ func (vs *Visor) InjectTransaction(txn coin.Transaction) (bool, *ErrTxnViolatesS return known, softErr, nil } -// InjectTransactionStrict records a coin.Transaction to the UnconfirmedTransactionPool if the txn is not +// InjectUserTransaction records a coin.Transaction to the UnconfirmedTransactionPool if the txn is not // already in the blockchain. // The bool return value is whether or not the transaction was already in the pool. // If the transaction violates hard or soft constraints, it is rejected, and error will not be nil. -func (vs *Visor) InjectTransactionStrict(txn coin.Transaction) (bool, error) { +func (vs *Visor) InjectUserTransaction(txn coin.Transaction) (bool, error) { var known bool - if err := vs.DB.Update("InjectTransactionStrict", func(tx *dbutil.Tx) error { + if err := vs.DB.Update("InjectUserTransaction", func(tx *dbutil.Tx) error { var err error - known, err = vs.InjectTransactionStrictTx(tx, txn) + known, err = vs.InjectUserTransactionTx(tx, txn) return err }); err != nil { return false, err @@ -953,23 +932,23 @@ func (vs *Visor) InjectTransactionStrict(txn coin.Transaction) (bool, error) { return known, nil } -// InjectTransactionStrictTx records a coin.Transaction to the UnconfirmedTransactionPool if the txn is not +// InjectUserTransactionTx records a coin.Transaction to the UnconfirmedTransactionPool if the txn is not // already in the blockchain. // The bool return value is whether or not the transaction was already in the pool. // If the transaction violates hard or soft constraints, it is rejected, and error will not be nil. // This method is only exported for use by the daemon gateway's InjectBroadcastTransaction method. -func (vs *Visor) InjectTransactionStrictTx(tx *dbutil.Tx, txn coin.Transaction) (bool, error) { +func (vs *Visor) InjectUserTransactionTx(tx *dbutil.Tx, txn coin.Transaction) (bool, error) { if err := VerifySingleTxnUserConstraints(txn); err != nil { return false, err } - if err := vs.Blockchain.VerifySingleTxnSoftHardConstraints(tx, txn, vs.Config.MaxBlockSize); err != nil { + if err := vs.Blockchain.VerifySingleTxnSoftHardConstraints(tx, txn, params.MaxUserTransactionSize, params.UserBurnFactor); err != nil { return false, err } - known, softErr, err := vs.Unconfirmed.InjectTransaction(tx, vs.Blockchain, txn, vs.Config.MaxBlockSize) + known, softErr, err := vs.Unconfirmed.InjectTransaction(tx, vs.Blockchain, txn, params.MaxUserTransactionSize, params.UserBurnFactor) if softErr != nil { - logger.WithError(softErr).Warning("InjectTransactionStrict vs.Unconfirmed.InjectTransaction returned a softErr unexpectedly") + logger.WithError(softErr).Warning("InjectUserTransaction vs.Unconfirmed.InjectTransaction returned a softErr unexpectedly") } return known, err @@ -1924,13 +1903,13 @@ func (vs *Visor) GetUnconfirmedTxn(hash cipher.SHA256) (*UnconfirmedTransaction, return txn, nil } -// GetUnconfirmedUnknown returns unconfirmed txn hashes with known ones removed -func (vs *Visor) GetUnconfirmedUnknown(txns []cipher.SHA256) ([]cipher.SHA256, error) { +// FilterKnownUnconfirmed returns unconfirmed txn hashes with known ones removed +func (vs *Visor) FilterKnownUnconfirmed(txns []cipher.SHA256) ([]cipher.SHA256, error) { var hashes []cipher.SHA256 - if err := vs.DB.View("GetUnconfirmedUnknown", func(tx *dbutil.Tx) error { + if err := vs.DB.View("FilterKnownUnconfirmed", func(tx *dbutil.Tx) error { var err error - hashes, err = vs.Unconfirmed.GetUnknown(tx, txns) + hashes, err = vs.Unconfirmed.FilterKnown(tx, txns) return err }); err != nil { return nil, err @@ -1939,11 +1918,11 @@ func (vs *Visor) GetUnconfirmedUnknown(txns []cipher.SHA256) ([]cipher.SHA256, e return hashes, nil } -// GetUnconfirmedKnown returns unconfirmed txn hashes with known ones removed -func (vs *Visor) GetUnconfirmedKnown(txns []cipher.SHA256) (coin.Transactions, error) { +// GetKnownUnconfirmed returns unconfirmed txn hashes with known ones removed +func (vs *Visor) GetKnownUnconfirmed(txns []cipher.SHA256) (coin.Transactions, error) { var hashes coin.Transactions - if err := vs.DB.View("GetUnconfirmedKnown", func(tx *dbutil.Tx) error { + if err := vs.DB.View("GetKnownUnconfirmed", func(tx *dbutil.Tx) error { var err error hashes, err = vs.Unconfirmed.GetKnown(tx, txns) return err @@ -2230,7 +2209,7 @@ func (vs *Visor) VerifyTxnVerbose(txn *coin.Transaction) ([]wallet.UxBalance, bo return err } - if err := VerifySingleTxnSoftConstraints(*txn, feeCalcTime, uxa, vs.Config.MaxBlockSize); err != nil { + if err := VerifySingleTxnSoftConstraints(*txn, feeCalcTime, uxa, params.MaxUserTransactionSize, params.UserBurnFactor); err != nil { return err } diff --git a/src/visor/visor_test.go b/src/visor/visor_test.go index 8836275414..40f10669de 100644 --- a/src/visor/visor_test.go +++ b/src/visor/visor_test.go @@ -19,6 +19,7 @@ import ( "github.com/skycoin/skycoin/src/cipher" "github.com/skycoin/skycoin/src/coin" + "github.com/skycoin/skycoin/src/params" "github.com/skycoin/skycoin/src/testutil" _require "github.com/skycoin/skycoin/src/testutil/require" "github.com/skycoin/skycoin/src/util/fee" @@ -275,9 +276,9 @@ func TestVisorCreateBlock(t *testing.T) { his := historydb.New() - cfg := NewVisorConfig() + cfg := NewConfig() cfg.DBPath = db.Path() - cfg.IsMaster = false + cfg.IsBlockPublisher = false cfg.BlockchainPubkey = genPublic cfg.GenesisAddress = genAddress @@ -289,8 +290,8 @@ func TestVisorCreateBlock(t *testing.T) { history: his, } - // CreateBlock panics if called when not master - _require.PanicsWithLogMessage(t, "Only master chain can create blocks", func() { + // CreateBlock panics if called when not a block publisher + _require.PanicsWithLogMessage(t, "Only a block publisher node can create blocks", func() { err := db.Update("", func(tx *dbutil.Tx) error { _, err := v.createBlock(tx, when) return err @@ -298,7 +299,7 @@ func TestVisorCreateBlock(t *testing.T) { require.NoError(t, err) }) - v.Config.IsMaster = true + v.Config.IsBlockPublisher = true v.Config.BlockchainSeckey = genSecret addGenesisBlockToVisor(t, v) @@ -323,13 +324,13 @@ func TestVisorCreateBlock(t *testing.T) { uxs := coin.CreateUnspents(gb.Head, gb.Body.Transactions[0]) nUnspents := 100 - txn := makeUnspentsTx(t, uxs, []cipher.SecKey{genSecret}, genAddress, nUnspents, maxDropletDivisor) + txn := makeUnspentsTx(t, uxs, []cipher.SecKey{genSecret}, genAddress, nUnspents, params.MaxDropletDivisor()) var known bool var softErr *ErrTxnViolatesSoftConstraint err = db.Update("", func(tx *dbutil.Tx) error { var err error - known, softErr, err = unconfirmed.InjectTransaction(tx, bc, txn, v.Config.MaxBlockSize) + known, softErr, err = unconfirmed.InjectTransaction(tx, bc, txn, v.Config.MaxBlockSize, v.Config.CreateBlockBurnFactor) return err }) require.NoError(t, err) @@ -399,7 +400,7 @@ func TestVisorCreateBlock(t *testing.T) { foundInvalidCoins := false for _, txn := range txns { for _, o := range txn.Out { - if err := DropletPrecisionCheck(o.Coins); err != nil { + if err := params.DropletPrecisionCheck(o.Coins); err != nil { foundInvalidCoins = true break } @@ -413,7 +414,7 @@ func TestVisorCreateBlock(t *testing.T) { var softErr *ErrTxnViolatesSoftConstraint err = db.Update("", func(tx *dbutil.Tx) error { var err error - known, softErr, err = unconfirmed.InjectTransaction(tx, bc, txn, v.Config.MaxBlockSize) + known, softErr, err = unconfirmed.InjectTransaction(tx, bc, txn, v.Config.MaxBlockSize, v.Config.CreateBlockBurnFactor) return err }) require.False(t, known) @@ -474,7 +475,7 @@ func TestVisorCreateBlock(t *testing.T) { // Check that decimal rules are enforced for i, txn := range blockTxns { for j, o := range txn.Out { - err := DropletPrecisionCheck(o.Coins) + err := params.DropletPrecisionCheck(o.Coins) require.NoError(t, err, "txout %d.%d coins=%d", i, j, o.Coins) } } @@ -496,9 +497,9 @@ func TestVisorInjectTransaction(t *testing.T) { his := historydb.New() - cfg := NewVisorConfig() + cfg := NewConfig() cfg.DBPath = db.Path() - cfg.IsMaster = false + cfg.IsBlockPublisher = false cfg.BlockchainPubkey = genPublic cfg.GenesisAddress = genAddress @@ -510,8 +511,8 @@ func TestVisorInjectTransaction(t *testing.T) { history: his, } - // CreateBlock panics if called when not master - _require.PanicsWithLogMessage(t, "Only master chain can create blocks", func() { + // CreateBlock panics if called when not a block publisher + _require.PanicsWithLogMessage(t, "Only a block publisher node can create blocks", func() { err := db.Update("", func(tx *dbutil.Tx) error { _, err := v.createBlock(tx, when) return err @@ -519,7 +520,7 @@ func TestVisorInjectTransaction(t *testing.T) { require.NoError(t, err) }) - v.Config.IsMaster = true + v.Config.IsBlockPublisher = true v.Config.BlockchainSeckey = genSecret addGenesisBlockToVisor(t, v) @@ -547,7 +548,7 @@ func TestVisorInjectTransaction(t *testing.T) { // Create a transaction with valid decimal places txn := makeSpendTx(t, uxs, []cipher.SecKey{genSecret}, genAddress, coins) - known, softErr, err := v.InjectTransaction(txn) + known, softErr, err := v.InjectForeignTransaction(txn) require.False(t, known) require.Nil(t, softErr) require.NoError(t, err) @@ -575,7 +576,7 @@ func TestVisorInjectTransaction(t *testing.T) { // Check transactions with overflowing output coins fail txn = makeOverflowCoinsSpendTx(coin.UxArray{uxs[0]}, []cipher.SecKey{genSecret}, toAddr) - _, softErr, err = v.InjectTransaction(txn) + _, softErr, err = v.InjectForeignTransaction(txn) require.IsType(t, ErrTxnViolatesHardConstraint{}, err) testutil.RequireError(t, err.(ErrTxnViolatesHardConstraint).Err, "Output coins overflow") require.Nil(t, softErr) @@ -592,7 +593,7 @@ func TestVisorInjectTransaction(t *testing.T) { // It should not be injected; when injecting a txn, the overflowing output hours is treated // as a hard constraint. It is only a soft constraint when the txn is included in a signed block. txn = makeOverflowHoursSpendTx(coin.UxArray{uxs[0]}, []cipher.SecKey{genSecret}, toAddr) - _, softErr, err = v.InjectTransaction(txn) + _, softErr, err = v.InjectForeignTransaction(txn) require.Nil(t, softErr) require.IsType(t, ErrTxnViolatesHardConstraint{}, err) testutil.RequireError(t, err.(ErrTxnViolatesHardConstraint).Err, "Transaction output hours overflow") @@ -607,11 +608,11 @@ func TestVisorInjectTransaction(t *testing.T) { // Create a transaction with invalid decimal places // It's still injected, because this is considered a soft error - invalidCoins := coins + (maxDropletDivisor / 10) + invalidCoins := coins + (params.MaxDropletDivisor() / 10) txn = makeSpendTx(t, uxs, []cipher.SecKey{genSecret, genSecret}, toAddr, invalidCoins) - _, softErr, err = v.InjectTransaction(txn) + _, softErr, err = v.InjectForeignTransaction(txn) require.NoError(t, err) - testutil.RequireError(t, softErr.Err, errInvalidDecimals.Error()) + testutil.RequireError(t, softErr.Err, params.ErrInvalidDecimals.Error()) err = db.View("", func(tx *dbutil.Tx) error { length, err := unconfirmed.Len(tx) @@ -625,7 +626,7 @@ func TestVisorInjectTransaction(t *testing.T) { uxs = coin.CreateUnspents(gb.Head, gb.Body.Transactions[0]) txn = makeSpendTx(t, uxs, []cipher.SecKey{genSecret}, genAddress, coins) txn.Out[0].Address = cipher.Address{} - known, err = v.InjectTransactionStrict(txn) + known, err = v.InjectUserTransaction(txn) require.False(t, known) require.IsType(t, ErrTxnViolatesUserConstraint{}, err) testutil.RequireError(t, err, "Transaction violates user constraint: Transaction output is sent to the null address") @@ -673,33 +674,6 @@ func makeOverflowHoursSpendTx(uxs coin.UxArray, keys []cipher.SecKey, toAddr cip return spendTx } -func TestVisorCalculatePrecision(t *testing.T) { - cases := []struct { - precision uint64 - divisor uint64 - }{ - {0, 1e6}, - {1, 1e5}, - {2, 1e4}, - {3, 1e3}, - {4, 1e2}, - {5, 1e1}, - {6, 1}, - } - - for _, tc := range cases { - name := fmt.Sprintf("calculateDivisor(%d)=%d", tc.precision, tc.divisor) - t.Run(name, func(t *testing.T) { - divisor := calculateDivisor(tc.precision) - require.Equal(t, tc.divisor, divisor, "%d != %d", tc.divisor, divisor) - }) - } - - _require.PanicsWithLogMessage(t, "precision must be <= droplet.Exponent", func() { - calculateDivisor(7) - }) -} - func makeTestData(t *testing.T, n int) ([]historydb.Transaction, []coin.SignedBlock, []UnconfirmedTransaction, uint64) { // nolint: unparam var txns []historydb.Transaction var blocks []coin.SignedBlock @@ -1946,9 +1920,9 @@ func TestRefreshUnconfirmed(t *testing.T) { his := historydb.New() - cfg := NewVisorConfig() + cfg := NewConfig() cfg.DBPath = db.Path() - cfg.IsMaster = true + cfg.IsBlockPublisher = true cfg.BlockchainSeckey = genSecret cfg.BlockchainPubkey = genPublic cfg.GenesisAddress = genAddress @@ -1978,7 +1952,7 @@ func TestRefreshUnconfirmed(t *testing.T) { // Create a valid transaction that will remain valid validTxn := makeSpendTx(t, uxs, []cipher.SecKey{genSecret}, genAddress, coins) - known, softErr, err := v.InjectTransaction(validTxn) + known, softErr, err := v.InjectForeignTransaction(validTxn) require.False(t, known) require.Nil(t, softErr) require.NoError(t, err) @@ -1994,11 +1968,11 @@ func TestRefreshUnconfirmed(t *testing.T) { // Create a transaction with invalid decimal places // It's still injected, because this is considered a soft error // This transaction will stay invalid on refresh - invalidCoins := coins + (maxDropletDivisor / 10) + invalidCoins := coins + (params.MaxDropletDivisor() / 10) alwaysInvalidTxn := makeSpendTx(t, uxs, []cipher.SecKey{genSecret}, toAddr, invalidCoins) - _, softErr, err = v.InjectTransaction(alwaysInvalidTxn) + _, softErr, err = v.InjectForeignTransaction(alwaysInvalidTxn) require.NoError(t, err) - testutil.RequireError(t, softErr.Err, errInvalidDecimals.Error()) + testutil.RequireError(t, softErr.Err, params.ErrInvalidDecimals.Error()) err = db.View("", func(tx *dbutil.Tx) error { length, err := unconfirmed.Len(tx) @@ -2008,13 +1982,15 @@ func TestRefreshUnconfirmed(t *testing.T) { }) require.NoError(t, err) - // Create a transaction that exceeds MaxBlockSize + // Create a transaction that exceeds MaxUnconfirmedTransactionSize // It's still injected, because this is considered a soft error - // This transaction will become valid on refresh (by increasing MaxBlockSize) - v.Config.MaxBlockSize = 1 + // This transaction will become valid on refresh (by increasing MaxUnconfirmedTransactionSize) + originalMaxUnconfirmedTxnSize := v.Config.MaxUnconfirmedTransactionSize + v.Config.MaxUnconfirmedTransactionSize = 1 sometimesInvalidTxn := makeSpendTx(t, uxs, []cipher.SecKey{genSecret}, toAddr, coins) - _, softErr, err = v.InjectTransaction(sometimesInvalidTxn) + _, softErr, err = v.InjectForeignTransaction(sometimesInvalidTxn) require.NoError(t, err) + require.NotNil(t, softErr) testutil.RequireError(t, softErr.Err, errTxnExceedsMaxBlockSize.Error()) err = db.View("", func(tx *dbutil.Tx) error { @@ -2028,7 +2004,7 @@ func TestRefreshUnconfirmed(t *testing.T) { // The first txn remains valid, // the second txn remains invalid, // the third txn becomes valid - v.Config.MaxBlockSize = DefaultMaxBlockSize + v.Config.MaxUnconfirmedTransactionSize = originalMaxUnconfirmedTxnSize hashes, err := v.RefreshUnconfirmed() require.NoError(t, err) require.Equal(t, []cipher.SHA256{sometimesInvalidTxn.Hash()}, hashes) @@ -2037,7 +2013,7 @@ func TestRefreshUnconfirmed(t *testing.T) { // The first txn becomes invalid, // the second txn remains invalid, // the third txn becomes invalid again - v.Config.MaxBlockSize = 1 + v.Config.MaxUnconfirmedTransactionSize = 1 hashes, err = v.RefreshUnconfirmed() require.NoError(t, err) require.Nil(t, hashes) @@ -2046,7 +2022,7 @@ func TestRefreshUnconfirmed(t *testing.T) { // The first txn was valid, became invalid, and is now valid again // The second txn was always invalid // The third txn was invalid, became valid, became invalid, and is now valid again - v.Config.MaxBlockSize = DefaultMaxBlockSize + v.Config.MaxUnconfirmedTransactionSize = originalMaxUnconfirmedTxnSize hashes, err = v.RefreshUnconfirmed() require.NoError(t, err) @@ -2076,9 +2052,9 @@ func TestRemoveInvalidUnconfirmedDoubleSpendArbitrating(t *testing.T) { his := historydb.New() - cfg := NewVisorConfig() + cfg := NewConfig() cfg.DBPath = db.Path() - cfg.IsMaster = true + cfg.IsBlockPublisher = true cfg.Arbitrating = true cfg.BlockchainPubkey = genPublic cfg.GenesisAddress = genAddress @@ -2111,7 +2087,7 @@ func TestRemoveInvalidUnconfirmedDoubleSpendArbitrating(t *testing.T) { var coins uint64 = 10e6 txn1 := makeSpendTx(t, uxs, []cipher.SecKey{genSecret}, genAddress, coins) - known, softErr, err := v.InjectTransaction(txn1) + known, softErr, err := v.InjectForeignTransaction(txn1) require.False(t, known) require.Nil(t, softErr) require.NoError(t, err) @@ -2126,7 +2102,7 @@ func TestRemoveInvalidUnconfirmedDoubleSpendArbitrating(t *testing.T) { var fee uint64 = 1 txn2 := makeSpendTxWithFee(t, uxs, []cipher.SecKey{genSecret}, genAddress, coins, fee) - known, softErr, err = v.InjectTransaction(txn2) + known, softErr, err = v.InjectForeignTransaction(txn2) require.False(t, known) require.Nil(t, softErr) require.NoError(t, err) @@ -2687,7 +2663,7 @@ func TestGetCreateTransactionAuxs(t *testing.T) { db, shutdown := testutil.PrepareDB(t) defer shutdown() - unconfirmed := &MockUnconfirmedTxnPooler{} + unconfirmed := &MockUnconfirmedTransactionPooler{} bc := &MockBlockchainer{} unspent := &MockUnspentPooler{} require.Implements(t, (*blockdb.UnspentPooler)(nil), unspent) @@ -2890,7 +2866,7 @@ func TestVerifyTxnVerbose(t *testing.T) { balances []wallet.UxBalance err error - maxBlockSize int + maxUserTransactionSize int getArrayRet coin.UxArray getArrayErr error @@ -2973,10 +2949,10 @@ func TestVerifyTxnVerbose(t *testing.T) { getArrayRet: inputs[:1], }, { - name: "transaction violate soft constraints, transaction size bigger than max block size", - maxBlockSize: 1, - txn: txn, - err: ErrTxnViolatesSoftConstraint{errors.New("Transaction size bigger than max block size")}, + name: "transaction violate soft constraints, transaction size bigger than max block size", + maxUserTransactionSize: 1, + txn: txn, + err: ErrTxnViolatesSoftConstraint{errors.New("Transaction size bigger than max block size")}, getArrayRet: inputs[:1], }, @@ -3052,13 +3028,16 @@ func TestVerifyTxnVerbose(t *testing.T) { Blockchain: bc, DB: db, history: history, - Config: Config{ - MaxBlockSize: tc.maxBlockSize, - }, + Config: Config{}, } - if v.Config.MaxBlockSize == 0 { - v.Config.MaxBlockSize = DefaultMaxBlockSize + originalMaxUnconfirmedTxnSize := params.MaxUserTransactionSize + defer func() { + params.MaxUserTransactionSize = originalMaxUnconfirmedTxnSize + }() + + if tc.maxUserTransactionSize != 0 { + params.MaxUserTransactionSize = tc.maxUserTransactionSize } var isConfirmed bool @@ -3100,17 +3079,17 @@ func (h *historyerMock2) ForEachTxn(tx *dbutil.Tx, f func(cipher.SHA256, *histor return nil } -// UnconfirmedTxnPoolerMock2 embeds UnconfirmedTxnPoolerMock, and rewrite the GetFiltered method -type UnconfirmedTxnPoolerMock2 struct { - MockUnconfirmedTxnPooler +// MockUnconfirmedTransactionPooler2 embeds UnconfirmedTxnPoolerMock, and rewrite the GetFiltered method +type MockUnconfirmedTransactionPooler2 struct { + MockUnconfirmedTransactionPooler txns []UnconfirmedTransaction } -func NewUnconfirmedTransactionPoolerMock2() *UnconfirmedTxnPoolerMock2 { - return &UnconfirmedTxnPoolerMock2{} +func NewUnconfirmedTransactionPoolerMock2() *MockUnconfirmedTransactionPooler2 { + return &MockUnconfirmedTransactionPooler2{} } -func (m *UnconfirmedTxnPoolerMock2) GetFiltered(tx *dbutil.Tx, f func(tx UnconfirmedTransaction) bool) ([]UnconfirmedTransaction, error) { +func (m *MockUnconfirmedTransactionPooler2) GetFiltered(tx *dbutil.Tx, f func(tx UnconfirmedTransaction) bool) ([]UnconfirmedTransaction, error) { var txns []UnconfirmedTransaction for i := range m.txns { if f(m.txns[i]) { diff --git a/src/visor/visor_wallet.go b/src/visor/visor_wallet.go index be5e175a6a..62ae0811a2 100644 --- a/src/visor/visor_wallet.go +++ b/src/visor/visor_wallet.go @@ -5,6 +5,7 @@ package visor import ( "github.com/skycoin/skycoin/src/cipher" "github.com/skycoin/skycoin/src/coin" + "github.com/skycoin/skycoin/src/params" "github.com/skycoin/skycoin/src/visor/dbutil" "github.com/skycoin/skycoin/src/wallet" ) @@ -102,16 +103,16 @@ func (vs *Visor) GetWalletUnconfirmedTransactionsVerbose(wltID string) ([]Unconf } // CreateTransaction creates a transaction based upon the parameters in wallet.CreateTransactionParams -func (vs *Visor) CreateTransaction(params wallet.CreateTransactionParams) (*coin.Transaction, []wallet.UxBalance, error) { - if err := params.Validate(); err != nil { +func (vs *Visor) CreateTransaction(p wallet.CreateTransactionParams) (*coin.Transaction, []wallet.UxBalance, error) { + if err := p.Validate(); err != nil { return nil, nil, err } var txn *coin.Transaction var inputs []wallet.UxBalance - if err := vs.Wallets.ViewSecrets(params.Wallet.ID, params.Wallet.Password, func(w *wallet.Wallet) error { - // Get all addresses from the wallet for checking params against + if err := vs.Wallets.ViewSecrets(p.Wallet.ID, p.Wallet.Password, func(w *wallet.Wallet) error { + // Get all addresses from the wallet for checking p against allAddrs, err := w.GetSkycoinAddresses() if err != nil { return err @@ -124,13 +125,13 @@ func (vs *Visor) CreateTransaction(params wallet.CreateTransactionParams) (*coin return err } - auxs, err := vs.getCreateTransactionAuxs(tx, params, allAddrs) + auxs, err := vs.getCreateTransactionAuxs(tx, p, allAddrs) if err != nil { return err } // Create and sign transaction - txn, inputs, err = w.CreateAndSignTransactionAdvanced(params, auxs, head.Time()) + txn, inputs, err = w.CreateAndSignTransactionAdvanced(p, auxs, head.Time()) if err != nil { logger.WithError(err).Error("CreateAndSignTransactionAdvanced failed") return err @@ -144,7 +145,7 @@ func (vs *Visor) CreateTransaction(params wallet.CreateTransactionParams) (*coin return err } - if err := vs.Blockchain.VerifySingleTxnSoftHardConstraints(tx, *txn, vs.Config.MaxBlockSize); err != nil { + if err := vs.Blockchain.VerifySingleTxnSoftHardConstraints(tx, *txn, params.MaxUserTransactionSize, params.UserBurnFactor); err != nil { logger.WithError(err).Error("Created transaction violates transaction constraints") return err } @@ -201,7 +202,7 @@ func (vs *Visor) CreateTransactionDeprecated(wltID string, password []byte, coin return err } - if err := vs.Blockchain.VerifySingleTxnSoftHardConstraints(tx, *txn, vs.Config.MaxBlockSize); err != nil { + if err := vs.Blockchain.VerifySingleTxnSoftHardConstraints(tx, *txn, params.MaxUserTransactionSize, params.UserBurnFactor); err != nil { logger.WithError(err).Error("Created transaction violates transaction constraints") return err } diff --git a/src/wallet/wallet.go b/src/wallet/wallet.go index 9fa7f0231d..4d33c3651c 100644 --- a/src/wallet/wallet.go +++ b/src/wallet/wallet.go @@ -20,6 +20,7 @@ import ( "github.com/skycoin/skycoin/src/cipher" "github.com/skycoin/skycoin/src/coin" + "github.com/skycoin/skycoin/src/params" "github.com/shopspring/decimal" @@ -1196,8 +1197,8 @@ func (w *Wallet) CreateAndSignTransaction(auxs coin.AddressUxOuts, headTime, coi haveChange := changeCoins > 0 changeHours, addrHours, outputHours := DistributeSpendHours(spending.Hours, 1, haveChange) - logger.Infof("wallet.CreateAndSignTransaction: spending.Hours=%d, fee.VerifyTransactionFeeForHours(%d, %d)", spending.Hours, outputHours, spending.Hours-outputHours) - if err := fee.VerifyTransactionFeeForHours(outputHours, spending.Hours-outputHours); err != nil { + logger.Infof("wallet.CreateAndSignTransaction: spending.Hours=%d, fee.VerifyTransactionFeeForHours(%d, %d, %d)", spending.Hours, outputHours, spending.Hours-outputHours, params.UserBurnFactor) + if err := fee.VerifyTransactionFeeForHours(outputHours, spending.Hours-outputHours, params.UserBurnFactor); err != nil { logger.Warningf("wallet.CreateAndSignTransaction: fee.VerifyTransactionFeeForHours failed: %v", err) return nil, err } @@ -1228,13 +1229,13 @@ func (w *Wallet) CreateAndSignTransaction(auxs coin.AddressUxOuts, headTime, coi // if the coinhour cost of adding that output is less than the coinhours that would be lost as change // If receiving hours are not explicitly specified, hours are allocated amongst the receiving outputs proportional to the number of coins being sent to them. // If the change address is not specified, the address whose bytes are lexically sorted first is chosen from the owners of the outputs being spent. -func (w *Wallet) CreateAndSignTransactionAdvanced(params CreateTransactionParams, auxs coin.AddressUxOuts, headTime uint64) (*coin.Transaction, []UxBalance, error) { - if err := params.Validate(); err != nil { +func (w *Wallet) CreateAndSignTransactionAdvanced(p CreateTransactionParams, auxs coin.AddressUxOuts, headTime uint64) (*coin.Transaction, []UxBalance, error) { + if err := p.Validate(); err != nil { return nil, nil, err } - if params.Wallet.ID != w.Filename() { - return nil, nil, NewError(errors.New("params.Wallet.ID does not match wallet")) + if p.Wallet.ID != w.Filename() { + return nil, nil, NewError(errors.New("p.Wallet.ID does not match wallet")) } if w.IsEncrypted() { @@ -1273,7 +1274,7 @@ func (w *Wallet) CreateAndSignTransactionAdvanced(params CreateTransactionParams // calculate total coins and minimum hours to send var totalOutCoins uint64 var requestedHours uint64 - for _, to := range params.To { + for _, to := range p.To { totalOutCoins, err = coin.AddUint64(totalOutCoins, to.Coins) if err != nil { return nil, nil, NewError(fmt.Errorf("total output coins error: %v", err)) @@ -1317,20 +1318,20 @@ func (w *Wallet) CreateAndSignTransactionAdvanced(params CreateTransactionParams txn.PushInput(spend.Hash) } - feeHours := fee.RequiredFee(totalInputHours) + feeHours := fee.RequiredFee(totalInputHours, params.UserBurnFactor) if feeHours == 0 { return nil, nil, fee.ErrTxnNoFee } remainingHours := totalInputHours - feeHours - switch params.HoursSelection.Type { + switch p.HoursSelection.Type { case HoursSelectionTypeManual: - txn.Out = append(txn.Out, params.To...) + txn.Out = append(txn.Out, p.To...) case HoursSelectionTypeAuto: var addrHours []uint64 - switch params.HoursSelection.Mode { + switch p.HoursSelection.Mode { case HoursSelectionModeShare: // multiply remaining hours after fee burn with share factor hours, err := coin.Uint64ToInt64(remainingHours) @@ -1338,14 +1339,14 @@ func (w *Wallet) CreateAndSignTransactionAdvanced(params CreateTransactionParams return nil, nil, err } - allocatedHoursInt := params.HoursSelection.ShareFactor.Mul(decimal.New(hours, 0)).IntPart() + allocatedHoursInt := p.HoursSelection.ShareFactor.Mul(decimal.New(hours, 0)).IntPart() allocatedHours, err := coin.Int64ToUint64(allocatedHoursInt) if err != nil { return nil, nil, err } - toCoins := make([]uint64, len(params.To)) - for i, to := range params.To { + toCoins := make([]uint64, len(p.To)) + for i, to := range p.To { toCoins[i] = to.Coins } @@ -1357,7 +1358,7 @@ func (w *Wallet) CreateAndSignTransactionAdvanced(params CreateTransactionParams return nil, nil, ErrInvalidHoursSelectionType } - for i, out := range params.To { + for i, out := range p.To { out.Hours = addrHours[i] txn.Out = append(txn.Out, out) } @@ -1408,7 +1409,7 @@ func (w *Wallet) CreateAndSignTransactionAdvanced(params CreateTransactionParams } // Calculate the new fee for this new amount of hours - newFee := fee.RequiredFee(newTotalHours) + newFee := fee.RequiredFee(newTotalHours, params.UserBurnFactor) if newFee < feeHours { err := errors.New("updated fee after adding extra input for change is unexpectedly less than it was initially") logger.WithError(err).Error() @@ -1446,19 +1447,19 @@ func (w *Wallet) CreateAndSignTransactionAdvanced(params CreateTransactionParams // With auto share mode, if there are leftover hours and change couldn't be force-added, // recalculate that share ratio at 100% - if changeCoins == 0 && changeHours > 0 && params.HoursSelection.Type == HoursSelectionTypeAuto && params.HoursSelection.Mode == HoursSelectionModeShare { + if changeCoins == 0 && changeHours > 0 && p.HoursSelection.Type == HoursSelectionTypeAuto && p.HoursSelection.Mode == HoursSelectionModeShare { oneDecimal := decimal.New(1, 0) - if params.HoursSelection.ShareFactor.Equal(oneDecimal) { + if p.HoursSelection.ShareFactor.Equal(oneDecimal) { return nil, nil, errors.New("share factor is 1.0 but changeHours > 0 unexpectedly") } - params.HoursSelection.ShareFactor = &oneDecimal - return w.CreateAndSignTransactionAdvanced(params, auxs, headTime) + p.HoursSelection.ShareFactor = &oneDecimal + return w.CreateAndSignTransactionAdvanced(p, auxs, headTime) } if changeCoins > 0 { var changeAddress cipher.Address - if params.ChangeAddress != nil { - changeAddress = *params.ChangeAddress + if p.ChangeAddress != nil { + changeAddress = *p.ChangeAddress } else { // Choose a change address from the unspent outputs // Sort spends by address, comparing bytes, and use the first @@ -1499,7 +1500,7 @@ func (w *Wallet) CreateAndSignTransactionAdvanced(params CreateTransactionParams inputs[i] = uxBalance } - if err := verifyCreatedTransactionInvariants(params, txn, inputs); err != nil { + if err := verifyCreatedTransactionInvariants(p, txn, inputs); err != nil { logger.Critical().Errorf("CreateAndSignTransactionAdvanced created transaction that violates invariants, aborting: %v", err) return nil, nil, fmt.Errorf("Created transaction that violates invariants, this is a bug: %v", err) } @@ -1510,7 +1511,7 @@ func (w *Wallet) CreateAndSignTransactionAdvanced(params CreateTransactionParams // verifyCreatedTransactionInvariants checks that the transaction that was created matches expectations. // Does not call visor verification methods because that causes import cycle. // daemon.Gateway checks that the transaction passes additional visor verification methods. -func verifyCreatedTransactionInvariants(params CreateTransactionParams, txn *coin.Transaction, inputs []UxBalance) error { +func verifyCreatedTransactionInvariants(p CreateTransactionParams, txn *coin.Transaction, inputs []UxBalance) error { for _, o := range txn.Out { // No outputs should be sent to the null address if o.Address.Null() { @@ -1522,20 +1523,20 @@ func verifyCreatedTransactionInvariants(params CreateTransactionParams, txn *coi } } - if len(txn.Out) != len(params.To) && len(txn.Out) != len(params.To)+1 { + if len(txn.Out) != len(p.To) && len(txn.Out) != len(p.To)+1 { return errors.New("Transaction has unexpected number of outputs") } - for i, o := range txn.Out[:len(params.To)] { - if o.Address != params.To[i].Address { + for i, o := range txn.Out[:len(p.To)] { + if o.Address != p.To[i].Address { return errors.New("Output address does not match requested address") } - if o.Coins != params.To[i].Coins { + if o.Coins != p.To[i].Coins { return errors.New("Output coins does not match requested coins") } - if params.To[i].Hours != 0 && o.Hours != params.To[i].Hours { + if p.To[i].Hours != 0 && o.Hours != p.To[i].Hours { return errors.New("Output hours does not match requested hours") } } @@ -1598,7 +1599,7 @@ func verifyCreatedTransactionInvariants(params CreateTransactionParams, txn *coi return errors.New("Total input hours is less than the output hours") } - if inputHours-outputHours < fee.RequiredFee(inputHours) { + if inputHours-outputHours < fee.RequiredFee(inputHours, params.UserBurnFactor) { return errors.New("Transaction will not satisy required fee") } @@ -1617,7 +1618,7 @@ func verifyCreatedTransactionInvariants(params CreateTransactionParams, txn *coi // an array of length nAddrs with the hours to give to each destination address, // and a sum of these values. func DistributeSpendHours(inputHours, nAddrs uint64, haveChange bool) (uint64, []uint64, uint64) { - feeHours := fee.RequiredFee(inputHours) + feeHours := fee.RequiredFee(inputHours, params.UserBurnFactor) remainingHours := inputHours - feeHours var changeHours uint64 @@ -1971,7 +1972,7 @@ func ChooseSpends(uxa []UxBalance, coins, hours uint64, sortStrategy func([]UxBa have.Coins += firstNonzero.Coins have.Hours += firstNonzero.Hours - if have.Coins >= coins && fee.RemainingHours(have.Hours) >= hours { + if have.Coins >= coins && fee.RemainingHours(have.Hours, params.UserBurnFactor) >= hours { return spending, nil } @@ -1989,7 +1990,7 @@ func ChooseSpends(uxa []UxBalance, coins, hours uint64, sortStrategy func([]UxBa } } - if have.Coins >= coins && fee.RemainingHours(have.Hours) >= hours { + if have.Coins >= coins && fee.RemainingHours(have.Hours, params.UserBurnFactor) >= hours { return spending, nil } @@ -2002,7 +2003,7 @@ func ChooseSpends(uxa []UxBalance, coins, hours uint64, sortStrategy func([]UxBa have.Coins += ux.Coins have.Hours += ux.Hours - if have.Coins >= coins && fee.RemainingHours(have.Hours) >= hours { + if have.Coins >= coins && fee.RemainingHours(have.Hours, params.UserBurnFactor) >= hours { return spending, nil } } diff --git a/src/wallet/wallet_test.go b/src/wallet/wallet_test.go index 5e918fcc46..b01ea2e83f 100644 --- a/src/wallet/wallet_test.go +++ b/src/wallet/wallet_test.go @@ -18,6 +18,7 @@ import ( "github.com/skycoin/skycoin/src/cipher" "github.com/skycoin/skycoin/src/cipher/encrypt" "github.com/skycoin/skycoin/src/coin" + "github.com/skycoin/skycoin/src/params" "github.com/skycoin/skycoin/src/testutil" "github.com/skycoin/skycoin/src/util/fee" "github.com/skycoin/skycoin/src/util/logging" @@ -1239,7 +1240,7 @@ var burnFactor10TestCases = []distributeSpendHoursTestCase{ } func TestWalletDistributeSpendHours(t *testing.T) { - originalBurnFactor := fee.BurnFactor + originalBurnFactor := params.UserBurnFactor cases := []struct { burnFactor uint64 @@ -1252,15 +1253,15 @@ func TestWalletDistributeSpendHours(t *testing.T) { tested := false for _, tcc := range cases { - if tcc.burnFactor == fee.BurnFactor { + if tcc.burnFactor == params.UserBurnFactor { tested = true } for _, tc := range tcc.cases { t.Run(tc.name, func(t *testing.T) { - fee.BurnFactor = tcc.burnFactor + params.UserBurnFactor = tcc.burnFactor defer func() { - fee.BurnFactor = originalBurnFactor + params.UserBurnFactor = originalBurnFactor }() changeHours, addrHours, totalHours := DistributeSpendHours(tc.inputHours, tc.nAddrs, tc.haveChange) @@ -1276,16 +1277,16 @@ func TestWalletDistributeSpendHours(t *testing.T) { require.Equal(t, outputHours, totalHours) if tc.inputHours != 0 { - err := fee.VerifyTransactionFeeForHours(outputHours, tc.inputHours-outputHours) + err := fee.VerifyTransactionFeeForHours(outputHours, tc.inputHours-outputHours, params.UserBurnFactor) require.NoError(t, err) } }) } t.Run(fmt.Sprintf("burn-factor-%d-range", tcc.burnFactor), func(t *testing.T) { - fee.BurnFactor = tcc.burnFactor + params.UserBurnFactor = tcc.burnFactor defer func() { - fee.BurnFactor = originalBurnFactor + params.UserBurnFactor = originalBurnFactor }() // Tests over range of values @@ -1303,13 +1304,13 @@ func TestWalletDistributeSpendHours(t *testing.T) { } if haveChange { - remainingHours := (inputHours - fee.RequiredFee(inputHours)) + remainingHours := (inputHours - fee.RequiredFee(inputHours, params.UserBurnFactor)) splitRemainingHours := remainingHours / 2 require.True(t, changeHours == splitRemainingHours || changeHours == splitRemainingHours+1) require.Equal(t, splitRemainingHours, sumAddrHours) } else { require.Equal(t, uint64(0), changeHours) - require.Equal(t, inputHours-fee.RequiredFee(inputHours), sumAddrHours) + require.Equal(t, inputHours-fee.RequiredFee(inputHours, params.UserBurnFactor), sumAddrHours) } outputHours := sumAddrHours + changeHours @@ -1317,7 +1318,7 @@ func TestWalletDistributeSpendHours(t *testing.T) { require.Equal(t, outputHours, totalHours) if inputHours != 0 { - err := fee.VerifyTransactionFeeForHours(outputHours, inputHours-outputHours) + err := fee.VerifyTransactionFeeForHours(outputHours, inputHours-outputHours, params.UserBurnFactor) require.NoError(t, err) } @@ -1332,7 +1333,7 @@ func TestWalletDistributeSpendHours(t *testing.T) { }) } - require.True(t, tested, "configured BurnFactor=%d has not been tested", fee.BurnFactor) + require.True(t, tested, "configured BurnFactor=%d has not been tested", params.UserBurnFactor) } func uxBalancesEqual(a, b []UxBalance) bool { diff --git a/template/coin.template b/template/coin.template index b001774873..1202e524e1 100755 --- a/template/coin.template +++ b/template/coin.template @@ -20,7 +20,7 @@ import ( var ( // Version of the node. Can be set by -ldflags - Version = "0.24.1" + Version = "0.25.0-rc1" // Commit ID. Can be set by -ldflags Commit = "" // Branch name. Can be set by -ldflags @@ -32,6 +32,9 @@ var ( logger = logging.MustGetLogger("main") + // CoinName name of coin + CoinName = "{{.CoinName}}" + // GenesisSignatureStr hex string of genesis signature GenesisSignatureStr = "{{.GenesisSignatureStr}}" // GenesisAddressStr genesis address string @@ -54,17 +57,22 @@ var ( } nodeConfig = skycoin.NewNodeConfig(ConfigMode, skycoin.NodeParameters{ - GenesisSignatureStr: GenesisSignatureStr, - GenesisAddressStr: GenesisAddressStr, - GenesisCoinVolume: GenesisCoinVolume, - GenesisTimestamp: GenesisTimestamp, - BlockchainPubkeyStr: BlockchainPubkeyStr, - BlockchainSeckeyStr: BlockchainSeckeyStr, - DefaultConnections: DefaultConnections, - PeerListURL: "{{.PeerListURL}}", - Port: {{.Port}}, - WebInterfacePort: {{.WebInterfacePort}}, - DataDirectory: "{{.DataDirectory}}", + CoinName: CoinName, + GenesisSignatureStr: GenesisSignatureStr, + GenesisAddressStr: GenesisAddressStr, + GenesisCoinVolume: GenesisCoinVolume, + GenesisTimestamp: GenesisTimestamp, + BlockchainPubkeyStr: BlockchainPubkeyStr, + BlockchainSeckeyStr: BlockchainSeckeyStr, + DefaultConnections: DefaultConnections, + PeerListURL: "{{.PeerListURL}}", + Port: {{.Port}}, + WebInterfacePort: {{.WebInterfacePort}}, + DataDirectory: "{{.DataDirectory}}", + UnconfirmedBurnFactor: {{.UnconfirmedBurnFactor}}, + CreateBlockBurnFactor: {{.CreateBlockBurnFactor}}, + MaxBlockSize: {{.MaxBlockSize}}, + MaxUnconfirmedTransactionSize: {{.MaxUnconfirmedTransactionSize}}, }) parseFlags = true diff --git a/template/visor.template b/template/params.template similarity index 70% rename from template/visor.template rename to template/params.template index 59a30959cb..72116a34e6 100644 --- a/template/visor.template +++ b/template/params.template @@ -1,4 +1,4 @@ -package visor +package params /* CODE GENERATED AUTOMATICALLY WITH FIBER COIN CREATOR @@ -20,12 +20,22 @@ const ( // Once the InitialUnlockedCount is exhausted, // UnlockAddressRate addresses will be unlocked per UnlockTimeInterval UnlockTimeInterval uint64 = {{.UnlockTimeInterval}} // in seconds +) + +var ( + // UserBurnFactor inverse fraction of coinhours that must be burned (can be overridden with `USER_BURN_FACTOR` env var), + // used when creating a transaction + UserBurnFactor uint64 = {{.UserBurnFactor}} + + // MaxUserTransactionSize is the maximum size of a user-created transaction + MaxUserTransactionSize = {{.MaxUserTransactionSize}} // in bytes + // MaxDropletPrecision represents the decimal precision of droplets MaxDropletPrecision uint64 = {{.MaxDropletPrecision}} - //DefaultMaxBlockSize is max block size - DefaultMaxBlockSize int = {{.DefaultMaxBlockSize}} // in bytes ) +// distributionAddresses are addresses that received coins from the genesis address in the first block, +// used to calculate current and max supply and do distribution timelocking var distributionAddresses = [DistributionAddressesTotal]string{ {{- range $index, $address := .DistributionAddresses}} "{{$address -}}", diff --git a/vendor/golang.org/x/sys/unix/gccgo_c.c b/vendor/golang.org/x/sys/unix/gccgo_c.c index 24e96b1198..0655d3a3d3 100644 --- a/vendor/golang.org/x/sys/unix/gccgo_c.c +++ b/vendor/golang.org/x/sys/unix/gccgo_c.c @@ -16,25 +16,25 @@ // Go to C does not support varargs functions. struct ret { - uintptr_t r; - uintptr_t err; + uintptr_t r; + uintptr_t err; }; struct ret gccgoRealSyscall(uintptr_t trap, uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5, uintptr_t a6, uintptr_t a7, uintptr_t a8, uintptr_t a9) { - struct ret r; + struct ret r; - errno = 0; - r.r = syscall(trap, a1, a2, a3, a4, a5, a6, a7, a8, a9); - r.err = errno; - return r; + errno = 0; + r.r = syscall(trap, a1, a2, a3, a4, a5, a6, a7, a8, a9); + r.err = errno; + return r; } uintptr_t gccgoRealSyscallNoError(uintptr_t trap, uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5, uintptr_t a6, uintptr_t a7, uintptr_t a8, uintptr_t a9) { - return syscall(trap, a1, a2, a3, a4, a5, a6, a7, a8, a9); + return syscall(trap, a1, a2, a3, a4, a5, a6, a7, a8, a9); } // Define the use function in C so that it is not inlined.