[1.1.0] Merge master into 1.1.0 (#2720)

* cleanup legacy "3 dot" check (#2625)

* Allow to peers behind NAT to get up to preferred_max connections (#2543)

Allow to peers behind NAT to get up to preffered_max connections

If peer has only outbound connections it's mot likely behind NAT and we should not stop it from getting more outbound connections

* Reduce usage of unwrap in p2p crate (#2627)

Also change store crate a bit

* Simplify (and fix) output_pos cleanup during chain compaction (#2609)

* expose leaf pos iterator
use it for various things in txhashset when iterating over outputs

* fix

* cleanup

* rebuild output_pos index (and clear it out first) when compacting the chain

* fixup tests

* refactor to match on (output, proof) tuple

* add comments to compact() to explain what is going on.

* get rid of some boxing around the leaf_set iterator

* cleanup

* [docs] Add switch commitment documentation (#2526)

* remove references to no-longer existing switch commitment hash

(as switch commitments were removed in ca8447f3bd
and moved into the blinding factor of the Pedersen Commitment)

* some rewording (points vs curves) and fix of small formatting issues

* Add switch commitment documentation

* [docs] Documents in grin repo had translated in Korean.  (#2604)

*  Start to M/W intro translate in Korean
*  translate in Korean
*  add korean translation  on intro
* table_of_content.md translate in Korean.
*  table_of_content_KR.md finish translate in Korean, start to translate State_KR.md
*  add state_KR.md & commit some translation in State_KR.md
*  WIP stat_KR.md translation
*  add build_KR.md && stratum_KR.md
*  finish translate stratum_KR.md & table_of_content_KR.md
*  rename intro.KR.md to intro_KR.md
*  add intro_KR.md file path each language's  intro.md
*  add Korean translation file path to stratum.md & table_of_contents.md
*  fix difference with grin/master

* Fix TxHashSet file filter for Windows. (#2641)

* Fix TxHashSet file filter for Windows.

* rustfmt

* Updating regexp

* Adding in test case

* Display the current download rate rather than the average when syncing the chain (#2633)

* When syncing the chain, calculate the displayed download speed using the current rate from the most recent iteration, rather than the average download speed from the entire syncing process.

* Replace the explicitly ignored variables in the pattern with an implicit ignore

* remove root = true from editorconfig (#2655)

* Add Medium post to intro (#2654)

Spoke to @yeastplume who agreed it makes sense to add the "Grin Transactions Explained, Step-by-Step" Medium post to intro.md

Open for suggestions on a better location.

* add a new configure item for log_max_files (#2601)

* add a new configure item for log_max_files

* rustfmt

* use a constant instead of multiple 32

* rustfmt

* Fix the build warning of deprecated trim_right_matches (#2662)

* [DOC] state.md, build.md and chain directory documents translate in Korean.  (#2649)

*  add md files for translation.

*  start to translation fast-sync, code_structure. add file build_KR.md, states_KR.md

* add dandelion_KR.md && simulation_KR.md for Korean translation.

*  add md files for translation.

*  start to translation fast-sync, code_structure. add file build_KR.md, states_KR.md

* add dandelion_KR.md && simulation_KR.md for Korean translation.

* remove some useless md files for translation. this is rearrange set up translation order.

*  add dot end of sentence & translate build.md in korean

*  remove fast-sync_KR.md

*  finish build_KR.md translation

*  finish build_KR.md translation

*  finish translation state_KR.md & add phrase in state.md to move other language md file

* translate blocks_and_headers.md && chain_sync.md in Korean

*  add . in chain_sync.md , translation finished in doc/chain dir.

* fix some miss typos

* Api documentation fixes (#2646)

* Fix the API documentation for Chain Validate (v1/chain/validate).  It was documented as a POST, but it is actually a GET request, which can be seen in its handler ChainValidationHandler
* Update the API V1 route list response to include the headers and merkleproof routes.  Also clarify that for the chain/outputs route you must specify either byids or byheight to select outputs.

* refactor(ci): reorganize CI related code (#2658)

Break-down the CI related code into smaller more maintainable pieces.

* Specify grin or nanogrins in API docs where applicable (#2642)

* Set Content-Type in API client (#2680)

* Reduce number of unwraps in chain crate (#2679)

* fix: the restart of state sync doesn't work sometimes (#2687)

* let check_txhashset_needed return true on abnormal case (#2684)

*  Reduce number of unwwaps in api crate  (#2681)

* Reduce number of unwwaps in api crate

* Format use section

* Small QoL improvements for wallet developers (#2651)

* Small changes for wallet devs

* Move create_nonce into Keychain trait

* Replace match by map_err

* Add flag to Slate to skip fee check

* Fix secp dependency

* Remove check_fee flag in Slate

* Add Japanese edition of build.md (#2697)

* catch the panic to avoid peer thread quit early (#2686)

* catch the panic to avoid peer thread quit before taking the chance to ban
* move catch wrapper logic down into the util crate
* log the panic info
* keep txhashset.rs untouched
* remove a warning

* [DOC] dandelion.md, simulation.md ,fast-sync.md and pruning.md documents translate in Korean. (#2678)

* Show response code in API client error message (#2683)

It's hard to investigate what happens when an API client error is
printed out

* Add some better logging for get_outputs_by_id failure states (#2705)

* Switch commitment doc fixes (#2645)

Fix some typos and remove the use of parentheses in a
couple of places to make the reading flow a bit better.

* docs: update/add new README.md badges (#2708)

Replace existing badges with SVG counterparts and add a bunch of new ones.

* Update intro.md (#2702)

Add mention of censoring attack prevented by range proofs

* use sandbox folder for txhashset validation on state sync (#2685)

* use sandbox folder for txhashset validation on state sync

* rustfmt

* use temp directory as the sandbox instead actual db_root txhashset dir

* rustfmt

* move txhashset overwrite to the end of full validation

* fix travis-ci test

* rustfmt

* fix: hashset have 2 folders including txhashset and header

* rustfmt

* 
(1)switch to rebuild_header_mmr instead of copy the sandbox header mmr 
(2)lock txhashset when overwriting and opening and rebuild

* minor improve on sandbox_dir

* add Japanese edition of state.md (#2703)

* Attempt to fix broken TUI locale (#2713)

Can confirm that on the same machine 1.0.2 TUI looks great and is broken on
the current master. Bump of `cursive` version fixed it for me.
Fixes #2676

* clean the header folder in sandbox (#2716)

* forgot to clean the header folder in sandbox in #2685

* Reduce number of unwraps in servers crate (#2707)

It doesn't include stratum server which is sufficiently changed in 1.1
branch and adapters, which is big enough for a separate PR.

* rustfmt

* change version to beta
This commit is contained in:
Yeastplume 2019-04-01 11:47:48 +01:00 committed by GitHub
parent bd6c73417d
commit 5cb8025ddd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
115 changed files with 3593 additions and 1370 deletions

View file

@ -1,81 +0,0 @@
#!/bin/bash
repo_slug="mimblewimble/grin"
token="$GITHUB_TOKEN"
export CHANGELOG_GITHUB_TOKEN="$token"
tagname=`git describe --tags --exact-match 2>/dev/null || git symbolic-ref -q --short HEAD`
echo 'package the release binary...\n'
if [[ $TRAVIS_OS_NAME == 'osx' ]]; then
# Do some custom requirements on OS X
cd target/release ; rm -f *.tgz; tar zcf "grin-$tagname-$TRAVIS_JOB_ID-osx.tgz" grin
/bin/ls -ls *.tgz | awk '{print $6,$7,$8,$9,$10}'
md5 "grin-$tagname-$TRAVIS_JOB_ID-osx.tgz" > "grin-$tagname-$TRAVIS_JOB_ID-osx.tgz"-md5sum.txt
/bin/ls -ls *-md5sum.txt | awk '{print $6,$7,$8,$9,$10}'
cd - > /dev/null;
echo "osx tarball generated\n"
# Only generate changelog on Linux platform, to avoid duplication
exit 0
elif [[ $TRAVIS_OS_NAME == 'windows' ]]; then
# Custom requirements on windows
cd target/release ; rm -f *.zip ; 7z a -tzip "grin-$tagname-$TRAVIS_JOB_ID-win-x64.zip" grin.exe
/bin/ls -ls *.zip | awk '{print $6,$7,$8,$9,$10}'
md5sum "grin-$tagname-$TRAVIS_JOB_ID-win-x64.zip" > "grin-$tagname-$TRAVIS_JOB_ID-win-x64.zip"-md5sum.txt
/bin/ls -ls *-md5sum.txt | awk '{print $6,$7,$8,$9,$10}'
cd - > /dev/null;
echo "win x64 zip file generated\n"
# Only generate changelog on Linux platform, to avoid duplication
exit 0
else
# Do some custom requirements on Linux
cd target/release ; rm -f *.tgz; tar zcf "grin-$tagname-$TRAVIS_JOB_ID-linux-amd64.tgz" grin
/bin/ls -ls *.tgz | awk '{print $6,$7,$8,$9,$10}'
md5sum "grin-$tagname-$TRAVIS_JOB_ID-linux-amd64.tgz" > "grin-$tagname-$TRAVIS_JOB_ID-linux-amd64.tgz"-md5sum.txt
/bin/ls -ls *-md5sum.txt | awk '{print $6,$7,$8,$9,$10}'
cd - > /dev/null;
echo "linux tarball generated\n"
fi
version="$tagname"
branch="`git symbolic-ref -q --short HEAD`"
# automatic changelog generator
gem install github_changelog_generator
LAST_REVISION=$(git rev-list --tags --skip=1 --max-count=1)
LAST_RELEASE_TAG=$(git describe --abbrev=0 --tags ${LAST_REVISION})
# Generate CHANGELOG.md
github_changelog_generator \
-u $(cut -d "/" -f1 <<< $repo_slug) \
-p $(cut -d "/" -f2 <<< $repo_slug) \
--since-tag ${LAST_RELEASE_TAG}
body="$(cat CHANGELOG.md)"
# Overwrite CHANGELOG.md with JSON data for GitHub API
jq -n \
--arg body "$body" \
--arg name "$version" \
--arg tag_name "$version" \
--arg target_commitish "$branch" \
'{
body: $body,
name: $name,
tag_name: $tag_name,
target_commitish: $target_commitish,
draft: false,
prerelease: false
}' > CHANGELOG.md
release_id="$(curl -0 -XGET -H "Authorization: token $token" https://api.github.com/repos/$repo_slug/releases/tags/$tagname 2>/dev/null | grep id | head -n 1 | sed 's/ *"id": *\(.*\),/\1/')"
echo "Updating release $version for repo: $repo_slug, branch: $branch. release id: $release_id"
curl -H "Authorization: token $token" --request PATCH --data @CHANGELOG.md "https://api.github.com/repos/$repo_slug/releases/$release_id"
echo "auto changelog uploaded.\n"

28
.ci/general-jobs Executable file
View file

@ -0,0 +1,28 @@
#!/usr/bin/env bash
# Copyright 2019 The Grin Developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# This script contains general jobs.
case "${CI_JOB}" in
"test")
for dir in ${CI_JOB_ARGS}; do
printf "executing tests in directory \`%s\`...\n" "${dir}"
cd "${dir}" && \
cargo test --release && \
cd - > /dev/null || exit 1
done
;;
esac

122
.ci/release-jobs Executable file
View file

@ -0,0 +1,122 @@
#!/usr/bin/env bash
# Copyright 2019 The Grin Developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# This script contains release-related jobs.
# Redeclare CI and VCP specific environment variables
# to make future migration to other providers easier.
readonly JOB_ID="${TRAVIS_JOB_ID}"
readonly OS_NAME="${TRAVIS_OS_NAME}"
readonly TEST_RESULT="${TRAVIS_TEST_RESULT}"
readonly VCP_AUTH_TOKEN="${GITHUB_TOKEN}"
case "${CI_JOB}" in
"release")
# The release can only be triggered after successful completion of all tests.
[[ "${TEST_RESULT}" != 0 ]] && exit 1
readonly REPO_TAG="$(git describe --tags --exact-match 2> /dev/null || git symbolic-ref -q --short HEAD)"
case "${OS_NAME}" in
"linux")
cargo clean && \
cargo build --release
readonly ARCHIVE_CMD="tar zcf"
readonly BIN_SUFFIX=""
readonly PKG_NAME="grin-${REPO_TAG}-${JOB_ID}-linux-amd64"
readonly PKG_SUFFIX=".tgz"
;;
"osx")
brew update
cargo clean && \
cargo build --release
readonly ARCHIVE_CMD="tar zcf"
readonly BIN_SUFFIX=""
readonly PKG_NAME="grin-${REPO_TAG}-${JOB_ID}-osx"
readonly PKG_SUFFIX=".tgz"
;;
"windows")
cargo clean && \
cargo build --release
readonly ARCHIVE_CMD="7z a -tzip"
readonly BIN_SUFFIX=".exe"
readonly PKG_NAME="grin-${REPO_TAG}-${JOB_ID}-win-x64"
readonly PKG_SUFFIX=".zip"
;;
*)
printf "Error! Unknown \$OS_NAME: \`%s\`" "${OS_NAME}"
exit 1
esac
printf "creating package \`%s\` for the release binary...\n" "${PKG_NAME}${PKG_SUFFIX}"
cd ./target/release/ || exit 1
rm -f -- *"${PKG_SUFFIX}"
${ARCHIVE_CMD} "${PKG_NAME}${PKG_SUFFIX}" "grin${BIN_SUFFIX}"
ls -ls -- *.tgz | cut -d' ' -f6-
openssl md5 "${PKG_NAME}${PKG_SUFFIX}" > "${PKG_NAME}${PKG_SUFFIX}-md5sum.txt"
ls -ls -- *-md5sum.txt | cut -d' ' -f6-
cd - > /dev/null || exit 1
printf "%s package \`%s\` generated\n" "${OS_NAME}" "${PKG_NAME}${PKG_SUFFIX}"
# Generate changelog only on the Linux platform to avoid duplication.
[[ "${OS_NAME}" != "linux" ]] && exit 0
# Generate CHANGELOG.md
readonly REPO_SLUG="mimblewimble/grin"
readonly REPO_BRANCH="$(git symbolic-ref -q --short HEAD)"
readonly REPO_PREV_RELEASE_TAG="$(git describe --abbrev=0 --tags "$(git rev-list --tags --skip=0 --max-count=1)")"
gem install github_changelog_generator
# Needed by github_changelog_generator.
export CHANGELOG_GITHUB_TOKEN="${VCP_AUTH_TOKEN}"
github_changelog_generator \
--user "$(cut -d "/" -f1 <<< ${REPO_SLUG})" \
--project "$(cut -d "/" -f2 <<< ${REPO_SLUG})" \
--since-tag "${REPO_PREV_RELEASE_TAG}"
readonly CHANGELOG_CONTENT="$(<CHANGELOG.md)"
# Overwrite CHANGELOG.md with JSON data for release patch.
jq --null-input \
--arg body "${CHANGELOG_CONTENT}" \
--arg name "${REPO_TAG}" \
--arg tag_name "${REPO_TAG}" \
--arg target_commitish "${REPO_BRANCH}" \
'{
body: $body,
name: $name,
tag_name: $tag_name,
target_commitish: $target_commitish,
draft: false,
prerelease: false
}' > CHANGELOG.md
readonly HEADERS="Authorization: token ${VCP_AUTH_TOKEN}"
readonly RELEASE_URL="https://api.github.com/repos/${REPO_SLUG}/releases"
readonly RELEASE_ID="$(curl -0 --request GET -H "${HEADERS}" "${RELEASE_URL}/tags/${REPO_TAG}" 2> /dev/null | grep id | head -n 1 | sed 's/ *"id": *\(.*\),/\1/')"
printf "updating release changelog %s for repo: %s, branch: %s, release id: %s\n" "${REPO_TAG}" "${REPO_SLUG}" "${REPO_BRANCH}" "${RELEASE_ID}"
curl -H "${HEADERS}" --request PATCH --data @CHANGELOG.md "${RELEASE_URL}/${RELEASE_ID}"
printf "changelog uploaded.\n"
;;
esac

View file

@ -1,6 +1,3 @@
# top-most .editorconfig file
root = true
# use hard tabs for rust source files # use hard tabs for rust source files
[*.rs] [*.rs]
indent_style = tab indent_style = tab

View file

@ -1,24 +1,28 @@
language: rust # Copyright 2019 The Grin Developers
#
git: # Licensed under the Apache License, Version 2.0 (the "License");
depth: false # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
dist: trusty dist: trusty
sudo: required sudo: required
cache: language: rust
cargo: true
timeout: 240
directories:
- $HOME/.cargo
- $TRAVIS_BUILD_DIR/target
before_cache:
- rm -rf $TRAVIS_BUILD_DIR/target/tmp
rust: rust:
- stable - stable
git:
depth: false
addons: addons:
apt: apt:
sources: sources:
@ -33,6 +37,13 @@ addons:
- gcc - gcc
- binutils-dev - binutils-dev
cache:
cargo: true
timeout: 240
directories:
- $HOME/.cargo
- $TRAVIS_BUILD_DIR/target
env: env:
global: global:
- RUST_BACKTRACE="1" - RUST_BACKTRACE="1"
@ -41,46 +52,29 @@ env:
matrix: matrix:
include: include:
- os: linux - os: linux
env: TEST_SUITE=servers env: CI_JOB="test" CI_JOB_ARGS="servers"
- os: linux - os: linux
env: TEST_SUITE=chain-core env: CI_JOB="test" CI_JOB_ARGS="chain core"
- os: linux - os: linux
env: TEST_SUITE=pool-p2p-src env: CI_JOB="test" CI_JOB_ARGS="pool p2p src"
- os: linux - os: linux
env: TEST_SUITE=keychain env: CI_JOB="test" CI_JOB_ARGS="keychain"
- os: linux - os: linux
env: TEST_SUITE=api-util-store env: CI_JOB="test" CI_JOB_ARGS="api util store"
- os: linux
env: CI_JOB="release" CI_JOB_ARGS=
- os: osx - os: osx
env: TEST_SUITE=release env: CI_JOB="release" CI_JOB_ARGS=
# - os: windows # - os: windows
# env: TEST_SUITE=release # env: CI_JOB="release" CI_JOB_ARGS=
script: script: .ci/general-jobs
- IFS='-' read -r -a DIRS <<< "$TEST_SUITE"; DIR=${DIRS[0]};
echo "start testing on folder $DIR..."; before_cache:
if [[ -n "$DIR" ]] && [[ "$TRAVIS_OS_NAME" == "linux" ]]; then cd $DIR && cargo test --release && cd - > /dev/null; fi; - rm -rf $TRAVIS_BUILD_DIR/target/tmp
- IFS='-' read -r -a DIRS <<< "$TEST_SUITE"; DIR=${DIRS[1]};
if [[ -n "$DIR" ]]; then
echo "start testing on folder $DIR...";
cd $DIR && cargo test --release && cd - > /dev/null;
fi;
- IFS='-' read -r -a DIRS <<< "$TEST_SUITE"; DIR=${DIRS[2]};
if [[ -n "$DIR" ]]; then
echo "start testing on folder $DIR...";
cd $DIR && cargo test --release && cd - > /dev/null;
fi;
before_deploy: before_deploy:
- if [[ "$TEST_SUITE" == "pool-p2p-src" ]]; then - bash .ci/release-jobs
cargo clean && cargo build --release && ./.auto-release.sh;
fi
- if [[ "$TEST_SUITE" == "release" ]] && [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
brew update;
cargo clean && cargo build --release && ./.auto-release.sh;
fi
- if [[ "$TEST_SUITE" == "release" ]] && [[ "$TRAVIS_OS_NAME" == "windows" ]]; then
cargo clean && cargo build --release && ./.auto-release.sh;
fi
deploy: deploy:
provider: releases provider: releases

776
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
[package] [package]
name = "grin" name = "grin"
version = "1.1.0" version = "1.1.0-beta.1"
authors = ["Grin Developers <mimblewimble@lists.launchpad.net>"] authors = ["Grin Developers <mimblewimble@lists.launchpad.net>"]
description = "Simple, private and scalable cryptocurrency implementation based on the MimbleWimble chain format." description = "Simple, private and scalable cryptocurrency implementation based on the MimbleWimble chain format."
license = "Apache-2.0" license = "Apache-2.0"
@ -32,13 +32,13 @@ term = "0.5"
failure = "0.1" failure = "0.1"
failure_derive = "0.1" failure_derive = "0.1"
grin_api = { path = "./api", version = "1.1.0" } grin_api = { path = "./api", version = "1.1.0-beta.1" }
grin_config = { path = "./config", version = "1.1.0" } grin_config = { path = "./config", version = "1.1.0-beta.1" }
grin_core = { path = "./core", version = "1.1.0" } grin_core = { path = "./core", version = "1.1.0-beta.1" }
grin_keychain = { path = "./keychain", version = "1.1.0" } grin_keychain = { path = "./keychain", version = "1.1.0-beta.1" }
grin_p2p = { path = "./p2p", version = "1.1.0" } grin_p2p = { path = "./p2p", version = "1.1.0-beta.1" }
grin_servers = { path = "./servers", version = "1.1.0" } grin_servers = { path = "./servers", version = "1.1.0-beta.1" }
grin_util = { path = "./util", version = "1.1.0" } grin_util = { path = "./util", version = "1.1.0-beta.1" }
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
cursive = { version = "0.10.0", default-features = false, features = ["pancurses-backend"] } cursive = { version = "0.10.0", default-features = false, features = ["pancurses-backend"] }
@ -46,11 +46,11 @@ cursive = { version = "0.10.0", default-features = false, features = ["pancurses
version = "0.16.0" version = "0.16.0"
features = ["win32"] features = ["win32"]
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
cursive = "0.9.0" cursive = "0.10.0"
[build-dependencies] [build-dependencies]
built = "0.3" built = "0.3"
[dev-dependencies] [dev-dependencies]
grin_chain = { path = "./chain", version = "1.1.0" } grin_chain = { path = "./chain", version = "1.1.0-beta.1" }
grin_store = { path = "./store", version = "1.1.0" } grin_store = { path = "./store", version = "1.1.0-beta.1" }

View file

@ -1,7 +1,10 @@
[![Build Status](https://travis-ci.org/mimblewimble/grin.svg?branch=master)](https://travis-ci.org/mimblewimble/grin) [![Build Status](https://img.shields.io/travis/mimblewimble/grin/master.svg)](https://travis-ci.org/mimblewimble/grin)
[![Gitter chat](https://badges.gitter.im/grin_community/Lobby.png)](https://gitter.im/grin_community/Lobby) [![Coverage Status](https://img.shields.io/codecov/c/github/mimblewimble/grin/master.svg)](https://codecov.io/gh/mimblewimble/grin)
[![Support chat](https://badges.gitter.im/grin_community/Lobby.png)](https://gitter.im/grin_community/support) [![Chat](https://img.shields.io/gitter/room/grin_community/Lobby.svg)](https://gitter.im/grin_community/Lobby)
[![Codecov coverage status](https://codecov.io/gh/mimblewimble/grin/branch/master/graph/badge.svg)](https://codecov.io/gh/mimblewimble/grin) [![Support](https://img.shields.io/badge/support-on%20gitter-brightgreen.svg)](https://gitter.im/grin_community/support)
[![Documentation Wiki](https://img.shields.io/badge/doc-wiki-blue.svg)](https://github.com/mimblewimble/docs/wiki)
[![Release Version](https://img.shields.io/github/release/mimblewimble/grin.svg)](https://github.com/mimblewimble/grin/releases)
[![License](https://img.shields.io/github/license/mimblewimble/grin.svg)](https://github.com/mimblewimble/grin/blob/master/LICENSE)
# Grin # Grin

View file

@ -1,6 +1,6 @@
[package] [package]
name = "grin_api" name = "grin_api"
version = "1.1.0" version = "1.1.0-beta.1"
authors = ["Grin Developers <mimblewimble@lists.launchpad.net>"] authors = ["Grin Developers <mimblewimble@lists.launchpad.net>"]
description = "APIs for grin, a simple, private and scalable cryptocurrency implementation based on the MimbleWimble chain format." description = "APIs for grin, a simple, private and scalable cryptocurrency implementation based on the MimbleWimble chain format."
license = "Apache-2.0" license = "Apache-2.0"
@ -30,9 +30,9 @@ futures = "0.1.21"
rustls = "0.13" rustls = "0.13"
url = "1.7.0" url = "1.7.0"
grin_core = { path = "../core", version = "1.1.0" } grin_core = { path = "../core", version = "1.1.0-beta.1" }
grin_chain = { path = "../chain", version = "1.1.0" } grin_chain = { path = "../chain", version = "1.1.0-beta.1" }
grin_p2p = { path = "../p2p", version = "1.1.0" } grin_p2p = { path = "../p2p", version = "1.1.0-beta.1" }
grin_pool = { path = "../pool", version = "1.1.0" } grin_pool = { path = "../pool", version = "1.1.0-beta.1" }
grin_store = { path = "../store", version = "1.1.0" } grin_store = { path = "../store", version = "1.1.0-beta.1" }
grin_util = { path = "../util", version = "1.1.0" } grin_util = { path = "../util", version = "1.1.0-beta.1" }

View file

@ -13,19 +13,25 @@
// limitations under the License. // limitations under the License.
use crate::router::{Handler, HandlerObj, ResponseFuture}; use crate::router::{Handler, HandlerObj, ResponseFuture};
use crate::web::response;
use futures::future::ok; use futures::future::ok;
use hyper::header::{HeaderValue, AUTHORIZATION, WWW_AUTHENTICATE}; use hyper::header::{HeaderValue, AUTHORIZATION, WWW_AUTHENTICATE};
use hyper::{Body, Request, Response, StatusCode}; use hyper::{Body, Request, Response, StatusCode};
use ring::constant_time::verify_slices_are_equal; use ring::constant_time::verify_slices_are_equal;
lazy_static! {
pub static ref GRIN_BASIC_REALM: HeaderValue =
HeaderValue::from_str("Basic realm=GrinAPI").unwrap();
}
// Basic Authentication Middleware // Basic Authentication Middleware
pub struct BasicAuthMiddleware { pub struct BasicAuthMiddleware {
api_basic_auth: String, api_basic_auth: String,
basic_realm: String, basic_realm: &'static HeaderValue,
} }
impl BasicAuthMiddleware { impl BasicAuthMiddleware {
pub fn new(api_basic_auth: String, basic_realm: String) -> BasicAuthMiddleware { pub fn new(api_basic_auth: String, basic_realm: &'static HeaderValue) -> BasicAuthMiddleware {
BasicAuthMiddleware { BasicAuthMiddleware {
api_basic_auth, api_basic_auth,
basic_realm, basic_realm,
@ -39,8 +45,12 @@ impl Handler for BasicAuthMiddleware {
req: Request<Body>, req: Request<Body>,
mut handlers: Box<dyn Iterator<Item = HandlerObj>>, mut handlers: Box<dyn Iterator<Item = HandlerObj>>,
) -> ResponseFuture { ) -> ResponseFuture {
let next_handler = match handlers.next() {
Some(h) => h,
None => return response(StatusCode::INTERNAL_SERVER_ERROR, "no handler found"),
};
if req.method().as_str() == "OPTIONS" { if req.method().as_str() == "OPTIONS" {
return handlers.next().unwrap().call(req, handlers); return next_handler.call(req, handlers);
} }
if req.headers().contains_key(AUTHORIZATION) if req.headers().contains_key(AUTHORIZATION)
&& verify_slices_are_equal( && verify_slices_are_equal(
@ -49,7 +59,7 @@ impl Handler for BasicAuthMiddleware {
) )
.is_ok() .is_ok()
{ {
handlers.next().unwrap().call(req, handlers) next_handler.call(req, handlers)
} else { } else {
// Unauthorized 401 // Unauthorized 401
unauthorized_response(&self.basic_realm) unauthorized_response(&self.basic_realm)
@ -57,13 +67,10 @@ impl Handler for BasicAuthMiddleware {
} }
} }
fn unauthorized_response(basic_realm: &str) -> ResponseFuture { fn unauthorized_response(basic_realm: &HeaderValue) -> ResponseFuture {
let response = Response::builder() let response = Response::builder()
.status(StatusCode::UNAUTHORIZED) .status(StatusCode::UNAUTHORIZED)
.header( .header(WWW_AUTHENTICATE, basic_realm)
WWW_AUTHENTICATE,
HeaderValue::from_str(basic_realm).unwrap(),
)
.body(Body::empty()) .body(Body::empty())
.unwrap(); .unwrap();
Box::new(ok(response)) Box::new(ok(response))

View file

@ -19,7 +19,7 @@ use crate::util::to_base64;
use failure::{Fail, ResultExt}; use failure::{Fail, ResultExt};
use futures::future::{err, ok, Either}; use futures::future::{err, ok, Either};
use http::uri::{InvalidUri, Uri}; use http::uri::{InvalidUri, Uri};
use hyper::header::{ACCEPT, AUTHORIZATION, USER_AGENT}; use hyper::header::{ACCEPT, AUTHORIZATION, CONTENT_TYPE, USER_AGENT};
use hyper::rt::{Future, Stream}; use hyper::rt::{Future, Stream};
use hyper::{Body, Client, Request}; use hyper::{Body, Client, Request};
use hyper_rustls; use hyper_rustls;
@ -136,9 +136,8 @@ fn build_request<'a>(
.into() .into()
})?; })?;
let mut builder = Request::builder(); let mut builder = Request::builder();
if api_secret.is_some() { if let Some(api_secret) = api_secret {
let basic_auth = let basic_auth = format!("Basic {}", to_base64(&format!("grin:{}", api_secret)));
"Basic ".to_string() + &to_base64(&("grin:".to_string() + &api_secret.unwrap()));
builder.header(AUTHORIZATION, basic_auth); builder.header(AUTHORIZATION, basic_auth);
} }
@ -147,6 +146,7 @@ fn build_request<'a>(
.uri(uri) .uri(uri)
.header(USER_AGENT, "grin-client") .header(USER_AGENT, "grin-client")
.header(ACCEPT, "application/json") .header(ACCEPT, "application/json")
.header(CONTENT_TYPE, "application/json")
.body(match body { .body(match body {
None => Body::empty(), None => Body::empty(),
Some(json) => json.into(), Some(json) => json.into(),
@ -202,9 +202,11 @@ fn send_request_async(req: Request<Body>) -> Box<dyn Future<Item = String, Error
.map_err(|e| ErrorKind::RequestError(format!("Cannot make request: {}", e)).into()) .map_err(|e| ErrorKind::RequestError(format!("Cannot make request: {}", e)).into())
.and_then(|resp| { .and_then(|resp| {
if !resp.status().is_success() { if !resp.status().is_success() {
Either::A(err(ErrorKind::RequestError( Either::A(err(ErrorKind::RequestError(format!(
"Wrong response code".to_owned(), "Wrong response code: {} with data {:?}",
) resp.status(),
resp.body()
))
.into())) .into()))
} else { } else {
Either::B( Either::B(
@ -223,6 +225,7 @@ fn send_request_async(req: Request<Body>) -> Box<dyn Future<Item = String, Error
fn send_request(req: Request<Body>) -> Result<String, Error> { fn send_request(req: Request<Body>) -> Result<String, Error> {
let task = send_request_async(req); let task = send_request_async(req);
let mut rt = Runtime::new().unwrap(); let mut rt =
Runtime::new().context(ErrorKind::Internal("can't create Tokio runtime".to_owned()))?;
Ok(rt.block_on(task)?) Ok(rt.block_on(task)?)
} }

View file

@ -20,39 +20,26 @@ mod server_api;
mod transactions_api; mod transactions_api;
mod utils; mod utils;
use crate::router::{Router, RouterError};
// Server
use self::server_api::IndexHandler;
use self::server_api::StatusHandler;
// Blocks
use self::blocks_api::BlockHandler; use self::blocks_api::BlockHandler;
use self::blocks_api::HeaderHandler; use self::blocks_api::HeaderHandler;
// TX Set
use self::transactions_api::TxHashSetHandler;
// Chain
use self::chain_api::ChainCompactHandler; use self::chain_api::ChainCompactHandler;
use self::chain_api::ChainHandler; use self::chain_api::ChainHandler;
use self::chain_api::ChainValidationHandler; use self::chain_api::ChainValidationHandler;
use self::chain_api::OutputHandler; use self::chain_api::OutputHandler;
// Pool Handlers
use self::pool_api::PoolInfoHandler;
use self::pool_api::PoolPushHandler;
// Peers
use self::peers_api::PeerHandler; use self::peers_api::PeerHandler;
use self::peers_api::PeersAllHandler; use self::peers_api::PeersAllHandler;
use self::peers_api::PeersConnectedHandler; use self::peers_api::PeersConnectedHandler;
use self::pool_api::PoolInfoHandler;
use crate::auth::BasicAuthMiddleware; use self::pool_api::PoolPushHandler;
use self::server_api::IndexHandler;
use self::server_api::StatusHandler;
use self::transactions_api::TxHashSetHandler;
use crate::auth::{BasicAuthMiddleware, GRIN_BASIC_REALM};
use crate::chain; use crate::chain;
use crate::p2p; use crate::p2p;
use crate::pool; use crate::pool;
use crate::rest::*; use crate::rest::*;
use crate::router::{Router, RouterError};
use crate::util; use crate::util;
use crate::util::RwLock; use crate::util::RwLock;
use std::net::SocketAddr; use std::net::SocketAddr;
@ -76,11 +63,10 @@ pub fn start_rest_apis(
) -> bool { ) -> bool {
let mut apis = ApiServer::new(); let mut apis = ApiServer::new();
let mut router = build_router(chain, tx_pool, peers).expect("unable to build API router"); let mut router = build_router(chain, tx_pool, peers).expect("unable to build API router");
if api_secret.is_some() { if let Some(api_secret) = api_secret {
let api_basic_auth = let api_basic_auth = format!("Basic {}", util::to_base64(&format!("grin:{}", api_secret)));
"Basic ".to_string() + &util::to_base64(&("grin:".to_string() + &api_secret.unwrap())); let basic_auth_middleware =
let basic_realm = "Basic realm=GrinAPI".to_string(); Arc::new(BasicAuthMiddleware::new(api_basic_auth, &GRIN_BASIC_REALM));
let basic_auth_middleware = Arc::new(BasicAuthMiddleware::new(api_basic_auth, basic_realm));
router.add_middleware(basic_auth_middleware); router.add_middleware(basic_auth_middleware);
} }
@ -103,16 +89,19 @@ pub fn build_router(
) -> Result<Router, RouterError> { ) -> Result<Router, RouterError> {
let route_list = vec![ let route_list = vec![
"get blocks".to_string(), "get blocks".to_string(),
"get headers".to_string(),
"get chain".to_string(), "get chain".to_string(),
"post chain/compact".to_string(), "post chain/compact".to_string(),
"post chain/validate".to_string(), "get chain/validate".to_string(),
"get chain/outputs".to_string(), "get chain/outputs/byids?id=xxx,yyy,zzz".to_string(),
"get chain/outputs/byheight?start_height=101&end_height=200".to_string(),
"get status".to_string(), "get status".to_string(),
"get txhashset/roots".to_string(), "get txhashset/roots".to_string(),
"get txhashset/lastoutputs?n=10".to_string(), "get txhashset/lastoutputs?n=10".to_string(),
"get txhashset/lastrangeproofs".to_string(), "get txhashset/lastrangeproofs".to_string(),
"get txhashset/lastkernels".to_string(), "get txhashset/lastkernels".to_string(),
"get txhashset/outputs?start_index=1&max=100".to_string(), "get txhashset/outputs?start_index=1&max=100".to_string(),
"get txhashset/merkleproof?n=1".to_string(),
"get pool".to_string(), "get pool".to_string(),
"post pool/push".to_string(), "post pool/push".to_string(),
"post peers/a.b.c.d:p/ban".to_string(), "post peers/a.b.c.d:p/ban".to_string(),

View file

@ -41,7 +41,7 @@ impl HeaderHandler {
return Ok(h); return Ok(h);
} }
if let Ok(height) = input.parse() { if let Ok(height) = input.parse() {
match w(&self.chain).get_header_by_height(height) { match w(&self.chain)?.get_header_by_height(height) {
Ok(header) => return Ok(BlockHeaderPrintable::from_header(&header)), Ok(header) => return Ok(BlockHeaderPrintable::from_header(&header)),
Err(_) => return Err(ErrorKind::NotFound)?, Err(_) => return Err(ErrorKind::NotFound)?,
} }
@ -50,7 +50,7 @@ impl HeaderHandler {
let vec = util::from_hex(input) let vec = util::from_hex(input)
.map_err(|e| ErrorKind::Argument(format!("invalid input: {}", e)))?; .map_err(|e| ErrorKind::Argument(format!("invalid input: {}", e)))?;
let h = Hash::from_vec(&vec); let h = Hash::from_vec(&vec);
let header = w(&self.chain) let header = w(&self.chain)?
.get_block_header(&h) .get_block_header(&h)
.context(ErrorKind::NotFound)?; .context(ErrorKind::NotFound)?;
Ok(BlockHeaderPrintable::from_header(&header)) Ok(BlockHeaderPrintable::from_header(&header))
@ -58,7 +58,7 @@ impl HeaderHandler {
fn get_header_for_output(&self, commit_id: String) -> Result<BlockHeaderPrintable, Error> { fn get_header_for_output(&self, commit_id: String) -> Result<BlockHeaderPrintable, Error> {
let oid = get_output(&self.chain, &commit_id)?.1; let oid = get_output(&self.chain, &commit_id)?.1;
match w(&self.chain).get_header_for_output(&oid) { match w(&self.chain)?.get_header_for_output(&oid) {
Ok(header) => Ok(BlockHeaderPrintable::from_header(&header)), Ok(header) => Ok(BlockHeaderPrintable::from_header(&header)),
Err(_) => Err(ErrorKind::NotFound)?, Err(_) => Err(ErrorKind::NotFound)?,
} }
@ -85,22 +85,23 @@ pub struct BlockHandler {
impl BlockHandler { impl BlockHandler {
fn get_block(&self, h: &Hash) -> Result<BlockPrintable, Error> { fn get_block(&self, h: &Hash) -> Result<BlockPrintable, Error> {
let block = w(&self.chain).get_block(h).context(ErrorKind::NotFound)?; let chain = w(&self.chain)?;
Ok(BlockPrintable::from_block(&block, w(&self.chain), false)) let block = chain.get_block(h).context(ErrorKind::NotFound)?;
BlockPrintable::from_block(&block, chain, false)
.map_err(|_| ErrorKind::Internal("chain error".to_owned()).into())
} }
fn get_compact_block(&self, h: &Hash) -> Result<CompactBlockPrintable, Error> { fn get_compact_block(&self, h: &Hash) -> Result<CompactBlockPrintable, Error> {
let block = w(&self.chain).get_block(h).context(ErrorKind::NotFound)?; let chain = w(&self.chain)?;
Ok(CompactBlockPrintable::from_compact_block( let block = chain.get_block(h).context(ErrorKind::NotFound)?;
&block.into(), CompactBlockPrintable::from_compact_block(&block.into(), chain)
w(&self.chain), .map_err(|_| ErrorKind::Internal("chain error".to_owned()).into())
))
} }
// Try to decode the string as a height or a hash. // Try to decode the string as a height or a hash.
fn parse_input(&self, input: String) -> Result<Hash, Error> { fn parse_input(&self, input: String) -> Result<Hash, Error> {
if let Ok(height) = input.parse() { if let Ok(height) = input.parse() {
match w(&self.chain).get_header_by_height(height) { match w(&self.chain)?.get_header_by_height(height) {
Ok(header) => return Ok(header.hash()), Ok(header) => return Ok(header.hash()),
Err(_) => return Err(ErrorKind::NotFound)?, Err(_) => return Err(ErrorKind::NotFound)?,
} }

View file

@ -21,6 +21,7 @@ use crate::types::*;
use crate::util; use crate::util;
use crate::util::secp::pedersen::Commitment; use crate::util::secp::pedersen::Commitment;
use crate::web::*; use crate::web::*;
use failure::ResultExt;
use hyper::{Body, Request, StatusCode}; use hyper::{Body, Request, StatusCode};
use std::sync::Weak; use std::sync::Weak;
@ -32,7 +33,7 @@ pub struct ChainHandler {
impl ChainHandler { impl ChainHandler {
fn get_tip(&self) -> Result<Tip, Error> { fn get_tip(&self) -> Result<Tip, Error> {
let head = w(&self.chain) let head = w(&self.chain)?
.head() .head()
.map_err(|e| ErrorKind::Internal(format!("can't get head: {}", e)))?; .map_err(|e| ErrorKind::Internal(format!("can't get head: {}", e)))?;
Ok(Tip::from_tip(head)) Ok(Tip::from_tip(head))
@ -53,7 +54,7 @@ pub struct ChainValidationHandler {
impl Handler for ChainValidationHandler { impl Handler for ChainValidationHandler {
fn get(&self, _req: Request<Body>) -> ResponseFuture { fn get(&self, _req: Request<Body>) -> ResponseFuture {
match w(&self.chain).validate(true) { match w_fut!(&self.chain).validate(true) {
Ok(_) => response(StatusCode::OK, "{}"), Ok(_) => response(StatusCode::OK, "{}"),
Err(e) => response( Err(e) => response(
StatusCode::INTERNAL_SERVER_ERROR, StatusCode::INTERNAL_SERVER_ERROR,
@ -72,7 +73,7 @@ pub struct ChainCompactHandler {
impl Handler for ChainCompactHandler { impl Handler for ChainCompactHandler {
fn post(&self, _req: Request<Body>) -> ResponseFuture { fn post(&self, _req: Request<Body>) -> ResponseFuture {
match w(&self.chain).compact() { match w_fut!(&self.chain).compact() {
Ok(_) => response(StatusCode::OK, "{}"), Ok(_) => response(StatusCode::OK, "{}"),
Err(e) => response( Err(e) => response(
StatusCode::INTERNAL_SERVER_ERROR, StatusCode::INTERNAL_SERVER_ERROR,
@ -105,9 +106,13 @@ impl OutputHandler {
let mut outputs: Vec<Output> = vec![]; let mut outputs: Vec<Output> = vec![];
for x in commitments { for x in commitments {
if let Ok(output) = self.get_output(&x) { match self.get_output(&x) {
outputs.push(output); Ok(output) => outputs.push(output),
} Err(e) => error!(
"Failure to get output for commitment {} with error {}",
x, e
),
};
} }
Ok(outputs) Ok(outputs)
} }
@ -118,13 +123,14 @@ impl OutputHandler {
commitments: Vec<Commitment>, commitments: Vec<Commitment>,
include_proof: bool, include_proof: bool,
) -> Result<BlockOutputs, Error> { ) -> Result<BlockOutputs, Error> {
let header = w(&self.chain) let header = w(&self.chain)?
.get_header_by_height(block_height) .get_header_by_height(block_height)
.map_err(|_| ErrorKind::NotFound)?; .map_err(|_| ErrorKind::NotFound)?;
// TODO - possible to compact away blocks we care about // TODO - possible to compact away blocks we care about
// in the period between accepting the block and refreshing the wallet // in the period between accepting the block and refreshing the wallet
let block = w(&self.chain) let chain = w(&self.chain)?;
let block = chain
.get_block(&header.hash()) .get_block(&header.hash())
.map_err(|_| ErrorKind::NotFound)?; .map_err(|_| ErrorKind::NotFound)?;
let outputs = block let outputs = block
@ -132,9 +138,10 @@ impl OutputHandler {
.iter() .iter()
.filter(|output| commitments.is_empty() || commitments.contains(&output.commit)) .filter(|output| commitments.is_empty() || commitments.contains(&output.commit))
.map(|output| { .map(|output| {
OutputPrintable::from_output(output, w(&self.chain), Some(&header), include_proof) OutputPrintable::from_output(output, chain.clone(), Some(&header), include_proof)
}) })
.collect(); .collect::<Result<Vec<_>, _>>()
.context(ErrorKind::Internal("cain error".to_owned()))?;
Ok(BlockOutputs { Ok(BlockOutputs {
header: BlockHeaderInfo::from_header(&header), header: BlockHeaderInfo::from_header(&header),

View file

@ -26,7 +26,7 @@ pub struct PeersAllHandler {
impl Handler for PeersAllHandler { impl Handler for PeersAllHandler {
fn get(&self, _req: Request<Body>) -> ResponseFuture { fn get(&self, _req: Request<Body>) -> ResponseFuture {
let peers = &w(&self.peers).all_peers(); let peers = &w_fut!(&self.peers).all_peers();
json_response_pretty(&peers) json_response_pretty(&peers)
} }
} }
@ -37,7 +37,7 @@ pub struct PeersConnectedHandler {
impl Handler for PeersConnectedHandler { impl Handler for PeersConnectedHandler {
fn get(&self, _req: Request<Body>) -> ResponseFuture { fn get(&self, _req: Request<Body>) -> ResponseFuture {
let peers: Vec<PeerInfoDisplay> = w(&self.peers) let peers: Vec<PeerInfoDisplay> = w_fut!(&self.peers)
.connected_peers() .connected_peers()
.iter() .iter()
.map(|p| p.info.clone().into()) .map(|p| p.info.clone().into())
@ -73,13 +73,13 @@ impl Handler for PeerHandler {
); );
} }
match w(&self.peers).get_peer(peer_addr) { match w_fut!(&self.peers).get_peer(peer_addr) {
Ok(peer) => json_response(&peer), Ok(peer) => json_response(&peer),
Err(_) => response(StatusCode::NOT_FOUND, "peer not found"), Err(_) => response(StatusCode::NOT_FOUND, "peer not found"),
} }
} }
fn post(&self, req: Request<Body>) -> ResponseFuture { fn post(&self, req: Request<Body>) -> ResponseFuture {
let mut path_elems = req.uri().path().trim_right_matches('/').rsplit('/'); let mut path_elems = req.uri().path().trim_end_matches('/').rsplit('/');
let command = match path_elems.next() { let command = match path_elems.next() {
None => return response(StatusCode::BAD_REQUEST, "invalid url"), None => return response(StatusCode::BAD_REQUEST, "invalid url"),
Some(c) => c, Some(c) => c,
@ -101,8 +101,8 @@ impl Handler for PeerHandler {
}; };
match command { match command {
"ban" => w(&self.peers).ban_peer(addr, ReasonForBan::ManualBan), "ban" => w_fut!(&self.peers).ban_peer(addr, ReasonForBan::ManualBan),
"unban" => w(&self.peers).unban_peer(addr), "unban" => w_fut!(&self.peers).unban_peer(addr),
_ => return response(StatusCode::BAD_REQUEST, "invalid command"), _ => return response(StatusCode::BAD_REQUEST, "invalid command"),
}; };

View file

@ -24,7 +24,7 @@ use crate::util;
use crate::util::RwLock; use crate::util::RwLock;
use crate::web::*; use crate::web::*;
use failure::ResultExt; use failure::ResultExt;
use futures::future::ok; use futures::future::{err, ok};
use futures::Future; use futures::Future;
use hyper::{Body, Request, StatusCode}; use hyper::{Body, Request, StatusCode};
use std::sync::Weak; use std::sync::Weak;
@ -37,7 +37,7 @@ pub struct PoolInfoHandler {
impl Handler for PoolInfoHandler { impl Handler for PoolInfoHandler {
fn get(&self, _req: Request<Body>) -> ResponseFuture { fn get(&self, _req: Request<Body>) -> ResponseFuture {
let pool_arc = w(&self.tx_pool); let pool_arc = w_fut!(&self.tx_pool);
let pool = pool_arc.read(); let pool = pool_arc.read();
json_response(&PoolInfo { json_response(&PoolInfo {
@ -63,7 +63,11 @@ impl PoolPushHandler {
let params = QueryParams::from(req.uri().query()); let params = QueryParams::from(req.uri().query());
let fluff = params.get("fluff").is_some(); let fluff = params.get("fluff").is_some();
let pool_arc = w(&self.tx_pool).clone(); let pool_arc = match w(&self.tx_pool) {
//w(&self.tx_pool).clone();
Ok(p) => p,
Err(e) => return Box::new(err(e)),
};
Box::new( Box::new(
parse_body(req) parse_body(req)

View file

@ -45,12 +45,12 @@ pub struct StatusHandler {
impl StatusHandler { impl StatusHandler {
fn get_status(&self) -> Result<Status, Error> { fn get_status(&self) -> Result<Status, Error> {
let head = w(&self.chain) let head = w(&self.chain)?
.head() .head()
.map_err(|e| ErrorKind::Internal(format!("can't get head: {}", e)))?; .map_err(|e| ErrorKind::Internal(format!("can't get head: {}", e)))?;
Ok(Status::from_tip_and_peers( Ok(Status::from_tip_and_peers(
head, head,
w(&self.peers).peer_count(), w(&self.peers)?.peer_count(),
)) ))
} }
} }

View file

@ -45,23 +45,26 @@ pub struct TxHashSetHandler {
impl TxHashSetHandler { impl TxHashSetHandler {
// gets roots // gets roots
fn get_roots(&self) -> TxHashSet { fn get_roots(&self) -> Result<TxHashSet, Error> {
TxHashSet::from_head(w(&self.chain)) Ok(TxHashSet::from_head(w(&self.chain)?))
} }
// gets last n outputs inserted in to the tree // gets last n outputs inserted in to the tree
fn get_last_n_output(&self, distance: u64) -> Vec<TxHashSetNode> { fn get_last_n_output(&self, distance: u64) -> Result<Vec<TxHashSetNode>, Error> {
TxHashSetNode::get_last_n_output(w(&self.chain), distance) Ok(TxHashSetNode::get_last_n_output(w(&self.chain)?, distance))
} }
// gets last n outputs inserted in to the tree // gets last n outputs inserted in to the tree
fn get_last_n_rangeproof(&self, distance: u64) -> Vec<TxHashSetNode> { fn get_last_n_rangeproof(&self, distance: u64) -> Result<Vec<TxHashSetNode>, Error> {
TxHashSetNode::get_last_n_rangeproof(w(&self.chain), distance) Ok(TxHashSetNode::get_last_n_rangeproof(
w(&self.chain)?,
distance,
))
} }
// gets last n outputs inserted in to the tree // gets last n outputs inserted in to the tree
fn get_last_n_kernel(&self, distance: u64) -> Vec<TxHashSetNode> { fn get_last_n_kernel(&self, distance: u64) -> Result<Vec<TxHashSetNode>, Error> {
TxHashSetNode::get_last_n_kernel(w(&self.chain), distance) Ok(TxHashSetNode::get_last_n_kernel(w(&self.chain)?, distance))
} }
// allows traversal of utxo set // allows traversal of utxo set
@ -70,18 +73,21 @@ impl TxHashSetHandler {
if max > 1000 { if max > 1000 {
max = 1000; max = 1000;
} }
let outputs = w(&self.chain) let chain = w(&self.chain)?;
let outputs = chain
.unspent_outputs_by_insertion_index(start_index, max) .unspent_outputs_by_insertion_index(start_index, max)
.context(ErrorKind::NotFound)?; .context(ErrorKind::NotFound)?;
Ok(OutputListing { let out = OutputListing {
last_retrieved_index: outputs.0, last_retrieved_index: outputs.0,
highest_index: outputs.1, highest_index: outputs.1,
outputs: outputs outputs: outputs
.2 .2
.iter() .iter()
.map(|x| OutputPrintable::from_output(x, w(&self.chain), None, true)) .map(|x| OutputPrintable::from_output(x, chain.clone(), None, true))
.collect(), .collect::<Result<Vec<_>, _>>()
}) .context(ErrorKind::Internal("cain error".to_owned()))?,
};
Ok(out)
} }
// return a dummy output with merkle proof for position filled out // return a dummy output with merkle proof for position filled out
@ -92,10 +98,9 @@ impl TxHashSetHandler {
id id
)))?; )))?;
let commit = Commitment::from_vec(c); let commit = Commitment::from_vec(c);
let output_pos = w(&self.chain) let chain = w(&self.chain)?;
.get_output_pos(&commit) let output_pos = chain.get_output_pos(&commit).context(ErrorKind::NotFound)?;
.context(ErrorKind::NotFound)?; let merkle_proof = chain::Chain::get_merkle_proof_for_pos(&chain, commit)
let merkle_proof = chain::Chain::get_merkle_proof_for_pos(&w(&self.chain), commit)
.map_err(|_| ErrorKind::NotFound)?; .map_err(|_| ErrorKind::NotFound)?;
Ok(OutputPrintable { Ok(OutputPrintable {
output_type: OutputType::Coinbase, output_type: OutputType::Coinbase,
@ -120,10 +125,10 @@ impl Handler for TxHashSetHandler {
let id = parse_param_no_err!(params, "id", "".to_owned()); let id = parse_param_no_err!(params, "id", "".to_owned());
match right_path_element!(req) { match right_path_element!(req) {
"roots" => json_response_pretty(&self.get_roots()), "roots" => result_to_response(self.get_roots()),
"lastoutputs" => json_response_pretty(&self.get_last_n_output(last_n)), "lastoutputs" => result_to_response(self.get_last_n_output(last_n)),
"lastrangeproofs" => json_response_pretty(&self.get_last_n_rangeproof(last_n)), "lastrangeproofs" => result_to_response(self.get_last_n_rangeproof(last_n)),
"lastkernels" => json_response_pretty(&self.get_last_n_kernel(last_n)), "lastkernels" => result_to_response(self.get_last_n_kernel(last_n)),
"outputs" => result_to_response(self.outputs(start_index, max)), "outputs" => result_to_response(self.outputs(start_index, max)),
"merkleproof" => result_to_response(self.get_merkle_proof_for_output(&id)), "merkleproof" => result_to_response(self.get_merkle_proof_for_output(&id)),
_ => response(StatusCode::BAD_REQUEST, ""), _ => response(StatusCode::BAD_REQUEST, ""),

View file

@ -24,8 +24,9 @@ use std::sync::{Arc, Weak};
// All handlers use `Weak` references instead of `Arc` to avoid cycles that // All handlers use `Weak` references instead of `Arc` to avoid cycles that
// can never be destroyed. These 2 functions are simple helpers to reduce the // can never be destroyed. These 2 functions are simple helpers to reduce the
// boilerplate of dealing with `Weak`. // boilerplate of dealing with `Weak`.
pub fn w<T>(weak: &Weak<T>) -> Arc<T> { pub fn w<T>(weak: &Weak<T>) -> Result<Arc<T>, Error> {
weak.upgrade().unwrap() weak.upgrade()
.ok_or_else(|| ErrorKind::Internal("failed to upgrade weak refernce".to_owned()).into())
} }
/// Retrieves an output from the chain given a commit id (a tiny bit iteratively) /// Retrieves an output from the chain given a commit id (a tiny bit iteratively)
@ -48,14 +49,16 @@ pub fn get_output(
OutputIdentifier::new(OutputFeatures::Coinbase, &commit), OutputIdentifier::new(OutputFeatures::Coinbase, &commit),
]; ];
for x in outputs.iter().filter(|x| w(chain).is_unspent(x).is_ok()) { let chain = w(chain)?;
let block_height = w(chain)
for x in outputs.iter().filter(|x| chain.is_unspent(x).is_ok()) {
let block_height = chain
.get_header_for_output(&x) .get_header_for_output(&x)
.context(ErrorKind::Internal( .context(ErrorKind::Internal(
"Can't get header for output".to_owned(), "Can't get header for output".to_owned(),
))? ))?
.height; .height;
let output_pos = w(chain).get_output_pos(&x.commit).unwrap_or(0); let output_pos = chain.get_output_pos(&x.commit).unwrap_or(0);
return Ok((Output::new(&commit, block_height, output_pos), x.clone())); return Ok((Output::new(&commit, block_height, output_pos), x.clone()));
} }
Err(ErrorKind::NotFound)? Err(ErrorKind::NotFound)?

View file

@ -39,7 +39,7 @@ mod rest;
mod router; mod router;
mod types; mod types;
pub use crate::auth::BasicAuthMiddleware; pub use crate::auth::{BasicAuthMiddleware, GRIN_BASIC_REALM};
pub use crate::handlers::start_rest_apis; pub use crate::handlers::start_rest_apis;
pub use crate::rest::*; pub use crate::rest::*;
pub use crate::router::*; pub use crate::router::*;

View file

@ -19,11 +19,12 @@
//! register them on a ApiServer. //! register them on a ApiServer.
use crate::router::{Handler, HandlerObj, ResponseFuture, Router}; use crate::router::{Handler, HandlerObj, ResponseFuture, Router};
use crate::web::response;
use failure::{Backtrace, Context, Fail, ResultExt}; use failure::{Backtrace, Context, Fail, ResultExt};
use futures::sync::oneshot; use futures::sync::oneshot;
use futures::Stream; use futures::Stream;
use hyper::rt::Future; use hyper::rt::Future;
use hyper::{rt, Body, Request, Server}; use hyper::{rt, Body, Request, Server, StatusCode};
use rustls; use rustls;
use rustls::internal::pemfile; use rustls::internal::pemfile;
use std::fmt::{self, Display}; use std::fmt::{self, Display};
@ -264,6 +265,9 @@ impl Handler for LoggingMiddleware {
mut handlers: Box<dyn Iterator<Item = HandlerObj>>, mut handlers: Box<dyn Iterator<Item = HandlerObj>>,
) -> ResponseFuture { ) -> ResponseFuture {
debug!("REST call: {} {}", req.method(), req.uri().path()); debug!("REST call: {} {}", req.method(), req.uri().path());
handlers.next().unwrap().call(req, handlers) match handlers.next() {
Some(handler) => handler.call(req, handlers),
None => response(StatusCode::INTERNAL_SERVER_ERROR, "no handler found"),
}
} }
} }

View file

@ -222,7 +222,9 @@ impl<'de> serde::de::Visitor<'de> for PrintableCommitmentVisitor {
E: serde::de::Error, E: serde::de::Error,
{ {
Ok(PrintableCommitment { Ok(PrintableCommitment {
commit: pedersen::Commitment::from_vec(util::from_hex(String::from(v)).unwrap()), commit: pedersen::Commitment::from_vec(
util::from_hex(String::from(v)).map_err(serde::de::Error::custom)?,
),
}) })
} }
} }
@ -255,7 +257,7 @@ impl OutputPrintable {
chain: Arc<chain::Chain>, chain: Arc<chain::Chain>,
block_header: Option<&core::BlockHeader>, block_header: Option<&core::BlockHeader>,
include_proof: bool, include_proof: bool,
) -> OutputPrintable { ) -> Result<OutputPrintable, chain::Error> {
let output_type = if output.is_coinbase() { let output_type = if output.is_coinbase() {
OutputType::Coinbase OutputType::Coinbase
} else { } else {
@ -266,7 +268,7 @@ impl OutputPrintable {
let spent = chain.is_unspent(&out_id).is_err(); let spent = chain.is_unspent(&out_id).is_err();
let block_height = match spent { let block_height = match spent {
true => None, true => None,
false => Some(chain.get_header_for_output(&out_id).unwrap().height), false => Some(chain.get_header_for_output(&out_id)?.height),
}; };
let proof = if include_proof { let proof = if include_proof {
@ -280,13 +282,15 @@ impl OutputPrintable {
// We require the rewind() to be stable even after the PMMR is pruned and // We require the rewind() to be stable even after the PMMR is pruned and
// compacted so we can still recreate the necessary proof. // compacted so we can still recreate the necessary proof.
let mut merkle_proof = None; let mut merkle_proof = None;
if output.is_coinbase() && !spent && block_header.is_some() { if output.is_coinbase() && !spent {
merkle_proof = chain.get_merkle_proof(&out_id, &block_header.unwrap()).ok() if let Some(block_header) = block_header {
merkle_proof = chain.get_merkle_proof(&out_id, &block_header).ok();
}
}; };
let output_pos = chain.get_output_pos(&output.commit).unwrap_or(0); let output_pos = chain.get_output_pos(&output.commit).unwrap_or(0);
OutputPrintable { Ok(OutputPrintable {
output_type, output_type,
commit: output.commit, commit: output.commit,
spent, spent,
@ -295,7 +299,7 @@ impl OutputPrintable {
block_height, block_height,
merkle_proof, merkle_proof,
mmr_index: output_pos, mmr_index: output_pos,
} })
} }
pub fn commit(&self) -> Result<pedersen::Commitment, ser::Error> { pub fn commit(&self) -> Result<pedersen::Commitment, ser::Error> {
@ -303,12 +307,13 @@ impl OutputPrintable {
} }
pub fn range_proof(&self) -> Result<pedersen::RangeProof, ser::Error> { pub fn range_proof(&self) -> Result<pedersen::RangeProof, ser::Error> {
let proof_str = self let proof_str = match self.proof.clone() {
.proof Some(p) => p,
.clone() None => return Err(ser::Error::HexError(format!("output range_proof missing"))),
.ok_or_else(|| ser::Error::HexError(format!("output range_proof missing"))) };
.unwrap();
let p_vec = util::from_hex(proof_str).unwrap(); let p_vec = util::from_hex(proof_str)
.map_err(|_| ser::Error::HexError(format!("invalud output range_proof")))?;
let mut p_bytes = [0; util::secp::constants::MAX_PROOF_SIZE]; let mut p_bytes = [0; util::secp::constants::MAX_PROOF_SIZE];
for i in 0..p_bytes.len() { for i in 0..p_bytes.len() {
p_bytes[i] = p_vec[i]; p_bytes[i] = p_vec[i];
@ -428,6 +433,15 @@ impl<'de> serde::de::Deserialize<'de> for OutputPrintable {
} }
} }
if output_type.is_none()
|| commit.is_none()
|| spent.is_none()
|| proof_hash.is_none()
|| mmr_index.is_none()
{
return Err(serde::de::Error::custom("invalid output"));
}
Ok(OutputPrintable { Ok(OutputPrintable {
output_type: output_type.unwrap(), output_type: output_type.unwrap(),
commit: commit.unwrap(), commit: commit.unwrap(),
@ -570,7 +584,7 @@ impl BlockPrintable {
block: &core::Block, block: &core::Block,
chain: Arc<chain::Chain>, chain: Arc<chain::Chain>,
include_proof: bool, include_proof: bool,
) -> BlockPrintable { ) -> Result<BlockPrintable, chain::Error> {
let inputs = block let inputs = block
.inputs() .inputs()
.iter() .iter()
@ -587,18 +601,19 @@ impl BlockPrintable {
include_proof, include_proof,
) )
}) })
.collect(); .collect::<Result<Vec<_>, _>>()?;
let kernels = block let kernels = block
.kernels() .kernels()
.iter() .iter()
.map(|kernel| TxKernelPrintable::from_txkernel(kernel)) .map(|kernel| TxKernelPrintable::from_txkernel(kernel))
.collect(); .collect();
BlockPrintable { Ok(BlockPrintable {
header: BlockHeaderPrintable::from_header(&block.header), header: BlockHeaderPrintable::from_header(&block.header),
inputs: inputs, inputs: inputs,
outputs: outputs, outputs: outputs,
kernels: kernels, kernels: kernels,
} })
} }
} }
@ -620,24 +635,24 @@ impl CompactBlockPrintable {
pub fn from_compact_block( pub fn from_compact_block(
cb: &core::CompactBlock, cb: &core::CompactBlock,
chain: Arc<chain::Chain>, chain: Arc<chain::Chain>,
) -> CompactBlockPrintable { ) -> Result<CompactBlockPrintable, chain::Error> {
let block = chain.get_block(&cb.hash()).unwrap(); let block = chain.get_block(&cb.hash())?;
let out_full = cb let out_full = cb
.out_full() .out_full()
.iter() .iter()
.map(|x| OutputPrintable::from_output(x, chain.clone(), Some(&block.header), false)) .map(|x| OutputPrintable::from_output(x, chain.clone(), Some(&block.header), false))
.collect(); .collect::<Result<Vec<_>, _>>()?;
let kern_full = cb let kern_full = cb
.kern_full() .kern_full()
.iter() .iter()
.map(|x| TxKernelPrintable::from_txkernel(x)) .map(|x| TxKernelPrintable::from_txkernel(x))
.collect(); .collect();
CompactBlockPrintable { Ok(CompactBlockPrintable {
header: BlockHeaderPrintable::from_header(&cb.header), header: BlockHeaderPrintable::from_header(&cb.header),
out_full, out_full,
kern_full, kern_full,
kern_ids: cb.kern_ids().iter().map(|x| x.to_hex()).collect(), kern_ids: cb.kern_ids().iter().map(|x| x.to_hex()).collect(),
} })
} }
} }

View file

@ -180,3 +180,12 @@ macro_rules! parse_param_no_err(
} }
} }
)); ));
#[macro_export]
macro_rules! w_fut(
($p: expr) =>(
match w($p) {
Ok(p) => p,
Err(_) => return response(StatusCode::INTERNAL_SERVER_ERROR, "weak reference upgrade failed" ),
}
));

View file

@ -2,7 +2,7 @@ use grin_api as api;
use grin_util as util; use grin_util as util;
use crate::api::*; use crate::api::*;
use hyper::{Body, Request}; use hyper::{Body, Request, StatusCode};
use std::net::SocketAddr; use std::net::SocketAddr;
use std::sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT}; use std::sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT};
use std::sync::Arc; use std::sync::Arc;
@ -43,7 +43,10 @@ impl Handler for CounterMiddleware {
mut handlers: Box<dyn Iterator<Item = HandlerObj>>, mut handlers: Box<dyn Iterator<Item = HandlerObj>>,
) -> ResponseFuture { ) -> ResponseFuture {
self.counter.fetch_add(1, Ordering::SeqCst); self.counter.fetch_add(1, Ordering::SeqCst);
handlers.next().unwrap().call(req, handlers) match handlers.next() {
Some(h) => h.call(req, handlers),
None => return response(StatusCode::INTERNAL_SERVER_ERROR, "no handler found"),
}
} }
} }

View file

@ -1,6 +1,6 @@
[package] [package]
name = "grin_chain" name = "grin_chain"
version = "1.1.0" version = "1.1.0-beta.1"
authors = ["Grin Developers <mimblewimble@lists.launchpad.net>"] authors = ["Grin Developers <mimblewimble@lists.launchpad.net>"]
description = "Chain implementation for grin, a simple, private and scalable cryptocurrency implementation based on the MimbleWimble chain format." description = "Chain implementation for grin, a simple, private and scalable cryptocurrency implementation based on the MimbleWimble chain format."
license = "Apache-2.0" license = "Apache-2.0"
@ -23,10 +23,10 @@ lru-cache = "0.1"
lazy_static = "1" lazy_static = "1"
regex = "1" regex = "1"
grin_core = { path = "../core", version = "1.1.0" } grin_core = { path = "../core", version = "1.1.0-beta.1" }
grin_keychain = { path = "../keychain", version = "1.1.0" } grin_keychain = { path = "../keychain", version = "1.1.0-beta.1" }
grin_store = { path = "../store", version = "1.1.0" } grin_store = { path = "../store", version = "1.1.0-beta.1" }
grin_util = { path = "../util", version = "1.1.0" } grin_util = { path = "../util", version = "1.1.0-beta.1" }
[dev-dependencies] [dev-dependencies]
env_logger = "0.5" env_logger = "0.5"

View file

@ -35,7 +35,9 @@ use crate::util::secp::pedersen::{Commitment, RangeProof};
use crate::util::{Mutex, RwLock, StopState}; use crate::util::{Mutex, RwLock, StopState};
use grin_store::Error::NotFoundErr; use grin_store::Error::NotFoundErr;
use std::collections::HashMap; use std::collections::HashMap;
use std::env;
use std::fs::File; use std::fs::File;
use std::path::PathBuf;
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc; use std::sync::Arc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
@ -688,7 +690,7 @@ impl Chain {
} }
// prepares the zip and return the corresponding Read // prepares the zip and return the corresponding Read
let txhashset_reader = txhashset::zip_read(self.db_root.clone(), &header)?; let txhashset_reader = txhashset::zip_read(self.db_root.clone(), &header, None)?;
Ok(( Ok((
header.output_mmr_size, header.output_mmr_size,
header.kernel_mmr_size, header.kernel_mmr_size,
@ -763,11 +765,15 @@ impl Chain {
} }
/// Check chain status whether a txhashset downloading is needed /// Check chain status whether a txhashset downloading is needed
pub fn check_txhashset_needed(&self, caller: String, hashes: &mut Option<Vec<Hash>>) -> bool { pub fn check_txhashset_needed(
&self,
caller: String,
hashes: &mut Option<Vec<Hash>>,
) -> Result<bool, Error> {
let horizon = global::cut_through_horizon() as u64; let horizon = global::cut_through_horizon() as u64;
let body_head = self.head().unwrap(); let body_head = self.head()?;
let header_head = self.header_head().unwrap(); let header_head = self.header_head()?;
let sync_head = self.get_sync_head().unwrap(); let sync_head = self.get_sync_head()?;
debug!( debug!(
"{}: body_head - {}, {}, header_head - {}, {}, sync_head - {}, {}", "{}: body_head - {}, {}, header_head - {}, {}, sync_head - {}, {}",
@ -785,7 +791,7 @@ impl Chain {
"{}: no need txhashset. header_head.total_difficulty: {} <= body_head.total_difficulty: {}", "{}: no need txhashset. header_head.total_difficulty: {} <= body_head.total_difficulty: {}",
caller, header_head.total_difficulty, body_head.total_difficulty, caller, header_head.total_difficulty, body_head.total_difficulty,
); );
return false; return Ok(false);
} }
let mut oldest_height = 0; let mut oldest_height = 0;
@ -797,6 +803,7 @@ impl Chain {
"{}: header_head not found in chain db: {} at {}", "{}: header_head not found in chain db: {} at {}",
caller, header_head.last_block_h, header_head.height, caller, header_head.last_block_h, header_head.height,
); );
return Ok(false);
} }
// //
@ -822,17 +829,30 @@ impl Chain {
if oldest_height < header_head.height.saturating_sub(horizon) { if oldest_height < header_head.height.saturating_sub(horizon) {
if oldest_height > 0 { if oldest_height > 0 {
// this is the normal case. for example:
// body head height is 1 (and not a fork), oldest_height will be 2
// body head height is 0 (a typical fresh node), oldest_height will be 1
// body head height is 10,001 (but at a fork), oldest_height will be 10,001
// body head height is 10,005 (but at a fork with depth 5), oldest_height will be 10,001
debug!( debug!(
"{}: need a state sync for txhashset. oldest block which is not on local chain: {} at {}", "{}: need a state sync for txhashset. oldest block which is not on local chain: {} at {}",
caller, oldest_hash, oldest_height, caller, oldest_hash, oldest_height,
); );
return true;
} else { } else {
error!("{}: something is wrong! oldest_height is 0", caller); // this is the abnormal case, when is_on_current_chain() already return Err, and even for genesis block.
return false; error!("{}: corrupted storage? oldest_height is 0 when check_txhashset_needed. state sync is needed", caller);
}; }
Ok(true)
} else {
Ok(false)
} }
return false; }
/// Clean the temporary sandbox folder
pub fn clean_txhashset_sandbox(&self) {
let sandbox_dir = env::temp_dir();
txhashset::clean_txhashset_folder(&sandbox_dir);
txhashset::clean_header_folder(&sandbox_dir);
} }
/// Writes a reading view on a txhashset state that's been provided to us. /// Writes a reading view on a txhashset state that's been provided to us.
@ -849,24 +869,27 @@ impl Chain {
// Initial check whether this txhashset is needed or not // Initial check whether this txhashset is needed or not
let mut hashes: Option<Vec<Hash>> = None; let mut hashes: Option<Vec<Hash>> = None;
if !self.check_txhashset_needed("txhashset_write".to_owned(), &mut hashes) { if !self.check_txhashset_needed("txhashset_write".to_owned(), &mut hashes)? {
warn!("txhashset_write: txhashset received but it's not needed! ignored."); warn!("txhashset_write: txhashset received but it's not needed! ignored.");
return Err(ErrorKind::InvalidTxHashSet("not needed".to_owned()).into()); return Err(ErrorKind::InvalidTxHashSet("not needed".to_owned()).into());
} }
let header = self.get_block_header(&h)?; let header = self.get_block_header(&h)?;
{ // Write txhashset to sandbox (in the os temporary directory)
let mut txhashset_ref = self.txhashset.write(); let sandbox_dir = env::temp_dir();
// Drop file handles in underlying txhashset txhashset::clean_txhashset_folder(&sandbox_dir);
txhashset_ref.release_backend_files(); txhashset::clean_header_folder(&sandbox_dir);
} txhashset::zip_write(sandbox_dir.clone(), txhashset_data.try_clone()?, &header)?;
// Rewrite hashset let mut txhashset = txhashset::TxHashSet::open(
txhashset::zip_write(self.db_root.clone(), txhashset_data, &header)?; sandbox_dir
.to_str()
let mut txhashset = .expect("invalid sandbox folder")
txhashset::TxHashSet::open(self.db_root.clone(), self.store.clone(), Some(&header))?; .to_owned(),
self.store.clone(),
Some(&header),
)?;
// The txhashset.zip contains the output, rangeproof and kernel MMRs. // The txhashset.zip contains the output, rangeproof and kernel MMRs.
// We must rebuild the header MMR ourselves based on the headers in our db. // We must rebuild the header MMR ourselves based on the headers in our db.
@ -918,9 +941,28 @@ impl Chain {
debug!("txhashset_write: finished committing the batch (head etc.)"); debug!("txhashset_write: finished committing the batch (head etc.)");
// Replace the chain txhashset with the newly built one. // Sandbox full validation ok, go to overwrite txhashset on db root
{ {
let mut txhashset_ref = self.txhashset.write(); let mut txhashset_ref = self.txhashset.write();
// Before overwriting, drop file handlers in underlying txhashset
txhashset_ref.release_backend_files();
// Move sandbox to overwrite
txhashset.release_backend_files();
txhashset::txhashset_replace(sandbox_dir.clone(), PathBuf::from(self.db_root.clone()))?;
// Re-open on db root dir
txhashset = txhashset::TxHashSet::open(
self.db_root.clone(),
self.store.clone(),
Some(&header),
)?;
self.rebuild_header_mmr(&Tip::from_header(&header), &mut txhashset)?;
txhashset::clean_header_folder(&sandbox_dir);
// Replace the chain txhashset with the newly built one.
*txhashset_ref = txhashset; *txhashset_ref = txhashset;
} }
@ -933,35 +975,22 @@ impl Chain {
Ok(()) Ok(())
} }
fn compact_txhashset(&self) -> Result<(), Error> {
debug!("Starting txhashset compaction...");
{
// Note: We take a lock on the stop_state here and do not release it until
// we have finished processing this chain compaction operation.
let stop_lock = self.stop_state.lock();
if stop_lock.is_stopped() {
return Err(ErrorKind::Stopped.into());
}
let mut txhashset = self.txhashset.write();
txhashset.compact()?;
}
debug!("... finished txhashset compaction.");
Ok(())
}
/// Cleanup old blocks from the db. /// Cleanup old blocks from the db.
/// Determine the cutoff height from the horizon and the current block height. /// Determine the cutoff height from the horizon and the current block height.
/// *Only* runs if we are not in archive mode. /// *Only* runs if we are not in archive mode.
fn compact_blocks_db(&self) -> Result<(), Error> { fn remove_historical_blocks(
&self,
txhashset: &txhashset::TxHashSet,
batch: &mut store::Batch<'_>,
) -> Result<(), Error> {
if self.archive_mode { if self.archive_mode {
return Ok(()); return Ok(());
} }
let horizon = global::cut_through_horizon() as u64; let horizon = global::cut_through_horizon() as u64;
let head = self.head()?; let head = batch.head()?;
let tail = match self.tail() { let tail = match batch.tail() {
Ok(tail) => tail, Ok(tail) => tail,
Err(_) => Tip::from_header(&self.genesis), Err(_) => Tip::from_header(&self.genesis),
}; };
@ -969,7 +998,7 @@ impl Chain {
let cutoff = head.height.saturating_sub(horizon); let cutoff = head.height.saturating_sub(horizon);
debug!( debug!(
"compact_blocks_db: head height: {}, tail height: {}, horizon: {}, cutoff: {}", "remove_historical_blocks: head height: {}, tail height: {}, horizon: {}, cutoff: {}",
head.height, tail.height, horizon, cutoff, head.height, tail.height, horizon, cutoff,
); );
@ -979,10 +1008,11 @@ impl Chain {
let mut count = 0; let mut count = 0;
let tail = self.get_header_by_height(head.height - horizon)?; let tail_hash = txhashset.get_header_hash_by_height(head.height - horizon)?;
let mut current = self.get_header_by_height(head.height - horizon - 1)?; let tail = batch.get_block_header(&tail_hash)?;
let batch = self.store.batch()?; let current_hash = txhashset.get_header_hash_by_height(head.height - horizon - 1)?;
let mut current = batch.get_block_header(&current_hash)?;
loop { loop {
// Go to the store directly so we can handle NotFoundErr robustly. // Go to the store directly so we can handle NotFoundErr robustly.
@ -1010,11 +1040,12 @@ impl Chain {
} }
} }
batch.save_body_tail(&Tip::from_header(&tail))?; batch.save_body_tail(&Tip::from_header(&tail))?;
batch.commit()?;
debug!( debug!(
"compact_blocks_db: removed {} blocks. tail height: {}", "remove_historical_blocks: removed {} blocks. tail height: {}",
count, tail.height count, tail.height
); );
Ok(()) Ok(())
} }
@ -1024,12 +1055,35 @@ impl Chain {
/// * removes historical blocks and associated data from the db (unless archive mode) /// * removes historical blocks and associated data from the db (unless archive mode)
/// ///
pub fn compact(&self) -> Result<(), Error> { pub fn compact(&self) -> Result<(), Error> {
self.compact_txhashset()?; // Note: We take a lock on the stop_state here and do not release it until
// we have finished processing this chain compaction operation.
if !self.archive_mode { // We want to avoid shutting the node down in the middle of compacting the data.
self.compact_blocks_db()?; let stop_lock = self.stop_state.lock();
if stop_lock.is_stopped() {
return Err(ErrorKind::Stopped.into());
} }
// Take a write lock on the txhashet and start a new writeable db batch.
let mut txhashset = self.txhashset.write();
let mut batch = self.store.batch()?;
// Compact the txhashset itself (rewriting the pruned backend files).
txhashset.compact(&mut batch)?;
// Rebuild our output_pos index in the db based on current UTXO set.
txhashset::extending(&mut txhashset, &mut batch, |extension| {
extension.rebuild_index()?;
Ok(())
})?;
// If we are not in archival mode remove historical blocks from the db.
if !self.archive_mode {
self.remove_historical_blocks(&txhashset, &mut batch)?;
}
// Commit all the above db changes.
batch.commit()?;
Ok(()) Ok(())
} }
@ -1142,12 +1196,20 @@ impl Chain {
} }
/// Gets the block header at the provided height. /// Gets the block header at the provided height.
/// Note: This takes a read lock on the txhashset. /// Note: Takes a read lock on the txhashset.
/// Take care not to call this repeatedly in a tight loop. /// Take care not to call this repeatedly in a tight loop.
pub fn get_header_by_height(&self, height: u64) -> Result<BlockHeader, Error> { pub fn get_header_by_height(&self, height: u64) -> Result<BlockHeader, Error> {
let hash = self.get_header_hash_by_height(height)?;
self.get_block_header(&hash)
}
/// Gets the header hash at the provided height.
/// Note: Takes a read lock on the txhashset.
/// Take care not to call this repeatedly in a tight loop.
fn get_header_hash_by_height(&self, height: u64) -> Result<Hash, Error> {
let txhashset = self.txhashset.read(); let txhashset = self.txhashset.read();
let header = txhashset.get_header_by_height(height)?; let hash = txhashset.get_header_hash_by_height(height)?;
Ok(header) Ok(hash)
} }
/// Gets the block header in which a given output appears in the txhashset. /// Gets the block header in which a given output appears in the txhashset.
@ -1208,10 +1270,10 @@ impl Chain {
/// Builds an iterator on blocks starting from the current chain head and /// Builds an iterator on blocks starting from the current chain head and
/// running backward. Specialized to return information pertaining to block /// running backward. Specialized to return information pertaining to block
/// difficulty calculation (timestamp and previous difficulties). /// difficulty calculation (timestamp and previous difficulties).
pub fn difficulty_iter(&self) -> store::DifficultyIter<'_> { pub fn difficulty_iter(&self) -> Result<store::DifficultyIter<'_>, Error> {
let head = self.head().unwrap(); let head = self.head()?;
let store = self.store.clone(); let store = self.store.clone();
store::DifficultyIter::from(head.last_block_h, store) Ok(store::DifficultyIter::from(head.last_block_h, store))
} }
/// Check whether we have a block without reading it /// Check whether we have a block without reading it

View file

@ -89,9 +89,15 @@ pub enum ErrorKind {
/// Error validating a Merkle proof (coinbase output) /// Error validating a Merkle proof (coinbase output)
#[fail(display = "Error validating merkle proof")] #[fail(display = "Error validating merkle proof")]
MerkleProof, MerkleProof,
/// output not found /// Output not found
#[fail(display = "Output not found")] #[fail(display = "Output not found")]
OutputNotFound, OutputNotFound,
/// Rangeproof not found
#[fail(display = "Rangeproof not found")]
RangeproofNotFound,
/// Tx kernel not found
#[fail(display = "Tx kernel not found")]
TxKernelNotFound,
/// output spent /// output spent
#[fail(display = "Output is spent")] #[fail(display = "Output is spent")]
OutputSpent, OutputSpent,

View file

@ -183,16 +183,21 @@ pub fn sync_block_headers(
headers: &[BlockHeader], headers: &[BlockHeader],
ctx: &mut BlockContext<'_>, ctx: &mut BlockContext<'_>,
) -> Result<Option<Tip>, Error> { ) -> Result<Option<Tip>, Error> {
if let Some(header) = headers.first() { let first_header = match headers.first() {
debug!( Some(header) => {
"pipe: sync_block_headers: {} headers from {} at {}", debug!(
headers.len(), "pipe: sync_block_headers: {} headers from {} at {}",
header.hash(), headers.len(),
header.height, header.hash(),
); header.height,
} else { );
return Ok(None); header
} }
None => {
error!("failed to get the first header");
return Ok(None);
}
};
let all_known = if let Some(last_header) = headers.last() { let all_known = if let Some(last_header) = headers.last() {
ctx.batch.get_block_header(&last_header.hash()).is_ok() ctx.batch.get_block_header(&last_header.hash()).is_ok()
@ -201,7 +206,6 @@ pub fn sync_block_headers(
}; };
if !all_known { if !all_known {
let first_header = headers.first().unwrap();
let prev_header = ctx.batch.get_previous_header(&first_header)?; let prev_header = ctx.batch.get_previous_header(&first_header)?;
txhashset::sync_extending(&mut ctx.txhashset, &mut ctx.batch, |extension| { txhashset::sync_extending(&mut ctx.txhashset, &mut ctx.batch, |extension| {
extension.rewind(&prev_header)?; extension.rewind(&prev_header)?;

View file

@ -50,12 +50,13 @@ impl ChainStore {
} }
} }
#[allow(missing_docs)]
impl ChainStore { impl ChainStore {
/// The current chain head.
pub fn head(&self) -> Result<Tip, Error> { pub fn head(&self) -> Result<Tip, Error> {
option_to_not_found(self.db.get_ser(&vec![HEAD_PREFIX]), "HEAD") option_to_not_found(self.db.get_ser(&vec![HEAD_PREFIX]), "HEAD")
} }
/// The current chain "tail" (earliest block in the store).
pub fn tail(&self) -> Result<Tip, Error> { pub fn tail(&self) -> Result<Tip, Error> {
option_to_not_found(self.db.get_ser(&vec![TAIL_PREFIX]), "TAIL") option_to_not_found(self.db.get_ser(&vec![TAIL_PREFIX]), "TAIL")
} }
@ -70,10 +71,12 @@ impl ChainStore {
option_to_not_found(self.db.get_ser(&vec![HEADER_HEAD_PREFIX]), "HEADER_HEAD") option_to_not_found(self.db.get_ser(&vec![HEADER_HEAD_PREFIX]), "HEADER_HEAD")
} }
/// The "sync" head.
pub fn get_sync_head(&self) -> Result<Tip, Error> { pub fn get_sync_head(&self) -> Result<Tip, Error> {
option_to_not_found(self.db.get_ser(&vec![SYNC_HEAD_PREFIX]), "SYNC_HEAD") option_to_not_found(self.db.get_ser(&vec![SYNC_HEAD_PREFIX]), "SYNC_HEAD")
} }
/// Get full block.
pub fn get_block(&self, h: &Hash) -> Result<Block, Error> { pub fn get_block(&self, h: &Hash) -> Result<Block, Error> {
option_to_not_found( option_to_not_found(
self.db.get_ser(&to_key(BLOCK_PREFIX, &mut h.to_vec())), self.db.get_ser(&to_key(BLOCK_PREFIX, &mut h.to_vec())),
@ -81,10 +84,12 @@ impl ChainStore {
) )
} }
/// Does this full block exist?
pub fn block_exists(&self, h: &Hash) -> Result<bool, Error> { pub fn block_exists(&self, h: &Hash) -> Result<bool, Error> {
self.db.exists(&to_key(BLOCK_PREFIX, &mut h.to_vec())) self.db.exists(&to_key(BLOCK_PREFIX, &mut h.to_vec()))
} }
/// Get block_sums for the block hash.
pub fn get_block_sums(&self, h: &Hash) -> Result<BlockSums, Error> { pub fn get_block_sums(&self, h: &Hash) -> Result<BlockSums, Error> {
option_to_not_found( option_to_not_found(
self.db.get_ser(&to_key(BLOCK_SUMS_PREFIX, &mut h.to_vec())), self.db.get_ser(&to_key(BLOCK_SUMS_PREFIX, &mut h.to_vec())),
@ -92,10 +97,12 @@ impl ChainStore {
) )
} }
/// Get previous header.
pub fn get_previous_header(&self, header: &BlockHeader) -> Result<BlockHeader, Error> { pub fn get_previous_header(&self, header: &BlockHeader) -> Result<BlockHeader, Error> {
self.get_block_header(&header.prev_hash) self.get_block_header(&header.prev_hash)
} }
/// Get block header.
pub fn get_block_header(&self, h: &Hash) -> Result<BlockHeader, Error> { pub fn get_block_header(&self, h: &Hash) -> Result<BlockHeader, Error> {
option_to_not_found( option_to_not_found(
self.db self.db
@ -104,6 +111,7 @@ impl ChainStore {
) )
} }
/// Get PMMR pos for the given output commitment.
pub fn get_output_pos(&self, commit: &Commitment) -> Result<u64, Error> { pub fn get_output_pos(&self, commit: &Commitment) -> Result<u64, Error> {
option_to_not_found( option_to_not_found(
self.db self.db
@ -126,12 +134,13 @@ pub struct Batch<'a> {
db: store::Batch<'a>, db: store::Batch<'a>,
} }
#[allow(missing_docs)]
impl<'a> Batch<'a> { impl<'a> Batch<'a> {
/// The head.
pub fn head(&self) -> Result<Tip, Error> { pub fn head(&self) -> Result<Tip, Error> {
option_to_not_found(self.db.get_ser(&vec![HEAD_PREFIX]), "HEAD") option_to_not_found(self.db.get_ser(&vec![HEAD_PREFIX]), "HEAD")
} }
/// The tail.
pub fn tail(&self) -> Result<Tip, Error> { pub fn tail(&self) -> Result<Tip, Error> {
option_to_not_found(self.db.get_ser(&vec![TAIL_PREFIX]), "TAIL") option_to_not_found(self.db.get_ser(&vec![TAIL_PREFIX]), "TAIL")
} }
@ -146,27 +155,33 @@ impl<'a> Batch<'a> {
option_to_not_found(self.db.get_ser(&vec![HEADER_HEAD_PREFIX]), "HEADER_HEAD") option_to_not_found(self.db.get_ser(&vec![HEADER_HEAD_PREFIX]), "HEADER_HEAD")
} }
/// Get "sync" head.
pub fn get_sync_head(&self) -> Result<Tip, Error> { pub fn get_sync_head(&self) -> Result<Tip, Error> {
option_to_not_found(self.db.get_ser(&vec![SYNC_HEAD_PREFIX]), "SYNC_HEAD") option_to_not_found(self.db.get_ser(&vec![SYNC_HEAD_PREFIX]), "SYNC_HEAD")
} }
/// Save head to db.
pub fn save_head(&self, t: &Tip) -> Result<(), Error> { pub fn save_head(&self, t: &Tip) -> Result<(), Error> {
self.db.put_ser(&vec![HEAD_PREFIX], t)?; self.db.put_ser(&vec![HEAD_PREFIX], t)?;
self.db.put_ser(&vec![HEADER_HEAD_PREFIX], t) self.db.put_ser(&vec![HEADER_HEAD_PREFIX], t)
} }
/// Save body head to db.
pub fn save_body_head(&self, t: &Tip) -> Result<(), Error> { pub fn save_body_head(&self, t: &Tip) -> Result<(), Error> {
self.db.put_ser(&vec![HEAD_PREFIX], t) self.db.put_ser(&vec![HEAD_PREFIX], t)
} }
/// Save body "tail" to db.
pub fn save_body_tail(&self, t: &Tip) -> Result<(), Error> { pub fn save_body_tail(&self, t: &Tip) -> Result<(), Error> {
self.db.put_ser(&vec![TAIL_PREFIX], t) self.db.put_ser(&vec![TAIL_PREFIX], t)
} }
/// Save header_head to db.
pub fn save_header_head(&self, t: &Tip) -> Result<(), Error> { pub fn save_header_head(&self, t: &Tip) -> Result<(), Error> {
self.db.put_ser(&vec![HEADER_HEAD_PREFIX], t) self.db.put_ser(&vec![HEADER_HEAD_PREFIX], t)
} }
/// Save "sync" head to db.
pub fn save_sync_head(&self, t: &Tip) -> Result<(), Error> { pub fn save_sync_head(&self, t: &Tip) -> Result<(), Error> {
self.db.put_ser(&vec![SYNC_HEAD_PREFIX], t) self.db.put_ser(&vec![SYNC_HEAD_PREFIX], t)
} }
@ -191,6 +206,7 @@ impl<'a> Batch<'a> {
) )
} }
/// Does the block exist?
pub fn block_exists(&self, h: &Hash) -> Result<bool, Error> { pub fn block_exists(&self, h: &Hash) -> Result<bool, Error> {
self.db.exists(&to_key(BLOCK_PREFIX, &mut h.to_vec())) self.db.exists(&to_key(BLOCK_PREFIX, &mut h.to_vec()))
} }
@ -224,6 +240,7 @@ impl<'a> Batch<'a> {
Ok(()) Ok(())
} }
/// Save block header to db.
pub fn save_block_header(&self, header: &BlockHeader) -> Result<(), Error> { pub fn save_block_header(&self, header: &BlockHeader) -> Result<(), Error> {
let hash = header.hash(); let hash = header.hash();
@ -234,6 +251,7 @@ impl<'a> Batch<'a> {
Ok(()) Ok(())
} }
/// Save output_pos to index.
pub fn save_output_pos(&self, commit: &Commitment, pos: u64) -> Result<(), Error> { pub fn save_output_pos(&self, commit: &Commitment, pos: u64) -> Result<(), Error> {
self.db.put_ser( self.db.put_ser(
&to_key(COMMIT_POS_PREFIX, &mut commit.as_ref().to_vec())[..], &to_key(COMMIT_POS_PREFIX, &mut commit.as_ref().to_vec())[..],
@ -241,6 +259,7 @@ impl<'a> Batch<'a> {
) )
} }
/// Get output_pos from index.
pub fn get_output_pos(&self, commit: &Commitment) -> Result<u64, Error> { pub fn get_output_pos(&self, commit: &Commitment) -> Result<u64, Error> {
option_to_not_found( option_to_not_found(
self.db self.db
@ -249,15 +268,21 @@ impl<'a> Batch<'a> {
) )
} }
pub fn delete_output_pos(&self, commit: &[u8]) -> Result<(), Error> { /// Clear all entries from the output_pos index (must be rebuilt after).
self.db pub fn clear_output_pos(&self) -> Result<(), Error> {
.delete(&to_key(COMMIT_POS_PREFIX, &mut commit.to_vec())) let key = to_key(COMMIT_POS_PREFIX, &mut "".to_string().into_bytes());
for (k, _) in self.db.iter::<u64>(&key)? {
self.db.delete(&k)?;
}
Ok(())
} }
/// Get the previous header.
pub fn get_previous_header(&self, header: &BlockHeader) -> Result<BlockHeader, Error> { pub fn get_previous_header(&self, header: &BlockHeader) -> Result<BlockHeader, Error> {
self.get_block_header(&header.prev_hash) self.get_block_header(&header.prev_hash)
} }
/// Get block header.
pub fn get_block_header(&self, h: &Hash) -> Result<BlockHeader, Error> { pub fn get_block_header(&self, h: &Hash) -> Result<BlockHeader, Error> {
option_to_not_found( option_to_not_found(
self.db self.db
@ -266,6 +291,7 @@ impl<'a> Batch<'a> {
) )
} }
/// Save the input bitmap for the block.
fn save_block_input_bitmap(&self, bh: &Hash, bm: &Bitmap) -> Result<(), Error> { fn save_block_input_bitmap(&self, bh: &Hash, bm: &Bitmap) -> Result<(), Error> {
self.db.put( self.db.put(
&to_key(BLOCK_INPUT_BITMAP_PREFIX, &mut bh.to_vec())[..], &to_key(BLOCK_INPUT_BITMAP_PREFIX, &mut bh.to_vec())[..],
@ -273,16 +299,19 @@ impl<'a> Batch<'a> {
) )
} }
/// Delete the block input bitmap.
fn delete_block_input_bitmap(&self, bh: &Hash) -> Result<(), Error> { fn delete_block_input_bitmap(&self, bh: &Hash) -> Result<(), Error> {
self.db self.db
.delete(&to_key(BLOCK_INPUT_BITMAP_PREFIX, &mut bh.to_vec())) .delete(&to_key(BLOCK_INPUT_BITMAP_PREFIX, &mut bh.to_vec()))
} }
/// Save block_sums for the block.
pub fn save_block_sums(&self, h: &Hash, sums: &BlockSums) -> Result<(), Error> { pub fn save_block_sums(&self, h: &Hash, sums: &BlockSums) -> Result<(), Error> {
self.db self.db
.put_ser(&to_key(BLOCK_SUMS_PREFIX, &mut h.to_vec())[..], &sums) .put_ser(&to_key(BLOCK_SUMS_PREFIX, &mut h.to_vec())[..], &sums)
} }
/// Get block_sums for the block.
pub fn get_block_sums(&self, h: &Hash) -> Result<BlockSums, Error> { pub fn get_block_sums(&self, h: &Hash) -> Result<BlockSums, Error> {
option_to_not_found( option_to_not_found(
self.db.get_ser(&to_key(BLOCK_SUMS_PREFIX, &mut h.to_vec())), self.db.get_ser(&to_key(BLOCK_SUMS_PREFIX, &mut h.to_vec())),
@ -290,10 +319,12 @@ impl<'a> Batch<'a> {
) )
} }
/// Delete the block_sums for the block.
fn delete_block_sums(&self, bh: &Hash) -> Result<(), Error> { fn delete_block_sums(&self, bh: &Hash) -> Result<(), Error> {
self.db.delete(&to_key(BLOCK_SUMS_PREFIX, &mut bh.to_vec())) self.db.delete(&to_key(BLOCK_SUMS_PREFIX, &mut bh.to_vec()))
} }
/// Build the input bitmap for the given block.
fn build_block_input_bitmap(&self, block: &Block) -> Result<Bitmap, Error> { fn build_block_input_bitmap(&self, block: &Block) -> Result<Bitmap, Error> {
let bitmap = block let bitmap = block
.inputs() .inputs()
@ -304,6 +335,7 @@ impl<'a> Batch<'a> {
Ok(bitmap) Ok(bitmap)
} }
/// Build and store the input bitmap for the given block.
fn build_and_store_block_input_bitmap(&self, block: &Block) -> Result<Bitmap, Error> { fn build_and_store_block_input_bitmap(&self, block: &Block) -> Result<Bitmap, Error> {
// Build the bitmap. // Build the bitmap.
let bitmap = self.build_block_input_bitmap(block)?; let bitmap = self.build_block_input_bitmap(block)?;
@ -314,8 +346,8 @@ impl<'a> Batch<'a> {
Ok(bitmap) Ok(bitmap)
} }
// Get the block input bitmap from the db or build the bitmap from /// Get the block input bitmap from the db or build the bitmap from
// the full block from the db (if the block is found). /// the full block from the db (if the block is found).
pub fn get_block_input_bitmap(&self, bh: &Hash) -> Result<Bitmap, Error> { pub fn get_block_input_bitmap(&self, bh: &Hash) -> Result<Bitmap, Error> {
if let Ok(Some(bytes)) = self if let Ok(Some(bytes)) = self
.db .db

View file

@ -32,13 +32,12 @@ use crate::util::secp::pedersen::{Commitment, RangeProof};
use crate::util::{file, secp_static, zip}; use crate::util::{file, secp_static, zip};
use croaring::Bitmap; use croaring::Bitmap;
use grin_store; use grin_store;
use grin_store::pmmr::{clean_files_by_prefix, PMMRBackend, PMMR_FILES}; use grin_store::pmmr::{PMMRBackend, PMMR_FILES};
use grin_store::types::prune_noop;
use std::collections::HashSet; use std::collections::HashSet;
use std::fs::{self, File}; use std::fs::{self, File};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::Arc;
use std::time::Instant; use std::time::{Instant, SystemTime, UNIX_EPOCH};
const HEADERHASHSET_SUBDIR: &'static str = "header"; const HEADERHASHSET_SUBDIR: &'static str = "header";
const TXHASHSET_SUBDIR: &'static str = "txhashset"; const TXHASHSET_SUBDIR: &'static str = "txhashset";
@ -67,7 +66,10 @@ impl<T: PMMRable> PMMRHandle<T> {
) -> Result<PMMRHandle<T>, Error> { ) -> Result<PMMRHandle<T>, Error> {
let path = Path::new(root_dir).join(sub_dir).join(file_name); let path = Path::new(root_dir).join(sub_dir).join(file_name);
fs::create_dir_all(path.clone())?; fs::create_dir_all(path.clone())?;
let backend = PMMRBackend::new(path.to_str().unwrap().to_string(), prunable, header)?; let path_str = path.to_str().ok_or(Error::from(ErrorKind::Other(
"invalid file path".to_owned(),
)))?;
let backend = PMMRBackend::new(path_str.to_string(), prunable, header)?;
let last_pos = backend.unpruned_size(); let last_pos = backend.unpruned_size();
Ok(PMMRHandle { backend, last_pos }) Ok(PMMRHandle { backend, last_pos })
} }
@ -206,21 +208,27 @@ impl TxHashSet {
.get_last_n_insertions(distance) .get_last_n_insertions(distance)
} }
/// Get the header at the specified height based on the current state of the txhashset. /// Get the header hash at the specified height based on the current state of the txhashset.
/// Derives the MMR pos from the height (insertion index) and retrieves the header hash. pub fn get_header_hash_by_height(&self, height: u64) -> Result<Hash, Error> {
/// Looks the header up in the db by hash.
pub fn get_header_by_height(&self, height: u64) -> Result<BlockHeader, Error> {
let pos = pmmr::insertion_to_pmmr_index(height + 1); let pos = pmmr::insertion_to_pmmr_index(height + 1);
let header_pmmr = let header_pmmr =
ReadonlyPMMR::at(&self.header_pmmr_h.backend, self.header_pmmr_h.last_pos); ReadonlyPMMR::at(&self.header_pmmr_h.backend, self.header_pmmr_h.last_pos);
if let Some(entry) = header_pmmr.get_data(pos) { if let Some(entry) = header_pmmr.get_data(pos) {
let header = self.commit_index.get_block_header(&entry.hash())?; Ok(entry.hash())
Ok(header)
} else { } else {
Err(ErrorKind::Other(format!("get header by height")).into()) Err(ErrorKind::Other(format!("get header hash by height")).into())
} }
} }
/// Get the header at the specified height based on the current state of the txhashset.
/// Derives the MMR pos from the height (insertion index) and retrieves the header hash.
/// Looks the header up in the db by hash.
pub fn get_header_by_height(&self, height: u64) -> Result<BlockHeader, Error> {
let hash = self.get_header_hash_by_height(height)?;
let header = self.commit_index.get_block_header(&hash)?;
Ok(header)
}
/// returns outputs from the given insertion (leaf) index up to the /// returns outputs from the given insertion (leaf) index up to the
/// specified limit. Also returns the last index actually populated /// specified limit. Also returns the last index actually populated
pub fn outputs_by_insertion_index( pub fn outputs_by_insertion_index(
@ -280,39 +288,30 @@ impl TxHashSet {
} }
/// Compact the MMR data files and flush the rm logs /// Compact the MMR data files and flush the rm logs
pub fn compact(&mut self) -> Result<(), Error> { pub fn compact(&mut self, batch: &mut Batch<'_>) -> Result<(), Error> {
let commit_index = self.commit_index.clone(); debug!("txhashset: starting compaction...");
let head_header = commit_index.head_header()?;
let head_header = batch.head_header()?;
let current_height = head_header.height; let current_height = head_header.height;
// horizon for compacting is based on current_height // horizon for compacting is based on current_height
let horizon = current_height.saturating_sub(global::cut_through_horizon().into()); let horizon_height = current_height.saturating_sub(global::cut_through_horizon().into());
let horizon_header = self.get_header_by_height(horizon)?; let horizon_hash = self.get_header_hash_by_height(horizon_height)?;
let horizon_header = batch.get_block_header(&horizon_hash)?;
let batch = self.commit_index.batch()?; let rewind_rm_pos = input_pos_to_rewind(&horizon_header, &head_header, batch)?;
let rewind_rm_pos = input_pos_to_rewind(&horizon_header, &head_header, &batch)?; debug!("txhashset: check_compact output mmr backend...");
self.output_pmmr_h
.backend
.check_compact(horizon_header.output_mmr_size, &rewind_rm_pos)?;
{ debug!("txhashset: check_compact rangeproof mmr backend...");
let clean_output_index = |commit: &[u8]| { self.rproof_pmmr_h
let _ = batch.delete_output_pos(commit); .backend
}; .check_compact(horizon_header.output_mmr_size, &rewind_rm_pos)?;
self.output_pmmr_h.backend.check_compact( debug!("txhashset: ... compaction finished");
horizon_header.output_mmr_size,
&rewind_rm_pos,
clean_output_index,
)?;
self.rproof_pmmr_h.backend.check_compact(
horizon_header.output_mmr_size,
&rewind_rm_pos,
&prune_noop,
)?;
}
// Finally commit the batch, saving everything to the db.
batch.commit()?;
Ok(()) Ok(())
} }
@ -797,11 +796,9 @@ impl<'a> Committed for Extension<'a> {
fn outputs_committed(&self) -> Vec<Commitment> { fn outputs_committed(&self) -> Vec<Commitment> {
let mut commitments = vec![]; let mut commitments = vec![];
for n in 1..self.output_pmmr.unpruned_size() + 1 { for pos in self.output_pmmr.leaf_pos_iter() {
if pmmr::is_leaf(n) { if let Some(out) = self.output_pmmr.get_data(pos) {
if let Some(out) = self.output_pmmr.get_data(n) { commitments.push(out.commit);
commitments.push(out.commit);
}
} }
} }
commitments commitments
@ -1259,20 +1256,18 @@ impl<'a> Extension<'a> {
pub fn rebuild_index(&self) -> Result<(), Error> { pub fn rebuild_index(&self) -> Result<(), Error> {
let now = Instant::now(); let now = Instant::now();
let mut count = 0; self.batch.clear_output_pos()?;
for n in 1..self.output_pmmr.unpruned_size() + 1 { let mut count = 0;
// non-pruned leaves only for pos in self.output_pmmr.leaf_pos_iter() {
if pmmr::bintree_postorder_height(n) == 0 { if let Some(out) = self.output_pmmr.get_data(pos) {
if let Some(out) = self.output_pmmr.get_data(n) { self.batch.save_output_pos(&out.commit, pos)?;
self.batch.save_output_pos(&out.commit, n)?; count += 1;
count += 1;
}
} }
} }
debug!( debug!(
"txhashset: rebuild_index ({} UTXOs), took {}s", "txhashset: rebuild_index: {} UTXOs, took {}s",
count, count,
now.elapsed().as_secs(), now.elapsed().as_secs(),
); );
@ -1325,13 +1320,23 @@ impl<'a> Extension<'a> {
let total_kernels = pmmr::n_leaves(self.kernel_pmmr.unpruned_size()); let total_kernels = pmmr::n_leaves(self.kernel_pmmr.unpruned_size());
for n in 1..self.kernel_pmmr.unpruned_size() + 1 { for n in 1..self.kernel_pmmr.unpruned_size() + 1 {
if pmmr::is_leaf(n) { if pmmr::is_leaf(n) {
if let Some(kernel) = self.kernel_pmmr.get_data(n) { let kernel = self
kernel.verify()?; .kernel_pmmr
kern_count += 1; .get_data(n)
.ok_or::<Error>(ErrorKind::TxKernelNotFound.into())?;
kernel.verify()?;
kern_count += 1;
if kern_count % 20 == 0 {
status.on_validation(kern_count, total_kernels, 0, 0);
}
if kern_count % 1_000 == 0 {
debug!(
"txhashset: verify_kernel_signatures: verified {} signatures",
kern_count,
);
} }
}
if n % 20 == 0 {
status.on_validation(kern_count, total_kernels, 0, 0);
} }
} }
@ -1353,30 +1358,34 @@ impl<'a> Extension<'a> {
let mut proof_count = 0; let mut proof_count = 0;
let total_rproofs = pmmr::n_leaves(self.output_pmmr.unpruned_size()); let total_rproofs = pmmr::n_leaves(self.output_pmmr.unpruned_size());
for n in 1..self.output_pmmr.unpruned_size() + 1 { for pos in self.output_pmmr.leaf_pos_iter() {
if pmmr::is_leaf(n) { let output = self.output_pmmr.get_data(pos);
if let Some(out) = self.output_pmmr.get_data(n) { let proof = self.rproof_pmmr.get_data(pos);
if let Some(rp) = self.rproof_pmmr.get_data(n) {
commits.push(out.commit);
proofs.push(rp);
} else {
// TODO - rangeproof not found
return Err(ErrorKind::OutputNotFound.into());
}
proof_count += 1;
if proofs.len() >= 1000 { // Output and corresponding rangeproof *must* exist.
Output::batch_verify_proofs(&commits, &proofs)?; // It is invalid for either to be missing and we fail immediately in this case.
commits.clear(); match (output, proof) {
proofs.clear(); (None, _) => return Err(ErrorKind::OutputNotFound.into()),
debug!( (_, None) => return Err(ErrorKind::RangeproofNotFound.into()),
"txhashset: verify_rangeproofs: verified {} rangeproofs", (Some(output), Some(proof)) => {
proof_count, commits.push(output.commit);
); proofs.push(proof);
}
} }
} }
if n % 20 == 0 {
proof_count += 1;
if proofs.len() >= 1_000 {
Output::batch_verify_proofs(&commits, &proofs)?;
commits.clear();
proofs.clear();
debug!(
"txhashset: verify_rangeproofs: verified {} rangeproofs",
proof_count,
);
}
if proof_count % 20 == 0 {
status.on_validation(0, 0, proof_count, total_rproofs); status.on_validation(0, 0, proof_count, total_rproofs);
} }
} }
@ -1404,38 +1413,22 @@ impl<'a> Extension<'a> {
/// Packages the txhashset data files into a zip and returns a Read to the /// Packages the txhashset data files into a zip and returns a Read to the
/// resulting file /// resulting file
pub fn zip_read(root_dir: String, header: &BlockHeader) -> Result<File, Error> { pub fn zip_read(root_dir: String, header: &BlockHeader, rand: Option<u32>) -> Result<File, Error> {
let txhashset_zip = format!("{}_{}.zip", TXHASHSET_ZIP, header.hash().to_string()); let ts = if let None = rand {
let now = SystemTime::now();
now.duration_since(UNIX_EPOCH).unwrap().subsec_micros()
} else {
rand.unwrap()
};
let txhashset_zip = format!("{}_{}.zip", TXHASHSET_ZIP, ts);
let txhashset_path = Path::new(&root_dir).join(TXHASHSET_SUBDIR); let txhashset_path = Path::new(&root_dir).join(TXHASHSET_SUBDIR);
let zip_path = Path::new(&root_dir).join(txhashset_zip); let zip_path = Path::new(&root_dir).join(txhashset_zip);
// create the zip archive
// if file exist, just re-use it {
let zip_file = File::open(zip_path.clone());
if let Ok(zip) = zip_file {
return Ok(zip);
} else {
// clean up old zips.
// Theoretically, we only need clean-up those zip files older than STATE_SYNC_THRESHOLD.
// But practically, these zip files are not small ones, we just keep the zips in last one hour
let data_dir = Path::new(&root_dir);
let pattern = format!("{}_", TXHASHSET_ZIP);
if let Ok(n) = clean_files_by_prefix(data_dir.clone(), &pattern, 60 * 60) {
debug!(
"{} zip files have been clean up in folder: {:?}",
n, data_dir
);
}
}
// otherwise, create the zip archive
let path_to_be_cleanup = {
// Temp txhashset directory // Temp txhashset directory
let temp_txhashset_path = Path::new(&root_dir).join(format!( let temp_txhashset_path =
"{}_zip_{}", Path::new(&root_dir).join(format!("{}_zip_{}", TXHASHSET_SUBDIR, ts));
TXHASHSET_SUBDIR,
header.hash().to_string()
));
// Remove temp dir if it exist // Remove temp dir if it exist
if temp_txhashset_path.exists() { if temp_txhashset_path.exists() {
fs::remove_dir_all(&temp_txhashset_path)?; fs::remove_dir_all(&temp_txhashset_path)?;
@ -1447,38 +1440,70 @@ pub fn zip_read(root_dir: String, header: &BlockHeader) -> Result<File, Error> {
// Compress zip // Compress zip
zip::compress(&temp_txhashset_path, &File::create(zip_path.clone())?) zip::compress(&temp_txhashset_path, &File::create(zip_path.clone())?)
.map_err(|ze| ErrorKind::Other(ze.to_string()))?; .map_err(|ze| ErrorKind::Other(ze.to_string()))?;
}
temp_txhashset_path
};
// open it again to read it back // open it again to read it back
let zip_file = File::open(zip_path.clone())?; let zip_file = File::open(zip_path)?;
// clean-up temp txhashset directory.
if let Err(e) = fs::remove_dir_all(&path_to_be_cleanup) {
warn!(
"txhashset zip file: {:?} fail to remove, err: {}",
zip_path.to_str(),
e
);
}
Ok(zip_file) Ok(zip_file)
} }
/// Extract the txhashset data from a zip file and writes the content into the /// Extract the txhashset data from a zip file and writes the content into the
/// txhashset storage dir /// txhashset storage dir
pub fn zip_write( pub fn zip_write(
root_dir: String, root_dir: PathBuf,
txhashset_data: File, txhashset_data: File,
header: &BlockHeader, header: &BlockHeader,
) -> Result<(), Error> { ) -> Result<(), Error> {
let txhashset_path = Path::new(&root_dir).join(TXHASHSET_SUBDIR); debug!("zip_write on path: {:?}", root_dir);
let txhashset_path = root_dir.clone().join(TXHASHSET_SUBDIR);
fs::create_dir_all(txhashset_path.clone())?; fs::create_dir_all(txhashset_path.clone())?;
zip::decompress(txhashset_data, &txhashset_path, expected_file) zip::decompress(txhashset_data, &txhashset_path, expected_file)
.map_err(|ze| ErrorKind::Other(ze.to_string()))?; .map_err(|ze| ErrorKind::Other(ze.to_string()))?;
check_and_remove_files(&txhashset_path, header) check_and_remove_files(&txhashset_path, header)
} }
/// Overwrite txhashset folders in "to" folder with "from" folder
pub fn txhashset_replace(from: PathBuf, to: PathBuf) -> Result<(), Error> {
debug!("txhashset_replace: move from {:?} to {:?}", from, to);
// clean the 'to' folder firstly
clean_txhashset_folder(&to);
// rename the 'from' folder as the 'to' folder
if let Err(e) = fs::rename(
from.clone().join(TXHASHSET_SUBDIR),
to.clone().join(TXHASHSET_SUBDIR),
) {
error!("hashset_replace fail on {}. err: {}", TXHASHSET_SUBDIR, e);
Err(ErrorKind::TxHashSetErr(format!("txhashset replacing fail")).into())
} else {
Ok(())
}
}
/// Clean the txhashset folder
pub fn clean_txhashset_folder(root_dir: &PathBuf) {
let txhashset_path = root_dir.clone().join(TXHASHSET_SUBDIR);
if txhashset_path.exists() {
if let Err(e) = fs::remove_dir_all(txhashset_path.clone()) {
warn!(
"clean_txhashset_folder: fail on {:?}. err: {}",
txhashset_path, e
);
}
}
}
/// Clean the header folder
pub fn clean_header_folder(root_dir: &PathBuf) {
let header_path = root_dir.clone().join(HEADERHASHSET_SUBDIR);
if header_path.exists() {
if let Err(e) = fs::remove_dir_all(header_path.clone()) {
warn!("clean_header_folder: fail on {:?}. err: {}", header_path, e);
}
}
}
fn expected_file(path: &Path) -> bool { fn expected_file(path: &Path) -> bool {
use lazy_static::lazy_static; use lazy_static::lazy_static;
use regex::Regex; use regex::Regex;
@ -1491,7 +1516,7 @@ fn expected_file(path: &Path) -> bool {
) )
.as_str() .as_str()
) )
.unwrap(); .expect("invalid txhashset regular expression");
} }
RE.is_match(&s_path) RE.is_match(&s_path)
} }
@ -1530,7 +1555,7 @@ fn check_and_remove_files(txhashset_path: &PathBuf, header: &BlockHeader) -> Res
} }
// Then compare the files found in the subdirectories // Then compare the files found in the subdirectories
let mut pmmr_files_expected: HashSet<_> = PMMR_FILES let pmmr_files_expected: HashSet<_> = PMMR_FILES
.iter() .iter()
.cloned() .cloned()
.map(|s| { .map(|s| {
@ -1541,8 +1566,6 @@ fn check_and_remove_files(txhashset_path: &PathBuf, header: &BlockHeader) -> Res
} }
}) })
.collect(); .collect();
// prevent checker from deleting 3 dot file, could be removed after mainnet
pmmr_files_expected.insert(format!("pmmr_leaf.bin.{}...", header.hash()));
let subdirectories = fs::read_dir(txhashset_path)?; let subdirectories = fs::read_dir(txhashset_path)?;
for subdirectory in subdirectories { for subdirectory in subdirectories {

View file

@ -76,7 +76,7 @@ fn data_files() {
for n in 1..4 { for n in 1..4 {
let prev = chain.head_header().unwrap(); let prev = chain.head_header().unwrap();
let next_header_info = consensus::next_difficulty(1, chain.difficulty_iter()); let next_header_info = consensus::next_difficulty(1, chain.difficulty_iter().unwrap());
let pk = ExtKeychainPath::new(1, n as u32, 0, 0, 0).to_identifier(); let pk = ExtKeychainPath::new(1, n as u32, 0, 0, 0).to_identifier();
let reward = libtx::reward::output(&keychain, &pk, 0, false).unwrap(); let reward = libtx::reward::output(&keychain, &pk, 0, false).unwrap();
let mut b = let mut b =

View file

@ -98,7 +98,7 @@ where
for n in 1..4 { for n in 1..4 {
let prev = chain.head_header().unwrap(); let prev = chain.head_header().unwrap();
let next_header_info = consensus::next_difficulty(1, chain.difficulty_iter()); let next_header_info = consensus::next_difficulty(1, chain.difficulty_iter().unwrap());
let pk = ExtKeychainPath::new(1, n as u32, 0, 0, 0).to_identifier(); let pk = ExtKeychainPath::new(1, n as u32, 0, 0, 0).to_identifier();
let reward = libtx::reward::output(keychain, &pk, 0, false).unwrap(); let reward = libtx::reward::output(keychain, &pk, 0, false).unwrap();
let mut b = let mut b =
@ -406,7 +406,7 @@ fn output_header_mappings() {
for n in 1..15 { for n in 1..15 {
let prev = chain.head_header().unwrap(); let prev = chain.head_header().unwrap();
let next_header_info = consensus::next_difficulty(1, chain.difficulty_iter()); let next_header_info = consensus::next_difficulty(1, chain.difficulty_iter().unwrap());
let pk = ExtKeychainPath::new(1, n as u32, 0, 0, 0).to_identifier(); let pk = ExtKeychainPath::new(1, n as u32, 0, 0, 0).to_identifier();
let reward = libtx::reward::output(&keychain, &pk, 0, false).unwrap(); let reward = libtx::reward::output(&keychain, &pk, 0, false).unwrap();
reward_outputs.push(reward.0.clone()); reward_outputs.push(reward.0.clone());
@ -540,7 +540,7 @@ fn actual_diff_iter_output() {
Arc::new(Mutex::new(StopState::new())), Arc::new(Mutex::new(StopState::new())),
) )
.unwrap(); .unwrap();
let iter = chain.difficulty_iter(); let iter = chain.difficulty_iter().unwrap();
let mut last_time = 0; let mut last_time = 0;
let mut first = true; let mut first = true;
for elem in iter.into_iter() { for elem in iter.into_iter() {

View file

@ -63,7 +63,7 @@ fn test_coinbase_maturity() {
let key_id3 = ExtKeychainPath::new(1, 3, 0, 0, 0).to_identifier(); let key_id3 = ExtKeychainPath::new(1, 3, 0, 0, 0).to_identifier();
let key_id4 = ExtKeychainPath::new(1, 4, 0, 0, 0).to_identifier(); let key_id4 = ExtKeychainPath::new(1, 4, 0, 0, 0).to_identifier();
let next_header_info = consensus::next_difficulty(1, chain.difficulty_iter()); let next_header_info = consensus::next_difficulty(1, chain.difficulty_iter().unwrap());
let reward = libtx::reward::output(&keychain, &key_id1, 0, false).unwrap(); let reward = libtx::reward::output(&keychain, &key_id1, 0, false).unwrap();
let mut block = core::core::Block::new(&prev, vec![], Difficulty::min(), reward).unwrap(); let mut block = core::core::Block::new(&prev, vec![], Difficulty::min(), reward).unwrap();
block.header.timestamp = prev.timestamp + Duration::seconds(60); block.header.timestamp = prev.timestamp + Duration::seconds(60);
@ -110,7 +110,7 @@ fn test_coinbase_maturity() {
let fees = txs.iter().map(|tx| tx.fee()).sum(); let fees = txs.iter().map(|tx| tx.fee()).sum();
let reward = libtx::reward::output(&keychain, &key_id3, fees, false).unwrap(); let reward = libtx::reward::output(&keychain, &key_id3, fees, false).unwrap();
let mut block = core::core::Block::new(&prev, txs, Difficulty::min(), reward).unwrap(); let mut block = core::core::Block::new(&prev, txs, Difficulty::min(), reward).unwrap();
let next_header_info = consensus::next_difficulty(1, chain.difficulty_iter()); let next_header_info = consensus::next_difficulty(1, chain.difficulty_iter().unwrap());
block.header.timestamp = prev.timestamp + Duration::seconds(60); block.header.timestamp = prev.timestamp + Duration::seconds(60);
block.header.pow.secondary_scaling = next_header_info.secondary_scaling; block.header.pow.secondary_scaling = next_header_info.secondary_scaling;
@ -144,7 +144,7 @@ fn test_coinbase_maturity() {
let reward = libtx::reward::output(&keychain, &pk, 0, false).unwrap(); let reward = libtx::reward::output(&keychain, &pk, 0, false).unwrap();
let mut block = core::core::Block::new(&prev, vec![], Difficulty::min(), reward).unwrap(); let mut block = core::core::Block::new(&prev, vec![], Difficulty::min(), reward).unwrap();
let next_header_info = consensus::next_difficulty(1, chain.difficulty_iter()); let next_header_info = consensus::next_difficulty(1, chain.difficulty_iter().unwrap());
block.header.timestamp = prev.timestamp + Duration::seconds(60); block.header.timestamp = prev.timestamp + Duration::seconds(60);
block.header.pow.secondary_scaling = next_header_info.secondary_scaling; block.header.pow.secondary_scaling = next_header_info.secondary_scaling;
@ -169,7 +169,7 @@ fn test_coinbase_maturity() {
let txs = vec![coinbase_txn]; let txs = vec![coinbase_txn];
let fees = txs.iter().map(|tx| tx.fee()).sum(); let fees = txs.iter().map(|tx| tx.fee()).sum();
let next_header_info = consensus::next_difficulty(1, chain.difficulty_iter()); let next_header_info = consensus::next_difficulty(1, chain.difficulty_iter().unwrap());
let reward = libtx::reward::output(&keychain, &key_id4, fees, false).unwrap(); let reward = libtx::reward::output(&keychain, &key_id4, fees, false).unwrap();
let mut block = core::core::Block::new(&prev, txs, Difficulty::min(), reward).unwrap(); let mut block = core::core::Block::new(&prev, txs, Difficulty::min(), reward).unwrap();

View file

@ -22,6 +22,7 @@ use std::fs::{self, File, OpenOptions};
use std::iter::FromIterator; use std::iter::FromIterator;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
use crate::chain::store::ChainStore; use crate::chain::store::ChainStore;
use crate::chain::txhashset; use crate::chain::txhashset;
@ -35,6 +36,9 @@ fn clean_output_dir(dir_name: &str) {
#[test] #[test]
fn test_unexpected_zip() { fn test_unexpected_zip() {
let now = SystemTime::now();
let rand = now.duration_since(UNIX_EPOCH).unwrap().subsec_micros();
let db_root = format!(".grin_txhashset_zip"); let db_root = format!(".grin_txhashset_zip");
clean_output_dir(&db_root); clean_output_dir(&db_root);
let chain_store = ChainStore::new(&db_root).unwrap(); let chain_store = ChainStore::new(&db_root).unwrap();
@ -42,13 +46,15 @@ fn test_unexpected_zip() {
txhashset::TxHashSet::open(db_root.clone(), store.clone(), None).unwrap(); txhashset::TxHashSet::open(db_root.clone(), store.clone(), None).unwrap();
let head = BlockHeader::default(); let head = BlockHeader::default();
// First check if everything works out of the box // First check if everything works out of the box
assert!(txhashset::zip_read(db_root.clone(), &head).is_ok()); assert!(txhashset::zip_read(db_root.clone(), &head, Some(rand)).is_ok());
let zip_path = Path::new(&db_root).join(format!( let zip_path = Path::new(&db_root).join(format!("txhashset_snapshot_{}.zip", rand));
"txhashset_snapshot_{}.zip",
head.hash().to_string()
));
let zip_file = File::open(&zip_path).unwrap(); let zip_file = File::open(&zip_path).unwrap();
assert!(txhashset::zip_write(db_root.clone(), zip_file, &head).is_ok()); assert!(txhashset::zip_write(
PathBuf::from(db_root.clone()),
zip_file,
&BlockHeader::default()
)
.is_ok());
// Remove temp txhashset dir // Remove temp txhashset dir
assert!(fs::remove_dir_all( assert!(fs::remove_dir_all(
Path::new(&db_root).join(format!("txhashset_zip_{}", head.hash().to_string())) Path::new(&db_root).join(format!("txhashset_zip_{}", head.hash().to_string()))
@ -56,7 +62,7 @@ fn test_unexpected_zip() {
.is_err()); .is_err());
// Then add strange files in the original txhashset folder // Then add strange files in the original txhashset folder
write_file(db_root.clone()); write_file(db_root.clone());
assert!(txhashset::zip_read(db_root.clone(), &head).is_ok()); assert!(txhashset::zip_read(db_root.clone(), &head, Some(rand)).is_ok());
// Check that the temp dir dos not contains the strange files // Check that the temp dir dos not contains the strange files
let txhashset_zip_path = let txhashset_zip_path =
Path::new(&db_root).join(format!("txhashset_zip_{}", head.hash().to_string())); Path::new(&db_root).join(format!("txhashset_zip_{}", head.hash().to_string()));
@ -70,7 +76,12 @@ fn test_unexpected_zip() {
.is_err()); .is_err());
let zip_file = File::open(zip_path).unwrap(); let zip_file = File::open(zip_path).unwrap();
assert!(txhashset::zip_write(db_root.clone(), zip_file, &head).is_ok()); assert!(txhashset::zip_write(
PathBuf::from(db_root.clone()),
zip_file,
&BlockHeader::default()
)
.is_ok());
// Check that the txhashset dir dos not contains the strange files // Check that the txhashset dir dos not contains the strange files
let txhashset_path = Path::new(&db_root).join("txhashset"); let txhashset_path = Path::new(&db_root).join("txhashset");
assert!(txhashset_contains_expected_files( assert!(txhashset_contains_expected_files(

View file

@ -1,6 +1,6 @@
[package] [package]
name = "grin_config" name = "grin_config"
version = "1.1.0" version = "1.1.0-beta.1"
authors = ["Grin Developers <mimblewimble@lists.launchpad.net>"] authors = ["Grin Developers <mimblewimble@lists.launchpad.net>"]
description = "Configuration for grin, a simple, private and scalable cryptocurrency implementation based on the MimbleWimble chain format." description = "Configuration for grin, a simple, private and scalable cryptocurrency implementation based on the MimbleWimble chain format."
license = "Apache-2.0" license = "Apache-2.0"
@ -16,10 +16,10 @@ serde_derive = "1"
toml = "0.4" toml = "0.4"
dirs = "1.0.3" dirs = "1.0.3"
grin_core = { path = "../core", version = "1.1.0" } grin_core = { path = "../core", version = "1.1.0-beta.1" }
grin_servers = { path = "../servers", version = "1.1.0" } grin_servers = { path = "../servers", version = "1.1.0-beta.1" }
grin_p2p = { path = "../p2p", version = "1.1.0" } grin_p2p = { path = "../p2p", version = "1.1.0-beta.1" }
grin_util = { path = "../util", version = "1.1.0" } grin_util = { path = "../util", version = "1.1.0-beta.1" }
[dev-dependencies] [dev-dependencies]
pretty_assertions = "0.5.1" pretty_assertions = "0.5.1"

View file

@ -413,6 +413,14 @@ fn comments() -> HashMap<String, String> {
.to_string(), .to_string(),
); );
retval.insert(
"log_max_files".to_string(),
"
#maximum count of the log files to rotate over
"
.to_string(),
);
retval retval
} }

View file

@ -1,6 +1,6 @@
[package] [package]
name = "grin_core" name = "grin_core"
version = "1.1.0" version = "1.1.0-beta.1"
authors = ["Grin Developers <mimblewimble@lists.launchpad.net>"] authors = ["Grin Developers <mimblewimble@lists.launchpad.net>"]
description = "Chain implementation for grin, a simple, private and scalable cryptocurrency implementation based on the MimbleWimble chain format." description = "Chain implementation for grin, a simple, private and scalable cryptocurrency implementation based on the MimbleWimble chain format."
license = "Apache-2.0" license = "Apache-2.0"
@ -28,8 +28,8 @@ uuid = { version = "0.6", features = ["serde", "v4"] }
log = "0.4" log = "0.4"
chrono = { version = "0.4.4", features = ["serde"] } chrono = { version = "0.4.4", features = ["serde"] }
grin_keychain = { path = "../keychain", version = "1.1.0" } grin_keychain = { path = "../keychain", version = "1.1.0-beta.1" }
grin_util = { path = "../util", version = "1.1.0" } grin_util = { path = "../util", version = "1.1.0-beta.1" }
[dev-dependencies] [dev-dependencies]
serde_json = "1" serde_json = "1"

View file

@ -95,9 +95,9 @@ pub fn amount_to_hr_string(amount: u64, truncate: bool) -> String {
if truncate { if truncate {
let nzeros = hr.chars().rev().take_while(|x| x == &'0').count(); let nzeros = hr.chars().rev().take_while(|x| x == &'0').count();
if nzeros < *WIDTH { if nzeros < *WIDTH {
return hr.trim_right_matches('0').to_string(); return hr.trim_end_matches('0').to_string();
} else { } else {
return format!("{}0", hr.trim_right_matches('0')); return format!("{}0", hr.trim_end_matches('0'));
} }
} }
hr hr

View file

@ -51,6 +51,9 @@ pub trait Backend<T: PMMRable> {
/// (ignoring the remove log). /// (ignoring the remove log).
fn get_data_from_file(&self, position: u64) -> Option<T::E>; fn get_data_from_file(&self, position: u64) -> Option<T::E>;
/// Iterator over current (unpruned, unremoved) leaf positions.
fn leaf_pos_iter(&self) -> Box<Iterator<Item = u64> + '_>;
/// Remove Hash by insertion position. An index is also provided so the /// Remove Hash by insertion position. An index is also provided so the
/// underlying backend can implement some rollback of positions up to a /// underlying backend can implement some rollback of positions up to a
/// given index (practically the index is the height of a block that /// given index (practically the index is the height of a block that

View file

@ -75,6 +75,11 @@ where
ReadonlyPMMR::at(&self.backend, self.last_pos) ReadonlyPMMR::at(&self.backend, self.last_pos)
} }
/// Iterator over current (unpruned, unremoved) leaf positions.
pub fn leaf_pos_iter(&self) -> impl Iterator<Item = u64> + '_ {
self.backend.leaf_pos_iter()
}
/// Returns a vec of the peaks of this MMR. /// Returns a vec of the peaks of this MMR.
pub fn peaks(&self) -> Vec<Hash> { pub fn peaks(&self) -> Vec<Hash> {
let peaks_pos = peaks(self.last_pos); let peaks_pos = peaks(self.last_pos);

View file

@ -1242,10 +1242,7 @@ impl Readable for OutputFeatures {
/// Output for a transaction, defining the new ownership of coins that are being /// Output for a transaction, defining the new ownership of coins that are being
/// transferred. The commitment is a blinded value for the output while the /// transferred. The commitment is a blinded value for the output while the
/// range proof guarantees the commitment includes a positive value without /// range proof guarantees the commitment includes a positive value without
/// overflow and the ownership of the private key. The switch commitment hash /// overflow and the ownership of the private key.
/// provides future-proofing against quantum-based attacks, as well as providing
/// wallet implementations with a way to identify their outputs for wallet
/// reconstruction.
#[derive(Debug, Copy, Clone, Serialize, Deserialize)] #[derive(Debug, Copy, Clone, Serialize, Deserialize)]
pub struct Output { pub struct Output {
/// Options for an output's structure or use /// Options for an output's structure or use

View file

@ -34,7 +34,8 @@ pub struct Context<'a, K>
where where
K: Keychain, K: Keychain,
{ {
keychain: &'a K, /// The keychain used for key derivation
pub keychain: &'a K,
} }
/// Function type returned by the transaction combinators. Transforms a /// Function type returned by the transaction combinators. Transforms a

View file

@ -14,29 +14,12 @@
//! Rangeproof library functions //! Rangeproof library functions
use crate::blake2;
use crate::keychain::{Identifier, Keychain}; use crate::keychain::{Identifier, Keychain};
use crate::libtx::error::{Error, ErrorKind}; use crate::libtx::error::{Error, ErrorKind};
use crate::util::secp::key::SecretKey; use crate::util::secp::key::SecretKey;
use crate::util::secp::pedersen::{Commitment, ProofInfo, ProofMessage, RangeProof}; use crate::util::secp::pedersen::{Commitment, ProofInfo, ProofMessage, RangeProof};
use crate::util::secp::{self, Secp256k1}; use crate::util::secp::{self, Secp256k1};
fn create_nonce<K>(k: &K, commit: &Commitment) -> Result<SecretKey, Error>
where
K: Keychain,
{
// hash(commit|wallet root secret key (m)) as nonce
let root_key = k.derive_key(0, &K::root_key_id())?;
let res = blake2::blake2b::blake2b(32, &commit.0, &root_key.0[..]);
let res = res.as_bytes();
match SecretKey::from_slice(k.secp(), &res) {
Ok(sk) => Ok(sk),
Err(e) => Err(ErrorKind::RangeProof(
format!("Unable to create nonce: {:?}", e).to_string(),
))?,
}
}
/// Create a bulletproof /// Create a bulletproof
pub fn create<K>( pub fn create<K>(
k: &K, k: &K,
@ -50,7 +33,9 @@ where
{ {
let commit = k.commit(amount, key_id)?; let commit = k.commit(amount, key_id)?;
let skey = k.derive_key(amount, key_id)?; let skey = k.derive_key(amount, key_id)?;
let nonce = create_nonce(k, &commit)?; let nonce = k
.create_nonce(&commit)
.map_err(|e| ErrorKind::RangeProof(e.to_string()))?;
let message = ProofMessage::from_bytes(&key_id.serialize_path()); let message = ProofMessage::from_bytes(&key_id.serialize_path());
Ok(k.secp() Ok(k.secp()
.bullet_proof(amount, skey, nonce, extra_data, Some(message))) .bullet_proof(amount, skey, nonce, extra_data, Some(message)))
@ -80,7 +65,9 @@ pub fn rewind<K>(
where where
K: Keychain, K: Keychain,
{ {
let nonce = create_nonce(k, &commit)?; let nonce = k
.create_nonce(&commit)
.map_err(|e| ErrorKind::RangeProof(e.to_string()))?;
let proof_message = k let proof_message = k
.secp() .secp()
.rewind_bullet_proof(commit, nonce, extra_data, proof); .rewind_bullet_proof(commit, nonce, extra_data, proof);

View file

@ -104,6 +104,10 @@ impl<T: PMMRable> Backend<T> for VecBackend<T> {
Some(data.as_elmt()) Some(data.as_elmt())
} }
fn leaf_pos_iter(&self) -> Box<Iterator<Item = u64> + '_> {
unimplemented!()
}
fn remove(&mut self, position: u64) -> Result<(), String> { fn remove(&mut self, position: u64) -> Result<(), String> {
self.remove_list.push(position); self.remove_list.push(position);
Ok(()) Ok(())

View file

@ -9,7 +9,7 @@
1. [Chain Endpoint](#chain-endpoint) 1. [Chain Endpoint](#chain-endpoint)
1. [GET Chain](#get-chain) 1. [GET Chain](#get-chain)
1. [POST Chain Compact](#post-chain-compact) 1. [POST Chain Compact](#post-chain-compact)
1. [POST Chain Validate](#post-chain-validate) 1. [GET Chain Validate](#get-chain-validate)
1. [GET Chain Outputs by IDs](#get-chain-outputs-by-ids) 1. [GET Chain Outputs by IDs](#get-chain-outputs-by-ids)
1. [GET Chain Outputs by Height](#get-chain-outputs-by-height) 1. [GET Chain Outputs by Height](#get-chain-outputs-by-height)
1. [Status Endpoint](#status-endpoint) 1. [Status Endpoint](#status-endpoint)
@ -282,7 +282,7 @@ Trigger a compaction of the chain state to regain storage space.
}); });
``` ```
### POST Chain Validate ### GET Chain Validate
Trigger a validation of the chain state. Trigger a validation of the chain state.
@ -292,7 +292,7 @@ Trigger a validation of the chain state.
* **Method:** * **Method:**
`POST` `GET`
* **URL Params** * **URL Params**
@ -316,7 +316,7 @@ Trigger a validation of the chain state.
$.ajax({ $.ajax({
url: "/v1/chain/validate", url: "/v1/chain/validate",
dataType: "json", dataType: "json",
type : "POST", type : "GET",
success : function(r) { success : function(r) {
console.log(r); console.log(r);
} }

View file

@ -108,7 +108,7 @@ Attempt to update and retrieve outputs.
* **Success Response:** * **Success Response:**
* **Code:** 200 * **Code:** 200
* **Content:** Array of * **Content:** All listed amounts in nanogrin. Array of
| Field | Type | Description | | Field | Type | Description |
|:----------------------------------|:---------|:----------------------------------------| |:----------------------------------|:---------|:----------------------------------------|
@ -341,7 +341,7 @@ Send a transaction either directly by http or file (then display the slate)
| Field | Type | Description | | Field | Type | Description |
|:------------------------------|:---------|:-------------------------------------| |:------------------------------|:---------|:-------------------------------------|
| amount | number | Amount to send | | amount | number | Amount to send (in nanogrin) |
| minimum_confirmations | number | Minimum confirmations | | minimum_confirmations | number | Minimum confirmations |
| method | string | Payment method | | method | string | Payment method |
| dest | string | Destination url | | dest | string | Destination url |

View file

@ -1,6 +1,6 @@
# Grin - Build, Configuration, and Running # Grin - Build, Configuration, and Running
*Read this in other languages: [Español](build_ES.md).* *Read this in other languages: [Español](build_ES.md), [Korean](build_KR.md), [日本語](build_JP.md).*
## Supported Platforms ## Supported Platforms
@ -115,7 +115,7 @@ You can bind-mount your grin cache to run inside the container.
docker run -it -d -v $HOME/.grin:/root/.grin grin docker run -it -d -v $HOME/.grin:/root/.grin grin
``` ```
If you prefer to use a docker named volume, you can pass `-v dotgrin:/root/.grin` instead. If you prefer to use a docker named volume, you can pass `-v dotgrin:/root/.grin` instead.
Using a named volume copies default configurations upon volume creation Using a named volume copies default configurations upon volume creation.
## Cross-platform builds ## Cross-platform builds

129
doc/build_JP.md Normal file
View file

@ -0,0 +1,129 @@
# grin - ビルド、設定、動作確認
*Read this in other languages: [Español](build_ES.md), [Korean](build_KR.md), [日本語](build_JP.md).*
## 動作環境
grinのプログラミング言語である`rust`はほぼ全ての環境に対応している。
現在の動作環境
* Linux x86\_64とmacOS [grin、マイニング、開発]
* Windows 10は未対応 [一部のビルドはできるがマイニングがまだ。助けを募集中!]
## 要件
* rust 1.31+ ([rustup]((https://www.rustup.rs/))を使えば`curl https://sh.rustup.rs -sSf | sh; source $HOME/.cargo/env`でインストール可)
* rustをインストール済みの場合は`rustup update`を実行
* clang
* ncursesとそのライブラリ (ncurses, ncursesw5)
* zlibとそのライブラリ (zlib1g-dev または zlib-devel)
* pkg-config
* libssl-dev
* linux-headers (Alpine linuxでは必要)
* llvm
Debianベースのディストリビューション(Debian、Ubuntu、Mintなど)ではrustを除き1コマンドでインストールできる:
```sh
apt install build-essential cmake git libgit2-dev clang libncurses5-dev libncursesw5-dev zlib1g-dev pkg-config libssl-dev llvm
```
Mac:
```sh
xcode-select --install
brew install --with-toolchain llvm
brew install pkg-config
brew install openssl
```
## ビルド手順
```sh
git clone https://github.com/mimblewimble/grin.git
cd grin
cargo build --release
```
grinはデバッグモードでもビルド可能(`--release`を付けない状態で、`--debug`か`--verbose`を付ける)。
しかし暗号の計算のオーバーヘッドが大きく、高速同期が著しく遅くなる。
## ビルドエラー
[Troubleshooting](https://github.com/mimblewimble/docs/wiki/Troubleshooting)
## 何がビルドされるか
ビルドの成果物
* `target/release/grin` - grinの実行ファイル
grinのデータ、設定ファイル、ログファイルはデフォルトでは(ホームディレクトリ配下の)`~/.grin`のディレクトリに格納されている。
全ての設定値は`~/.grin/main/grin-server.toml`を編集することで変更可能。
データファイルをカレントディレクトリに出力することも可能。
そのためには以下のコマンドを実行。
```sh
grin server config
```
カレントディレクトリに`grin-server.toml`がある場合、カレントディレクトリにデータが出力される。
grinを`grin-server.toml`を含むディレクトリで起動する場合、デフォルトである`~/.grin/main/grin-server.toml`よりも優先される。
テスト中はgrinのバイナリにpathを通す:
```sh
export PATH=`pwd`/target/release:$PATH
```
ただし、grinをインストールしたルートディレクトリから実行することを想定している。
これにより`grin`が直接実行可能になる(オプションは`grin help`で調べられる)。
## 設定
grinは気の利いたデフォルト設定で起動するようになっており、さらに`grin-server.toml`のファイルを通じて設定可能。
このファイルはgrinの初回起動で作成され、利用可能なオプションに関するドキュメントを含んでいる。
`grin-server.toml`を通じて設定することが推奨されるが、全ての設定はコマンドラインで上書きすることも可能。
コマンドライン関連のヘルプについてはこちらを実行:
```sh
grin help
grin wallet help
grin client help
```
## Docker
```sh
docker build -t grin -f etc/Dockerfile .
```
floonetを使用する場合、代わりに`etc/Dockerfile.floonet`を指定。
コンテナ内で実行する場合、grinのキャッシュをバインドマウントすることも可能。
```sh
docker run -it -d -v $HOME/.grin:/root/.grin grin
```
dockerの名前付きボリュームを使用する場合、代わりに`-v dotgrin:/root/.grin`を指定。
ボリュームが作成される前に、名前付きボリュームがコピーされる。
## クロスプラットフォームビルド
rust(cargo)はあらゆるプラットフォームでビルド可能なので、`grin`をバリデーションノードとして省電力なデバイスで実行することも可能である。
x86のLinux上で`grin`をクロスコンパイルしARMバイナリを作成し、Raspberry Piで実行することも可能。
## grinの使用
機能やトラブルシューティングなどに関するより多くの情報については[Wallet User Guide](https://github.com/mimblewimble/docs/wiki/Wallet-User-Guide)。
## grinのマイニング
grinのマイニングに関する全ての機能は[grin-miner](https://github.com/mimblewimble/grin-miner)と呼ばれるスタンドアローンなパッケージに分離されていることに注意。
grin-minerをgrinードと通信させるためには、`grin-server.toml`の設定ファイルで`enable_stratum_server = true`と設定し、ウォレットリスナーを起動(`grin wallet listen`)しておく必要がある。

131
doc/build_KR.md Normal file
View file

@ -0,0 +1,131 @@
# Grin - Build, Configuration, and Running
*다른 언어로 되어있는 문서를 읽으려면:[에스파냐어](build_ES.md).
## 지원하는 플랫폼들에 대해서
장기적으로는 대부분의 플랫폼에서 어느정도 지원하게 될 것입니다.
Grin 프로그래밍 언어는 `rust`로 대부분의 플랫폼들에서 빌드 할 수 있습니다.
지금까지 작동하는 플랫폼은 무엇인가요?
* Linux x86_64 그리고 macOS [grin + mining + development]
* Windows 10은 아직 지원하지 않습니다 [grin kind-of builds, mining은 아직 지원하지 않음 . 도움이 필요해요!]
## 요구사항
* rust 1.31 버전 이상 (다음 명령어를 사용하세요. [rustup]((https://www.rustup.rs/))- 예.) `curl https://sh.rustup.rs -sSf | sh; source $HOME/.cargo/env`)
* 만약 rust 가 설치되어 있다면, 다음 명령어를 사용해서 업데이트 할 수 있습니다.
`rustup update`
* clang
* ncurses 과 libs (ncurses, ncursesw5)
* zlib libs (zlib1g-dev or zlib-devel)
* pkg-config
* libssl-dev
* linux-headers (reported needed on Alpine linux)
* llvm
Debian 기반의 배포들은 (Debian, Ubuntu, Mint, 등등) 다음 명령어 한 줄로 설치 됩니다.
```sh
apt install build-essential cmake git libgit2-dev clang libncurses5-dev libncursesw5-dev zlib1g-dev pkg-config libssl-dev llvm
```
Mac 사용자:
```sh
xcode-select --install
brew install --with-toolchain llvm
brew install pkg-config
brew install openssl
```
## 빌드 순서
```sh
git clone https://github.com/mimblewimble/grin.git
cd grin
cargo build --release
```
Grin은 Debug 모드로 Build 할 수 있습니다. (`--release` 플래그가 사용하지 않고, `--debug` 또는 `--verbose` 플래그를 사용하세요.) 그러나 이 명령어는 암호 오퍼레이션으로 인해 큰 오버헤드를 가지므로 fast sync 가 어려울 정도로 느려집니다.
## Build 에러들
[트러블 슈팅 관련해서는 이 링크를 클릭하세요.](https://github.com/mimblewimble/docs/wiki/Troubleshooting)
## 무엇을 Build 해야 되나요?
성공적으로 빌드한다면:
* `target/release/grin` - 메인 grin 바이너리 디렉토리가 생성됩니다.
모든 데이터, 설정, 로그 파일들은 기본적으로 숨겨진 `~/.grin` 디렉토리에 생성되고 사용됩니다. (user home 디렉토리 안에 있습니다.)
`~/.grin/main/grin-server.toml` 을 수정해서 모든 설정값들을 바꿀 수 있습니다.
Grin은 현재 디렉토리 내에서도 데이터 파일들을 만들 수 있습니다. 밑에 있는 Bash 명령어를 작동하세요.
```sh
grin server config
```
이 명령어는 `grin-server.toml`를 현재 디렉토리에서 생성합니다.
이 파일은 현재 디렉토리 내의 모든 데이터에 대해서 사용하도록 미리 구성되어 있습니다.
`grin-server.toml` 파일이 있는 디렉토리에서 grin을 실행하면 기본값`~ / .grin / main / grin-server.toml` 대신 그 파일의 값을 사용하게됩니다.
Testing 중에서는 Grin 바이너리를 이렇게 path 에 삽입 할 수도 있습니다.
```sh
export PATH=`pwd`/target/release:$PATH
```
만약 Grin을 root 디렉토리에서 실행한다고 가정하면, `grin` 명령어를 바로 실행할 수 있습니다. (`grin help` 명령어를 통해서 좀 더 많은 옵션을 알아보세요.)
## 설정하기
Grin 은 기본적으로 설정되어 있는 올바른 값으로 실행하고 `grin-server.toml`를 통해 추가로 설정하는 것이 가능합니다.
Grin이 처음 실행될때 설정파일이 생성되고 각 사용가능한 옵션에 대한 매뉴얼을 포함하고 있습니다.
`grin-server.toml` 파일을 통해 모든 Grin 서버 구성을 수행하는 것이 좋지만,
커맨드 라인 명령어를 사용하면 `grin-server.toml` 파일의 모든설정을 덮어쓰는 것이 가능합니다.
Grin을 작동시키는 명령어에 대한 도움말은 다음 명령어를 실행하세요.
```sh
grin help
grin wallet help
grin client help
```
## Docker 사용하기
```sh
docker build -t grin -f etc/Dockerfile .
```
floonet을 사용하려면 `etc/Dockerfile.floonet` 을 사용하세요.
container 안에서 grin cache를 bind-mount로 사용 할 수 있습니다.
```sh
docker run -it -d -v $HOME/.grin:/root/.grin grin
```
Docker를 named volume으로 사용하는 것을 선호한다면 `-v dotgrin:/root/.grin` 명령어를 대신 사용할 수 있습니다.
named volume샤용시 volume 생성시 기본 설정을 복사합니다.
## 크로스 플랫폼 빌드
Rust(Cargo)는 여러 플랫폼에서 Grin을 빌드 할 수 있습니다. 그래서 이론적으로 낮은 성능의 디바이스 에서도 인증받은 노드로 grin을 아마도 작동 시킬 수 있을것 입니다.
예를 들자면 라즈베리 파이 같은 x96 리눅스플랫폼 위에서 `grin` 크로스 컴파일을 하고 ARM 바이너릐를 만듭니다.
## Grin 사용하기
[지갑 유저 가이드](https://github.com/mimblewimble/docs/wiki/Wallet-User-Guide) 위키페이지와 링크된 페이지들은 어떤 Feature 를 가지고 있는지 , 트러블 슈팅 등등에 대한 좀 더 많은 정보를 가지고 있습니다.
## Grin 채굴하기
Grin의 모든 마이닝 기능은 분리된 독랍형 패키지인 [grin-miner](https://github.com/mimblewimble/grin-miner)로 옮겨졌습니다.
일단 Grin 노드가 실행되면 실행중인 노드에 대해 grin-miner를 빌드하고 실행하여 마이닝을 시작할 수 있습니다.
grin-miner가 grin 노드와 통신 할 수 있게 하려면, `grin-server.toml` 설정 파일에서`enable_stratum_server = true`가 설정되어 있는지 확인하세요. 그 다음 Wallet listener인 `grin wallet listen` 명령어를 실행하세요 .

View file

@ -1,5 +1,7 @@
# Blocks and Block headers # Blocks and Block headers
*Read this in other languages: [Korean](blocks_and_headers_KR.md).*
## Node receives block from peer (normal operation) ## Node receives block from peer (normal operation)
During normal operation the Grin node will receive blocks from connected peers via the gossip protocol. During normal operation the Grin node will receive blocks from connected peers via the gossip protocol.

View file

@ -0,0 +1,38 @@
# Blocks and Block headers
## 정상 운영시 노드가 피어로부터 블록을 받는것에 대해서
정상 동작 중에 Grin 노드는 가십 프로토콜을 통해 연결된 피어로부터 블록을 받습니다.
블록과 블록 헤더의 유효성이 성공적으로 확인되면 둘 다 저장됩니다. 헤더 헤드가 최신 블록 헤더를 가리키도록 업데이트되고 블록 헤드가 최신 블록을 가리키도록 업데이트됩니다.
![Simple Block](images/simple_block.png)
## 노드가 처음 동기화 될때
[tbd]
## 노드가 블록의 상태를 따라잡기 위해 피어와 동기화 하는것에 대해서
노드는 주기적으로 현재의`total_difficulty`와 연결된 모든 피어들의 `total_difficulty`를 비교할 것입니다. total_difficulty가 더 높은 피어가 표시되면이 가장 많이 일한 피어(most_work_peer)와 동기화를 시도합니다. most_work_peers가 여러 개 있으면 그 중 하나가 무작위로 선택됩니다.
동기화 프로세스는 현재 알려진 체인 상태 (locator에 대한 자세한 정보는 [tbd] 참고하세요.)를 기반으로 "locator"를 빌드하고 그 다음 피어에게서 헤더 목록을 요청하고 해당 헤더를 선택하는 데 도움이되는 locator를 전달하면서 시작됩니다.
헤더 목록을 수신하면 노드는 노드의 유효성을 확인한 다음 저장합니다. 각 헤더에 대해 가장 최근 헤더를 반영하도록 헤더 헤드가 업데이트 됩니다.
그러면 노드는 헤더체인 (헤더 헤드에서 부터 역순으로)과 현재 블록체인 (블록 헤드에서부터 역순으로)을 비교하여 각 "누락한" 블록을 요청합니다. 블록은 노드보다 total_difficulty가 큰 피어에게 요청됩니다. 이 프로세스는 (노드보다) 더 높은 total_difficulty를 가진 피어가 보이지 않고 두 헤드(헤더헤드 & 블록헤드) 가 일치한 상태 (동일한 헤드 / 블록을 가리키고 있음)에 있을 때까지 반복됩니다.
![Simple sync](images/simple_sync.png)
## 새로운 피어가 이전에 알려지지 않은 가장 긴 fork 와 연결된 경우
![Sync on fork](images/sync_on_fork.png)
## 노드가 매우 뒤떨어져 있을때에 대해서 ( 500 블록 이상 )
현재 헤더 검색은 약 500 헤더 (512?)의 배치로 제한됩니다. 500개 헤더의 첫 배치를 받은 이후에 새로운 헤더 체인이 되지만 새로운 체인의 total_difficulty가 기존 체인을 추월하기에 충분하지 않을 때 어떤 일이 발생하는지 정확히 설명해야합니다.
여기서 어떻게될까요?
## 노드가 블록을 성공적으로 마이닝 했을때에 대해서
[tbd]
## 두 블록이 같이 채굴 되었을때 ( 일시적인 포크)에 대해서
[tbd]

View file

@ -1,5 +1,7 @@
# Blockchain Syncing # Blockchain Syncing
*Read this in other languages: [Korean](chain_sync_KR.md).*
We describe here the different methods used by a new node when joining the network We describe here the different methods used by a new node when joining the network
to catch up with the latest chain state. We start with reminding the reader of the to catch up with the latest chain state. We start with reminding the reader of the
following assumptions, which are all characteristics of Grin or MimbleWimble: following assumptions, which are all characteristics of Grin or MimbleWimble:
@ -110,7 +112,7 @@ that block.
If a hard fork occurs, the network may become split, forcing new nodes to always If a hard fork occurs, the network may become split, forcing new nodes to always
push their horizon back to when the hard fork occurred. While this is not a problem push their horizon back to when the hard fork occurred. While this is not a problem
for short-term hard forks, it may become an issue for long-term or permanent forks for short-term hard forks, it may become an issue for long-term or permanent forks.
To prevent this situation, peers should always be checked for hard fork related To prevent this situation, peers should always be checked for hard fork related
capabilities (a bitmask of features a peer exposes) on connection. capabilities (a bitmask of features a peer exposes) on connection.

View file

@ -0,0 +1,79 @@
# 블록체인의 동기화
최신 노드 상태를 따라 가기 위해 네트워크에 참여할 때 새 노드가 사용하는 여러 가지 방법을 설명합니다.
먼저, 독자에게 다음과 같은 Grin 또는 MimbleWimble의 특성을 먼저 전제 하고 설명하겠습니다.
* 해당 블록 안의 모든 블록 헤더는 체인 안에 사용하지 않는 출력값의 모든 루트해시를 가지고 있습니다.
* 입력 또는 출력은 전체 블록 상태를 무효화하지 않고선 변조되거나 위조 될 수 없습니다
오직 보안 모델에 영향을 줄 수있는 주요 노드 유형과 고급 알고리즘에만 의도적으로 초점을 두고 있습니다. 예를 들어 헤더 우선 같은 몇몇 추가 개선점들을 줄 수 있는 휴리스틱은 유용하지만 이 섹션에서는 언급하지 않을것입니다.
## Full 히스토리 동기화
### 설명
이 모델은 대부분의 메이저 퍼블릭 블록체인 에서 "풀 노드"가 사용하는 모델입니다. 새로운 노드는 제네시스 블록에 대한 사전 정보를 가지고 있습니다. 노드는 네트워크의 다른 피어와 연결되어 피어에게 알려진 최신 블록(호라이즌 블록)에 도달 할 때까지 블록을 요청하기 시작합니다.
보안 모델은 비트 코인과 비슷합니다. 전체 체인, 총 작업, 각 블록의 유효성, 전체 내용 등을 검증 할 수 있습니다. 또한 MimbleWimble 및 전체 UTXO 세트 실행들을 통해 훨씬 더 무결성 검증이 잘 수행될 수 있습니다.
이 모드에서는 저장공간 최적화 또는 대역폭 최적화를 시도하지 않습니다 (예를 들자면 유효성 검증 후 Range proof 가 삭제 될 수 있습니다). 여기서 중요한 것은 기록 아카이브를 제공하고 나중에 확인 및 증명을 하게 하는 것입니다.
### 무엇이 잘못 될 수 있나요?
다른 블록체인과 동일하게 아래와 같은 문제가 생길 수 있습니다.
* 연결된 모든 노드가 부정직하다면 (sybil 공격 또는 그와 비슷한 상태를 말합니다.), 전체 체인 상태에 대해 거짓말을 할 수 있습니다.
* 엄청난 마이닝 파워를 가진 누군가가 전체 블록체인 기록을 다시 쓸 수 있습니다.
* Etc.
## 부분 블록체인 히스토리 동기화
이 모델에서는 보안에 대해서 가능한 한 적게 ​​타협하면서 매우 빠른 동기화를 위힌 최적화를 하려고 합니다. 사실 보안 모델은 다운로드 할 데이터의 양이 훨씬 적음에도 불구하고 풀 노드와 거의 동일합니다.
새로 네트워크에 참여하는 노드는 블록헤드에서 블록 수만큼 떨어진 값인 `Z`로 미리 구성됩니다. ( 원문에서는 horizon `Z` 로 표현되었습니다. 블록헤드 - 블록 = `Z`라고 할 수 있습니다. - 역자 주 ) 예를 들어 `Z = 5000` 이고 헤드가 높이 `H = 23000` 에 있으면, 가장 높은 블록은 가장 긴 체인에서 높이가 `h = 18000`인 블록입니다.
또한 새로운 노드에는 제네시스 블록에 대한 사전 정보가 있습니다. 노드는 다른 피어들과 연결하고 가장 긴 체인의 헤드에 대해 알게 됩니다. 가장 높은 블록의 블록 헤더(horizon block 이라고 원문에 표시되어 있음 - 역자 주 )를 요청하며 다른 피어의 동의가 필요하게 됩니다. 컨센서스가 `h = H - Z`에 이르지 않으면 노드는 `Z`값( horizon Z 라고 원문에 표시되어 있음 - 역자 주 )을 점차 증가시켜 컨센서스가 이루어질 때까지`h`를 뒤로 이동시킵니다. 그런 다음 가장 긴 블록에서의 전체 UTXO 정보를 얻습니다. 이 정보를 통해 다음을 증명할 수 있습니다.
* 모든 블록헤더안에 있는 해당 체인의 전체 난이도
* 예상되는 코인 공급량과 같은 모든 UTXO 실행값의 합.
* 블록헤더에 있는 루트 해시와 매치되는 모든 UTXO의 루트해시
블록의 유효성 검사가 완료되면 피어는 블록 콘텐츠를 `Z`값에서 (from the horizon 이라고 원문에 표시되어 있음 - 역자 주) 헤드까지 다운로드하고 유효성을 검사 할 수 있습니다.
이 알고리즘은 `Z`의 매우 낮은 값 (또는 `Z = 1`인 극단적인 경우에도)에서도 작동합니다. 그러나 어느 블록체인에서도 발생할 수있는 정상적인 포크 때문에 낮은 값이 문제가 될 수 있습니다. 이러한 문제를 방지하고 로컬 검증된 을 늘리려면 최소한 며칠 분량의 `Z`값에서 최대 몇 주간의 `Z`값을 사용하는 것을 권장합니다.
### 무엇이 잘못 될 수 있나요?
이 동기화 모드는 간단하게 설명 할 수 있지만 어떻게 보안을 유지 할 것인가에 대해선 불분명해 보일 수 있습니다.
여기서는 몇몇 가능 할 수 있는 공격 유형과 퇴치 방식 및 기타 가능한 실패 시나리오에 대해 설명합니다.
### 공격자가 가장 긴 블록에서 부터 상태를 위조하려고 할때
이 공격은 노드가 네트워크와 올바르게 동기화되었다고 노드가 인식하도록 하나 실제로 노드가 위조 상태에 있게 합니다.
이에 대해 아래와 같은 여러 전략을 시도 할 수 있습니다.
* 완전히 가짜지만 유효한 최신 블록상태 (horizon state 라고 원문에 표시되 어있음 - 역자 주) 일때 (헤더 및 작업 증명 포함), 최소한 하나의 정직한 피어가 있다고 가정하면, UTXO 세트의 루트 해시와 블록 해시도 다른 피어의 수평 상태와 일치하지 않습니다.
* 유효한 블록 헤더이지만 가짜 UTXO 세트일때, 헤더의 UTXO 세트의 루트 해시는 노드가 수신 한 UTXO 세트 자체에서 계산 한 것과 일치하지 않습니다.
* 가짜 총 난이도를 가진 완전히 유효한 블록으로 노드를 가짜 포크로 유도할 경우, 전체 난이도가 변경되면 블록 해시가 변경되며 어떤 정직한 피어도 해당 해시에 유효한 헤드를 생성하지 않습니다.
### 로컬 UTXO 히스토리 보다 오래된 포크가 발생하려 할때
노드는 최신 블록 높이 (horizon height - 역자 주) 로 설정된 전체 UTXO를 다운로드 했습니다. 만약 수평선 H + delta에 있는 블록에서 포크가 발생하면 UTXO 세트의 유효성을 검사 할 수 없습니다. 이 상황에서 노드는 `Z '= Z + delta`인 새로운 `Z`값 (new horizon - 역자 주) 을 가진 동기화 모드로 돌아 갈 수 밖에 없습니다.
(네트웍에서 받아들여지는) 승리한 포크는 현재의 (우리의) 헤드보다 작업 증명의 총 량이 더 많은 헤드일 뿐이라서 현재의 헤드보다 더 적은 작업 증명인 Z + delta의 다른 포크는 무시 될 수 있습니다. 이 방법을 위해서 모든 블록 헤더에는 해당 블록까지의 총 체인 난이도가 포함됩니다.
#### 체인이 완전히 포크되었을 때
하드포크가 발생하면 네트워크가 분리되고 새로운 노드는 하드포크가 발생했을 때로 자신의 최신 블록 상태(horizon)을 항상 되돌릴 것을 강제합니다. 짧은 기간의 하드포크에는 문제가 되지 않지만 장기 또는 완전한 하드포크에선 문제가 될 수 있습니다. 이러한 상황을 방지하기 위해, 피어는 연결이 될때 하드포크 관련 기능 (피어가 노출하는 피쳐의 bitmask)을 항상 확인해야합니다.
### 몇몇 노드가 가짜 최신블록 (호라이즌 블록)을 지속적으로 줄 때
피어가 만약 h의 헤더에서 컨센서스에 도달 할 수 없으면 서서히 뒤로 돌아갑니다 (블록의 유효성을 확인하기 위해). 뒤로 물러나는 유효성을 확인하는 경우, 사기꾼 피어는 시스템적으로 합의가 이뤄지는것을 막고 가짜 헤더를 보내 줌으로써 모든 새로운 피어가 제네시스 블록까지 (유효성을 확인하기 위해) 뒤로 이동하게 함으로써 항상 풀 노드가 되도록 강제 할 수 있습니다.
상기 얘기한 상태는 유효한 문제이긴 하지만 이래와 같이 몇가지 완화 전략이 있습니다.
* 피어는`Z`값 (Horizon Z)에서 유효한 블록 헤더를 제공해야합니다. 여기에는 작업 증명이 포함됩니다.
* 최신 블록 (Horizon) 주위의 블록 헤더 그룹은 공격 비용을 증가 시키도록 요청받을 수 있습니다.
* 현저하게 낮은 작업 증명을 제공하는 상이한 블록 헤더는 거부 될 수 있습니다.
* 사용자 또는 노드 운영자는 블록 해시를 확인하도록 요청받을 수 있습니다.
* 최후의 수단으로 위의 전략 중 어느 것도 효과적이지 않으면 체크포인트를 사용할 수 있습니다.

View file

@ -119,9 +119,7 @@ To summarize -
Output MMR stores output hashes based on `commitment|features` (the commitment itself is not sufficient). Output MMR stores output hashes based on `commitment|features` (the commitment itself is not sufficient).
We do not need to include the range proof or the switch commitment hash in the generation of the output hash. We do not need to include the range proof in the generation of the output hash.
We do not need to encode the lock height in the switch commitment hash.
To spend an output we continue to need - To spend an output we continue to need -

View file

@ -1,5 +1,6 @@
# Dandelion in Grin: Privacy-Preserving Transaction Aggregation and Propagation # Dandelion in Grin: Privacy-Preserving Transaction Aggregation and Propagation
*Read this document in other languages: [Korean](dandelion_KR.md).*
This document describes the implementation of Dandelion in Grin and its modification to handle transactions aggregation in the P2P protocol. This document describes the implementation of Dandelion in Grin and its modification to handle transactions aggregation in the P2P protocol.
## Introduction ## Introduction

View file

@ -0,0 +1,89 @@
# Grin에서 사용하는 Dandelion : 프라이버시 보호 트랜잭션의 통합과 전파
*역자 주 : Dandelion 은 민들레라는 뜻으로 앞으로 설명될 각종 용어에서 민들레의 홑씨와 관련된 용어들이 있으니 참고바람.*
이 문서에서는 Grin에서 구현된 Dandelion 구현체에 대해서 설명하고 그리고 Dandelion이 P2P 프로토콜내에서 트랜잭션 통합을 다루기 위해 어떻게 수정되었는지 설명합니다.
## 소개
Dandelion은 발신 IP에 도청 연결 트랜잭션(eavesdroppers linking transactions)의 위험을 줄이는 새로운 트랜잭션 전파 메커니즘입니다. 또한 전체 네트워크에 퍼지기 전에 추가적인 프라이버시 보호 기능을 제공합니다. 프라이버시 보호 기능은 Grin 트랜잭션을 입력-출력 쌍을 제거하는 방식으로 제공됩니다.
Dandelion은 G. Fanti 에 의해 소개되었습니다([1] 참고). 그리고 ACM Sigmetrics 2017에서 발표되었습니다. 2017 년 6 월 BIP [2]는 2018 년 후반에 발표될 Dandelion ++ [3]이라고 불리는 더 실용적이고 견고한 Dandelion의 다른 버전을 소개하려고 제안되었습니다. 이 문서는 Grin을 위한 BIP의 개정판이라고 생각하시면 됩니다.
우선은 오리지널 Dandelion 전파에 대해서 정의한 다음, 트랜잭션 에그레게이션 (transaction aggregation)과 함께 어떻게 Grin의 프로토콜에 적용되었는지 알아보겠습니다.
## Original Dandelion
### 매커니즘에 대해서
Dandelion 트랜잭션 전파는 두 가지 단계로 진행됩니다. 첫 번째는 "stem" (줄기)페이즈, "fluff"(솜털/보풀, 민들레 홀씨를 연상하면 될 듯 - 역자 주)페이즈 입니다. Stem 페이즈에서 각 노드는 트랜잭션을 *단일* 피어로 릴레이 합니다. Stem를 따라 임의의 수로 (노드를) 몇번 건너뛴 후 (hops) 후 트랜잭션은 일반적인 플러딩 / 확산과 같이 동작하는 fluff 페이즈에 들어갑니다. 공격자가 fluff 단계의 위치를 ​​식별 할 수 있더라도 Stem 페이즈의 발신처를 식별하는 것이 훨씬 더 어렵습니다.
그림 설명:
```
┌-> F ...
┌-> D --┤
| └-> G ...
A --[stem]--> B --[stem]--> C --[fluff]--┤
| ┌-> H ...
└-> E --┤
└-> I ...
```
### Specifications
Dandelion 프로토콜은 아래와 같은 세가지 매커니즘을 기반으로 합니다.
1. *Stem/fluff 전파.*
Dandelion 트랜잭션은 "Stem(줄기) 모드"에서 시작됩니다. 각 노드는 거래를 무작위로 선택된 하나의 노드에게 중계합니다. 고정 된 확률로 트랜잭션은 "fluff(솜털)" 모드로 전환되고 이후에는 일반적인 플러딩 / 확산에 따라 릴레이됩니다.
2. *Stem Mempool.* Stem 페이즈 동안, 각 Stem node(앨리스)는 transaction pool에 stem transaction만을 포함한 transaction을 저장합니다. 이것이 stem pool 입니다. stem pool의 내용은 각 노드마다 다르므로 공유 할 수 없습니다. 다음 경우에 stem pool에서 stem transaction이 제거됩니다.
1. 앨리스는 fluff 모드인 transaction을 받았다고 "정상적으로" 알립니다.
2. Alice는 이 트랜잭션이 포함 된 블록을 받았고 이러한 것은 이 트랜잭션이 전파되고 블록에 포함되었음을 의미합니다.
3. *Robust propagation.* 프라이버시 강화가 transaction을 전파 하지 않게 해서는 안됩니다. stem node가 transaction을 중계(relay)하지 못해서 fluff 단계에 가지 못할 상황일때 (악의적이거나 우발적인) 실패를 방지하기 위해 각 노드는 stem 페이즈에서 transaction을 수신 할 때 임의의 타이머를 시작합니다. 타이머가 만료되기 전에 노드가 해당 transaction에 대한 transaction 메시지 또는 블록을 수신하지 않으면 노드는 transaction을 정상적으로 전파합니다.
Dandelion stem mode transaction은 새로운 타입의 릴레이 메시지 타입으로 표시됩니다.
Stem transaction relay 메시지 타입은 아래와 같습니다.
```rust
Type::StemTransaction;
```
Stem transaction을 수신한 후 node는 바이어스(biased) 된 코인을 뒤집어서 "stem mode"로 전달할지 또는 "fluff mode"로 전환할지 결정합니다. 바이어스는 설정파일에 있는 파라미터에 의해 제어되는데, 초기에는 90 % 확률로 stem mode로 유지하게 합니다.(예상되는 stem 길이가 10번 건너뜀(hops)이 될 것임을 의미함)
Stem transacion을 수신하는 노드를 stem relay라고합니다. 이 릴레이는 밖으로 향하거나 connection 이나 허가된 연결 중에서 선택 되어지는데 침입자들이 stem graph에 쉽게 잡입하는 것을 방지합니다. 각 노드는 매 10 분마다 주기적으로 무작위로 stem relay를 선택합니다.
### 고려해야 하는 것들
주요 구현의 도전 과제는 (1) Dandelion의 프라이버시를 보장하는 것 과 latency/overhead 사이의 만족스러운 트레이드 오프를 밝히는 것, (2) 기존 매커니즘의 남용을 통해 프라이버시가 질적으로 저하 될 수 없다는 것을 보장하는 것입니다.
특히 구현에서는 효율적이고 DoS 내성 전파를 위한 다양한 기존 메커니즘을 너무 많이 방해하지 않고 공격자가 stem node를 식별하는 것을 방지해야합니다.
* Dandelion이 제공하는 프라이버시는 다음과 같은 3가지 파라미터에 달려있습니다. Stem 확률(stem probability), Dandelion 중계(relay)역할을 할 수 있는 아웃바운드(outbound) 피어 수 그리고 stem 중계(relay)의 재-무작위 추출 간의 시간입니다. 이러한 파라미터는 프라이버시와 전파 latency/processing overhead 간의 트레이드 오프를 정의합니다.
Stem 확률(stem probability)을 낮추면 프라이버시가 손상되지만 평균 stem 길이를 짧게 해서 latency를 줄이는 데 도움이 됩니다.(그래서) 이론,시뮬레이션 및 실험을 기준으로 stem 확률은 90%를 기본값으로 선택 되었습니다. 각 노드의 stem 중계(relay)의 재-무작위 추출 사이의 시간을 줄이면 공격자가 각 노드의 stem 중계(relay)를 알 기회가 줄어들고 overhead가 증가합니다.
* Dandelion stem transaction을 받을 때, 그 transaction을 `tracking_adapter`에 두는 것을 피합니다. 이렇게 하면 Transaction이 fluff 페이즈에서 Stem을 "위로" 이동할 수도 있습니다.
* 일반 거래와 마찬가지로, Dandelion stem transaction은 mempool에 성공적으로 승인 된 후에 만 ​​중계(relay)됩니다. 이렇게 하면 Dandelion stem transaction을 중계(relay) 할 때 노드가 절대로 불이익을 받지 않습니다.
* Stem orphan transaction이 접수되면 `고아 (orphan)`pool에 추가되고 stem mode로 표시됩니다. transaction이 나중에 mempool에 승인되면 stem transaction 또는 일반 transaction (stem mode 또는 fluff mode, 동전 반전에 따라 다름)으로 중계(relay)됩니다.
* 노드가 하나나 또는 그 이상의 현재 엠바고 상태인 Dandelion transaction에 의존하는 하위 거래(child transaction)를 받으면 transaction도 stem mode로 중계(relay)되고 엠바고 타이머는 상위 트랜잭션 (parent)의 엠바고 기간의 최대치로 설정됩니다. 이렇게 하면 상위 트랜잭션이 하위 트랜잭션 전에 fluff 모드로 들어갈 수 있습니다. 나중에 이 두 transaction은 유니크한 하나의 transaction으로 통합되어 타이머가 필요하지 않게됩니다.
* 트랜잭션 전파 latency는 프라이버시 기능을 넣어도 최소한으로 영향을 받아야 합니다. 특히 Dandelion 때문에 거래가 전혀 방해받지 않아야 합니다. 무작위 타이머는 엠바고 메커니즘이 일시적이며 일반 확산 메커니즘에 따라 모든 트랜잭션이 최대 (임의로) 30-60 초 정도의 딜레이 후에 중계(relay)되도록 보장합니다.
## Grin 에서의 Dandelion
Dandelion은 또한 Grin Transaction을 Stem phase에서 통합(Aggregated) 한 다음 네트워크의 모든 노드에 전파 할 수 있습니다. 이로 인해 트랜잭션 통합(transaction aggregation)과 컷 스루(cut-through, 소비 된 출력값 삭제)가 가능해져 컷 스루(cut-through) 방식의 non-interactive coinjoin 과 유사한 매우 중요한 프라이버시 이득을 얻을 수 있습니다.
### 통합 매커니즘 (Aggregation Mechanism)
트랜잭션을 통합(aggregate)하기 위해 Grin은 Dandelion 프로토콜의 수정 된 버전을 구현합니다 [4].
기본적으로 노드가 네트워크에서 transaction을 전송하면 Dandelion 프로토콜을 사용하여 Dandelion 중계기(relay)에 stem transaction으로 전파됩니다. Dandelion 중계기(relay)는 더 많은 stem transaction를 통합하기 위해 일정 기간 (patience 타이머) 대기합니다. 타이머가 끝날때 중계기(relay)는 새로운 stem transaction마다 코인 플립을 해서 stem을 하거나 (다음 Dandelion relay로 보내거나) fluff(정상적으로 전파하거나) 할지를 결정합니다. 그런 다음 relay는 모든 transaction을 stem으로 가져 와서 통합하여 다음 Dandelion 릴레이에 전파합니다. Transaction이 "정상적으로" 피어의 무작위 하위 집합으로 통합된 Transaction을 전파한다는 점을 제외하고는 fluff(단계)로 가는 transaction에 대해 동일한 작업을 수행합니다.
이 매커니즘은 transaction 병합을 다룰수 있는 P2P protocol을 제공합니다.
이 시나리오에 대한 시뮬레이션은 [여기](simulation_KR.md)에서 확인 할 수 있습니다.
## 레퍼런스
* [1] (Sigmetrics 2017) [Dandelion: Redesigning the Bitcoin Network for Anonymity](https://arxiv.org/abs/1701.04439)
* [2] [Dandelion BIP](https://github.com/dandelion-org/bips/blob/master/bip-dandelion.mediawiki)
* [3] (Sigmetrics 2018) [Dandelion++: Lightweight Cryptocurrency Networking with Formal Anonymity Guarantees](https://arxiv.org/abs/1805.11060)
* [4] [Dandelion Grin Pull Request #1067](https://github.com/mimblewimble/grin/pull/1067)

View file

@ -1,5 +1,7 @@
# Dandelion Simulation # Dandelion Simulation
*Read this document in other languages: [Korean](simulation_KR.md).*
This document describes a network of node using the Dandelion protocol with transaction aggregation. This document describes a network of node using the Dandelion protocol with transaction aggregation.
In this scenario, we simulate a successful aggregation. In this scenario, we simulate a successful aggregation.

View file

@ -0,0 +1,73 @@
# Dandelion 시뮬레이션
이 문서는 노드의 네트워크가 Dandelion 프로토콜을 트랜잭션 통합(Transaction aggregation)과 함께 사용하는 것에 대해서 설명합니다. 이 시나리오에서 성공적인 (트랜잭션)통합을 시뮬레이션 할 것입니다.
이 문서는 (트랜잭션의) 모든 순간 순간에 대해서 간단히 시각화 하는것을 도와줄것입니다.
## T = 0 - Initial Situation
![t = 0](images/t0.png)
## T = 5
A는 B에게 grin를 보냅니다. A는 거래를 스템풀(stem pool)에 추가하고 이 트랜잭션에 대한 엠바고 타이머를 시작합니다.
![t = 5](images/t5.png)
## T = 10
A는 인내심이 바닥날때까지 기다립니다. ( 아마도 엠바고 타이머가 끝나는 때를 의미하는 듯 - 역자 주)
![t = 10](images/t10.png)
## T = 30
A는 인내심이 바닥나면 동전을 뒤집고 Stem transaction을 G에게 Dandelion을 중계(Relay)합니다. G는 Stem transaction을 받은뒤 Stem pool에 Transaction을 추가하고 이 Transaction의 엠바고 타이머를 시작합니다.
![t = 30](images/t30.png)
## T = 40
G는 E에게 Grin을 보냅니다ㅏ.
G는 이 Transaction을 Stem pool에 Transaction을 추가하고 이 Transaction의 엠바고 타이머를 시작합니다.
![t = 40](images/t40.png)
## T = 45
G는 인내심이 바닥나면 동전을 뒤집고 Stem transaction을 D에게 Dandelion을 중계(Relay)합니다.
![t = 45](images/t45.png)
## T = 50
B는 B1을 D에게 씁니다.
B는 B1을 Stem pool에 추가하고 이 Transaction의 엠바고 타이머를 시작합니다.
![t = 55](images/t55.png)
## T = 55
B는 인내심이 바닥나면 동전을 뒤집고 Stem transaction을 H에게 Dandelion을 중계(Relay)합니다.
D는 인내심이 바닥나면 동전을 뒤집고 통합된(aggregated) Stem transaction을 E에게 Dandelion을 중계(Relay)합니다.
E는 Stem transaction을 받은뒤 Stem pool에 Transaction을 추가하고 이 Transaction의 엠바고 타이머를 시작합니다.
![t = 55](images/t55.png)
## T = 60
H는 인내심이 바닥나면 동전을 뒤집고 Stem transaction을 E에게 Dandelion을 중계(Relay)합니다.
E는 Stem transaction을 받은뒤 Stem pool에 Transaction을 추가하고 이 Transaction의 엠바고 타이머를 시작합니다.
![t = 60](images/t60.png)
## T = 70 - Step 1
E는 인내심이 바닥나면 동전을 뒤집고 transaction을 모든 피어에게 전송하기로 합니다.(mempool안의 fluff 상태)
![t = 70_1](images/t70_1.png)
## T = 70 - Step 2
All the nodes add this transaction to their mempool and remove the related transactions from their stempool.
모든 노드는 이 transaction을 자신의 mempool에 넣고 자신의 stempool 에서 이 transaction과 관련된 transaction을 제거합니다.
![t = 70_2](images/t70_2.png)

View file

@ -1,6 +1,6 @@
# Fast Sync # Fast Sync
*Read this in other languages: [Español](fast-sync_ES.md).* *Read this in other languages: [Español](fast-sync_ES.md), [Korean](fast-sync_KR.md).*
In Grin, we call "sync" the process of synchronizing a new node or a node that In Grin, we call "sync" the process of synchronizing a new node or a node that
hasn't been keeping up with the chain for a while, and bringing it up to the hasn't been keeping up with the chain for a while, and bringing it up to the

16
doc/fast-sync_KR.md Normal file
View file

@ -0,0 +1,16 @@
# 빠른 동기화
*이 문서를 다른 언어로 읽으시려면: [에스파냐어](fast-sync_ES.md).*
Grin에서는 새로 네트워크에 참여하는 노드나 얼마 동안 체인을 따라 잡지 않은 노드(의 상태)를 알려진 최신 블록으로( 원문에서는 most-worked block 이라고 표현- 역자 주 ) 가져 오는 프로세스를 "동기화"라고 부릅니다. Initial Block Download (또는 IBD)는 다른 블록 체인에서 자주 사용되지만 빠른 동기화를 사용하는 Grin에서는 일반적으로 전체 블록을 다운로드하지 않으므로 문제가 됩니다.
요약하자면 Grin 에서의 빠른 동기화는 다음을 설명하는 요소들을 실행합니다.
1. 다른 노드에서 보여지는 것처럼 가장 긴 체인에서 블록 헤더를 청크별로 다운로드합니다.
1. 최신 블록헤드(원문에서는 chain head 라고 표현 - 역자 주)에서 부터 (동기화 해야 될) 헤더를 찾습니다. 이것은 node horizon 이라고 불리고 node horizon은 가장 긴 체인 ( 가장 긴 체인의 블록헤드에서 부터 동기화 해야 블록헤드 까지이므로 원문에서는 furthest 라고 표현 - 역자 주) 이기 때문에 다른 새로운 전체 동기화의 트리거가 없는 경우 새로운 포크에서 체인을 재 구성 할 수 있습니다.
1. 현재의 horizon에서 unspent 데이터, range proof, kernel 데이터와 해당하는 모든 MMR들 및 모든 스테이트를 다운로드 합니다.
이러한 데이터는 하나의 큰 zip 파일 입니다.
1. 모든 스테이트를 입증합니다.
1. Horizon 에서부터 최신 체인( 원문에서는 chain head 라고 표현함 - 역자 주 )까지 모든 블록을 다운로드 합니다.
이 섹션의 나머지는 각각의 단계에 대해서 자세히 설명할 것입니다.

View file

@ -1,6 +1,6 @@
# Introduction to MimbleWimble and Grin # Introduction to MimbleWimble and Grin
*Read this in other languages: [English](intro.md), [简体中文](intro_ZH-CN.md), [Español](intro_ES.md), [Nederlands](intro_NL.md), [Русский](intro_RU.md), [日本語](intro_JP.md), [Deutsch](intro_DE.md), [Portuguese](intro_PT-BR.md).* *Read this in other languages: [English](intro.md), [简体中文](intro_ZH-CN.md), [Español](intro_ES.md), [Nederlands](intro_NL.md), [Русский](intro_RU.md), [日本語](intro_JP.md), [Deutsch](intro_DE.md), [Portuguese](intro_PT-BR.md), [Korean](intro_KR.md).*
MimbleWimble is a blockchain format and protocol that provides MimbleWimble is a blockchain format and protocol that provides
extremely good scalability, privacy and fungibility by relying on strong extremely good scalability, privacy and fungibility by relying on strong
@ -23,6 +23,8 @@ The main goal and characteristics of the Grin project are:
* Design simplicity that makes it easy to audit and maintain over time. * Design simplicity that makes it easy to audit and maintain over time.
* Community driven, encouraging mining decentralization. * Community driven, encouraging mining decentralization.
A detailed post on the step-by-step of how Grin transactions work (with graphics) can be found [in this Medium post](https://medium.com/@brandonarvanaghi/grin-transactions-explained-step-by-step-fdceb905a853).
## Tongue Tying for Everyone ## Tongue Tying for Everyone
This document is targeted at readers with a good This document is targeted at readers with a good
@ -90,7 +92,7 @@ fundamental properties are achieved.
Building upon the properties of ECC we described above, one can obscure the values Building upon the properties of ECC we described above, one can obscure the values
in a transaction. in a transaction.
If _v_ is the value of a transaction input or output and _H_ an elliptic curve, we can simply If _v_ is the value of a transaction input or output and _H_ a point on the elliptic curve _C_, we can simply
embed `v*H` instead of _v_ in a transaction. This works because using the ECC embed `v*H` instead of _v_ in a transaction. This works because using the ECC
operations, we can still validate that the sum of the outputs of a transaction equals the operations, we can still validate that the sum of the outputs of a transaction equals the
sum of inputs: sum of inputs:
@ -99,11 +101,12 @@ sum of inputs:
Verifying this property on every transaction allows the protocol to verify that a Verifying this property on every transaction allows the protocol to verify that a
transaction doesn't create money out of thin air, without knowing what the actual transaction doesn't create money out of thin air, without knowing what the actual
values are. However, there are a finite number of usable values and one could try every single values are. However, there are a finite number of usable values (transaction amounts) and one
could try every single
one of them to guess the value of your transaction. In addition, knowing v1 (from one of them to guess the value of your transaction. In addition, knowing v1 (from
a previous transaction for example) and the resulting `v1*H` reveals all outputs with a previous transaction for example) and the resulting `v1*H` reveals all outputs with
value v1 across the blockchain. For these reasons, we introduce a second elliptic curve value v1 across the blockchain. For these reasons, we introduce a second point _G_ on the same elliptic curve
_G_ (practically _G_ is just another generator point on the same curve group as _H_) and (practically _G_ is just another generator point on the same curve group as _H_) and
a private key _r_ used as a *blinding factor*. a private key _r_ used as a *blinding factor*.
An input or output value in a transaction can then be expressed as: An input or output value in a transaction can then be expressed as:
@ -112,9 +115,10 @@ An input or output value in a transaction can then be expressed as:
Where: Where:
* _r_ is a private key used as a blinding factor, _G_ is an elliptic curve and * _r_ is a private key used as a blinding factor, _G_ is a point on the elliptic curve _C_ and
their product `r*G` is the public key for _r_ on _G_. their product `r*G` is the public key for _r_ (using _G_ as generator point).
* _v_ is the value of an input or output and _H_ is another elliptic curve. * _v_ is the value of an input or output and _H_ is another point on the elliptic curve _C_,
together producing another public key `v*H` (using _H_ as generator point).
Neither _v_ nor _r_ can be deduced, leveraging the fundamental properties of Elliptic Neither _v_ nor _r_ can be deduced, leveraging the fundamental properties of Elliptic
Curve Cryptography. `r*G + v*H` is called a _Pedersen Commitment_. Curve Cryptography. `r*G + v*H` is called a _Pedersen Commitment_.
@ -122,8 +126,8 @@ Curve Cryptography. `r*G + v*H` is called a _Pedersen Commitment_.
As an example, let's assume we want to build a transaction with two inputs and one As an example, let's assume we want to build a transaction with two inputs and one
output. We have (ignoring fees): output. We have (ignoring fees):
* vi1 and vi2 as input values. * `vi1` and `vi2` as input values.
* vo3 as output value. * `vo3` as output value.
Such that: Such that:
@ -143,8 +147,9 @@ transaction can be done without knowing any of the values.
As a final note, this idea is actually derived from Greg Maxwell's As a final note, this idea is actually derived from Greg Maxwell's
[Confidential Transactions](https://elementsproject.org/features/confidential-transactions/investigation), [Confidential Transactions](https://elementsproject.org/features/confidential-transactions/investigation),
which is itself derived from an Adam Back proposal for homomorphic values applied which is itself derived from an
to Bitcoin. [Adam Back proposal for homomorphic values](https://bitcointalk.org/index.php?topic=305791.0)
applied to Bitcoin.
#### Ownership #### Ownership
@ -168,7 +173,7 @@ You need to build a simple transaction such that:
Xi => Y Xi => Y
Where _Xi_ is an input that spends your _X_ output and Y is Carol's output. There is no way to build Where _Xi_ is an input that spends your _X_ output and _Y_ is Carol's output. There is no way to build
such a transaction and balance it without knowing your private key of 28. Indeed, if Carol such a transaction and balance it without knowing your private key of 28. Indeed, if Carol
is to balance this transaction, she needs to know both the value sent and your private key is to balance this transaction, she needs to know both the value sent and your private key
so that: so that:
@ -190,14 +195,19 @@ She picks 113 say, and what ends up on the blockchain is:
Now the transaction no longer sums to zero and we have an _excess value_ on _G_ Now the transaction no longer sums to zero and we have an _excess value_ on _G_
(85), which is the result of the summation of all blinding factors. But because `85*G` is (85), which is the result of the summation of all blinding factors. But because `85*G` is
a valid public key on the elliptic curve _G_, with private key 85, a valid public key on the elliptic curve _G_, with private key 85,
for any x and y, only if `y = 0` is `x*G + y*H` a valid public key on _G_. for any x and y, only if `y = 0` is `x*G + y*H` a valid public key on the elliptic curve
using generator point _G_.
So all the protocol needs to verify is that (`Y - Xi`) is a valid public key on _G_ and that So all the protocol needs to verify is that (`Y - Xi`) is a valid public key on the curve
the transacting parties collectively know the private key (85 in our transaction with Carol). The and that the transacting parties collectively know the private key `x` (85 in our transaction with
simplest way to do so is to require a signature built with the excess value (85), Carol) of this public key. If they can prove that they know the private key to `x*G + y*H` using
generator point _G_ then this proves that `y` must be `0` (meaning above that the sum of all
inputs and outputs equals `0`).
The simplest way to do so is to require a signature built with the excess value (85),
which then validates that: which then validates that:
* The transacting parties collectively know the private key, and * The transacting parties collectively know the private key (the excess value 85), and
* The sum of the transaction outputs, minus the inputs, sum to a zero value * The sum of the transaction outputs, minus the inputs, sum to a zero value
(because only a valid public key, matching the private key, will check against (because only a valid public key, matching the private key, will check against
the signature). the signature).
@ -237,16 +247,23 @@ create new funds in every transaction.
For example, one could create a transaction with an input of 2 and outputs of 5 For example, one could create a transaction with an input of 2 and outputs of 5
and -3 and still obtain a well-balanced transaction, following the definition in and -3 and still obtain a well-balanced transaction, following the definition in
the previous sections. This can't be easily detected because even if _x_ is the previous sections. This can't be easily detected because even if _x_ is
negative, the corresponding point `x.H` on the curve looks like any other. negative, the corresponding point `x*H` on the curve looks like any other.
To solve this problem, MimbleWimble leverages another cryptographic concept (also To solve this problem, MimbleWimble leverages another cryptographic concept (also
coming from Confidential Transactions) called coming from Confidential Transactions) called
range proofs: a proof that a number falls within a given range, without revealing range proofs: a proof that a number falls within a given range, without revealing
the number. We won't elaborate on the range proof, but you just need to know the number. We won't elaborate on the range proof, but you just need to know
that for any `r.G + v.H` we can build a proof that will show that _v_ is greater than that for any `r*G + v*H` we can build a proof that will show that _v_ is greater than
zero and does not overflow. zero and does not overflow.
It's also important to note that in order to create a valid range proof from the example above, both of the values 113 and 28 used in creating and signing for the excess value must be known. The reason for this, as well as a more detailed description of range proofs are further detailed in the [range proof paper](https://eprint.iacr.org/2017/1066.pdf). It's also important to note that in order to create a valid range proof from the example above, both of the values 113 and 28 used in creating and signing for the excess value must be known. The reason for this, as well as a more detailed description of range proofs are further detailed in the [range proof paper](https://eprint.iacr.org/2017/1066.pdf).
The requirement to know both values to generate valid rangeproofs is an important feature since it prevents a censoring attack where a third party could lock up UTXOs without knowing their private key by creating a transaction from
Carol's UTXO: 113*G + 2*H
Attacker's output: (113 + 99)*G + 2*H
which can be signed by the attacker since Carols private key of 113 cancels due to the adverserial choice of keys. The new output could only be spent by both the attacker and Carol together. However, while the attacker can provide a valid signature for the transaction, it is impossible to create a valid rangeproof for the new output invalidating this attack.
#### Putting It All Together #### Putting It All Together
@ -255,7 +272,7 @@ A MimbleWimble transaction includes the following:
* A set of inputs, that reference and spend a set of previous outputs. * A set of inputs, that reference and spend a set of previous outputs.
* A set of new outputs that include: * A set of new outputs that include:
* A value and a blinding factor (which is just a new private key) multiplied on * A value and a blinding factor (which is just a new private key) multiplied on
a curve and summed to be `r.G + v.H`. a curve and summed to be `r*G + v*H`.
* A range proof that shows that v is non-negative. * A range proof that shows that v is non-negative.
* An explicit transaction fee, in clear. * An explicit transaction fee, in clear.
* A signature, computed by taking the excess blinding value (the sum of all * A signature, computed by taking the excess blinding value (the sum of all

View file

@ -1,6 +1,6 @@
# Einführung in MimbleWimble und Grin # Einführung in MimbleWimble und Grin
*In anderen Sprachen lesen: [English](intro.md), [简体中文](intro_ZH-CN.md), [Español](intro_ES.md), [Nederlands](intro_NL.md), [Русский](intro_RU.md), [日本語](intro_JP.md), [Deutsch](intro_DE.md), [Portuguese](intro_PT-BR.md).* *In anderen Sprachen lesen: [English](intro.md), [简体中文](intro_ZH-CN.md), [Español](intro_ES.md), [Nederlands](intro_NL.md), [Русский](intro_RU.md), [日本語](intro_JP.md), [Deutsch](intro_DE.md), [Portuguese](intro_PT-BR.md), [Korean](intro_KR.md)*
MimbleWimble ist ein Blockchain-Format und Protokoll, welches auf starke kryptographische Primitiven setzt und dadurch äußerst gute Skalierbarkeit, Privatsphäre und Fungibilität bietet. Es befasst sich mit Lücken, die in fast allen gegenwärtigen Blockchainimplementierungen existieren. MimbleWimble ist ein Blockchain-Format und Protokoll, welches auf starke kryptographische Primitiven setzt und dadurch äußerst gute Skalierbarkeit, Privatsphäre und Fungibilität bietet. Es befasst sich mit Lücken, die in fast allen gegenwärtigen Blockchainimplementierungen existieren.

View file

@ -1,6 +1,6 @@
# Introducción a MimbleWimble y Grin # Introducción a MimbleWimble y Grin
*Lea esto en otros idiomas: [English](intro.md), [简体中文](intro_ZH-CN.md), [Español](intro_ES.md), [Nederlands](intro_NL.md), [Русский](intro_RU.md), [日本語](intro_JP.md), [Deutsch](intro_DE.md), [Portuguese](intro_PT-BR.md).* *Lea esto en otros idiomas: [English](intro.md), [简体中文](intro_ZH-CN.md), [Español](intro_ES.md), [Nederlands](intro_NL.md), [Русский](intro_RU.md), [日本語](intro_JP.md), [Deutsch](intro_DE.md), [Portuguese](intro_PT-BR.md), [Korean](intro_KR.md).*
MimbleWimble es un formato y un protocolo de cadena de bloques que proporciona una escalabilidad, privacidad y funcionalidad MimbleWimble es un formato y un protocolo de cadena de bloques que proporciona una escalabilidad, privacidad y funcionalidad
extremadamente buenas al basarse en fuertes algoritmos criptográficos. Aborda los vacíos existentes en casi todas las extremadamente buenas al basarse en fuertes algoritmos criptográficos. Aborda los vacíos existentes en casi todas las

View file

@ -1,6 +1,6 @@
# MimbleWimble と Grin 概論 # MimbleWimble と Grin 概論
*この文章を他の言語で読む: [English](intro.md), [简体中文](intro_ZH-CN.md), [Español](intro_ES.md), [Nederlands](intro_NL.md), [Русский](intro_RU.md), [日本語](intro_JP.md), [Deutsch](intro_DE.md), [Portuguese](intro_PT-BR.md).* *この文章を他の言語で読む: [English](intro.md), [简体中文](intro_ZH-CN.md), [Español](intro_ES.md), [Nederlands](intro_NL.md), [Русский](intro_RU.md), [日本語](intro_JP.md), [Deutsch](intro_DE.md), [Portuguese](intro_PT-BR.md), [Korean](intro_KR.md).*
MimbleWimble は、極めてよいスケーラビリティ、プライバシー、そして代替可能性fungibilityの解決法を提供 MimbleWimble は、極めてよいスケーラビリティ、プライバシー、そして代替可能性fungibilityの解決法を提供
するブロックチェーンのフォーマット・プロトコルである。MimbleWimble は、ほとんどすべてのブロックチェーンの するブロックチェーンのフォーマット・プロトコルである。MimbleWimble は、ほとんどすべてのブロックチェーンの

318
doc/intro_KR.md Normal file
View file

@ -0,0 +1,318 @@
# MimbleWimble 과 Grin 에 대한 소개
*다른 언어로 Intro를 읽으시려면: [English](intro.md), [简体中文](intro.zh-cn.md), [Español](intro_ES.md), [Русский](intro.ru.md), [日本語](intro.jp.md).*
MimbleWimlbe은 블록체인 포맷이면서 프로토콜 입니다.
MimbleWimble은 암호학적 기반에 의해서 극대화된 좋은 확장성, 프라이버시, 그리고 대체가능성을 제공합니다. 이러한 특성은 지금 현존하는 모든 블록체인 구현체에 존재하는 문제점들을 처리합니다.
Grin 은 Mimble Wimble 블록체인을 구현한 오픈소스 프로젝트 입니다. 또한 완전한 블록체인와 크립토 커런시의 배포에 필요한 갭을 채워줍니다.
Grin 프로젝트의 주요 목적과 특성들은 아래 설명을 참고하십시오.
* 프라이버시가 기본으로 제공됩니다. 이 기능은 필요에 따라서 선택적으로 정보를 공개 할 수 없도록 해서 완전한 대체가능성을 할 수 있게 합니다.
* 주로 유저의 규모와 최소한의 트랜잭션 수의 규모로 (100byte 미만의 kernel(transaction)) 다른 블록체인들과 비교하면 많은 저장공간을 절약할 수 있습니다.
* Mimble Wimble 은 수십년 동안 테스트하고 사용되었던 강력한 암호기술인 ECC만 사용합니다.
* 간단한 디자인은 감사와 유지보수를 시간이 지나도 수월하게 만듭니다.
* 커뮤니티가 주도하며, 채굴 탈중앙화가 권장됩니다.
## 모두의 혀를 묶자.
이 문서는 블록체인에 대해 어느정도 이해가 있고 암호학에 대한 기본적인 이해가 있는 독자들을 대상으로 합니다. 이것을 염두에 두고 우리는 MimbleWimble의 기술적인 발전과 어떻게 Grin에 적용되었는지 관해 설명 할 것입니다.
저희는 이 문서가 대부분의 기술적인 성격을 가진 독자들을 이해시킬 수 있길 바랍니다. 우리의 목적은 독자가 Grin에 대해 흥미를 느끼게 하고 어떤 방식으로든 Grin에 기여할 수 있게 이끄는 것입니다.
이러한 목적을 이루기 위해, 우리는 MimbleWimble 의 구현체인 Grin을 이해하는데 필요한 주요 컨셉들에 대해서 소개할것입니다.
우선 Grin이 어디에서 부터 기초로 하고 있는지에 대해 이해하기 위해서 타원 곡선 암호 (ECC)의 몇몇 속성들에 대한 간단한 설명으로 시작하겠습니다. 그 다음, MimbleWimble 블록체인의 트랜잭션과 블록에 한 모든 요소들을 설명하겠습니다.
### 타원곡선에 대한 조그마한 조각들
ECC의 너무 복잡한 사항을 캐지 않고 어떻게 mimble wimble 이 어떻게 작동하는지에 대해 이해하는데 필요한 요소들만 리뷰할 것입니다. 이런 가정들을 좀 더 알고싶은 독자들은 [이 링크](http://andrea.corbellini.name/2015/05/17/elliptic-curve-cryptography-a-gentle-introduction/)를 참고하세요.
암호학에서의 타원 곡선이란 우리가 _C_ 라고 부르는 단순히 아주 큰 좌표의 집합입니다.
이 좌표들은 정수들로 (인티저, 또는 스칼라 ) 더하고 빼고 곱할 수 있습니다.
주어진 정수 _K_ 에 스칼라 곱셈을 한다면 우리는 곡선 _c_ 위에 있는 좌표 K*H를 계산 할 수 있습니다.
또 달리 주어진 정수 _j_ 에 우리는`k*H + j*H` 와 같은 `(k+j)*H`를 계산 할 수 있습니다.
타원곡선 위에서의 덧셈과 정수 곱셈은 제시된 수의 순서에 관계없이 결과가 동일하다는 성질과 덧셈과 곱셈의 계산 순서와 관계없이 동일한 결과가 나온다는 성질을 가지고 있습니다.
(k+j)*H = k*H + j*H
ECC 안에서 우리가 매우 큰 숫자인 _k_ 를 프라이빗 키로 가정할 때 `k*H` 는 해당하는 퍼블릭 키로 해당되어 집니다. 누군가 공개키인 `k*H`의 값을 알더라도 _k_ 를 추론해 내는것은 불가능에 가깝습니다. ( 달리 얘기하자면, 곱셉은 쉬우나 곡선 좌표에 의한 "나눗셈"은 정말 어렵습니다. )
_k_ 와 _j_ 둘다 비밀키인 이전 공식 `(k+j)*H = k*H + j*H` 는 두개의 비밀키를 더해서 얻은 한 개의 공개키 (`(k+j)*H`) 와 각각 두개의 비밀키에 공개키를 더한것과 같습니다. Bitcoin blockchain에서도 HD 지갑은 이 원칙에 의존하고 있습니다. MimbleWimble 과 Grin의 구현또한 마찬가지 입니다.
### MimbleWimble 함께 거래하기
트랜잭션의 구조는 MimbleWimble의 강력한 프라이버시와 비밀이 유지된다라고 하는 중요한 규칙을 나타냅니다.
MimbleWimble 트랜잭션의 확인은 두가지 기본적인 성격을 전제로 합니다.
* **제로섬의 검증:** 결과값에서 입력값을 뺸 합은 항상 0과 같습니다. 이것은 실제 전송되는 코인의 양을 드러내지 않고도 트랜잭션ㅇ이 새로운 코인을 만들지 않았다는 것을 증명합니다.
* **비밀키의 소유:** 다른 많은 크립토 커런시 들처럼 , 트랜잭션의 소유권은 ECC 비밀키에 의해 보장됩니다. 그러나 어떤 실체가 이런 비밀키들을 소유하고 있다고 증명하는것이 직접적으로 트랜잭션에 사인한다고해서 얻어지는 것은 아닙니다.
다음 섹션들에서는 잔고, 소유권, 거스름돈과 증명들의 상세들이 어떻게 저 두가지 기본적인 성질에 의해서 얻어지는지 알아보겠습니다.
#### 잔고
위에서 언급한 ECC의 특성들을 기반으로 해서 트랜잭션안의 가치들을 보기 어렵게 할 수 있습니다.
만약 _v_ 가 트랜잭션 입력값이거나 출력값이고 _H_ 가 타원곡선이라면 , 단순히 _v_ 대신 `v*H`를 끼워 넣을 수 있습니다.
이것은 ECC를 사용하기 때문에 작동하는 것입니다. 우리는 출력값의 합이 입력값의 합과 같다는 것을 여전히 확인할 수 있습니다.
v1 + v2 = v3 => v1*H + v2*H = v3*H
이 특성을 모든 트랜잭션에 확인하는것은 프로토콜이 트랜잭션은 돈을 난데없이 만들지 않는다는 것을 실제 돈이 얼마나 있는지 알지 않아도 검증할 수 있게 합니다.
그러나 사용가능한 한정된 숫자가 있고 그 숫자 중 하나를 사용해서 당신의 트랜잭션이 얼마만큼의 코인을 가졌는지 추측 할 수 있습니다. 더해서, v1을 알고 ( 예시로 사용된 이전의 트랜잭션에서 온 값 ) 그에따른 `v1*H`의 결과를 알면 블록체인 전체에 걸쳐서 v1 값이 있는 모든 출력값들이 드러나게 됩니다.
이러한 이유로 두번째 타원곡선인 _G_ 를 제시합니다. ( 실제로 _G__H_ 의 그룹과 같은 곡선에 있으며 단지 다른 좌표를 생성해 냅니다.) 그리고 비밀키 _r_*blinding factor* 로 사용됩니다.
그렇다면 트랜잭션 안의 입력값과 출력값은 다음과 같이 표현됩니다.
r*G + v*H
여기서
* _r_ 은 비밀키이고 blinding factor 로 사용됩니다. _G_ 는 타원 곡선 이고 `r*G`_G_ 안에 있는 _r_ 의 공개키 입니다.
* _v_ 는 출력값이거나 입력값이고 _H_ 는 다른 타원곡선입니다.타원곡선의 근본적인 특성을 이용했기 때문에 _v__r_ 은 추측될 수 없습니다. `r*G + v*H`_Pedersen Commitment_ 라고 부릅니다.
예를 들어 , ( 전송료는 무시하고) 두개의 입력값과 한개의 출력값으로 트랜잭션을 만들기 원한다고 가정해봅시다.
* vi1 과 v2 는 출력값
* vo3는 출력값 이라면
그렇다면
vi1 + vi2 = vo3
입니다.
각각의 입력값에 대해서 blining factor 로 비밀키를 만들고 각각의 값을 각각의 이전의 공식에 있던 Pederson Commitment로 교체한다고 하면 다음과 같습니다.
(ri1*G + vi1*H) + (ri2*G + vi2*H) = (ro3*G + vo3*H)
결과로 다음과 같습니다.
ri1 + ri2 = ro3
이것이 MimbleWimble의 첫번째 특징입니다. 트랜잭션을 검증하는 산술적인 연산은 아무런 값을 알지 못해도 가능합니다.
이 아이디어는 Greg Maxwell 의 [Confidential Transactions](https://elementsproject.org/features/confidential-transactions/investigation) 에서 유래했습니다. Confidential transaction은 Adam back의 비트코인에 동형암호를 적용하자는 제안에서 비롯되었습니다.
#### 소유권
이전의 섹션에서 트랜잭션의 값을 보기 어렵게 하는 Blinding factor로서 비밀키를 소개했습니다. MimbleWimble 의 두번째 통찰은 비밀키가 어떤 값의 소유권을 증명하는데 사용할 수 있다는 것입니다.
Alice는 당신에게 3 코인을 보내면서 그 양을 가렸고, 당신은 28을 당신의 blinding factor로 선택했습니다. ( 실제로 blinding factor는 비밀키로 정말 무진장 큰 숫자 입니다.)
블록체인 어딘가에 다음과 같은 출력값이 나타나 있고 당신에 의해서만 소비될 수 있습니다.
X = 28*G + 3*H
_X_ 는 덧셈의 결과이면서 모두에게 다 보여집니다. 3은 당신과 Alice만 알고 있고 28은 당신만이 알고 있습니다.
다시 3코인을 보내기 위해선, 프로토콜은 어떻게든 28을 알고 있어야 됩니다. 어떻게 이것이 작동하는지 보기 위해서, 당신이 캐롤에게 같은 3코인을 보내고 싶어한다고 합시다. 그렇다면 당신은 아래와 같은 간단한 트랜잭션을 작성해야 합니다.
Xi => Y
여기서 _Xi_는 _X_ 출력을 사용하는 입력이고 Y는 Carol의 출력입니다.
당신의 비밀키인 28을 모르고서는 트랜잭션과 잔액을 만들 수 있는 방법이 없습니다.
실제로 캐롤이 이 트랜잭션의 잔액을 위해선 그녀는 받는 값과 당신의 비밀키를 알아야 합니다.
그러므로
Y - Xi = (28*G + 3*H) - (28*G + 3*H) = 0*G + 0*H
입니다.
모든계산이 0으로 되었는지 확인함으로써, 새로운 돈이 만들어지지 않았다는 것을 확인할 수 있습니다.
오 잠시만요! 당신은 지금 캐롤의 출력값에 비밀키가 있다는것을 알았습니다. ( 이런경우에는 당신의 잔액이 나간것과 동일 해야 합니다.) 그리고 당신은 캐롤로 부터 돈을 훔칠수 있습니다. 이걸 해결하기위해서 캐롤은 그녀가 선택한 비밀키를 사용합니다.
캐롤이 113을 비밀키로 선택했다면 블록체인 안에서는 아래와 같이 마무리 됩니다.
Y - Xi = (113*G + 3*H) - (28*G + 3*H) = 85*G + 0*H
모든 blinding factor합계 결과로 타원곡선 _G_ 위에서 트랜잭션은 _초과값_ (85) 을 가지게 되고 트랜잭션의 합은 더이상 0 이 아닙니다.
그러나 `85*G` 은 비밀키 85 와 함께 타원곡선 _G_ 에서 유효한 공개키이기 때문에 모든 x와 y는 `y = 0``x*G + y*H`일때 곡선 _G_ 에서 유효한 공개키입니다.
그러므로 모든 프로토콜은 (`Y - Xi`) 가 _G_위에서 유효한 공개키인지 ,거래당사자들이 비밀키를 알고있는지 ( 캐롤과의 트랜잭션에서는 85) 를 검증해야 할 필요가 있습니다. 가장 간단하게 검증하는 방법은 Signature가 초과값과 함께 만들어졌다는 것을 요구한 다음 아래와 같은것을 인증하는 겁니다.
* 거래하는 당사자들은 모두 비밀키를 알고 있고
* 트랜잭션의 입력값을 뺀 출력값들의 합은 0입니다. ( 왜냐하면 비밀키와 매칭된 유효한 공개키만 Signature 를 체크할 것이기 때문입니다. )
모든 트랜잭션에 포함된 이 Signature 는 덧붙여진 어떤 데이터와 함께(채굴 수수료와 같은 데이터) _transaction kernel_ 이라고 부르고 모든 Validator 에 의해 체크됩니다.
#### 몇몇 더 좋은 점들
이 섹션은 트랜잭션을 만들때 잔돈이 어떻게 보여지고 범위 증명(range proofs)의 요구사항에 대해서 모든 값이 음수가 아닌지에 대해서 좀 더 자세하게 설명하려고 합니다. 이러한 개념들 역시 MimbleWimble 과 Grin 에 대한 이해가 당연히 필요합니다. 만약 당신이 조급하다면 [이 링크를 참고하세요.](#putting-it-all-together).
##### 잔돈에 대해서
캐롤에게 2개의 코인을 보내고 3개를 앨리스에게서 받는다고 해봅시다.이렇게 하려면 당신은 남은 1개의 코인을 잔돈으로 당신에게 돌려줘야 합니다. 이때, 다른 비밀키를 blinding factor 로 만들어서 (12라고 합시다.) 출력값을 보호해야 합니다. 캐롤은 이전에 썻던 그녀의 비밀키를 씁니다.
잔돈의 출력값 : 12*G + 1*H
캐롤의 출력값 : 113*G + 2*H
블록체인 안에서의 결과는 예전과 매우 흡사합니다. 그리고 Signature 은 초과되는 값과 함께 다시 만들어질겁니다. 이 예시에서는 97이라고 합시다.
(12*G + 1*H) + (113*G + 2*H) - (28*G + 3*H) = 97*G + 0*H
##### Range Proofs
위의 모든 계산에서 트랜잭션의 값들은 항상 양의(+)값입니다. 음의 값은 모든 트랜잭션마다 새로운 돈을 만들수 있다는 것이므로 매우 문제점이 될겁니다.
예를 들어 입력값이 3이고 출력값이 5과 -3인 트랜잭션을 만들수 있으며 이것은 이전 섹션의 정의에 따라 잘 구성된 트랜잭션입니다. 적절한 좌표 `x.H`가 다른 좌표처럼 곡선위에 있어서 _x_가 음수이더라도 찾기가 쉽지 않습니다.
이 문제점을 해결하기 위해서, MimbleWimble 은 Range proofs 라는 다른 암호학 개념을 사용합니다. ( 이 또한 Confidential Transaction 에서 유래했습니다.)
Range proof 란 숫자를 밝히지 않고 어떤 숫자가 주어진 범위안에 있는지 증명하는 것입니다.
Range proof 에 대해서 자세히 설명하지 않을것이지만은, 그래도 어떤 `r.G + v.H` 의 결과가 _v_ 가 0보다 크고 오버플로우가 일어나지 않는다는 것을 증명할 수 있습니다. 또한 위의 예에서 유효한 Range proof 를 만들기 위해서 트랜잭션을 만들고 Signing 할때 사용된 초과값인 113과 28 두 값이 알려지는것은 중요합니다. 그 이유에 대해선 [range proof paper](https://eprint.iacr.org/2017/1066.pdf) 안에 Range proof에 대해 좀더 자세한 설명이 있습니다.
#### 모든것을 함깨 놓고 이해하기
MimbleWimlbe 트랜잭션은 다음을 포함합니다.
* 이전의 출력값들이 참조하고 사용한 입력값의 셋트들
* 새로운 출력값들은 다음을 포함합니다.
* 곡선위에서 `r.G + v.H` 로 합해 지는 값 과 blinding factor (그냥 새로운 비밀 키).
* v 가 음수가 아님을 보여주는 Range proof.
* 분명히 명시된 트랜잭션 수수료
* 수수료가 더해진 모든 출력밧에서 입력값을 뺸 초과 blinding 값이 계산되고 그것이 비밀키로 사용된 Signature.
### 블록들과 체인 state에 대해서
위에서 MimbleWimble 트랜잭션이 유요한 블록체인에 필요한 속성을 유지하면서 어떻게 강한 익명성을 보장하는지 설명했습니다.예를 들면 트랜잭션이 더이상 코인을 만들지 않으면서 비밀키를 통해 소유권을 증명하지 않는 방법들 같은것 말이죠.
추가적으로 _cut-through_ 라는 개념이 MimbleWimble 블록 포멧에 사용 됩니다. 이로 인해 MimbleWimble 체인은 아래와 같은 장점을 얻습니다.
* 대부분의 트랜잭션 데이터는 보안을 희생하지 않고서도 시간이 지나면 없어 질 수 있으므로 엄청나게 좋은 확장성을 얻게 됩니다.
* 트랜잭션 데이터를 섞고 없애서 익명성을 추가로 획득합니다.
* 새로운 노드가 네트웍에서 동기화를 이룰때 매우 효과적입니다.
#### 트랜잭션 합치기
트랜잭션은 다음와 같은것들로 이뤄져 있다는걸 상기해봅시다.
* 이전의 출력값들이 참조하고 사용한 입력값의 셋트들
* 새로운 출력값의 세트들 ( Pederson commitment)
* kernal execess와 (kernel 초과값이 공개키로 사용된) 트랜잭션 Signature로 이뤄진 트랜잭션 Kernel.
sign 된 트랜잭션과 Signature 은 _transaction kernel_ 에 포함됩니다.
Signature 공개키로서 트랜잭션의 합이 0임을 증명하는 _kernel excess_ 를 이용해서 생성됩니다.
(42*G + 1*H) + (99*G + 2*H) - (113*G + 3*H) = 28*G + 0*H
이번 예시에서 공개키는 `28*G` 입니다.
다음은 어떠한 유효한 트랜잭션에서도 참이라고 말 할 수 있습니다. (단순함을 위해 수수료는 무시합니다. )
출력값의 합 - 입력값의 합 = kernel_excess
블록이 입력값과 출력값의 합 그리고 트랜잭션 kernel들의 집합이면 블록도 마찬가지라고 할 수 있습니다. 트랜잭션의 출력값을 더할 수 있고 입력값의 합을 뺀다음 그 결과인 Perderson commitment 와 kernal excess와 비교합니다.
출력값의 합 - 입력값의 합 = kernel_excess의 합
약간 단순화 시켜서 ( 트랜잭션 수수료를 무시하고) 우리는 MimbleWimbl block 이 MimbleWimble 트랜잭션들로 다뤄진다고 말 할 수 있습니다.
##### Kernel 오프셋들
위에 설명했던겉 처럼 MimbleWimble 블록과 트랜잭션에 조그마한 문제가 있습니다. 그것은 블록에 있는 구성 트랜잭션을 재구성하는것이 가능합다는 겁니다.(그리고 어떤 사소한 경우에도요).
이것은 분명히 프라이버시에는 좋지 않습니다. 이걸 "subset" 문제 라고 합니다.
"Subset" 문제란 주어진 입력값들, 출력값들과 트랜잭션 kernel들의 Subset 들이 재조합되어서 유효한 트랜잭션을 다시 만든다는 것입니다.
예를 들어 다음과 같이 두 트랜잭션이 있다고 해봅시다.
(in1, in2) -> (out1), (kern1)
(in3) -> (out2), (kern2)
다음과 같은 블록에 합칠 수 있을겁니다. ( 아니면 트랜잭션을 합쳐도 됩니다.)
(in1, in2, in3) -> (out1, out2), (kern1, kern2)
(합계가 0일경우 ) 트랜잭션들 중 하나를 복구하기 위해서 가능한 모든 순열 조합을 조합해보는것은 쉽습니다.
(in1, in2) -> (out1), (kern1)
또한 남은 트랜잭션이 다른 유효한 트랜잭션을 만드는데 사용되기도 합니다.
(in3) -> (out2), (kern2)
이런것을 완화 시키기 위해 _kernel offset_ 이라는 것을 모든 트랜잭션 kernel 에 포함시킵니다. 실행 값이 0이라는 것을 증명하기 위해 kernel excess 에 더해져야 하는 blinding factor (비밀키)입니다.
출력값의 합 - 입력값의 합 = kernel_excess + kernel 오프셋(offset)
블록 안에서 트랜잭션을 합칠때, _single_ 통합 오프셋(offset)을 블록 헤더에 저장합니다.
그래서 single 오프셋으로 인해 개별 트랜잭션 kernel offset 을 개별로 분리할 수 없고 트랜잭션 들은 더이상 재구성 될 수 없습니다.
출력값의 합 - 입력값의 합 = kernel_excess의 합 + kernel_offset
`k`를 트랜잭션 구성 중에 `k1+k2` 안에 나누어서 넣었습니다. 트랜잭션 커널인`(k1+k2)*G` 에 대해 excess 인 `k1*G`와 오프셋(offset) 인 `k2`를 보여주고 이전처럼 `k1*G`로 트랜잭션에 sign 합니다.
블록을 만드는 동안 블록안의 모든 트랜잭션을 커버하기 위한 한개의 통합 `k` 오프셋을 만들기 위해 `k2`오프셋을 간단히 합할 수 있습니다. `k2`오프셋은 어떤 개별 트랜잭션이 복구되지 못하도록 합니다.
#### 컷 스루 (Cut-through)
블록들은 채굴자들이 여러 트랜잭션들을 하나의 세트에 넣고 체인에 더할수 있게 합니다.
다음 블록은 3개의 트랜잭션을 포함하고 있습니다. 오직 입력과 출력만을 보여줍니다.
소비한 출력값은 입력값을 참고 합니다. 출력값은 소문자 x로 표시된 이전 블록을 포함합니다.
I1(x1) --- O1
|- O2
I2(x2) --- O3
I3(O2) -|
I4(O3) --- O4
|- O5
다음과 같은 두가지 성질을 알려드리자면:
* 이 블록 안에서는 어떤 출력값은 포함된 입력값을 바로 사용합니다.(I3는 02를 소비하고 I4는 03을 소비합니다.)
* 실제로 각 트랜잭션의 구조는 문제가 아닙니다. 모든 트랜잭션들의 개개의 합계가 0이듯이 모든 트랜잭션의 입력값과 출력값이 0이여야만 합니다.
트랜잭션과 비슷하게 블록에서 체크해야 되는 것은 _transaction kernels_ 에서 비롯되는 소유권의 증명과 coinbase 에서 증가하는 코인 외 모든 블록이 돈의 공급을 추가하지 않았다는 것입니다.
매칭된 값은 전체의 값을 상쇄하므로 매치되는 입력값과 출력값은 없앨 수 있고 다음과 같이 좀 더 작은 블록이 됩니다.
I1(x1) | O1
I2(x2) | O4
| O5
모든 트랜잭션 구조는 다 제거되었고 입력값과 출력값의 순서는 더이상 중요하지 않습니다.
그러나 블록에서 입력값을 뺸 모든 출력값의 합은 여전히 0임을 보증합니다.
블록은 아래와 간단히 말하자면 아래를 포함합니다.
* 블록헤더
* 컷 스루 이후 남은 입력값의 리스트
* 컷 스루 이후 남은 출력값의 리스트
* 모든 블록을 커버하기 위한 단일 kernel offset
* 트랜잭션 kernel들은 각 트랜잭션에 아래와 같은 것들을 포함합니다.
* The public key `r*G` obtained from the summation of all the commitments.
* 모든 커밋들의 합을 포함한 공개키 `r*G`
* 초과값 (excess value) 을 이용해 생성된 Signature
* 채굴 수수료
이런 방법으로 구조가 만들어진다면 MimbleWimblw 블록은 엄청나게 좋은 프라이버시를 보장할 수 있습니다.
* 중간 트랜잭션 ( 컷 스루 트랜잭션) 은 트랜잭션 kernel에서만 표시될것 입니다.
* 모든 출력값은 똑같이 보일것입니다. 출력값은 다른 것과 구분하기 블가능한 아주 큰 숫자일겁니다. 만약 하나를 다른 출력값에서 제외하려면 모든 출력값을 제외해야 합니다.
* 모든 트랜잭션 구조는 지워지고 출력값이 각 입력값과 매치된다고 말하기엔 불가능 해집니다.
그리고 아직까진 모든게 유효합니다!
#### 모두 컷 스루( Cut-through ) 하기
이전 예시의 블록으로 돌아가서 출력값인 x1,과 x2는 I1과 I2에 대해서 사용되고 이것은 반드시 이전 블록체인 안에서 나타나야 합니다. 이 블록이 추가된 이후에 I1과 I2 과 출력값들은 전체 합계에 영향을 주지 않으므로 모든 체인에서 지워 질 수 있습니다.
일반화 하자면, 헤더를 제외하고 어떤 시점에서든 체인의 스테이트는 다음과 같은 정보로 요약될 수 있습니다.
1. 체인안에서 채굴에 의해서 만들어진 코인의 총량
2. 쓰지 않은 출력값의 모든 세트
3. 각 트랜잭션의 트랜잭션 kernel
첫번째 정보는 Genesis 블록으로부터의 거리인 블록 높이를 가지고 유추 될 수 있습니다. 그리고 쓰지 않는 출력값과 트랜잭션 kernel은 매우 작습니다. 이것에는 아래와 같이 2가지 중요한 결과를 가지고 있습니다.
* MimbleWimble 블록체인에 있는 노드가 유지해야 되는 스테이트가 매우 작습니다.(비트코인 사이즈의 Blockchain의 경우 수 기가 바이트이고 잠재젹으로 수백 메가바이트까지 최적화 될 수 있습니다.)
* 새로운 노드가 MimbleWimble 체인에 가입히면, 전달해야 하는 정보의 양이 매우 적습니다.
덧붙여서 출력값을 더하거나 제거하더라도 쓰지 않는 출력값의 모든 세트를 조작할 순 없습니다. 그렇게 하면 트랜잭션 kernel 내의 모든 blinding factor의 합과 출력값 내의 blinding factor의 합이 달라집니다.
### 결론 내리기
이 문서에서는 MimbleWimble 블록체인 안의 기본적인 원리에 대해서 다루었습니다.
타원 곡선 암호의 다른 성질을 사용해서 알아보기 어려우나 적절하게 입증될 수 있는 트랜잭션을 만들수 있습니다. 블록에 이러한 성질들을 일반화 시키면 큰 용량의 블록체인 데이터를 없앨 수 있고 새로운 피어들에게 높은 확장성과 빠른 동기화를 가능하게 할 수 있습니다.

View file

@ -1,6 +1,6 @@
# Inleiding tot MimbleWimble en Grin # Inleiding tot MimbleWimble en Grin
*Lees dit in andere talen: [English](intro.md), [简体中文](intro_ZH-CN.md), [Español](intro_ES.md), [Nederlands](intro_NL.md), [Русский](intro_RU.md), [日本語](intro_JP.md), [Deutsch](intro_DE.md), [Portuguese](intro_PT-BR.md).* *Lees dit in andere talen: [English](intro.md), [简体中文](intro_ZH-CN.md), [Español](intro_ES.md), [Nederlands](intro_NL.md), [Русский](intro_RU.md), [日本語](intro_JP.md), [Deutsch](intro_DE.md), [Portuguese](intro_PT-BR.md), [Korean](intro_KR.md).*
MimbleWimble is een blockchain formaat en protocol die extreem goede schaalbaarheid, privacy en fungibiliteit biedt door zich te berusten op sterke cryptografische primiteven. Het adresseert de lacunes die in bijna alle huidige blockchain-implementaties bestaan. MimbleWimble is een blockchain formaat en protocol die extreem goede schaalbaarheid, privacy en fungibiliteit biedt door zich te berusten op sterke cryptografische primiteven. Het adresseert de lacunes die in bijna alle huidige blockchain-implementaties bestaan.

View file

@ -1,6 +1,6 @@
# Introdução ao MimbleWimble e ao Grin # Introdução ao MimbleWimble e ao Grin
*Leia isto em outros idiomas: [English](intro.md), [简体中文](intro_ZH-CN.md), [Español](intro_ES.md), [Nederlands](intro_NL.md), [Русский](intro_RU.md), [日本語](intro_JP.md), [Deutsch](intro_DE.md), [Portuguese](intro_PT-BR.md).* *Leia isto em outros idiomas: [English](intro.md), [简体中文](intro_ZH-CN.md), [Español](intro_ES.md), [Nederlands](intro_NL.md), [Русский](intro_RU.md), [日本語](intro_JP.md), [Deutsch](intro_DE.md), [Portuguese](intro_PT-BR.md), [Korean](intro_KR.md).*
O MimbleWimble é um formato e protocolo blockchain que fornece ótima escalabilidade, privacidade e fungibilidade, para isso contando com primitivas criptográficas fortes. Ele aborda as lacunas existentes em quase todos as implementações blockchain atuais. O MimbleWimble é um formato e protocolo blockchain que fornece ótima escalabilidade, privacidade e fungibilidade, para isso contando com primitivas criptográficas fortes. Ele aborda as lacunas existentes em quase todos as implementações blockchain atuais.

View file

@ -1,6 +1,6 @@
# Введение в МимблВимбл и Grin # Введение в МимблВимбл и Grin
*На других языках: [English](intro.md), [简体中文](intro_ZH-CN.md), [Español](intro_ES.md), [Nederlands](intro_NL.md), [Русский](intro_RU.md), [日本語](intro_JP.md), [Deutsch](intro_DE.md), [Portuguese](intro_PT-BR.md).* *На других языках: [English](intro.md), [简体中文](intro_ZH-CN.md), [Español](intro_ES.md), [Nederlands](intro_NL.md), [Русский](intro_RU.md), [日本語](intro_JP.md), [Deutsch](intro_DE.md), [Portuguese](intro_PT-BR.md), [Korean](intro_KR.md).*
МимблВимбл это формат и протокол блокчейна, предоставляющий МимблВимбл это формат и протокол блокчейна, предоставляющий
исключительную масштабируемость, приватность и обезличенность криптовалюты, исключительную масштабируемость, приватность и обезличенность криптовалюты,

View file

@ -1,6 +1,6 @@
# Introduktion till MimbleWimble och Grin # Introduktion till MimbleWimble och Grin
*Läs detta på andra språk: [English](intro.md), [简体中文](intro_ZH-CN.md), [Español](intro_ES.md), [Nederlands](intro_NL.md), [Русский](intro_RU.md), [日本語](intro_JP.md), [Deutsch](intro_DE.md), [Portuguese](intro_PT-BR.md).* *Läs detta på andra språk: [English](intro.md), [简体中文](intro_ZH-CN.md), [Español](intro_ES.md), [Nederlands](intro_NL.md), [Русский](intro_RU.md), [日本語](intro_JP.md), [Deutsch](intro_DE.md), [Portuguese](intro_PT-BR.md), [Korean](intro_KR.md).*
MimbleWimble är ett blockkedjeformat och protokoll som erbjuder extremt bra MimbleWimble är ett blockkedjeformat och protokoll som erbjuder extremt bra
skalbarhet, integritet, och fungibilitet genom starka kryptografiska primitiver. skalbarhet, integritet, och fungibilitet genom starka kryptografiska primitiver.

View file

@ -1,7 +1,7 @@
MimbleWimble 和 Grin 简介 MimbleWimble 和 Grin 简介
===================================== =====================================
*阅读其它语言版本: [English](intro.md), [简体中文](intro_ZH-CN.md), [Español](intro_ES.md), [Nederlands](intro_NL.md), [Русский](intro_RU.md), [日本語](intro_JP.md), [Deutsch](intro_DE.md), [Portuguese](intro_PT-BR.md).* *阅读其它语言版本: [English](intro.md), [简体中文](intro_ZH-CN.md), [Español](intro_ES.md), [Nederlands](intro_NL.md), [Русский](intro_RU.md), [日本語](intro_JP.md), [Deutsch](intro_DE.md), [Portuguese](intro_PT-BR.md), [Korean](intro_KR.md).*
MimbleWimble是一个区块链格式和协议依托于健壮的加密原语提供非常好的可扩展性、隐私和可替代性。它解决了当前几乎所有实现的区块链与现实需求之间差距。MimbleWimble 的白皮书在[本项目的WiKi](https://github.com/mimblewimble/docs/wiki/A-Brief-History-of-MinbleWimble-White-Paper)中可以找到WiKi是开放的。 MimbleWimble是一个区块链格式和协议依托于健壮的加密原语提供非常好的可扩展性、隐私和可替代性。它解决了当前几乎所有实现的区块链与现实需求之间差距。MimbleWimble 的白皮书在[本项目的WiKi](https://github.com/mimblewimble/docs/wiki/A-Brief-History-of-MinbleWimble-White-Paper)中可以找到WiKi是开放的。

View file

@ -1,5 +1,7 @@
# Pruning Blockchain Data # Pruning Blockchain Data
*Read this in other languages: [Korean](pruning_KR.md).*
One of the principal attractions of MimbleWimble is its theoretical space One of the principal attractions of MimbleWimble is its theoretical space
efficiency. Indeed, a trusted or pre-validated full blockchain state only efficiency. Indeed, a trusted or pre-validated full blockchain state only
requires unspent transaction outputs, which could be tiny. requires unspent transaction outputs, which could be tiny.
@ -63,8 +65,7 @@ The full validation of the chain state requires that:
In addition, while not necessary to validate the full chain state, to be able In addition, while not necessary to validate the full chain state, to be able
to accept and validate new blocks additional data is required: to accept and validate new blocks additional data is required:
* The output features and switch commitments, making the full output data * The output features, making the full output data necessary for all UTXOs.
necessary for all UTXOs.
At minimum, this requires the following data: At minimum, this requires the following data:

62
doc/pruning_KR.md Normal file
View file

@ -0,0 +1,62 @@
# 블록체인 데이터 프루닝(가지치기)에 대해
MimbleWimble의 주된 매력 중 하나는 이론적인 공간효율성 입니다. 실제로 신뢰 할수 있거나 또는 사전에 입증된 전체 블록체인 스테이트는 아주 작을수도 있는 UTXO(unspent transaction outputs)만 나타냅니다.
Grin의 블록체인에는 다음 유형의 데이터가 포함됩니다 (MimbleWimble 프로토콜에 대한 사전 지식이 있다고 가정합니다).
1. 아래를 포함하는 트랜잭션 출력값
1. Pedersen commitment (33 bytes).
2. range proof (현재는 5KB 이상)
2. 출력값의 레퍼런스인 트랜잭션 입력값 (32 bytes)
3. 각각의 트랜잭션에 포함된 트랜잭션 "증명들"
1. 트랜잭션의 excess commitment 합계(33 bytes)
2. 초과값과 함께 생성된 서명 (평균 71 bytes)
4. 머클트리와 작업증명을 포함한 블록헤더 (약 250 bytes)
백만개의 블록에 천만 개의 트랜잭션 (2 개의 입력이 있고 평균 2.5 개의 출력값이 있다고 가정할때) 과 10만개의 UTXO(원문에서는 unspent outputs라고 표기 - 역자 주)를 가정 할 때 전체 체인 (Pruing 없음, 컷 쓰루 없음)과 함께 대략적인 체인의 크기를 얻습니다.
* 128GB 크기의 트랜잭션 데이터 (inputs and outputs).
* 1 GB 크기의 트랜잭션 proof data.
* 250MB 크기의 block headers.
* 약 130GB 크기의 전체 체인 사이즈.
* 1.8GB크기의 컷-스루(cut-through) 이후의 전체 체인 사이즈(헤더 데이터는 포함함)
* 520MB 크기의 UTXO 사이즈.
* Total chain size, without range proofs of 4GB.
* 4GB크기의 range proof가 없는 경우 전체 체인 사이즈
* 3.3MB 크기의 range proof가 없는 경우 UTXO 사이즈
모든 데이터에서 체인이 완전히 검증되면 UTXO commitment의 세트 만 노드 작동에 필수적으로 필요합니다.
데이터가 정리(prune) 될 수있는 아래와 같은 몇 가지 상황이 있을 수 있습니다.
* 입증된 풀 노드는 여유공간에 확인된 데이터들을 삭제 할 수 있습니다.
* 풀 노드는 빈 공간의 유효성을 확인
* 부분 검증 노드 (SPV와 유사함)는 모든 데이터를 수신하거나 유지하는 데 관심이 없을 수 있습니다.
* 새 노드가 네트워크에 참여하면 결과적으로 풀 노드가 될지라도 더 빨리 사용할 수있도록 하기 위해 부분 검증 노드로 일시적으로 작동 할 수 있습니다.
## 완전히 정리된 스테이트(Fully Pruned State)의 입증에 대해서
(데이터)Pruning은 가능한 한 많은 양의 데이터를 제거하면서 MimbleWimble 스타일의 검증을 보장하는 것이 필요합니다.
이는 pruning 노드 상태를 정상적으로 유지하는 데 필요할 뿐만 아니라 최소한의 양의 데이터만 새 노드로 전송할 첫번째 고속 동기화에서도 필요합니다.
체인 스테이트의 완전한 입증을 위해 아래와 같은 사항들이 필요합니다.
* 모든 Kernel의 서명들은 kernel의 공개키에 의해서 증명됩니다.
* The sum of all UTXO commitments, minus the supply is a valid public key (can
be used to sign the empty string).
* 모든 커널의 pubkeys 합계는 모든 UTXO commitment에서 공급을 뺀 값과 같습니다.
* UTXO PMMR의 루트 해시, Range proof의 PMMR 및 Kernel의 MMR은 유효한 작업증명 체인의 블록헤더와 일치힙니다.
* 모든 Range proof가 유효해야 합니다.
또한 전체 체인의 스테이트에 대해 확인 할 필요는 없지만 새 블록을 받아들이고 유효성 입증을 하려면 아래와 같은 추가 데이터가 필요합니다.
* 출력 기능에서 모든 UTXO에 필요한 전체 출력 데이터를 만듭니다
(그러기 위해선)최소한 다음과 같은 데이터가 필요합니다.
* 블록헤더의 체인
* 체인에 포함된 순서로 되어있는 모든 Kernel들. 이 Kernel들은 Kernel MMR 의 재구성을 가능하게 합니다.
* 모든 UTXO(원문에서는 unspent output 으로 표기 - 역자 주)
* 정리된 데이터(Pruned data)의 해시를 알기위한 UTXO MMR과 Range proof MMR.
입증된 노드에 의해서 랜덤하게 선택된 Range proof의 하위 set만 증명함으로써 추가 pruning이 가능 할 수 있습니다.

View file

@ -123,9 +123,9 @@ Remember to replace `0.3.1-pre1` as the real version, and warmly remind the [[Ve
If you're NOT the owner of the github repo, but at least you have to be a committer which has the right to do a release, the following steps are needed to trigger a version release: If you're NOT the owner of the github repo, but at least you have to be a committer which has the right to do a release, the following steps are needed to trigger a version release:
1. Go to release page of the repo, click **Draft a new release**, remember to check the branch is what you're working on! set the **Tag version** to the release number (for example: `0.3.1-pre1`), and set anything in **Release title** and **description**, then click **Publish release**. Don't worry the title and description parts because we need delete it in next step. 1. Go to release page of the repo, click **Draft a new release**, remember to check the branch is what you're working on! set the **Tag version** to the release number (for example: `0.3.1-pre1`), and set anything in **Release title** and **description**, then click **Publish release**. Don't worry the title and description parts because we need delete it in next step.
1. Because github **release** will be auto-created by our `auto-release` building script, we MUST delete the **release** which we just created in previous step! (Unfortunately, there's no way to only create **tag** by web.) 1. Because github **release** will be auto-created by our `release-jobs` building script, we MUST delete the **release** which we just created in previous step! (Unfortunately, there's no way to only create **tag** by web.)
Even normally Travis-CI need tens of minutes to complete building, I suggest you complete step 2 quickly, otherwise the `auto-release` script will fail on error "release already exist". Even normally Travis-CI need tens of minutes to complete building, I suggest you complete step 2 quickly, otherwise the `release-jobs` script will fail on error "release already exist".
### 2. Travis-CI Building ### 2. Travis-CI Building
@ -135,7 +135,7 @@ The release building is just one of the **TEST_DIRS**, named as `none`. So each
So, the point is: the release building job is that one tagged with `TEST_DIR=none`. So, the point is: the release building job is that one tagged with `TEST_DIR=none`.
Note: `auto-release` script will only be executed on `deploy` stage, and according to Travis-CI, it will be skipped for any **pull-request** trigger, and since we set `tag: true` it will be only executed when triggered by a tag. Note: `release-jobs` script will only be executed on `deploy` stage, and according to Travis-CI, it will be skipped for any **pull-request** trigger, and since we set `tag: true` it will be only executed when triggered by a tag.
### 3. Check the Release Page ### 3. Check the Release Page

View file

@ -1,5 +1,7 @@
# State and Storage # State and Storage
*Read this in other languages: [Korean](state_KR.md), [日本語](state_JP.md).*
## The Grin State ## The Grin State
### Structure ### Structure

48
doc/state_JP.md Normal file
View file

@ -0,0 +1,48 @@
# 状態とストレージ
*別の言語で読む: [Korean](state_KR.md), [日本語](state_JP.md).*
## Grinの状態
### 構造
Grinのチェーンの全ての情報はこれらによって成り立っている:
1. 全てのUTXOのセット
1. それぞれのアウトプットのレンジプルーフ
1. 全てのトランザクションカーネル
1. 上記に対応するMMR(アウトプットのMMRはUTXOだけでなく、 *全ての* アウトプットのハッシュを含むという例外はある)
加えて、チェーン内の全てのハッシュは最も(PoWの)仕事をしているチェーンにアンカーされている必要がある。
一度それぞれのレンジプルーフをバリデートし、全てのカーネルコミットメントの合計値を計算すれば、もはやレンジプルーフとカーネルはノードにとって必要ないことに注意。
### バリデーション
Grinの全ての状態を知っていれば、これらをバリデートできる:
1. カーネルシグチャがそれぞれのコミットメント(公開鍵)に対して正しいこと。これによりカーネルが正しいと言える。
1. 全てのカーネルコミットメントの合計値が、全てのUTXOコミットメント-全ての共有量と等しいこと。これにより、カーネルとアウトプットコミットメントが全て正しく、予想外のコインが生まれていないことが言える。
1. 全てのUTXO、レンジプルーフ、カーネルのハッシュがそれぞれのMMR内にあり、正しいルートにハッシュされていること。
1. ある時点で与えられているブロックヘッダーの中で最も(PoWの)仕事をしているブロックヘッダーが3つのMMRのルートを含んでいること。これにより、MMRが正しいことと、全ての状態は最も仕事をしているチェーンによって作られたことが検証できる。
### MMRと剪定
それぞれのMMRのリーフードを生成するためのデータは、それらの位置の情報に加え以下の通り:
* アウトプットMMRはフィーチャーフィールドとジェネシス以降の全てのアウトプットのコミットメントのハッシュ
* レンジプルーフMMRは全てのレンジプルーフのデータのハッシュ
* カーネルMMRは全てのカーネルのフィールド(フィーチャー、手数料、ロックハイト、余剰なコミットメント、余剰な署名)のハッシュ
全てのアウトプット、レンジプルーフ、カーネルに対応するMMRはそれらが発生したブロックのMMRに加えられる(全てのブロックデータはソートされている必要があるのはこのため)。
アウトプットが使用されるように、それぞれのコミットメントとレンジプルーフデータは削除されうる。
加えて、対応するアウトプットとレンジプルーフのMMRは剪定されうる。
## 状態のストレージ
Grinにおけるアウトプット、レンジプルーフ、カーネルのデータストレージはシンプルで、メモリーマップドなデータアクセスができる追記型のプレーンなファイルを使用。
アウトプットが使用されたら、削除ログが削除可能な状態として保持される。
それらの状態は全て同じオーダーとしてインサートされるので、MMRードの状態と上手いこに一致している。
削除ログが大きくなった場合、時々削除されたものを除いたうえでリライトする。
これにより、対応するファイルがコンパクト化され(ここでも追記のみ)、削除ログは空になる。
MMRのためには少し複雑化が必要。

46
doc/state_KR.md Normal file
View file

@ -0,0 +1,46 @@
# 상태와 스토리지
## Grin의 상태
### 구조
Grin chain의 모든 상태는 다음 데이터와 같이 이루어져 있습니다.
1. unspent output(UTXO) 세트
1. 각 출력값에 대한 range proof
1. 모든 트랜잭션 커널(kernel)들
1. 1,2,3번의 각각의 MMR들 (예외적으로 출력값 MMR은 사용되지 않은 것 뿐만 아니라 *모든* 출력값의 해쉬를 포함합니다.)
더해서, 유효한 Proof of work 와 함께 chain 안의 모든 헤더들은 상기 상태에 대해 고정되어야 합니다. (상태는 가장 많이 일한 체인과 일치합니다.)
한번 각각의 range proof 가 인증되고 모든 kernel의 실행 합계가 계산되었다면 range proof와 kernel 들은 node 의 작동에 꼭 필요하진 않습니다.
### 인증하기
완전한 Grin의 상태를 사용해서 우리는 다음과 같은 것들을 인증 할 수 있습니다.
1. Kernel 의 signature 가 Kernel의 실행에 대해 유효하다면 (공개키), 이것은 Kernel이 유효하다는것을 증명합니다.
2. 모든 커밋 실행의 합이 모든 UTXO 실행의 합에서 총 공급량을 뺸 값이 같다면 이것은 Kernal과 출력값의 실행들이 유효하고 코인이 새로이 만들어지지 않았다는 것을 증명합니다.
3. 모든 UTXO, range prook 와 Kernel 해쉬들은 각각의 MMR이 있고 그 MMR 들은 유효한 root 를 해쉬합니다.
4. 특정 시점에 가장 많이 일했다고 알려진 Block header 에는 3개의 MMR에 대한 root 가 포함됩니다. 이것은 전체 상태가 가장 많이 일한 chain (가장 긴 체인)에서 MMR과 증명들이 만들어졌다는 것을 입증합니다.
### MMR 과 Pruning
각각의 MMR에서 리프 노드에 대한 해시를 생성하는 데 사용되는 데이터 위치는 다음과 같습니다.
* MMR의 출력값은 제네시스 블록 이후부터 피쳐 필드와 모든 출력값의 실행들을 해시합니다.
* range proof MMR은 모든 Range proof 데이터를 해시합니다.
* Kernel MMR 은 피쳐, 수수료, lock height, excess commitment와 excess Signature같은 모든 값을 해시합니다.
모든 출력, 범위 증명 및 커널은 각 블록에서 발생하는 순서대로 각 MMR에 추가됩니다.블록 데이터는 정렬이(to be sorted) 되어야 합니다.
산출물이 소비됨에 따라 commitment 및 range proof 데이터를 지울 수 있습니다. 또한 해당 출력 및 range proof MMR을 pruning 할 수 있습니다.
## 상태 스토리지
Grin 에 있는 출력값에 대한 데이터 스토리지, Range proof 와 kernel은 간단합니다.
그 형태는 데이터 엑세스를 위한 메모리 매핑 된 append only 파일입니다.
출력값이 소비되는것에 따라서 제거 로그는 지울수 있는 위치를 유지힙니다.
이런 포지션은 MMR과 노드 포지션이 같은 순서로 입력되었으므로 잘 일치합니다.
제거 로그가 커지면 (Append only 파일도 )때때로 해당 파일을 지워진 부분 없이 다시 작성해서 압축하고 제거 로그를 비울 수 있습니다.
MMR은 약간 더 복잡합니다.

View file

@ -1,5 +1,7 @@
# Grin Stratum RPC Protocol # Grin Stratum RPC Protocol
*Read this in other languages: [Korean](stratum_KR.md).*
This document describes the current Stratum RPC protocol implemented in Grin. This document describes the current Stratum RPC protocol implemented in Grin.
## Table of Contents ## Table of Contents

536
doc/stratum_KR.md Normal file
View file

@ -0,0 +1,536 @@
# Grin Stratum RPC 프로토콜
이 문서는 Grin에 구현되어 있는 현재 Stratum RPC protocol 을 설명한 것입니다.
## 목차
1. [Messages](#메세지_들)
1. [getjobtemplate](#getjobtemplate)
2. [job](#job)
3. [keepalive](#keepalive)
4. [login](#login)
5. [status](#status)
6. [submit](#submit)
2. [에러 메시지들](#error-messages)
3. [채굴자의 행동양식](#miner-behavior)
4. [참고 구현체](#reference-implementation)
## 메세지 들
이 섹션에서는 각 메시지와 그 응답에 대해서 상술합니다.
어느때든, 채굴자가 로그인을 제외한 다음 중 한 요청을 하고 login 이 요구된다면 채굴자는 다음과 같은 에러 메시지를 받게 됩니다.
| Field | Content |
| :------------ | :-------------------------------------- |
| id | 요청 ID |
| jsonrpc | "2.0" |
| method | 채굴자가 보낸 method |
| error | {"code":-32500,"message":"login first"} |
예시:
```JSON
{
"id":"10",
"jsonrpc":"2.0",
"method":"getjobtemplate",
"error":{
"code":-32500,
"message":"login first"
}
}
```
만약에 요청이 다음중 하나가 아니라면, Stratum 서버가 아래와 같은 에러 메시지를 보내게 됩니다.
| Field | Content |
| :------------ | :------------------------------------------- |
| id | 요청 ID |
| jsonrpc | "2.0" |
| method | 채굴자가 보낸 method |
| error | {"code":-32601,"message":"Method not found"} |
예시:
```JSON
{
"id":"10",
"jsonrpc":"2.0",
"method":"getgrins",
"error":{
"code":-32601,
"message":"Method not found"
}
}
```
### `getjobtemplate`
채굴자에 의해 시작되는 메시지입니다.
채굴자는 이 메시지로 작업을 요청 할 수 있습니다.
#### Request
| Field | Content |
| :------------ | :----------------------------- |
| id | 요청 ID |
| jsonrpc | "2.0" |
| method | "getjobtemplate" |
| params | null |
예시 :
``` JSON
{
"id":"2",
"jsonrpc":"2.0",
"method":"getjobtemplate",
"params":null
}
```
#### Response
Response 는 두가지 타입이 될 수 있습니다.
##### OK response
예시
``` JSON
{
"id":"0",
"jsonrpc":"2.0",
"method":"getjobtemplate",
"result":{
"difficulty":1,
"height":13726,
"job_id":4,
"pre_pow":"00010000000000003c4d0171369781424b39c81eb39de10cdf4a7cc27bbc6769203c7c9bc02cc6a1dfc6000000005b50f8210000000000395f123c6856055aab2369fe325c3d709b129dee5c96f2db60cdbc0dc123a80cb0b89e883ae2614f8dbd169888a95c0513b1ac7e069de82e5d479cf838281f7838b4bf75ea7c9222a1ad7406a4cab29af4e018c402f70dc8e9ef3d085169391c78741c656ec0f11f62d41b463c82737970afaa431c5cabb9b759cdfa52d761ac451276084366d1ba9efff2db9ed07eec1bcd8da352b32227f452dfa987ad249f689d9780000000000000b9e00000000000009954"
}
}
```
##### Error response
만약 노드가 동기화 중이라면, 다음과 같은 메시지를 보낼것입니다.
| Field | Content |
| :------------ | :-------------------------------------------------------- |
| id | 요청 ID |
| jsonrpc | "2.0" |
| method | "getjobtemplate" |
| error | {"code":-32701,"message":"Node is syncing - Please wait"} |
예시:
```JSON
{
"id":"10",
"jsonrpc":"2.0",
"method":"getjobtemplate",
"error":{
"code":-32000,
"message":"Node is syncing - Please wait"
}
}
```
### `job`
Stratum 서버로 인해 시작되는 메세지입니다.
Stratum 서버는 연결된 채굴자에게 작업을 자동적으로 보냅니다.
채굴자는 job_id=0 이면 현재의 작업을 중단해야 합니다. 그리고 현재의 작업을 현재 graph 가 완료되면 이 작업으로 대체해야 합니다.
#### Request
| Field | Content |
| :------------ | :------------------------------------------------------------------------- |
| id | 요청 ID |
| jsonrpc | "2.0" |
| method | "job" |
| params | Int `difficulty`, `height`, `job_id` and string `pre_pow` |
예시:
``` JSON
{
"id":"Stratum",
"jsonrpc":"2.0",
"method":"job",
"params":{
"difficulty":1,
"height":16375,
"job_id":5,
"pre_pow":"00010000000000003ff723bc8c987b0c594794a0487e52260c5343288749c7e288de95a80afa558c5fb8000000005b51f15f00000000003cadef6a45edf92d2520bf45cbd4f36b5ef283c53d8266bbe9aa1b8daaa1458ce5578fcb0978b3995dd00e3bfc5a9277190bb9407a30d66aec26ff55a2b50214b22cdc1f3894f27374f568b2fe94d857b6b3808124888dd5eff7e8de7e451ac805a4ebd6551fa7a529a1b9f35f761719ed41bfef6ab081defc45a64a374dfd8321feac083741f29207b044071d93904986fa322df610e210c543c2f95522c9bdaef5f598000000000000c184000000000000a0cf"
}
}
```
#### Response
이 메세지에는 Response 가 필요하지 않습니다.
### `keepalive`
연결을 계속 하기 위해서 채굴자에 의해 초기화 되는 메시지입니다.
#### Request
| Field | Content |
| :------------ | :--------------------- |
| id | 요청 ID |
| jsonrpc | "2.0" |
| method | "keepalive" |
| params | null |
예시:
``` JSON
{
"id":"2",
"jsonrpc":"2.0",
"method":"keepalive",
"params":null
}
```
#### Response
| Field | Content |
| :------------ | :----------------------------- |
| id | 요청 ID |
| jsonrpc | "2.0" |
| method | "keepalive" |
| result | "ok" |
| error | null |
예시:
``` JSON
{
"id":"2",
"jsonrpc":"2.0",
"method":"keepalive",
"result":"ok",
"error":null
}
```
### `login`
***
채굴자에 의해 시작되는 메시지입니다.
채굴자는 보통 채굴 프로그램으로 고정적으로 정해지는 login, password, agent 로 Grin Stratum 서버에 로그인 할 수 있습니다.
#### Request
| Field | Content |
| :------------ | :----------------------------- |
| id | 요청 ID |
| jsonrpc | "2.0" |
| method | "login" |
| params | Strings: login, pass and agent |
예시:
``` JSON
{
"id":"0",
"jsonrpc":"2.0",
"method":"login",
"params":{
"login":"login",
"pass":"password",
"agent":"grin-miner"
}
}
```
#### Response
Response 는 두가지 타입이 될 수 있습니다.
##### OK response
| Field | Content |
| :------------ | :----------------------------- |
| id | 요청 ID |
| jsonrpc | "2.0" |
| method | "login" |
| result | "ok" |
| error | null |
예사:
``` JSON
{
"id":"1",
"jsonrpc":"2.0",
"method":"login",
"result":"ok",
"error":null
}
```
##### Error response
아직 구현되지 않았습니다. login이 필요할때, -32500 "Login firtst" 라는 에러를 리턴합니다.
### `status`
채굴자에 의해 시작되는 메시지입니다.
이 메시지는 채굴자에게 현재의 워커와 네트워크의 상태를 얻을 수 있게 합니다.
#### Request
| Field | Content |
| :------------ | :--------------------- |
| id | 요청 ID |
| jsonrpc | "2.0" |
| method | "status" |
| params | null |
예시:
``` JSON
{
"id":"2",
"jsonrpc":"2.0",
"method":"status",
"params":null
}
```
#### Response
Response 는 아래와 같습니다.
| Field | Content |
| :------------ | :------------------------------------------------------------------------------------------------------- |
| id | 요청 ID |
| jsonrpc | "2.0" |
| method | "status" |
| result | String `id`. Integers `height`, `difficulty`, `accepted`, `rejected` and `stale` |
| error | null |
예시:
```JSON
{
"id":"5",
"jsonrpc":"2.0",
"method":"status",
"result":{
"id":"5",
"height":13726,
"difficulty":1,
"accepted":0,
"rejected":0,
"stale":0
},
"error":null
}
```
### `submit`
채굴자에 의해 시작되는 메시지입니다.
마이너가 쉐어를 찾았을때, 노드에게 보내집니다.
#### Request
채굴자는 Stratum 서버에 작업 솔루션을 보냅니다.
| Field | Content |
| :------------ | :-------------------------------------------------------------------------- |
| id | 요청 ID |
| jsonrpc | "2.0" |
| method | "submit" |
| params | Int `edge_bits`,`nonce`, `height`, `job_id` and array of integers `pow` |
예시:
``` JSON
{
"id":"0",
"jsonrpc":"2.0",
"method":"submit",
"params":{
"edge_bits":29,
"height":16419,
"job_id":0,
"nonce":8895699060858340771,
"pow":[
4210040,10141596,13269632,24291934,28079062,84254573,84493890,100560174,100657333,120128285,130518226,140371663,142109188,159800646,163323737,171019100,176840047,191220010,192245584,198941444,209276164,216952635,217795152,225662613,230166736,231315079,248639876,263910393,293995691,298361937,326412694,330363619,414572127,424798984,426489226,466671748,466924466,490048497,495035248,496623057,502828197, 532838434
]
}
}
```
#### Response
Response 는 세가지 타입이 될 수 있습니다.
##### OK response
이 타입은 Stratum 에 받아들여지지만 네트워크 타켓 난이도에서는 유효한 cuck(at)oo 솔루션이 아닙니다.
| Field | Content |
| :------------ | :----------------------------- |
| id | 요청 ID |
| jsonrpc | "2.0" |
| method | "submit" |
| result | "ok" |
| error | null |
예시:
``` JSON
{
"id":"2",
"jsonrpc":"2.0",
"method":"submit",
"result":"ok",
"error":null
}
```
##### Blockfound response
이 타입은 Stratum 에 받아들여지고 네트워크 타켓 난이도에서는 유효한 cuck(at)oo 솔루션 입니다.
| Field | Content |
| :------------ | :----------------------------- |
| id | 요청 ID |
| jsonrpc | "2.0" |
| method | "submit" |
| result | "block - " + hash of the block |
| error | null |
예시:
``` JSON
{
"id":"6",
"jsonrpc":"2.0",
"method":"submit",
"result":"blockfound - 23025af9032de812d15228121d5e4b0e977d30ad8036ab07131104787b9dcf10",
"error":null
}
```
##### Error response
에러 response는 stale과 rejected 라는 두가지 타입이 될 수 있습니다.
##### Stale share error response
이 타입은 유효한 솔루션이나 지난 작업이 현재의 것이 아닙니다.
| Field | Content |
| :------------ | :-------------------------------------------------------- |
| id | 요청 ID |
| jsonrpc | "2.0" |
| method | "submit" |
| error | {"code":-32503,"message":"Solution submitted too late"} |
Example:
```JSON
{
"id":"5",
"jsonrpc":"2.0",
"method":"submit",
"error":{
"code":-32503,
"message":"Solution submitted too late"
}
}
```
##### Rejected share error responses
솔루션이 유효하지 않거나 너무 낮은 난이도일 수 있는 두 가지 가능성이 있습니다.
###### Failed to validate solution error
재출된 솔루션이 유효하지 않을 수 았습니다.
| Field | Content |
| :------------ | :-------------------------------------------------------- |
| id | 요청 ID |
| jsonrpc | "2.0" |
| method | "submit" |
| error | {"code":-32502,"message":"Failed to validate solution"} |
Example:
```JSON
{
"id":"5",
"jsonrpc":"2.0",
"method":"submit",
"error":{
"code":-32502,
"message":"Failed to validate solution"
}
}
```
###### Share rejected due to low difficulty error
제출된 솔루션의 난이도가 너무 낮습니다.
| Field | Content |
| :------------ | :--------------------------------------------------------------- |
| id | 요청 ID |
| jsonrpc | "2.0" |
| method | "submit" |
| error | {"code":-32501,"message":"Share rejected due to low difficulty"} |
Example:
```JSON
{
"id":"5",
"jsonrpc":"2.0",
"method":"submit",
"error":{
"code":-32501,
"message":"Share rejected due to low difficulty"
}
}
```
## Error Messages
Grin Stratum protocole 구현체는 다음과 같은 에러 메시지를 포함하고 있습니다.
| Error code | Error Message |
| :---------- | :------------------------------------- |
| -32000 | Node is syncing - please wait |
| -32500 | Login first |
| -32501 | Share rejected due to low difficulty |
| -32502 | Failed to validate solution |
| -32503 | Solution Submitted too late |
| -32600 | Invalid Request |
| -32601 | Method not found |
## Miner behavior
채굴자들은 반드시 다음과 같은 규칙들을 존중해야 할 것입니다.
- 마이너들은 작업을 시작하기 전에 작업 nounce를 랜덤화 시켜야 합니다.
- 채굴자들은 반드시 서버가 샤로운 작업을 보낼때끼지 같은 작업을 채굴해야 하지만 언제든 새로운 작업을 요청 할 수 있습니다.
- 채굴자들은 서버로 부터 온 작업 요청을 rpc response로 보내면 안됩니다.
- 채굴자들은 RPC "id"를 정할 수 있고 같은 id로 response를 받기를 요구 할 수 있습니다.
- 마이너들은 keepalive 메시지를 보낼수 있습니다.
- 채굴자들은 로그인 request를 보낼 수 있습니다.(어떤 채굴자가 쉐어를 찾았는지 확인하기 위해서 / Log안에서 솔루션을 확인하기 위해 ) 로그인 request는 3가지 파라미터를 가지고 있어야만 합니다.
- Miners MUST return the supplied job_id with submit messages.
- 채굴자들은 주어진 job_id를 제출하는 메시지에 리턴해야 합니다.
## Reference Implementation
현재 구현체는 [mimblewimble/grin-miner](https://github.com/mimblewimble/grin-miner/blob/master/src/bin/client.rs) 에서 참고하세요.

292
doc/switch_commitment.md Normal file
View file

@ -0,0 +1,292 @@
# Introduction to Switch Commitments
## General introduction
In cryptography a _Commitment_ (or _commitment scheme_) refers to a concept which can be imagined
like a box with a lock. You can put something into the box (for example a piece of a paper with a
secret number written on it), lock it and give it to another person (or the public).
The other person doesn't know yet what's the secret number in the box, but if you decide to publish
your secret number later in time and want to prove that this really is the secret which you came
up with in the first place (and not a different one) you can prove this simply by giving the
key of the box to the other person.
They can unlock the box, compare the secret within the box with the secret you just published
and can be sure that you didn't change your secret since you locked it. You "**committed**"
to the secret number beforehand, meaning you cannot change it between the time of
commitment and the time of revealing.
## Examples
### Hash Commitment
A simple commitment scheme can be realized with a cryptographic hash function. For example: Alice and Bob
want to play _"Guess my number"_ and Alice comes up with with her really secret number `29` which
Bob has to guess in the game, then before the game starts, Alice calculates:
hash( 29 + r )
and publishes the result to Bob. The `r` is a randomly chosen _Blinding Factor_ which is
needed because otherwise Bob could just try hashing all the possible numbers for the game and
compare the hashes.
When the game is finished, Alice simply needs to publish her secret number `29` and the
blinding factor `r` and Bob can calculate the hash himself and easily verify that Alice
did not change the secret number during the game.
### Pedersen Commitment
Other, more advanced commitment schemes can have additional properties. For example MimbleWimble
and Confidential Transactions (CT) make heavy use of
_[Pedersen Commitments](https://link.springer.com/content/pdf/10.1007/3-540-46766-1_9.pdf)_,
which are _homomorphic_ commitments. Homomorphic in this context means that (speaking in the
"box" metaphor from above) you can take two of these locked boxes (_box1_ and _box2_) and
somehow "_add_" them together, so that you
get a single box as result (which still is locked), and if you open this single box later
(like in the examples before) the secret it contains, is the sum of the secrets
from _box1_ and _box2_.
While this "box" metaphor no longer seems to be reasonable in the real-world this
is perfectly possible using the properties of operations on elliptic curves.
Look into [Introduction to MimbleWimble](intro.md) for further details on Pedersen Commitments
and how they are used in Grin.
## Properties of commitment schemes:
In general for any commitment scheme we can identify two important properties
which can be weaker or stronger, depending on the type of commitment scheme:
- **Hidingness (or Confidentiality):** How good is the commitment scheme protecting the secret
commitment. Or speaking in terms of our example from above: what would an attacker need to
open the box (and learn the secret number) without having the key to unlock it?
- **Bindingness:** Is it possible at all (or how hard would it be) for an attacker to somehow
find a different secret, which would produce the same commitment, so that the attacker could
later open the commitment to a different secret, thus breaking the _binding_ of the
commitment.
### Security of these properties:
For these two properties different security levels can be identified.
The two most important combinations of these are
- **perfectly binding** and **computationally hiding** commitment schemes and
- **computationally binding** and **perfectly hiding** commitment schemes
"_Computationally_" binding or hiding means that the property (bindingness/hidingness)
is secured by the fact that the underlying mathematical problem is too hard to be solved
with existing computing power in reasonable time (i.e. not breakable today as computational
resources are bound in the real world).
"_Perfectly_" binding or hiding means that even with infinite computing power
it would be impossible to break the property (bindingness/hidingness).
### Mutual exclusivity:
It is important to realize that it's **impossible** that any commitment scheme can be
_perfectly binding_ **and** _perfectly hiding_ at the same time. This can be easily shown
with a thought experiment: Imagine an attacker having infinite computing power, he could
simply generate a commitment for all possible values (and blinding factors) until finding a
pair that outputs the same commitment. If we further assume the commitment scheme is
_perfectly binding_ (meaning there cannot be two different values leading to the same
commitment) this uniquely would identify the value within the commitment, thus
breaking the hidingness.
The same is true the other way around. If a commitment scheme is _perfectly hiding_
there must exist several input values resulting in the same commitment (otherwise an
attacker with infinite computing power could just try all possible values as
described above). This concludes that the commitment scheme cannot be _perfectly
binding_.
#### Always a compromise
The key take-away point is this: it's **always a compromise**, you can never have both
properties (_hidingness_ and _bindingness_) with _perfect_ security. If one is _perfectly_
secure then the other can be at most _computationally_ secure
(and the other way around).
### Considerations for cryptocurrencies
Which roles do these properties play in the design of cryptocurrencies?
**Hidingness**:
In privacy oriented cryptocurrencies like Grin, commitment schemes are used to secure
the contents of transactions. The sender commits to an amount of coins he sends, but for
the general public the concrete amount should remain private (protected by the _hidingness_ property of the commitment scheme).
**Bindingness**:
At the same time no transaction creator should ever be able to change his commitment
to a different transaction amount later in time. If this would be possible, an attacker
could spend more coins than previously committed to in an UTXO (unspent transaction
output) and therefore inflate coins out of thin air. Even worse, as the amounts are
hidden, this could go undetected.
So there is a valid interest in having both of these properties always secured and
never be violated.
Even with the intent being that both of these properties will hold for the lifetime
of a cryptocurrency, still a choice has to be made about which commitment scheme to use.
#### A hard choice?
Which one of these two properties needs to be _perfectly_ safe
and for which one it would be sufficient to be _computationally_ safe?
Or in other words: in case of a disaster, if the commitment scheme unexpectedly
gets broken, which one of the two properties should be valued higher?
Economical soundness (no hidden inflation possible) or ensured privacy (privacy will
be preserved)?
This seems like a hard to choice to make.
If we look closer into this we realize that the commitment scheme only needs to be
_perfectly_ binding at the point in time when the scheme actually gets broken. Until
then it will be safe even if it's only _computationally_ binding.
At the same time a privacy-oriented cryptocurrency needs to ensure the _hidingness_
property **forever**. Unlike the _binding_ property, which only is important at the
time when a transaction is created and will not affect past transactions, the _hidingness_
property must be ensured at all times. Otherwise, in the unfortunate case should the
commitment scheme be broken, an attacker could go back in the chain and unblind
past transactions, thus break the privacy property retroactively.
## Properties of Pedersen Commitments
Pedersen Commitments are **computationally binding** and **perfectly hiding** as for a given
commitment to the value `v`: `v*H + r*G` there may exist a pair of different values `r1`
and `v1` such that the sum will be the same. Even if you have infinite computing power
and could try all possible values, you would not be able to tell which one is the original one
(thus _perfectly hiding_).
## Introducing Switch Commitments
So what can be done if the bindingness of the Pedersen Commitment unexpectedly gets broken?
In general a cryptocurrency confronted with a broken commitment scheme could choose to
change the scheme in use, but the problem with this approach would be that it requires to
create new transaction outputs using the new scheme to make funds secure again. This would
require every coin holder to move his coins into new transaction outputs.
If coins are not moved into new outputs, they will not profit from the
security of the new commitment scheme. Also, this has to happen **before** the scheme gets
actually broken in the wild, otherwise the existing UTXOs no longer can be assumed
to contain correct values.
In this situation [_Switch Commitments_](https://eprint.iacr.org/2017/237.pdf) offer a neat
solution. These type of commitments allow changing the properties of the commitments just
by changing the revealing / validating procedure without changing the way commitments
are created. (You "_switch_" to a new validation scheme which is backwards
compatible with commitments created long before the actual "_switch_").
### How does this work in detail
First let's introduce a new commitment scheme: The **ElGamal commitment** scheme is a commitment
scheme similiar to Pedersen Commitments and it's _perfectly binding_ (but only _computationally
hiding_ as we can never have both).
It looks very similar to a Pedersen Commitment, with the addition of a new
element, calculated by multiplying the blinding factor `r` with another generator point `J`:
v*H + r*G , r*J
So if we store the additional field `r*J` and ignore it for now, we can treat it like
Pedersen Commitments, until we decide to also validate the full ElGamal
commitment at some time in future. This is exactly what was implemented in an
[earlier version of Grin](https://github.com/mimblewimble/grin/blob/5a47a1710112153fb38e4406251c9874c366f1c0/core/src/core/transaction.rs#L812),
before mainnet was launched. In detail: the hashed value of `r*J`
(_switch\_commit\_hash_) was added to the transaction output, but this came with
the burden of increasing the size of each output by 32 bytes.
Fortunately, later on the Mimblewimble mailinglist Tim Ruffing came up with a really
[beautiful idea](https://lists.launchpad.net/mimblewimble/msg00479.html)
(initially suggested by Pieter Wuille), which offers the same advantages but doesn't
need this extra storage of an additional element per transaction output:
The idea is the following:
A normal Pedersen commitment looks like this:
v*H + r*G
(`v` is value of the input/output, `r` is a truly random blinding factor, and `H` and `G` are
two generator points on the elliptic curve).
If we adapt this by having `r` not being random itself, but using another random number `r'`
and create the Pedersen Commitment:
v*H + r*G
such that:
r = r' + hash( v*H + r'*G , r'*J )
(using the additional third generation point `J` on the curve) then `r` still is perfectly
valid as a blinding factor, as it's still randomly distributed, but now we see
that the part within the brackets of the hash function (`v*H + r'*G , r'*J`) is an
**ElGamal commitment**.
This neat idea lead to the removal of the switch commitment hash from the outputs in this
(and following) [pull requests](https://github.com/mimblewimble/grin/issues/998) as now it
could be easily included into the Pedersen Commitments.
This is how it is currently implemented in Grin. Pedersen commitments are
used for the Confidential Transaction but instead of choosing the blinding factor `r`
only by random, it is calculated by adding the hash of an ElGamal commitment to a random `r'`
(see here in [main_impl.h#L267](https://github.com/mimblewimble/secp256k1-zkp/blob/73617d0fcc4f51896cce4f9a1a6977a6958297f8/src/modules/commitment/main_impl.h#L267)).
In general switch commitments were first described in the paper
["Switch Commitments: A Safety Switch for Confidential Transactions"](https://eprint.iacr.org/2017/237.pdf)).
The **"switch"** in the name comes from the fact that you can virtually flip a "switch" in
the future and simply by changing the validation procedure you can change the strength of
the bindingness and hidingness property of your commitments and this even works in a
backwards compatible way with commitments created today.
## Conclusion
Grin uses Pedersen Commitments - like other privacy cryptocurrencies do as well - with
the only difference that the random blinding factor `r` is created using the ElGamal
commitment scheme.
This might not seem like a big change on a first look, but it provides an
important safety measure:
Pedersen Commitments are already _perfectly hiding_ so whatever happens, privacy will
never be at risk without requiring any action from users. But in case of a disaster if the
bindingness of the commitment scheme gets broken, then switch commitments can be enabled
(via a soft fork) requiring that all new transactions prove that their commitment is not
breaking the bindingness by validating the full ElGamal commitment.
But in this case users would still have a choice:
- they can decide to continue to create new transactions, even if this might compromise
their privacy (only on their **last** UTXOs) as the ElGamal commitment scheme is
only computationally hiding, but at least they would still have access to their coins
- or users can decide to just leave the money alone, walk away and make no more transactions
(but preserve their privacy, as their old transactions only validated the Pedersen commitment
which is perfectly hiding)
There are many cases where a privacy leak is much more dangerous to one's life than
some cryptocurrency might be worth. But this is a decision that should be left up to
the individual user and switch commitments enable this type of choice.
It should be made clear that this is a safety measure meant to be enabled in case of a
disaster. If advances in computing would put the hardness of the discrete log problem
in question, a lot of other cryptographic systems, including other cryptocurrencies,
will be in urgent need of updating their primitives to a future-proof system. The switch
commitments just provide an additional layer of security if the bindingness of Pedersen
commitments ever breaks unexpectedly.

View file

@ -1,5 +1,7 @@
# Documentation structure # Documentation structure
*Read this in other languages: [Korean](table_of_contents_KR.md).*
## Explaining grin ## Explaining grin
- [intro](intro.md) - Technical introduction to grin - [intro](intro.md) - Technical introduction to grin

View file

@ -0,0 +1,35 @@
# 문서 목록
## Grin 에 대한 설명들
- [intro](intro_KR.md) - Grin 에 대한 기술적인 소개
- [grin4bitcoiners](grin4bitcoiners.md) - Bitcoinner 의 관점에서 Grin 을 설명하기
## Grin 구현에 대해서 이해하기
- [grin4bitcoiners](grin4bitcoiners.md) - Grin 의 Blockchain이 어떻게 동기화 되는가에 대해서
- [blocks_and_headers](chain/blocks_and_headers.md) - Grin이 어떻게 block과 header 를 chain안에서 찾는지에 대해서
- [contract_ideas](contract_ideas.md) - 어떻게 smart contract 를 구현할 것인가에 대한 아이디어
- [dandelion/dandelion](dandelion/dandelion.md) - 트랜잭션 전파 와 [컷 스루 방식](http://www.ktword.co.kr/abbr_view.php?m_temp1=1823). Stemming과 fluffing.
- [dandelion/simulation](dandelion/simulation.md) - Dandelion 시뮬레이션 - lock height 스테밍과 플러핑 없이 트랜잭션 합치기
- [internal/pool](internal/pool.md) - 트랜잭션 풀에 대한 기술적인 설명에 대해서
- [merkle](merkle.md) - Grin의 Merkle tree 에 대한 기술적인 설명
- [merkle_proof graph](merkle_proof/merkle_proof.png) - Prunning 이 적용된 merkle proof의 예시
- [pruning](pruning.md) - Pruning 의 기술적인 설명
- [stratum](stratum.md) -Grin Stratum RPC protocol 의 기술적 설명
- [transaction UML](wallet/transaction/basic-transaction-wf.png) - 상호작용 트랜잭션의 UML (`lock_height` 없이 트랜잭션 합치기)
## 빌드하고 사용하기
- [api](api/api.md) - Grin 에 있는 다른 API 들과 어떻게 사용하는지에 대해서
- [build](build.md) - Grin 바이너리를 어떻게 빌드하고 작동시키는 지에 대해서
- [release](release_instruction.md) - Release 를 만드는것에 대한 안내
- [usage](usage.md) - Testnet3 에서 어떻게 Grin 을 사용하는지에 대한 설명
- [wallet](wallet/usage.md) - wallet 디자인에 대한 설명과 `grin wallet` 의 세부 명령어
## 외부자료 (위키)
- [FAQ](https://github.com/mimblewimble/docs/wiki/FAQ) - 자주 물어보는 질문들
- [Grin 빌드하기](https://github.com/mimblewimble/docs/wiki/Building)
- [Grin을 어떻게 사용하나요?](https://github.com/mimblewimble/docs/wiki/How-to-use-grin)
- [해킹과 기여하기](https://github.com/mimblewimble/docs/wiki/Hacking-and-contributing)

View file

@ -1,6 +1,6 @@
[package] [package]
name = "grin_keychain" name = "grin_keychain"
version = "1.1.0" version = "1.1.0-beta.1"
authors = ["Grin Developers <mimblewimble@lists.launchpad.net>"] authors = ["Grin Developers <mimblewimble@lists.launchpad.net>"]
description = "Chain implementation for grin, a simple, private and scalable cryptocurrency implementation based on the MimbleWimble chain format." description = "Chain implementation for grin, a simple, private and scalable cryptocurrency implementation based on the MimbleWimble chain format."
license = "Apache-2.0" license = "Apache-2.0"
@ -26,4 +26,4 @@ ripemd160 = "0.7"
sha2 = "0.7" sha2 = "0.7"
pbkdf2 = "0.2" pbkdf2 = "0.2"
grin_util = { path = "../util", version = "1.1.0" } grin_util = { path = "../util", version = "1.1.0-beta.1" }

View file

@ -142,6 +142,15 @@ impl Keychain for ExtKeychain {
Ok(BlindingFactor::from_secret_key(sum)) Ok(BlindingFactor::from_secret_key(sum))
} }
fn create_nonce(&self, commit: &Commitment) -> Result<SecretKey, Error> {
// hash(commit|wallet root secret key (m)) as nonce
let root_key = self.derive_key(0, &Self::root_key_id())?;
let res = blake2::blake2b::blake2b(32, &commit.0, &root_key.0[..]);
let res = res.as_bytes();
SecretKey::from_slice(&self.secp, &res)
.map_err(|e| Error::RangeProof(format!("Unable to create nonce: {:?}", e).to_string()))
}
fn sign(&self, msg: &Message, amount: u64, id: &Identifier) -> Result<Signature, Error> { fn sign(&self, msg: &Message, amount: u64, id: &Identifier) -> Result<Signature, Error> {
let skey = self.derive_key(amount, id)?; let skey = self.derive_key(amount, id)?;
let sig = self.secp.sign(msg, &skey)?; let sig = self.secp.sign(msg, &skey)?;

View file

@ -468,6 +468,7 @@ pub trait Keychain: Sync + Send + Clone {
fn derive_key(&self, amount: u64, id: &Identifier) -> Result<SecretKey, Error>; fn derive_key(&self, amount: u64, id: &Identifier) -> Result<SecretKey, Error>;
fn commit(&self, amount: u64, id: &Identifier) -> Result<Commitment, Error>; fn commit(&self, amount: u64, id: &Identifier) -> Result<Commitment, Error>;
fn blind_sum(&self, blind_sum: &BlindSum) -> Result<BlindingFactor, Error>; fn blind_sum(&self, blind_sum: &BlindSum) -> Result<BlindingFactor, Error>;
fn create_nonce(&self, commit: &Commitment) -> Result<SecretKey, Error>;
fn sign(&self, msg: &Message, amount: u64, id: &Identifier) -> Result<Signature, Error>; fn sign(&self, msg: &Message, amount: u64, id: &Identifier) -> Result<Signature, Error>;
fn sign_with_blinding(&self, _: &Message, _: &BlindingFactor) -> Result<Signature, Error>; fn sign_with_blinding(&self, _: &Message, _: &BlindingFactor) -> Result<Signature, Error>;
fn set_use_switch_commits(&mut self, value: bool); fn set_use_switch_commits(&mut self, value: bool);

View file

@ -1,6 +1,6 @@
[package] [package]
name = "grin_p2p" name = "grin_p2p"
version = "1.1.0" version = "1.1.0-beta.1"
authors = ["Grin Developers <mimblewimble@lists.launchpad.net>"] authors = ["Grin Developers <mimblewimble@lists.launchpad.net>"]
description = "Chain implementation for grin, a simple, private and scalable cryptocurrency implementation based on the MimbleWimble chain format." description = "Chain implementation for grin, a simple, private and scalable cryptocurrency implementation based on the MimbleWimble chain format."
license = "Apache-2.0" license = "Apache-2.0"
@ -21,9 +21,9 @@ serde_derive = "1"
log = "0.4" log = "0.4"
chrono = { version = "0.4.4", features = ["serde"] } chrono = { version = "0.4.4", features = ["serde"] }
grin_core = { path = "../core", version = "1.1.0" } grin_core = { path = "../core", version = "1.1.0-beta.1" }
grin_store = { path = "../store", version = "1.1.0" } grin_store = { path = "../store", version = "1.1.0-beta.1" }
grin_util = { path = "../util", version = "1.1.0" } grin_util = { path = "../util", version = "1.1.0-beta.1" }
[dev-dependencies] [dev-dependencies]
grin_pool = { path = "../pool", version = "1.1.0" } grin_pool = { path = "../pool", version = "1.1.0-beta.1" }

View file

@ -113,19 +113,18 @@ impl<'a> Response<'a> {
resp_type: Type, resp_type: Type,
body: T, body: T,
stream: &'a mut dyn Write, stream: &'a mut dyn Write,
) -> Response<'a> { ) -> Result<Response<'a>, Error> {
let body = ser::ser_vec(&body).unwrap(); let body = ser::ser_vec(&body)?;
Response { Ok(Response {
resp_type, resp_type,
body, body,
stream, stream,
attachment: None, attachment: None,
} })
} }
fn write(mut self, sent_bytes: Arc<RwLock<RateCounter>>) -> Result<(), Error> { fn write(mut self, sent_bytes: Arc<RwLock<RateCounter>>) -> Result<(), Error> {
let mut msg = let mut msg = ser::ser_vec(&MsgHeader::new(self.resp_type, self.body.len() as u64))?;
ser::ser_vec(&MsgHeader::new(self.resp_type, self.body.len() as u64)).unwrap();
msg.append(&mut self.body); msg.append(&mut self.body);
write_all(&mut self.stream, &msg[..], time::Duration::from_secs(10))?; write_all(&mut self.stream, &msg[..], time::Duration::from_secs(10))?;
// Increase sent bytes counter // Increase sent bytes counter
@ -177,7 +176,7 @@ impl Tracker {
where where
T: ser::Writeable, T: ser::Writeable,
{ {
let buf = write_to_buf(body, msg_type); let buf = write_to_buf(body, msg_type)?;
let buf_len = buf.len(); let buf_len = buf.len();
self.send_channel.try_send(buf)?; self.send_channel.try_send(buf)?;

View file

@ -160,18 +160,18 @@ pub fn read_message<T: Readable>(stream: &mut dyn Read, msg_type: Type) -> Resul
read_body(&header, stream) read_body(&header, stream)
} }
pub fn write_to_buf<T: Writeable>(msg: T, msg_type: Type) -> Vec<u8> { pub fn write_to_buf<T: Writeable>(msg: T, msg_type: Type) -> Result<Vec<u8>, Error> {
// prepare the body first so we know its serialized length // prepare the body first so we know its serialized length
let mut body_buf = vec![]; let mut body_buf = vec![];
ser::serialize(&mut body_buf, &msg).unwrap(); ser::serialize(&mut body_buf, &msg)?;
// build and serialize the header using the body size // build and serialize the header using the body size
let mut msg_buf = vec![]; let mut msg_buf = vec![];
let blen = body_buf.len() as u64; let blen = body_buf.len() as u64;
ser::serialize(&mut msg_buf, &MsgHeader::new(msg_type, blen)).unwrap(); ser::serialize(&mut msg_buf, &MsgHeader::new(msg_type, blen))?;
msg_buf.append(&mut body_buf); msg_buf.append(&mut body_buf);
msg_buf Ok(msg_buf)
} }
pub fn write_message<T: Writeable>( pub fn write_message<T: Writeable>(
@ -179,7 +179,7 @@ pub fn write_message<T: Writeable>(
msg: T, msg: T,
msg_type: Type, msg_type: Type,
) -> Result<(), Error> { ) -> Result<(), Error> {
let buf = write_to_buf(msg, msg_type); let buf = write_to_buf(msg, msg_type)?;
stream.write_all(&buf[..])?; stream.write_all(&buf[..])?;
Ok(()) Ok(())
} }
@ -268,11 +268,11 @@ impl Writeable for Hand {
[write_u32, self.capabilities.bits()], [write_u32, self.capabilities.bits()],
[write_u64, self.nonce] [write_u64, self.nonce]
); );
self.total_difficulty.write(writer).unwrap(); self.total_difficulty.write(writer)?;
self.sender_addr.write(writer).unwrap(); self.sender_addr.write(writer)?;
self.receiver_addr.write(writer).unwrap(); self.receiver_addr.write(writer)?;
writer.write_bytes(&self.user_agent).unwrap(); writer.write_bytes(&self.user_agent)?;
self.genesis.write(writer).unwrap(); self.genesis.write(writer)?;
Ok(()) Ok(())
} }
} }
@ -323,9 +323,9 @@ impl Writeable for Shake {
[write_u32, self.version], [write_u32, self.version],
[write_u32, self.capabilities.bits()] [write_u32, self.capabilities.bits()]
); );
self.total_difficulty.write(writer).unwrap(); self.total_difficulty.write(writer)?;
writer.write_bytes(&self.user_agent).unwrap(); writer.write_bytes(&self.user_agent)?;
self.genesis.write(writer).unwrap(); self.genesis.write(writer)?;
Ok(()) Ok(())
} }
} }
@ -379,7 +379,7 @@ impl Writeable for PeerAddrs {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> { fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
writer.write_u32(self.peers.len() as u32)?; writer.write_u32(self.peers.len() as u32)?;
for p in &self.peers { for p in &self.peers {
p.write(writer).unwrap(); p.write(writer)?;
} }
Ok(()) Ok(())
} }
@ -484,8 +484,8 @@ pub struct Ping {
impl Writeable for Ping { impl Writeable for Ping {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> { fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
self.total_difficulty.write(writer).unwrap(); self.total_difficulty.write(writer)?;
self.height.write(writer).unwrap(); self.height.write(writer)?;
Ok(()) Ok(())
} }
} }
@ -511,8 +511,8 @@ pub struct Pong {
impl Writeable for Pong { impl Writeable for Pong {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> { fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
self.total_difficulty.write(writer).unwrap(); self.total_difficulty.write(writer)?;
self.height.write(writer).unwrap(); self.height.write(writer)?;
Ok(()) Ok(())
} }
} }
@ -537,7 +537,7 @@ pub struct BanReason {
impl Writeable for BanReason { impl Writeable for BanReason {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> { fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
let ban_reason_i32 = self.ban_reason as i32; let ban_reason_i32 = self.ban_reason as i32;
ban_reason_i32.write(writer).unwrap(); ban_reason_i32.write(writer)?;
Ok(()) Ok(())
} }
} }

View file

@ -55,6 +55,15 @@ pub struct Peer {
connection: Option<Mutex<conn::Tracker>>, connection: Option<Mutex<conn::Tracker>>,
} }
macro_rules! connection {
($holder:expr) => {
match $holder.connection.as_ref() {
Some(conn) => conn.lock(),
None => return Err(Error::Internal),
}
};
}
impl fmt::Debug for Peer { impl fmt::Debug for Peer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Peer({:?})", &self.info) write!(f, "Peer({:?})", &self.info)
@ -240,29 +249,15 @@ impl Peer {
total_difficulty, total_difficulty,
height, height,
}; };
self.connection connection!(self).send(ping_msg, msg::Type::Ping)
.as_ref()
.unwrap()
.lock()
.send(ping_msg, msg::Type::Ping)
} }
/// Send the ban reason before banning /// Send the ban reason before banning
pub fn send_ban_reason(&self, ban_reason: ReasonForBan) { pub fn send_ban_reason(&self, ban_reason: ReasonForBan) -> Result<(), Error> {
let ban_reason_msg = BanReason { ban_reason }; let ban_reason_msg = BanReason { ban_reason };
match self connection!(self)
.connection
.as_ref()
.unwrap()
.lock()
.send(ban_reason_msg, msg::Type::BanReason) .send(ban_reason_msg, msg::Type::BanReason)
{ .map(|_| ())
Ok(_) => debug!("Sent ban reason {:?} to {}", ban_reason, self.info.addr),
Err(e) => error!(
"Could not send ban reason {:?} to {}: {:?}",
ban_reason, self.info.addr, e
),
};
} }
/// Sends the provided block to the remote peer. The request may be dropped /// Sends the provided block to the remote peer. The request may be dropped
@ -270,11 +265,7 @@ impl Peer {
pub fn send_block(&self, b: &core::Block) -> Result<bool, Error> { pub fn send_block(&self, b: &core::Block) -> Result<bool, Error> {
if !self.tracking_adapter.has_recv(b.hash()) { if !self.tracking_adapter.has_recv(b.hash()) {
trace!("Send block {} to {}", b.hash(), self.info.addr); trace!("Send block {} to {}", b.hash(), self.info.addr);
self.connection connection!(self).send(b, msg::Type::Block)?;
.as_ref()
.unwrap()
.lock()
.send(b, msg::Type::Block)?;
Ok(true) Ok(true)
} else { } else {
debug!( debug!(
@ -289,11 +280,7 @@ impl Peer {
pub fn send_compact_block(&self, b: &core::CompactBlock) -> Result<bool, Error> { pub fn send_compact_block(&self, b: &core::CompactBlock) -> Result<bool, Error> {
if !self.tracking_adapter.has_recv(b.hash()) { if !self.tracking_adapter.has_recv(b.hash()) {
trace!("Send compact block {} to {}", b.hash(), self.info.addr); trace!("Send compact block {} to {}", b.hash(), self.info.addr);
self.connection connection!(self).send(b, msg::Type::CompactBlock)?;
.as_ref()
.unwrap()
.lock()
.send(b, msg::Type::CompactBlock)?;
Ok(true) Ok(true)
} else { } else {
debug!( debug!(
@ -308,11 +295,7 @@ impl Peer {
pub fn send_header(&self, bh: &core::BlockHeader) -> Result<bool, Error> { pub fn send_header(&self, bh: &core::BlockHeader) -> Result<bool, Error> {
if !self.tracking_adapter.has_recv(bh.hash()) { if !self.tracking_adapter.has_recv(bh.hash()) {
debug!("Send header {} to {}", bh.hash(), self.info.addr); debug!("Send header {} to {}", bh.hash(), self.info.addr);
self.connection connection!(self).send(bh, msg::Type::Header)?;
.as_ref()
.unwrap()
.lock()
.send(bh, msg::Type::Header)?;
Ok(true) Ok(true)
} else { } else {
debug!( debug!(
@ -327,11 +310,7 @@ impl Peer {
pub fn send_tx_kernel_hash(&self, h: Hash) -> Result<bool, Error> { pub fn send_tx_kernel_hash(&self, h: Hash) -> Result<bool, Error> {
if !self.tracking_adapter.has_recv(h) { if !self.tracking_adapter.has_recv(h) {
debug!("Send tx kernel hash {} to {}", h, self.info.addr); debug!("Send tx kernel hash {} to {}", h, self.info.addr);
self.connection connection!(self).send(h, msg::Type::TransactionKernel)?;
.as_ref()
.unwrap()
.lock()
.send(h, msg::Type::TransactionKernel)?;
Ok(true) Ok(true)
} else { } else {
debug!( debug!(
@ -359,11 +338,7 @@ impl Peer {
if !self.tracking_adapter.has_recv(kernel.hash()) { if !self.tracking_adapter.has_recv(kernel.hash()) {
debug!("Send full tx {} to {}", tx.hash(), self.info.addr); debug!("Send full tx {} to {}", tx.hash(), self.info.addr);
self.connection connection!(self).send(tx, msg::Type::Transaction)?;
.as_ref()
.unwrap()
.lock()
.send(tx, msg::Type::Transaction)?;
Ok(true) Ok(true)
} else { } else {
debug!( debug!(
@ -380,21 +355,12 @@ impl Peer {
/// embargo). /// embargo).
pub fn send_stem_transaction(&self, tx: &core::Transaction) -> Result<(), Error> { pub fn send_stem_transaction(&self, tx: &core::Transaction) -> Result<(), Error> {
debug!("Send (stem) tx {} to {}", tx.hash(), self.info.addr); debug!("Send (stem) tx {} to {}", tx.hash(), self.info.addr);
self.connection connection!(self).send(tx, msg::Type::StemTransaction)
.as_ref()
.unwrap()
.lock()
.send(tx, msg::Type::StemTransaction)?;
Ok(())
} }
/// Sends a request for block headers from the provided block locator /// Sends a request for block headers from the provided block locator
pub fn send_header_request(&self, locator: Vec<Hash>) -> Result<(), Error> { pub fn send_header_request(&self, locator: Vec<Hash>) -> Result<(), Error> {
self.connection connection!(self).send(&Locator { hashes: locator }, msg::Type::GetHeaders)
.as_ref()
.unwrap()
.lock()
.send(&Locator { hashes: locator }, msg::Type::GetHeaders)
} }
pub fn send_tx_request(&self, h: Hash) -> Result<(), Error> { pub fn send_tx_request(&self, h: Hash) -> Result<(), Error> {
@ -402,37 +368,25 @@ impl Peer {
"Requesting tx (kernel hash) {} from peer {}.", "Requesting tx (kernel hash) {} from peer {}.",
h, self.info.addr h, self.info.addr
); );
self.connection connection!(self).send(&h, msg::Type::GetTransaction)
.as_ref()
.unwrap()
.lock()
.send(&h, msg::Type::GetTransaction)
} }
/// Sends a request for a specific block by hash /// Sends a request for a specific block by hash
pub fn send_block_request(&self, h: Hash) -> Result<(), Error> { pub fn send_block_request(&self, h: Hash) -> Result<(), Error> {
debug!("Requesting block {} from peer {}.", h, self.info.addr); debug!("Requesting block {} from peer {}.", h, self.info.addr);
self.tracking_adapter.push_req(h); self.tracking_adapter.push_req(h);
self.connection connection!(self).send(&h, msg::Type::GetBlock)
.as_ref()
.unwrap()
.lock()
.send(&h, msg::Type::GetBlock)
} }
/// Sends a request for a specific compact block by hash /// Sends a request for a specific compact block by hash
pub fn send_compact_block_request(&self, h: Hash) -> Result<(), Error> { pub fn send_compact_block_request(&self, h: Hash) -> Result<(), Error> {
debug!("Requesting compact block {} from {}", h, self.info.addr); debug!("Requesting compact block {} from {}", h, self.info.addr);
self.connection connection!(self).send(&h, msg::Type::GetCompactBlock)
.as_ref()
.unwrap()
.lock()
.send(&h, msg::Type::GetCompactBlock)
} }
pub fn send_peer_request(&self, capab: Capabilities) -> Result<(), Error> { pub fn send_peer_request(&self, capab: Capabilities) -> Result<(), Error> {
trace!("Asking {} for more peers {:?}", self.info.addr, capab); trace!("Asking {} for more peers {:?}", self.info.addr, capab);
self.connection.as_ref().unwrap().lock().send( connection!(self).send(
&GetPeerAddrs { &GetPeerAddrs {
capabilities: capab, capabilities: capab,
}, },
@ -445,7 +399,7 @@ impl Peer {
"Asking {} for txhashset archive at {} {}.", "Asking {} for txhashset archive at {} {}.",
self.info.addr, height, hash self.info.addr, height, hash
); );
self.connection.as_ref().unwrap().lock().send( connection!(self).send(
&TxHashSetRequest { hash, height }, &TxHashSetRequest { hash, height },
msg::Type::TxHashSetRequest, msg::Type::TxHashSetRequest,
) )
@ -453,11 +407,16 @@ impl Peer {
/// Stops the peer, closing its connection /// Stops the peer, closing its connection
pub fn stop(&self) { pub fn stop(&self) {
stop_with_connection(&self.connection.as_ref().unwrap().lock()); if let Some(conn) = self.connection.as_ref() {
stop_with_connection(&conn.lock());
}
} }
fn check_connection(&self) -> bool { fn check_connection(&self) -> bool {
let connection = self.connection.as_ref().unwrap().lock(); let connection = match self.connection.as_ref() {
Some(conn) => conn.lock(),
None => return false,
};
match connection.error_channel.try_recv() { match connection.error_channel.try_recv() {
Ok(Error::Serialization(e)) => { Ok(Error::Serialization(e)) => {
let need_stop = { let need_stop = {

View file

@ -182,11 +182,10 @@ impl Peers {
return vec![]; return vec![];
} }
let max_total_difficulty = peers let max_total_difficulty = match peers.iter().map(|x| x.info.total_difficulty()).max() {
.iter() Some(v) => v,
.map(|x| x.info.total_difficulty()) None => return vec![],
.max() };
.unwrap();
let mut max_peers = peers let mut max_peers = peers
.into_iter() .into_iter()
@ -221,7 +220,10 @@ impl Peers {
if let Some(peer) = self.get_connected_peer(peer_addr) { if let Some(peer) = self.get_connected_peer(peer_addr) {
debug!("Banning peer {}", peer_addr); debug!("Banning peer {}", peer_addr);
// setting peer status will get it removed at the next clean_peer // setting peer status will get it removed at the next clean_peer
peer.send_ban_reason(ban_reason); match peer.send_ban_reason(ban_reason) {
Err(e) => error!("failed to send a ban reason to{}: {:?}", peer_addr, e),
Ok(_) => debug!("ban reason {:?} was sent to {}", ban_reason, peer_addr),
};
peer.set_banned(); peer.set_banned();
peer.stop(); peer.stop();
} }
@ -328,12 +330,24 @@ impl Peers {
/// All peer information we have in storage /// All peer information we have in storage
pub fn all_peers(&self) -> Vec<PeerData> { pub fn all_peers(&self) -> Vec<PeerData> {
self.store.all_peers() match self.store.all_peers() {
Ok(peers) => peers,
Err(e) => {
error!("all_peers failed: {:?}", e);
vec![]
}
}
} }
/// Find peers in store (not necessarily connected) and return their data /// Find peers in store (not necessarily connected) and return their data
pub fn find_peers(&self, state: State, cap: Capabilities, count: usize) -> Vec<PeerData> { pub fn find_peers(&self, state: State, cap: Capabilities, count: usize) -> Vec<PeerData> {
self.store.find_peers(state, cap, count) match self.store.find_peers(state, cap, count) {
Ok(peers) => peers,
Err(e) => {
error!("failed to find peers: {:?}", e);
vec![]
}
}
} }
/// Get peer in store by address /// Get peer in store by address
@ -373,11 +387,12 @@ impl Peers {
debug!("clean_peers {:?}, not connected", peer.info.addr); debug!("clean_peers {:?}, not connected", peer.info.addr);
rm.push(peer.info.addr.clone()); rm.push(peer.info.addr.clone());
} else if peer.is_abusive() { } else if peer.is_abusive() {
let counts = peer.last_min_message_counts().unwrap(); if let Some(counts) = peer.last_min_message_counts() {
debug!( debug!(
"clean_peers {:?}, abusive ({} sent, {} recv)", "clean_peers {:?}, abusive ({} sent, {} recv)",
peer.info.addr, counts.0, counts.1, peer.info.addr, counts.0, counts.1,
); );
}
let _ = self.update_state(peer.info.addr, State::Banned); let _ = self.update_state(peer.info.addr, State::Banned);
rm.push(peer.info.addr.clone()); rm.push(peer.info.addr.clone());
} else { } else {

View file

@ -72,7 +72,7 @@ impl MessageHandler for Protocol {
height: adapter.total_height(), height: adapter.total_height(),
}, },
writer, writer,
))) )?))
} }
Type::Pong => { Type::Pong => {
@ -105,7 +105,7 @@ impl MessageHandler for Protocol {
); );
let tx = adapter.get_transaction(h); let tx = adapter.get_transaction(h);
if let Some(tx) = tx { if let Some(tx) = tx {
Ok(Some(Response::new(Type::Transaction, tx, writer))) Ok(Some(Response::new(Type::Transaction, tx, writer)?))
} else { } else {
Ok(None) Ok(None)
} }
@ -141,7 +141,7 @@ impl MessageHandler for Protocol {
let bo = adapter.get_block(h); let bo = adapter.get_block(h);
if let Some(b) = bo { if let Some(b) = bo {
return Ok(Some(Response::new(Type::Block, b, writer))); return Ok(Some(Response::new(Type::Block, b, writer)?));
} }
Ok(None) Ok(None)
} }
@ -163,7 +163,7 @@ impl MessageHandler for Protocol {
let h: Hash = msg.body()?; let h: Hash = msg.body()?;
if let Some(b) = adapter.get_block(h) { if let Some(b) = adapter.get_block(h) {
let cb: CompactBlock = b.into(); let cb: CompactBlock = b.into();
Ok(Some(Response::new(Type::CompactBlock, cb, writer))) Ok(Some(Response::new(Type::CompactBlock, cb, writer)?))
} else { } else {
Ok(None) Ok(None)
} }
@ -190,7 +190,7 @@ impl MessageHandler for Protocol {
Type::Headers, Type::Headers,
Headers { headers }, Headers { headers },
writer, writer,
))) )?))
} }
// "header first" block propagation - if we have not yet seen this block // "header first" block propagation - if we have not yet seen this block
@ -235,7 +235,7 @@ impl MessageHandler for Protocol {
Type::PeerAddrs, Type::PeerAddrs,
PeerAddrs { peers }, PeerAddrs { peers },
writer, writer,
))) )?))
} }
Type::PeerAddrs => { Type::PeerAddrs => {
@ -263,7 +263,7 @@ impl MessageHandler for Protocol {
bytes: file_sz, bytes: file_sz,
}, },
writer, writer,
); )?;
resp.add_attachment(txhashset.reader); resp.add_attachment(txhashset.reader);
Ok(Some(resp)) Ok(Some(resp))
} else { } else {
@ -312,7 +312,10 @@ impl MessageHandler for Protocol {
received_bytes.inc_quiet(size as u64); received_bytes.inc_quiet(size as u64);
} }
} }
tmp_zip.into_inner().unwrap().sync_all()?; tmp_zip
.into_inner()
.map_err(|_| Error::Internal)?
.sync_all()?;
Ok(()) Ok(())
}; };

View file

@ -83,10 +83,9 @@ impl Readable for PeerData {
let lc = reader.read_i64(); let lc = reader.read_i64();
// this only works because each PeerData is read in its own vector and this // this only works because each PeerData is read in its own vector and this
// is the last data element // is the last data element
let last_connected = if let Err(_) = lc { let last_connected = match lc {
Utc::now().timestamp() Err(_) => Utc::now().timestamp(),
} else { Ok(lc) => lc,
lc.unwrap()
}; };
let user_agent = String::from_utf8(ua).map_err(|_| ser::Error::CorruptedData)?; let user_agent = String::from_utf8(ua).map_err(|_| ser::Error::CorruptedData)?;
@ -147,22 +146,31 @@ impl PeerStore {
batch.commit() batch.commit()
} }
pub fn find_peers(&self, state: State, cap: Capabilities, count: usize) -> Vec<PeerData> { pub fn find_peers(
&self,
state: State,
cap: Capabilities,
count: usize,
) -> Result<Vec<PeerData>, Error> {
let mut peers = self let mut peers = self
.db .db
.iter::<PeerData>(&to_key(PEER_PREFIX, &mut "".to_string().into_bytes())) .iter::<PeerData>(&to_key(PEER_PREFIX, &mut "".to_string().into_bytes()))?
.unwrap() .map(|(_, v)| v)
.filter(|p| p.flags == state && p.capabilities.contains(cap)) .filter(|p| p.flags == state && p.capabilities.contains(cap))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
thread_rng().shuffle(&mut peers[..]); thread_rng().shuffle(&mut peers[..]);
peers.iter().take(count).cloned().collect() Ok(peers.iter().take(count).cloned().collect())
} }
/// List all known peers /// List all known peers
/// Used for /v1/peers/all api endpoint /// Used for /v1/peers/all api endpoint
pub fn all_peers(&self) -> Vec<PeerData> { pub fn all_peers(&self) -> Result<Vec<PeerData>, Error> {
let key = to_key(PEER_PREFIX, &mut "".to_string().into_bytes()); let key = to_key(PEER_PREFIX, &mut "".to_string().into_bytes());
self.db.iter::<PeerData>(&key).unwrap().collect::<Vec<_>>() Ok(self
.db
.iter::<PeerData>(&key)?
.map(|(_, v)| v)
.collect::<Vec<_>>())
} }
/// Convenience method to load a peer data, update its status and save it /// Convenience method to load a peer data, update its status and save it
@ -190,7 +198,7 @@ impl PeerStore {
{ {
let mut to_remove = vec![]; let mut to_remove = vec![];
for x in self.all_peers() { for x in self.all_peers()? {
if predicate(&x) { if predicate(&x) {
to_remove.push(x) to_remove.push(x)
} }

View file

@ -75,6 +75,7 @@ pub enum Error {
}, },
Send(String), Send(String),
PeerException, PeerException,
Internal,
} }
impl From<ser::Error> for Error { impl From<ser::Error> for Error {

View file

@ -1,6 +1,6 @@
[package] [package]
name = "grin_pool" name = "grin_pool"
version = "1.1.0" version = "1.1.0-beta.1"
authors = ["Grin Developers <mimblewimble@lists.launchpad.net>"] authors = ["Grin Developers <mimblewimble@lists.launchpad.net>"]
description = "Chain implementation for grin, a simple, private and scalable cryptocurrency implementation based on the MimbleWimble chain format." description = "Chain implementation for grin, a simple, private and scalable cryptocurrency implementation based on the MimbleWimble chain format."
license = "Apache-2.0" license = "Apache-2.0"
@ -19,10 +19,10 @@ chrono = "0.4.4"
failure = "0.1" failure = "0.1"
failure_derive = "0.1" failure_derive = "0.1"
grin_core = { path = "../core", version = "1.1.0" } grin_core = { path = "../core", version = "1.1.0-beta.1" }
grin_keychain = { path = "../keychain", version = "1.1.0" } grin_keychain = { path = "../keychain", version = "1.1.0-beta.1" }
grin_store = { path = "../store", version = "1.1.0" } grin_store = { path = "../store", version = "1.1.0-beta.1" }
grin_util = { path = "../util", version = "1.1.0" } grin_util = { path = "../util", version = "1.1.0-beta.1" }
[dev-dependencies] [dev-dependencies]
grin_chain = { path = "../chain", version = "1.1.0" } grin_chain = { path = "../chain", version = "1.1.0-beta.1" }

View file

@ -1,6 +1,6 @@
[package] [package]
name = "grin_servers" name = "grin_servers"
version = "1.1.0" version = "1.1.0-beta.1"
authors = ["Grin Developers <mimblewimble@lists.launchpad.net>"] authors = ["Grin Developers <mimblewimble@lists.launchpad.net>"]
description = "Simple, private and scalable cryptocurrency implementation based on the MimbleWimble chain format." description = "Simple, private and scalable cryptocurrency implementation based on the MimbleWimble chain format."
license = "Apache-2.0" license = "Apache-2.0"
@ -25,11 +25,11 @@ serde_json = "1"
chrono = "0.4.4" chrono = "0.4.4"
tokio = "0.1.11" tokio = "0.1.11"
grin_api = { path = "../api", version = "1.1.0" } grin_api = { path = "../api", version = "1.1.0-beta.1" }
grin_chain = { path = "../chain", version = "1.1.0" } grin_chain = { path = "../chain", version = "1.1.0-beta.1" }
grin_core = { path = "../core", version = "1.1.0" } grin_core = { path = "../core", version = "1.1.0-beta.1" }
grin_keychain = { path = "../keychain", version = "1.1.0" } grin_keychain = { path = "../keychain", version = "1.1.0-beta.1" }
grin_p2p = { path = "../p2p", version = "1.1.0" } grin_p2p = { path = "../p2p", version = "1.1.0-beta.1" }
grin_pool = { path = "../pool", version = "1.1.0" } grin_pool = { path = "../pool", version = "1.1.0-beta.1" }
grin_store = { path = "../store", version = "1.1.0" } grin_store = { path = "../store", version = "1.1.0-beta.1" }
grin_util = { path = "../util", version = "1.1.0" } grin_util = { path = "../util", version = "1.1.0-beta.1" }

View file

@ -331,14 +331,20 @@ impl p2p::ChainAdapter for NetToChainAdapter {
total_size: u64, total_size: u64,
) -> bool { ) -> bool {
match self.sync_state.status() { match self.sync_state.status() {
SyncStatus::TxHashsetDownload { .. } => { SyncStatus::TxHashsetDownload {
self.sync_state update_time: old_update_time,
.update_txhashset_download(SyncStatus::TxHashsetDownload { downloaded_size: old_downloaded_size,
start_time, ..
downloaded_size, } => self
total_size, .sync_state
}) .update_txhashset_download(SyncStatus::TxHashsetDownload {
} start_time,
prev_update_time: old_update_time,
update_time: Utc::now(),
prev_downloaded_size: old_downloaded_size,
downloaded_size,
total_size,
}),
_ => false, _ => false,
} }
} }
@ -358,6 +364,7 @@ impl p2p::ChainAdapter for NetToChainAdapter {
.chain() .chain()
.txhashset_write(h, txhashset_data, self.sync_state.as_ref()) .txhashset_write(h, txhashset_data, self.sync_state.as_ref())
{ {
self.chain().clean_txhashset_sandbox();
error!("Failed to save txhashset archive: {}", e); error!("Failed to save txhashset archive: {}", e);
let is_good_data = !e.is_bad_data(); let is_good_data = !e.is_bad_data();
self.sync_state.set_sync_error(types::Error::Chain(e)); self.sync_state.set_sync_error(types::Error::Chain(e));

View file

@ -22,7 +22,8 @@ use rand::prelude::*;
use crate::api; use crate::api;
use crate::chain; use crate::chain;
use crate::core::global::ChainTypes; use crate::core::global::ChainTypes;
use crate::core::{core, pow}; use crate::core::{core, libtx, pow};
use crate::keychain;
use crate::p2p; use crate::p2p;
use crate::pool; use crate::pool;
use crate::pool::types::DandelionConfig; use crate::pool::types::DandelionConfig;
@ -34,6 +35,8 @@ use crate::util::RwLock;
pub enum Error { pub enum Error {
/// Error originating from the core implementation. /// Error originating from the core implementation.
Core(core::block::Error), Core(core::block::Error),
/// Error originating from the libtx implementation.
LibTx(libtx::Error),
/// Error originating from the db storage. /// Error originating from the db storage.
Store(store::Error), Store(store::Error),
/// Error originating from the blockchain implementation. /// Error originating from the blockchain implementation.
@ -46,12 +49,18 @@ pub enum Error {
Cuckoo(pow::Error), Cuckoo(pow::Error),
/// Error originating from the transaction pool. /// Error originating from the transaction pool.
Pool(pool::PoolError), Pool(pool::PoolError),
/// Error originating from the keychain.
Keychain(keychain::Error),
/// Invalid Arguments. /// Invalid Arguments.
ArgumentError(String), ArgumentError(String),
/// Wallet communication error /// Wallet communication error
WalletComm(String), WalletComm(String),
/// Error originating from some I/O operation (likely a file on disk). /// Error originating from some I/O operation (likely a file on disk).
IOError(std::io::Error), IOError(std::io::Error),
/// Configuration error
Configuration(String),
/// General error
General(String),
} }
impl From<core::block::Error> for Error { impl From<core::block::Error> for Error {
@ -99,6 +108,18 @@ impl From<pool::PoolError> for Error {
} }
} }
impl From<keychain::Error> for Error {
fn from(e: keychain::Error) -> Error {
Error::Keychain(e)
}
}
impl From<libtx::Error> for Error {
fn from(e: libtx::Error) -> Error {
Error::LibTx(e)
}
}
/// Type of seeding the server will use to find other peers on the network. /// Type of seeding the server will use to find other peers on the network.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ChainValidationMode { pub enum ChainValidationMode {
@ -299,6 +320,9 @@ pub enum SyncStatus {
/// Downloading the various txhashsets /// Downloading the various txhashsets
TxHashsetDownload { TxHashsetDownload {
start_time: DateTime<Utc>, start_time: DateTime<Utc>,
prev_update_time: DateTime<Utc>,
update_time: DateTime<Utc>,
prev_downloaded_size: u64,
downloaded_size: u64, downloaded_size: u64,
total_size: u64, total_size: u64,
}, },

View file

@ -183,6 +183,5 @@ fn process_expired_entries(
Err(e) => warn!("dand_mon: failed to fluff expired tx {}, {:?}", txhash, e), Err(e) => warn!("dand_mon: failed to fluff expired tx {}, {:?}", txhash, e),
}; };
} }
Ok(()) Ok(())
} }

View file

@ -292,7 +292,7 @@ fn listen_for_addrs(
let addrs: Vec<PeerAddr> = rx.try_iter().collect(); let addrs: Vec<PeerAddr> = rx.try_iter().collect();
// If we have a healthy number of outbound peers then we are done here. // If we have a healthy number of outbound peers then we are done here.
if peers.healthy_peers_mix() { if peers.peer_count() > peers.peer_outbound_count() && peers.healthy_peers_mix() {
return; return;
} }
@ -312,7 +312,9 @@ fn listen_for_addrs(
); );
continue; continue;
} else { } else {
*connecting_history.get_mut(&addr).unwrap() = now; if let Some(history) = connecting_history.get_mut(&addr) {
*history = now;
}
} }
} }
connecting_history.insert(addr, now); connecting_history.insert(addr, now);

View file

@ -219,9 +219,14 @@ impl Server {
warn!("No seed configured, will stay solo until connected to"); warn!("No seed configured, will stay solo until connected to");
seed::predefined_seeds(vec![]) seed::predefined_seeds(vec![])
} }
p2p::Seeding::List => { p2p::Seeding::List => match &config.p2p_config.seeds {
seed::predefined_seeds(config.p2p_config.seeds.clone().unwrap()) Some(seeds) => seed::predefined_seeds(seeds.clone()),
} None => {
return Err(Error::Configuration(
"Seeds must be configured for seeding type List".to_owned(),
));
}
},
p2p::Seeding::DNSSeed => seed::dns_seeds(), p2p::Seeding::DNSSeed => seed::dns_seeds(),
_ => unreachable!(), _ => unreachable!(),
}; };
@ -388,13 +393,13 @@ impl Server {
} }
/// The chain head /// The chain head
pub fn head(&self) -> chain::Tip { pub fn head(&self) -> Result<chain::Tip, Error> {
self.chain.head().unwrap() self.chain.head().map_err(|e| e.into())
} }
/// The head of the block header chain /// The head of the block header chain
pub fn header_head(&self) -> chain::Tip { pub fn header_head(&self) -> Result<chain::Tip, Error> {
self.chain.header_head().unwrap() self.chain.header_head().map_err(|e| e.into())
} }
/// Returns a set of stats about this server. This and the ServerStats /// Returns a set of stats about this server. This and the ServerStats
@ -412,11 +417,11 @@ impl Server {
// for release // for release
let diff_stats = { let diff_stats = {
let last_blocks: Vec<consensus::HeaderInfo> = let last_blocks: Vec<consensus::HeaderInfo> =
global::difficulty_data_to_vector(self.chain.difficulty_iter()) global::difficulty_data_to_vector(self.chain.difficulty_iter()?)
.into_iter() .into_iter()
.collect(); .collect();
let tip_height = self.chain.head().unwrap().height as i64; let tip_height = self.head()?.height as i64;
let mut height = tip_height as i64 - last_blocks.len() as i64 + 1; let mut height = tip_height as i64 - last_blocks.len() as i64 + 1;
let txhashset = self.chain.txhashset(); let txhashset = self.chain.txhashset();
@ -474,8 +479,8 @@ impl Server {
.collect(); .collect();
Ok(ServerStats { Ok(ServerStats {
peer_count: self.peer_count(), peer_count: self.peer_count(),
head: self.head(), head: self.head()?,
header_head: self.header_head(), header_head: self.header_head()?,
sync_status: self.sync_state.status(), sync_status: self.sync_state.status(),
stratum_stats: stratum_stats, stratum_stats: stratum_stats,
peer_stats: peer_stats, peer_stats: peer_stats,

View file

@ -51,11 +51,15 @@ impl BodySync {
/// Check whether a body sync is needed and run it if so. /// Check whether a body sync is needed and run it if so.
/// Return true if txhashset download is needed (when requested block is under the horizon). /// Return true if txhashset download is needed (when requested block is under the horizon).
pub fn check_run(&mut self, head: &chain::Tip, highest_height: u64) -> bool { pub fn check_run(
&mut self,
head: &chain::Tip,
highest_height: u64,
) -> Result<bool, chain::Error> {
// run the body_sync every 5s // run the body_sync every 5s
if self.body_sync_due() { if self.body_sync_due()? {
if self.body_sync() { if self.body_sync()? {
return true; return Ok(true);
} }
self.sync_state.update(SyncStatus::BodySync { self.sync_state.update(SyncStatus::BodySync {
@ -63,23 +67,37 @@ impl BodySync {
highest_height: highest_height, highest_height: highest_height,
}); });
} }
false Ok(false)
} }
/// Return true if txhashset download is needed (when requested block is under the horizon). /// Return true if txhashset download is needed (when requested block is under the horizon).
fn body_sync(&mut self) -> bool { fn body_sync(&mut self) -> Result<bool, chain::Error> {
let mut hashes: Option<Vec<Hash>> = Some(vec![]); let mut hashes: Option<Vec<Hash>> = Some(vec![]);
if self let txhashset_needed = match self
.chain .chain
.check_txhashset_needed("body_sync".to_owned(), &mut hashes) .check_txhashset_needed("body_sync".to_owned(), &mut hashes)
{ {
Ok(v) => v,
Err(e) => {
error!("body_sync: failed to call txhashset_needed: {:?}", e);
return Ok(false);
}
};
if txhashset_needed {
debug!( debug!(
"body_sync: cannot sync full blocks earlier than horizon. will request txhashset", "body_sync: cannot sync full blocks earlier than horizon. will request txhashset",
); );
return true; return Ok(true);
} }
let mut hashes = hashes.unwrap(); let mut hashes = match hashes {
Some(v) => v,
None => {
error!("unexpected: hashes is None");
return Ok(false);
}
};
hashes.reverse(); hashes.reverse();
let peers = self.peers.more_work_peers(); let peers = self.peers.more_work_peers();
@ -103,8 +121,8 @@ impl BodySync {
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if hashes_to_get.len() > 0 { if hashes_to_get.len() > 0 {
let body_head = self.chain.head().unwrap(); let body_head = self.chain.head()?;
let header_head = self.chain.header_head().unwrap(); let header_head = self.chain.header_head()?;
debug!( debug!(
"block_sync: {}/{} requesting blocks {:?} from {} peers", "block_sync: {}/{} requesting blocks {:?} from {} peers",
@ -129,12 +147,12 @@ impl BodySync {
} }
} }
} }
return false; return Ok(false);
} }
// Should we run block body sync and ask for more full blocks? // Should we run block body sync and ask for more full blocks?
fn body_sync_due(&mut self) -> bool { fn body_sync_due(&mut self) -> Result<bool, chain::Error> {
let blocks_received = self.blocks_received(); let blocks_received = self.blocks_received()?;
// some blocks have been requested // some blocks have been requested
if self.blocks_requested > 0 { if self.blocks_requested > 0 {
@ -145,7 +163,7 @@ impl BodySync {
"body_sync: expecting {} more blocks and none received for a while", "body_sync: expecting {} more blocks and none received for a while",
self.blocks_requested, self.blocks_requested,
); );
return true; return Ok(true);
} }
} }
@ -162,16 +180,16 @@ impl BodySync {
if self.blocks_requested < 2 { if self.blocks_requested < 2 {
// no pending block requests, ask more // no pending block requests, ask more
debug!("body_sync: no pending block request, asking more"); debug!("body_sync: no pending block request, asking more");
return true; return Ok(true);
} }
return false; Ok(false)
} }
// Total numbers received on this chain, including the head and orphans // Total numbers received on this chain, including the head and orphans
fn blocks_received(&self) -> u64 { fn blocks_received(&self) -> Result<u64, chain::Error> {
self.chain.head().unwrap().height Ok((self.chain.head()?).height
+ self.chain.orphans_len() as u64 + self.chain.orphans_len() as u64
+ self.chain.orphans_evicted_len() as u64 + self.chain.orphans_evicted_len() as u64)
} }
} }

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