diff options
94 files changed, 7558 insertions, 3692 deletions
diff --git a/.travis.yml b/.travis.yml index beea1dfab..36c48f8d6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,42 +1,117 @@ sudo: required dist: trusty language: cpp -compiler: -- gcc -- clang -addons: - apt: - packages: - - build-essential - - cmake - - doxygen - - g++ - - gcc - - clang - - graphviz - - libdb++-dev - - libdb-dev - - libgtest-dev - - libminiupnpc-dev - - libssl-dev - - libssl1.0.0 - - libunbound-dev - - libunwind8-dev - sources: - - ubuntu-toolchain-r-test -before_install: -- sudo add-apt-repository -y ppa:kojoley/boost -- sudo apt-get -q update -install: -- sudo apt-get -y install libboost-{chrono,program-options,date-time,thread,system,filesystem,regex,serialization}1.58{-dev,.0} -script: make -j2 && HAVE_DOT=YES doxygen Doxyfile + +matrix: + include: + + # + # Coveralls.io + # + - os: linux + compiler: gcc + addons: + apt: + packages: + - build-essential + - cmake + - doxygen + - g++ + - gcc + - clang + - graphviz + - libdb++-dev + - libdb-dev + - libgtest-dev + - libminiupnpc-dev + - libssl-dev + - libssl1.0.0 + - libunbound-dev + - libunwind8-dev + sources: + - ubuntu-toolchain-r-test + before_install: + - sudo add-apt-repository -y ppa:kojoley/boost + - sudo apt-get -q update + - pip install --user cpp-coveralls + install: + - sudo apt-get -y install libboost-{chrono,program-options,date-time,thread,system,filesystem,regex,serialization}1.58{-dev,.0} + script: + - make -j2 debug-test + after_success: + - travis_wait coveralls -e external -e tests -e cmake -e contrib -e translations -e utils --gcov-options '\-lp' &> /dev/null + + # + # Monero release-all (gcc) + # + - os: linux + compiler: gcc + addons: + apt: + packages: + - build-essential + - cmake + - doxygen + - g++ + - gcc + - clang + - graphviz + - libdb++-dev + - libdb-dev + - libgtest-dev + - libminiupnpc-dev + - libssl-dev + - libssl1.0.0 + - libunbound-dev + - libunwind8-dev + sources: + - ubuntu-toolchain-r-test + before_install: + - sudo add-apt-repository -y ppa:kojoley/boost + - sudo apt-get -q update + install: + - sudo apt-get -y install libboost-{chrono,program-options,date-time,thread,system,filesystem,regex,serialization}1.58{-dev,.0} + script: + - make -j2 && HAVE_DOT=YES doxygen Doxyfile + + # + # Monero release-all (clang) + # + - os: linux + compiler: clang + addons: + apt: + packages: + - build-essential + - cmake + - doxygen + - clang + - graphviz + - libdb++-dev + - libdb-dev + - libgtest-dev + - libminiupnpc-dev + - libssl-dev + - libssl1.0.0 + - libunbound-dev + - libunwind8-dev + sources: + - ubuntu-toolchain-r-test + before_install: + - sudo add-apt-repository -y ppa:kojoley/boost + - sudo apt-get -q update + install: + - sudo apt-get -y install libboost-{chrono,program-options,date-time,thread,system,filesystem,regex,serialization}1.58{-dev,.0} + script: + - make -j2 && HAVE_DOT=YES doxygen Doxyfile + notifications: email: false irc: on_success: change on_failure: change channels: - - "chat.freenode.net#monero-dev" + - "chat.freenode.net#monero-bots" nick: monero template: - "%{result} | %{repository}#%{build_number} (%{commit} : %{author}) | Build details : %{build_url}" diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ac4a2f95..5b382264b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,34 +45,33 @@ function (die msg) message(FATAL_ERROR "${BoldRed}${msg}${ColourReset}") endfunction () -if ("${ARCH}" STREQUAL "" OR "${ARCH}" STREQUAL "native") - set(ARCH ${CMAKE_SYSTEM_PROCESSOR}) - message(STATUS "Building natively on ${ARCH}") +# ARCH defines the target architecture, either by an explicit identifier or +# one of the following two keywords. By default, ARCH a value of 'native': +# target arch = host arch, binary is not portable. When ARCH is set to the +# string 'default', no -march arg is passed, which creates a binary that is +# portable across processors in the same family as host processor. In cases +# when ARCH is not set to an explicit identifier, cmake's builtin is used +# to identify the target architecture, to direct logic in this cmake script. +# Since ARCH is a cached variable, it will not be set on first cmake invocation. +if (NOT ARCH OR ARCH STREQUAL "" OR ARCH STREQUAL "native" OR ARCH STREQUAL "default") + set(ARCH_ID "${CMAKE_SYSTEM_PROCESSOR}") +else() + set(ARCH_ID "${ARCH}") endif() - -if (NOT "${ARCH}" STREQUAL "") - string(SUBSTRING ${ARCH} 0 3 IS_ARM) - string(TOLOWER ${IS_ARM} IS_ARM) - - if (${IS_ARM} STREQUAL "arm") - string(SUBSTRING ${ARCH} 0 5 ARM_TEST) - string(TOLOWER ${ARM_TEST} ARM_TEST) - - if (${ARM_TEST} STREQUAL "armv6") - set(ARM6 1) - else() - set(ARM6 0) - endif() - - if (${ARM_TEST} STREQUAL "armv7") - set(ARM7 1) - else() - set(ARM7 0) - endif() +string(TOLOWER ${ARCH_ID} ARM_ID) +string(SUBSTRING ${ARCH_ID} 0 3 ARM_TEST) +if (ARM_TEST STREQUAL "arm") + set(ARM 1) + string(SUBSTRING ${ARCH_ID} 0 5 ARM_TEST) + if (ARM_TEST STREQUAL "armv6") + set(ARM6 1) + endif() + if (ARM_TEST STREQUAL "armv7") + set(ARM7 1) endif() endif() -if(WIN32 OR ARM7 OR ARM6) +if(WIN32 OR ARM) set(OPT_FLAGS_RELEASE "-O2") else() set(OPT_FLAGS_RELEASE "-Ofast") @@ -235,10 +234,6 @@ elseif (DATABASE STREQUAL "berkeleydb") message(STATUS "Using Berkeley DB as default DB type") add_definitions("-DDEFAULT_DB_TYPE=\"berkeley\"") -elseif (DATABASE STREQUAL "memory") - set(BLOCKCHAIN_DB DB_MEMORY) - message(STATUS "Using Serialised In Memory as default DB type") - add_definitions("-DDEFAULT_DB_TYPE=\"memory\"") else() die("Invalid database type: ${DATABASE}") endif() @@ -251,7 +246,8 @@ add_definitions("-DBLOCKCHAIN_DB=${BLOCKCHAIN_DB}") find_package(Libunwind) # Can't install hook in static build on OSX, because OSX linker does not support --wrap -if(LIBUNWIND_FOUND AND NOT (STATIC AND APPLE)) +# On ARM, having libunwind package (with .so's only) installed breaks static link. +if(LIBUNWIND_FOUND AND NOT (STATIC AND (APPLE OR ARM))) set(DEFAULT_STACK_TRACE ON) else() set(DEFAULT_STACK_TRACE OFF) @@ -312,16 +308,12 @@ if(MSVC) endif() include_directories(SYSTEM src/platform/msc) else() - set(ARCH native CACHE STRING "CPU to build for: -march value or default") - # -march=armv7-a conflicts with -mcpu=cortex-a7 - if(ARCH STREQUAL "default" OR ARM7 OR ARM6) + set(ARCH native CACHE STRING "CPU to build for: -march value or 'default' to not pass -march at all") + message(STATUS "Building on ${CMAKE_SYSTEM_PROCESSOR} for ${ARCH}") + if(ARCH STREQUAL "default") set(ARCH_FLAG "") else() - if(ARCH STREQUAL "x86_64") - set(ARCH_FLAG "-march=x86-64") - else() - set(ARCH_FLAG "-march=${ARCH}") - endif() + set(ARCH_FLAG "-march=${ARCH}") endif() set(WARNINGS "-Wall -Wextra -Wpointer-arith -Wundef -Wvla -Wwrite-strings -Wno-error=extra -Wno-error=deprecated-declarations -Wno-unused-parameter -Wno-unused-variable -Wno-error=unused-variable -Wno-error=undef -Wno-error=uninitialized") if(NOT MINGW) @@ -329,7 +321,7 @@ else() endif() if(CMAKE_C_COMPILER_ID STREQUAL "Clang") set(WARNINGS "${WARNINGS} -Wno-deprecated-register -Wno-error=mismatched-tags -Wno-error=null-conversion -Wno-overloaded-shift-op-parentheses -Wno-error=shift-count-overflow -Wno-error=tautological-constant-out-of-range-compare -Wno-error=unused-private-field -Wno-error=unneeded-internal-declaration") - if(ARM6 OR ARM7) + if(ARM) set(WARNINGS "${WARNINGS} -Wno-error=inline-asm") endif() else() @@ -366,11 +358,11 @@ else() option(NO_AES "Explicitly disable AES support" ${NO_AES}) - if(NOT NO_AES AND NOT (ARM6 OR ARM7)) + if(NOT NO_AES AND NOT ARM) message(STATUS "AES support enabled") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -maes") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -maes") - elseif(ARM7 OR ARM6) + elseif(ARM) message(STATUS "AES support disabled (not available on ARM)") else() message(STATUS "AES support disabled") @@ -392,9 +384,9 @@ else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DGTEST_HAS_TR1_TUPLE=0") endif() if(CMAKE_C_COMPILER_ID STREQUAL "GNU" AND NOT (CMAKE_C_COMPILER_VERSION VERSION_LESS 4.8)) - set(DEBUG_FLAGS "-g3 -Og") + set(DEBUG_FLAGS "-g3 -Og -fprofile-arcs -ftest-coverage --coverage") else() - set(DEBUG_FLAGS "-g3 -O0") + set(DEBUG_FLAGS "-g3 -O0 -fprofile-arcs -ftest-coverage --coverage") endif() if(NOT DEFINED USE_LTO_DEFAULT) @@ -430,8 +422,18 @@ else() set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} ${RELEASE_FLAGS}") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${RELEASE_FLAGS}") - if(STATIC AND NOT APPLE AND NOT FREEBSD AND NOT OPENBSD) - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libgcc -static-libstdc++") + if(STATIC) + # STATIC already configures most deps to be linked in statically, + # here we make more deps static if the platform permits it + if (MINGW) + # On Windows, this is as close to fully-static as we get: + # this leaves only deps on /c/Windows/system32/*.dll + set(STATIC_FLAGS "-static") + elseif (NOT (APPLE OR FREEBSD OR OPENBSD)) + # On Linux, we don't support fully static build, but these can be static + set(STATIC_FLAGS "-static-libgcc -static-libstdc++") + endif() + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${STATIC_FLAGS} ") endif() endif() @@ -35,9 +35,9 @@ cmake-debug: debug: cmake-debug cd build/debug && $(MAKE) -debug-test: debug +debug-test: mkdir -p build/debug - cd build/debug && cmake -D BUILD_TESTS=ON -D CMAKE_BUILD_TYPE=Debug ../.. && $(MAKE) test + cd build/debug && cmake -D BUILD_TESTS=ON -D CMAKE_BUILD_TYPE=Debug ../.. && $(MAKE) && $(MAKE) test debug-all: mkdir -p build/debug @@ -50,14 +50,22 @@ cmake-release: release: cmake-release cd build/release && $(MAKE) -release-test: release +release-test: mkdir -p build/release - cd build/release && cmake -D BUILD_TESTS=ON -D CMAKE_BUILD_TYPE=release ../.. && $(MAKE) test + cd build/release && cmake -D BUILD_TESTS=ON -D CMAKE_BUILD_TYPE=release ../.. && $(MAKE) && $(MAKE) test release-all: mkdir -p build/release cd build/release && cmake -D BUILD_TESTS=ON -D CMAKE_BUILD_TYPE=release ../.. && $(MAKE) +release-static-arm6: + mkdir -p build/release + cd build/release && cmake -D BUILD_TESTS=OFF -D ARCH="armv6zk" -D STATIC=ON -D BUILD_64=OFF -D CMAKE_BUILD_TYPE=release ../.. && $(MAKE) + +release-static-arm7: + mkdir -p build/release + cd build/release && cmake -D BUILD_TESTS=OFF -D ARCH="armv7-a" -D STATIC=ON -D BUILD_64=OFF -D CMAKE_BUILD_TYPE=release ../.. && $(MAKE) + release-static: release-static-64 release-static-64: @@ -3,6 +3,7 @@ Copyright (c) 2014-2016, The Monero Project [![Build Status](https://travis-ci.org/monero-project/bitmonero.svg?branch=master)](https://travis-ci.org/monero-project/bitmonero) +[![Coverage Status](https://coveralls.io/repos/github/monero-project/bitmonero/badge.svg?branch=master)](https://coveralls.io/github/monero-project/bitmonero?branch=master) ## Development Resources @@ -56,11 +57,22 @@ There are also several mining pools that kindly donate a portion of their fees, See [LICENSE](LICENSE). -## Compiling Monero +## Installing Monero from a Package -### Overview: +Packages are available for -Dependencies: +* Arch Linux via AUR: [`bitmonero-git`](https://aur.archlinux.org/packages/bitmonero-git) + +* OS X via [Homebrew](http://brew.sh) + + brew tap sammy007/cryptonight + brew install bitmonero --build-from-source + +Packaging for your favorite distribution would be a welcome contribution! + +## Compiling Monero from Source + +### Dependencies * GCC `>=4.7.3` * CMake `>=3.0.0` @@ -72,113 +84,95 @@ Dependencies: * BerkeleyDB `>=4.8` (note: on Ubuntu this means installing libdb-dev and libdb++-dev) * libunwind (optional, for stack trace on exception) * miniupnpc (optional, for NAT punching) +* ldns `>=1.6.17` (optional, for statically-linked binaries) +* expat `>=1.1` (optional, for statically-linked binaries) +* bison or yacc (optional, for statically-linked binaries) +* Doxygen (optional, for generating documentation) +* graphviz (optional, for generating documentation) -Additional dependencies for statically-linked build: +### Build instructions -* ldns `>=1.6.17` -* expat `>=1.1` -* bison or yacc +Monero uses the CMake build system and a top-level [Makefile](Makefile) that +invokes cmake commands as needed. -Additional dependencies for building documentation: +#### On Linux and OS X -* Doxygen -* graphviz +* Install the dependencies +* Change to the root of the source code directory and build: -**Basic Process:** + cd bitmonero + make -* Install the dependencies (see below for more detailed instructions for your OS) -* To build, change to the root of the source code directory, and run `make`. Please note that Windows systems follow a slightly different process, outlined below. -* The resulting executables can be found in `build/release/bin` or `build/debug/bin`, depending on what you're building. + *Optional*: If your machine has several cores and enough memory, enable + parallel build by running `make -j<number of threads>` instead of `make`. For + this to be worthwhile, the machine should have one core and about 2GB of RAM + available per thread. -**Advanced options:** +* The resulting executables can be found in `build/release/bin`. -* Parallel build: run `make -j<number of threads>` instead of `make`. -* Statically linked release build: run `make release-static`. -* Debug build: run `make debug`. -* Test suite: run `make release-test` to run tests in addition to building. Running `make debug-test` will do the same to the debug version. +* **Optional**: build and run the test suite to verify the binaries: -**Makefile Targets for Static Builds:** + make release-test -For static builds there are a number of Makefile targets to make the build process easier. + *NOTE*: `coretests` test may take a few hours to complete. -* ```make release-static-win64``` builds statically for 64-bit Windows systems -* ```make release-static-win32``` builds statically for 32-bit Windows systems -* ```make release-static-64``` the default, builds statically for 64-bit non-Windows systems -* ```make release-static-32``` builds statically for 32-bit non-Windows systems -* ```make release-static-arm6``` builds statically for ARMv6 devices, such as the Raspberry Pi +* **Optional**: to build binaries suitable for debugging: -### On Linux: + make debug -The instructions above should provide enough detail. +* **Optional**: to build statically-linked binaries: -### On OS X: + make release-static -**Basic Process:** +#### On Windows: -The project can be built from scratch by following instructions for Unix and Linux above. +Binaries for Windows are built on Windows using the MinGW toolchain within +[MSYS2 environment](http://msys2.github.io). The MSYS2 environment emulates a +POSIX system. The toolchain runs within the environment and *cross-compiles* +binaries that can run outside of the environment as a regular Windows +application. -**Alternate Process:** +**Preparing the Build Environment** -Alternatively, it can be built in an easier and more automated fashion using Homebrew: +* Download and install the [MSYS2 installer](http://msys2.github.io), either the 64-bit or the 32-bit package, depending on your system. +* Open the MSYS shell via the `MSYS2 Shell` shortcut +* Update the packages in your MSYS2 install: -* Ensure Homebrew is installed, it can be found at http://brew.sh -* Add the repository to brew: `brew tap sammy007/cryptonight` -* Build Monero: `brew install bitmonero --build-from-source` + pacman -Sy + pacman -Su --ignoregroup base + pacman -Su -### On Windows: + For those of you already familiar with pacman, you can run the normal `pacman -Syu` to update, but you may get errors and need to restart MSYS2 if pacman's dependencies are updated. -Dependencies: +* Install dependencies: -* mingw-w64 -* msys2 -* CMake `>=3.0.0` -* libunbound `>=1.4.16` (note: Unbound is not a dependency, libunbound is) -* Boost `>=1.58` -* BerkeleyDB `>=4.8` + To build for 64-bit Windows: -**Preparing the Build Environment** + pacman -S mingw-w64-x86_64-toolchain make mingw-w64-x86_64-cmake mingw-w64-x86_64-boost -* Download the [MSYS2 installer](http://msys2.github.io), 64-bit or 32-bit as needed, and run it. -* Use the shortcut associated with your architecture to launch the MSYS2 environment. On 64-bit systems that would be the MinGW-w64 Win64 Shell shortcut. Note that if you are running 64-bit Windows, you will have both 64-bit and 32-bit environments. -* Update the packages in your MSYS2 install: -``` -pacman -Sy -pacman -Su --ignoregroup base -pacman -Su -``` -* For those of you already familiar with pacman, you can run the normal `pacman -Syu` to update, but you may get errors and need to restart MSYS2 if pacman's dependencies are updated. -* Install dependencies: `pacman -S mingw-w64-x86_64-gcc make mingw-w64-x86_64-cmake mingw-w64-x86_64-expat mingw-w64-x86_64-boost` + To build for 32-bit Windows: + + pacman -S mingw-w64-i686-toolchain make mingw-w64-i686-cmake mingw-w64-i686-boost + +* Open the MingW shell via `MinGW-w64-Win64 Shell` shortcut on 64-bit Windows + or `MinGW-w64-Win64 Shell` shortcut on 32-bit Windows. Note that if you are + running 64-bit Windows, you will have both 64-bit and 32-bit MinGW shells. **Building** -* From the root of the source code directory run: -``` -mkdir build -cd build -``` * If you are on a 64-bit system, run: -``` -cmake -G "MSYS Makefiles" -D CMAKE_BUILD_TYPE=Release -D ARCH="x86-64" -D BUILD_64=ON -D CMAKE_TOOLCHAIN_FILE=../cmake/64-bit-toolchain.cmake -D MSYS2_FOLDER=c:/msys64 .. -``` -* If you are on a 32-bit system, run: -``` -cmake -G "MSYS Makefiles" -D CMAKE_BUILD_TYPE=Release -D ARCH="i686" -D BUILD_64=OFF -D CMAKE_TOOLCHAIN_FILE=../cmake/32-bit-toolchain.cmake -D MSYS2_FOLDER=c:/msys32 .. -``` -* You can now run `make` to have it build -* The resulting executables can be found in `build/release/bin` or `build/debug/bin`, depending on what you're building. -If you installed MSYS2 in a folder other than c:/msys64, make the appropriate substitution above. + make release-static-win64 + +* If you are on a 32-bit system, run: -**Advanced options:** + make release-static-win32 -* Parallel build: run `make -j<number of threads>` instead of `make`. -* Statically linked release build: run `make release-static`. -* Debug build: run `make debug`. -* Test suite: run `make release-test` to run tests in addition to building. Running `make debug-test` will do the same to the debug version. +* The resulting executables can be found in `build/release/bin` ### On FreeBSD: -The project can be built from scratch by following instructions for Unix and Linux above. +The project can be built from scratch by following instructions for Linux above. We expect to add Monero into the ports tree in the near future, which will aid in managing installations using ports or packages. @@ -197,14 +191,32 @@ You will have to add the serialization, date_time, and regex modules to Boost wh To build: `env CC=egcc CXX=eg++ CPP=ecpp DEVELOPER_LOCAL_TOOLS=1 BOOST_ROOT=/path/to/the/boost/you/built make release-static-64` -## Building Documentation +### Building Portable Statically Linked Binaries + +By default, in either dynamically or statically linked builds, binaries target the specific host processor on which the build happens and are not portable to other processors. Portable binaries can be built using the following targets: + +* ```make release-static-64``` builds binaries on Linux on x86_64 portable across POSIX systems on x86_64 processors +* ```make release-static-32``` builds binaries on Linux on x86_64 or i686 portable across POSIX systems on i686 processors +* ```make release-static-arm7``` builds binaries on Linux on armv7 portable across POSIX systesm on armv7 processors +* ```make release-static-arm6``` builds binaries on Linux on armv7 or armv6 portable across POSIX systems on armv6 processors, such as the Raspberry Pi +* ```make release-static-win64``` builds binaries on 64-bit Windows portable across 64-bit Windows systems +* ```make release-static-win32``` builds binaries on 64-bit or 32-bit Windows portable across 32-bit Windows systems + +### Building Documentation Monero developer documentation uses Doxygen, and is currently a work-in-progress. -Dependencies: Doxygen 1.8.0 or later, Graphviz 2.28 or later (optional). +Dependencies: Doxygen `>=1.8.0`, Graphviz `>=2.28` (optional). + +* To build the HTML documentation without diagrams, change + to the root of the source code directory, and run + + doxygen Doxyfile + +* To build the HTML documentation with diagrams (Graphviz required): + + HAVE_DOT=YES doxygen Doxyfile -* To build, change to the root of the source code directory, and run `doxygen Doxyfile` -* If you have installed Graphviz, you can also generate in-doc diagrams by instead running `HAVE_DOT=YES doxygen Doxyfile` * The output will be built in doc/html/ ## Running bitmonerod @@ -215,16 +227,23 @@ foreground: ./bin/bitmonerod -To run in background: - - ./bin/bitmonerod --log-file bitmonerod.log --detach - To list all available options, run `./bin/bitmonerod --help`. Options can be specified either on the command line or in a configuration file passed by the `--config-file` argument. To specify an option in the configuration file, add a line with the syntax `argumentname=value`, where `argumentname` is the name of the argument without the leading dashes, for example `log-level=1`. +To run in background: + + ./bin/bitmonerod --log-file bitmonerod.log --detach + +To run as a systemd service, copy +[bitmonerod.service](utils/systemd/bitmonerod.service) to `/etc/systemd/system/` and +[bitmonerod.conf](utils/conf/bitmonerod.conf) to `/etc/`. The [example +service](utils/systemd/bitmonerod.service) assumes that the user `bitmonero` exists +and its home is the data directory specified in the [example +config](utils/conf/bitmonerod.conf). + ## Internationalization See README.i18n diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index dfa31aa72..70bb215d0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -91,6 +91,7 @@ endfunction () add_subdirectory(common) add_subdirectory(crypto) +add_subdirectory(ringct) add_subdirectory(cryptonote_core) add_subdirectory(blockchain_db) add_subdirectory(mnemonics) diff --git a/src/blockchain_db/berkeleydb/db_bdb.cpp b/src/blockchain_db/berkeleydb/db_bdb.cpp index 4d759d157..4ec284e38 100644 --- a/src/blockchain_db/berkeleydb/db_bdb.cpp +++ b/src/blockchain_db/berkeleydb/db_bdb.cpp @@ -361,7 +361,7 @@ void BlockchainBDB::remove_transaction_data(const crypto::hash& tx_hash, const t throw1(DB_ERROR("Failed to add removal of tx outputs to db transaction")); } -void BlockchainBDB::add_output(const crypto::hash& tx_hash, const tx_out& tx_output, const uint64_t& local_index, const uint64_t unlock_time) +void BlockchainBDB::add_output(const crypto::hash& tx_hash, const tx_out& tx_output, const uint64_t& local_index, const uint64_t unlock_time, const rct::key *commitment) { LOG_PRINT_L3("BlockchainBDB::" << __func__); check_open(); diff --git a/src/blockchain_db/berkeleydb/db_bdb.h b/src/blockchain_db/berkeleydb/db_bdb.h index 6bc26ed3d..f8e9440bd 100644 --- a/src/blockchain_db/berkeleydb/db_bdb.h +++ b/src/blockchain_db/berkeleydb/db_bdb.h @@ -364,7 +364,7 @@ private: virtual void remove_transaction_data(const crypto::hash& tx_hash, const transaction& tx); - virtual void add_output(const crypto::hash& tx_hash, const tx_out& tx_output, const uint64_t& local_index, const uint64_t unlock_time); + virtual void add_output(const crypto::hash& tx_hash, const tx_out& tx_output, const uint64_t& local_index, const uint64_t unlock_time, const rct::key *commitment); virtual void remove_output(const tx_out& tx_output); diff --git a/src/blockchain_db/blockchain_db.cpp b/src/blockchain_db/blockchain_db.cpp index 68f635d18..7eb81d933 100644 --- a/src/blockchain_db/blockchain_db.cpp +++ b/src/blockchain_db/blockchain_db.cpp @@ -46,6 +46,7 @@ void BlockchainDB::pop_block() void BlockchainDB::add_transaction(const crypto::hash& blk_hash, const transaction& tx, const crypto::hash* tx_hash_ptr) { + bool miner_tx = false; crypto::hash tx_hash; if (!tx_hash_ptr) { @@ -67,6 +68,7 @@ void BlockchainDB::add_transaction(const crypto::hash& blk_hash, const transacti else if (tx_input.type() == typeid(txin_gen)) { /* nothing to do here */ + miner_tx = true; } else { @@ -90,7 +92,21 @@ void BlockchainDB::add_transaction(const crypto::hash& blk_hash, const transacti // we need the index for (uint64_t i = 0; i < tx.vout.size(); ++i) { - amount_output_indices.push_back(add_output(tx_hash, tx.vout[i], i, tx.unlock_time)); + // miner v2 txes have their coinbase output in one single out to save space, + // and we store them as rct outputs with an identity mask + if (miner_tx && tx.version == 2) + { + cryptonote::tx_out vout = tx.vout[i]; + rct::key commitment = rct::zeroCommit(vout.amount); + vout.amount = 0; + amount_output_indices.push_back(add_output(tx_hash, vout, i, tx.unlock_time, + &commitment)); + } + else + { + amount_output_indices.push_back(add_output(tx_hash, tx.vout[i], i, tx.unlock_time, + tx.version > 1 ? &tx.rct_signatures.outPk[i].mask : NULL)); + } } add_tx_amount_output_indices(tx_id, amount_output_indices); } diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h index 4c2157a39..d26080a3b 100644 --- a/src/blockchain_db/blockchain_db.h +++ b/src/blockchain_db/blockchain_db.h @@ -110,6 +110,7 @@ struct output_data_t crypto::public_key pubkey; //!< the output's public key (for spend verification) uint64_t unlock_time; //!< the output's unlock time (or height) uint64_t height; //!< the height of the block which created the output + rct::key commitment; //!< the output's amount commitment (for spend verification) }; #pragma pack(pop) @@ -407,9 +408,10 @@ private: * @param tx_output the output * @param local_index index of the output in its transaction * @param unlock_time unlock time/height of the output + * @param commitment the rct commitment to the output amount * @return amount output index */ - virtual uint64_t add_output(const crypto::hash& tx_hash, const tx_out& tx_output, const uint64_t& local_index, const uint64_t unlock_time) = 0; + virtual uint64_t add_output(const crypto::hash& tx_hash, const tx_out& tx_output, const uint64_t& local_index, const uint64_t unlock_time, const rct::key *commitment) = 0; /** * @brief store amount output indices for a tx's outputs diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index 0464ece59..a0a0a041d 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -51,6 +51,13 @@ using epee::string_tools::pod_to_hex; namespace { +struct pre_rct_output_data_t +{ + crypto::public_key pubkey; //!< the output's public key (for spend verification) + uint64_t unlock_time; //!< the output's unlock time (or height) + uint64_t height; //!< the height of the block which created the output +}; + template <typename T> inline void throw0(const T &e) { @@ -242,6 +249,12 @@ typedef struct txindex { tx_data_t data; } txindex; +typedef struct pre_rct_outkey { + uint64_t amount_index; + uint64_t output_id; + pre_rct_output_data_t data; +} pre_rct_outkey; + typedef struct outkey { uint64_t amount_index; uint64_t output_id; @@ -765,7 +778,8 @@ void BlockchainLMDB::remove_transaction_data(const crypto::hash& tx_hash, const uint64_t BlockchainLMDB::add_output(const crypto::hash& tx_hash, const tx_out& tx_output, const uint64_t& local_index, - const uint64_t unlock_time) + const uint64_t unlock_time, + const rct::key *commitment) { LOG_PRINT_L3("BlockchainLMDB::" << __func__); check_open(); @@ -778,6 +792,8 @@ uint64_t BlockchainLMDB::add_output(const crypto::hash& tx_hash, if (tx_output.target.type() != typeid(txout_to_key)) throw0(DB_ERROR("Wrong output type: expected txout_to_key")); + if (tx_output.amount == 0 && !commitment) + throw0(DB_ERROR("RCT output without commitment")); outtx ot = {m_num_outputs, tx_hash, local_index}; MDB_val_set(vot, ot); @@ -806,8 +822,16 @@ uint64_t BlockchainLMDB::add_output(const crypto::hash& tx_hash, ok.data.pubkey = boost::get < txout_to_key > (tx_output.target).key; ok.data.unlock_time = unlock_time; ok.data.height = m_height; + if (tx_output.amount == 0) + { + ok.data.commitment = *commitment; + data.mv_size = sizeof(ok); + } + else + { + data.mv_size = sizeof(pre_rct_outkey); + } data.mv_data = &ok; - data.mv_size = sizeof(ok); if ((result = mdb_cursor_put(m_cur_output_amounts, &val_amount, &data, MDB_APPENDDUP))) throw0(DB_ERROR(lmdb_error("Failed to add output pubkey to db transaction: ", result).c_str())); @@ -1234,6 +1258,7 @@ void BlockchainLMDB::reset() mdb_txn_safe txn; if (auto result = mdb_txn_begin(m_env, NULL, 0, txn)) throw0(DB_ERROR(lmdb_error("Failed to create a transaction for the db: ", result).c_str())); + if (auto result = mdb_drop(txn, m_blocks, 0)) throw0(DB_ERROR(lmdb_error("Failed to drop m_blocks: ", result).c_str())); if (auto result = mdb_drop(txn, m_block_info, 0)) @@ -1242,6 +1267,8 @@ void BlockchainLMDB::reset() throw0(DB_ERROR(lmdb_error("Failed to drop m_block_heights: ", result).c_str())); if (auto result = mdb_drop(txn, m_txs, 0)) throw0(DB_ERROR(lmdb_error("Failed to drop m_txs: ", result).c_str())); + if (auto result = mdb_drop(txn, m_tx_indices, 0)) + throw0(DB_ERROR(lmdb_error("Failed to drop m_tx_indices: ", result).c_str())); if (auto result = mdb_drop(txn, m_tx_outputs, 0)) throw0(DB_ERROR(lmdb_error("Failed to drop m_tx_outputs: ", result).c_str())); if (auto result = mdb_drop(txn, m_output_txs, 0)) @@ -1255,6 +1282,13 @@ void BlockchainLMDB::reset() throw0(DB_ERROR(lmdb_error("Failed to drop m_hf_versions: ", result).c_str())); if (auto result = mdb_drop(txn, m_properties, 0)) throw0(DB_ERROR(lmdb_error("Failed to drop m_properties: ", result).c_str())); + + // init with current version + MDB_val_copy<const char*> k("version"); + MDB_val_copy<uint32_t> v(VERSION); + if (auto result = mdb_put(txn, m_properties, &k, &v, 0)) + throw0(DB_ERROR(lmdb_error("Failed to write version to database: ", result).c_str())); + txn.commit(); m_height = 0; m_num_outputs = 0; @@ -1923,8 +1957,19 @@ output_data_t BlockchainLMDB::get_output_key(const uint64_t& amount, const uint6 throw1(OUTPUT_DNE("Attempting to get output pubkey by index, but key does not exist")); else if (get_result) throw0(DB_ERROR("Error attempting to retrieve an output pubkey from the db")); - outkey *okp = (outkey *)v.mv_data; - output_data_t ret = okp->data; + + output_data_t ret; + if (amount == 0) + { + const outkey *okp = (const outkey *)v.mv_data; + ret = okp->data; + } + else + { + const pre_rct_outkey *okp = (const pre_rct_outkey *)v.mv_data; + memcpy(&ret, &okp->data, sizeof(pre_rct_output_data_t));; + ret.commitment = rct::zeroCommit(amount); + } TXN_POSTFIX_RDONLY(); return ret; } @@ -2547,8 +2592,18 @@ void BlockchainLMDB::get_output_key(const uint64_t &amount, const std::vector<ui else if (get_result) throw0(DB_ERROR(lmdb_error("Error attempting to retrieve an output pubkey from the db", get_result).c_str())); - outkey *okp = (outkey *)v.mv_data; - output_data_t data = okp->data; + output_data_t data; + if (amount == 0) + { + const outkey *okp = (const outkey *)v.mv_data; + data = okp->data; + } + else + { + const pre_rct_outkey *okp = (const pre_rct_outkey *)v.mv_data; + memcpy(&data, &okp->data, sizeof(pre_rct_output_data_t)); + data.commitment = rct::zeroCommit(amount); + } outputs.push_back(data); } diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h index d60701bbe..050e9e0ae 100644 --- a/src/blockchain_db/lmdb/db_lmdb.h +++ b/src/blockchain_db/lmdb/db_lmdb.h @@ -30,6 +30,7 @@ #include "blockchain_db/blockchain_db.h" #include "cryptonote_protocol/blobdatatype.h" // for type blobdata +#include "ringct/rctTypes.h" #include <boost/thread/tss.hpp> #include <lmdb.h> @@ -292,7 +293,8 @@ private: virtual uint64_t add_output(const crypto::hash& tx_hash, const tx_out& tx_output, const uint64_t& local_index, - const uint64_t unlock_time + const uint64_t unlock_time, + const rct::key *commitment ); virtual void add_tx_amount_output_indices(const uint64_t tx_id, diff --git a/src/blockchain_utilities/CMakeLists.txt b/src/blockchain_utilities/CMakeLists.txt index 96b30b83b..d98c2ce83 100644 --- a/src/blockchain_utilities/CMakeLists.txt +++ b/src/blockchain_utilities/CMakeLists.txt @@ -26,15 +26,6 @@ # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -set(blockchain_converter_sources - blockchain_converter.cpp - ) - -set(blockchain_converter_private_headers) - -bitmonero_private_headers(blockchain_converter - ${blockchain_converter_private_headers}) - set(blockchain_import_sources blockchain_import.cpp bootstrap_file.cpp @@ -87,30 +78,6 @@ bitmonero_private_headers(cn_deserialize ${cn_deserialize_private_headers}) -if (BLOCKCHAIN_DB STREQUAL DB_LMDB) -bitmonero_add_executable(blockchain_converter - ${blockchain_converter_sources} - ${blockchain_converter_private_headers}) - -target_link_libraries(blockchain_converter - LINK_PRIVATE - cryptonote_core - p2p - blockchain_db - ${CMAKE_THREAD_LIBS_INIT}) - -if(ARCH_WIDTH) - target_compile_definitions(blockchain_converter - PUBLIC -DARCH_WIDTH=${ARCH_WIDTH}) -endif() - -add_dependencies(blockchain_converter - version) -set_property(TARGET blockchain_converter - PROPERTY - OUTPUT_NAME "blockchain_converter") -endif () - bitmonero_add_executable(blockchain_import ${blockchain_import_sources} ${blockchain_import_private_headers}) diff --git a/src/blockchain_utilities/README.md b/src/blockchain_utilities/README.md index 9c69647ad..7df6937ad 100644 --- a/src/blockchain_utilities/README.md +++ b/src/blockchain_utilities/README.md @@ -18,9 +18,10 @@ e.g. This is also the default compile setting on the master branch. -By default, the exporter will use the original in-memory database (blockchain.bin) as its source. -This default is to make migrating to an LMDB database easy, without having to recompile anything. -To change the source, adjust `SOURCE_DB` in `src/blockchain_utilities/bootstrap_file.h` according to the comments. +The exporter will use the LMDB database as its source. +If you are still using an old style in-memory database (blockchain.bin), you will +have to either resync from scratch, or use an older version of the tools to export +it and import it. ## Usage: diff --git a/src/blockchain_utilities/blockchain_converter.cpp b/src/blockchain_utilities/blockchain_converter.cpp deleted file mode 100644 index 7c33ec399..000000000 --- a/src/blockchain_utilities/blockchain_converter.cpp +++ /dev/null @@ -1,306 +0,0 @@ -// Copyright (c) 2014-2016, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#include "include_base_utils.h" -#include "common/util.h" -#include "warnings.h" -#include "crypto/crypto.h" -#include "cryptonote_config.h" -#include "cryptonote_core/cryptonote_format_utils.h" -#include "misc_language.h" -#include "cryptonote_core/blockchain_storage.h" -#include "blockchain_db/blockchain_db.h" -#include "cryptonote_core/blockchain.h" -#include "blockchain_db/lmdb/db_lmdb.h" -#include "cryptonote_core/tx_pool.h" -#include "common/command_line.h" -#include "serialization/json_utils.h" -#include "include_base_utils.h" -#include "version.h" -#include <iostream> - - -namespace -{ - -// CONFIG -bool opt_batch = true; -bool opt_resume = true; -bool opt_testnet = false; - -// number of blocks per batch transaction -// adjustable through command-line argument according to available RAM -#if ARCH_WIDTH != 32 -uint64_t db_batch_size_verify = 5000; -#else -// set a lower default batch size for Windows, pending possible LMDB issue with -// large batch size. -uint64_t db_batch_size_verify = 100; -#endif - -// converter only uses verify mode -uint64_t db_batch_size = db_batch_size_verify; - -} - -namespace po = boost::program_options; - -using namespace cryptonote; -using namespace epee; - -struct fake_core -{ - Blockchain dummy; - tx_memory_pool m_pool; - - blockchain_storage m_storage; - -#if !defined(BLOCKCHAIN_DB) - // for multi_db_runtime: - fake_core(const boost::filesystem::path &path, const bool use_testnet) : dummy(m_pool), m_pool(&dummy), m_storage(m_pool) -#else - // for multi_db_compile: - fake_core(const boost::filesystem::path &path, const bool use_testnet) : dummy(m_pool), m_pool(dummy), m_storage(&m_pool) -#endif - { - m_pool.init(path.string()); - m_storage.init(path.string(), use_testnet); - } -}; - -int main(int argc, char* argv[]) -{ - uint64_t height = 0; - uint64_t start_block = 0; - uint64_t end_block = 0; - uint64_t num_blocks = 0; - - boost::filesystem::path default_data_path {tools::get_default_data_dir()}; - boost::filesystem::path default_testnet_data_path {default_data_path / "testnet"}; - - - po::options_description desc_cmd_only("Command line options"); - po::options_description desc_cmd_sett("Command line options and settings options"); - const command_line::arg_descriptor<uint32_t> arg_log_level = {"log-level", "", LOG_LEVEL_0}; - const command_line::arg_descriptor<uint64_t> arg_batch_size = {"batch-size", "", db_batch_size}; - const command_line::arg_descriptor<bool> arg_testnet_on = { - "testnet" - , "Run on testnet." - , opt_testnet - }; - const command_line::arg_descriptor<uint64_t> arg_block_number = - {"block-number", "Number of blocks (default: use entire source blockchain)", - 0}; - - command_line::add_arg(desc_cmd_sett, command_line::arg_data_dir, default_data_path.string()); - command_line::add_arg(desc_cmd_sett, command_line::arg_testnet_data_dir, default_testnet_data_path.string()); - command_line::add_arg(desc_cmd_sett, arg_log_level); - command_line::add_arg(desc_cmd_sett, arg_batch_size); - command_line::add_arg(desc_cmd_sett, arg_testnet_on); - command_line::add_arg(desc_cmd_sett, arg_block_number); - - command_line::add_arg(desc_cmd_only, command_line::arg_help); - - const command_line::arg_descriptor<bool> arg_batch = {"batch", - "Batch transactions for faster import", true}; - const command_line::arg_descriptor<bool> arg_resume = {"resume", - "Resume from current height if output database already exists", true}; - - // call add_options() directly for these arguments since command_line helpers - // support only boolean switch, not boolean argument - desc_cmd_sett.add_options() - (arg_batch.name, make_semantic(arg_batch), arg_batch.description) - (arg_resume.name, make_semantic(arg_resume), arg_resume.description) - ; - - po::options_description desc_options("Allowed options"); - desc_options.add(desc_cmd_only).add(desc_cmd_sett); - - - po::variables_map vm; - bool r = command_line::handle_error_helper(desc_options, [&]() - { - po::store(po::parse_command_line(argc, argv, desc_options), vm); - po::notify(vm); - - return true; - }); - if (!r) - return 1; - - int log_level = command_line::get_arg(vm, arg_log_level); - opt_batch = command_line::get_arg(vm, arg_batch); - opt_resume = command_line::get_arg(vm, arg_resume); - db_batch_size = command_line::get_arg(vm, arg_batch_size); - - if (command_line::get_arg(vm, command_line::arg_help)) - { - std::cout << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")" << ENDL << ENDL; - std::cout << desc_options << std::endl; - return 1; - } - - if (! opt_batch && ! vm["batch-size"].defaulted()) - { - std::cerr << "Error: batch-size set, but batch option not enabled" << ENDL; - return 1; - } - if (! db_batch_size) - { - std::cerr << "Error: batch-size must be > 0" << ENDL; - return 1; - } - - log_space::get_set_log_detalisation_level(true, log_level); - log_space::log_singletone::add_logger(LOGGER_CONSOLE, NULL, NULL); - LOG_PRINT_L0("Starting..."); - - std::string src_folder; - opt_testnet = command_line::get_arg(vm, arg_testnet_on); - auto data_dir_arg = opt_testnet ? command_line::arg_testnet_data_dir : command_line::arg_data_dir; - src_folder = command_line::get_arg(vm, data_dir_arg); - boost::filesystem::path dest_folder(src_folder); - - num_blocks = command_line::get_arg(vm, arg_block_number); - - if (opt_batch) - { - LOG_PRINT_L0("batch: " << std::boolalpha << opt_batch << std::noboolalpha - << " batch size: " << db_batch_size); - } - else - { - LOG_PRINT_L0("batch: " << std::boolalpha << opt_batch << std::noboolalpha); - } - LOG_PRINT_L0("resume: " << std::boolalpha << opt_resume << std::noboolalpha); - LOG_PRINT_L0("testnet: " << std::boolalpha << opt_testnet << std::noboolalpha); - - fake_core c(src_folder, opt_testnet); - - height = c.m_storage.get_current_blockchain_height(); - - BlockchainDB *blockchain; - blockchain = new BlockchainLMDB(opt_batch); - dest_folder /= blockchain->get_db_name(); - LOG_PRINT_L0("Source blockchain: " << src_folder); - LOG_PRINT_L0("Dest blockchain: " << dest_folder.string()); - LOG_PRINT_L0("Opening dest blockchain (BlockchainDB " << blockchain->get_db_name() << ")"); - blockchain->open(dest_folder.string()); - LOG_PRINT_L0("Source blockchain height: " << height); - LOG_PRINT_L0("Dest blockchain height: " << blockchain->height()); - - if (opt_resume) - // next block number to add is same as current height - start_block = blockchain->height(); - - if (! num_blocks || (start_block + num_blocks > height)) - end_block = height - 1; - else - end_block = start_block + num_blocks - 1; - - LOG_PRINT_L0("start height: " << start_block+1 << " stop height: " << - end_block+1); - - if (start_block > end_block) - { - LOG_PRINT_L0("Finished: no blocks to add"); - delete blockchain; - return 0; - } - - if (opt_batch) - blockchain->batch_start(db_batch_size); - uint64_t i = 0; - for (i = start_block; i < end_block + 1; ++i) - { - // block: i height: i+1 end height: end_block + 1 - if ((i+1) % 10 == 0) - { - std::cout << "\r \r" << "height " << i+1 << "/" << - end_block+1 << " (" << (i+1)*100/(end_block+1)<< "%)" << std::flush; - } - // for debugging: - // std::cout << "height " << i+1 << "/" << end_block+1 - // << " ((" << i+1 << ")*100/(end_block+1))" << "%)" << ENDL; - - block b = c.m_storage.get_block(i); - size_t bsize = c.m_storage.get_block_size(i); - difficulty_type bdiff = c.m_storage.get_block_cumulative_difficulty(i); - uint64_t bcoins = c.m_storage.get_block_coins_generated(i); - std::vector<transaction> txs; - std::vector<crypto::hash> missed; - - c.m_storage.get_transactions(b.tx_hashes, txs, missed); - if (missed.size()) - { - std::cout << ENDL; - std::cerr << "Missed transaction(s) for block at height " << i + 1 << ", exiting" << ENDL; - delete blockchain; - return 1; - } - - try - { - blockchain->add_block(b, bsize, bdiff, bcoins, txs); - - if (opt_batch) - { - if ((i < end_block) && ((i + 1) % db_batch_size == 0)) - { - std::cout << "\r \r"; - std::cout << "[- batch commit at height " << i + 1 << " -]" << ENDL; - blockchain->batch_stop(); - blockchain->batch_start(db_batch_size); - std::cout << ENDL; - blockchain->show_stats(); - } - } - } - catch (const std::exception& e) - { - std::cout << ENDL; - std::cerr << "Error adding block " << i << " to new blockchain: " << e.what() << ENDL; - delete blockchain; - return 2; - } - } - if (opt_batch) - { - std::cout << "\r \r" << "height " << i << "/" << - end_block+1 << " (" << (i)*100/(end_block+1)<< "%)" << std::flush; - std::cout << ENDL; - std::cout << "[- batch commit at height " << i << " -]" << ENDL; - blockchain->batch_stop(); - } - std::cout << ENDL; - blockchain->show_stats(); - std::cout << "Finished at height: " << i << " block: " << i-1 << ENDL; - - delete blockchain; - return 0; -} diff --git a/src/blockchain_utilities/blockchain_dump.cpp b/src/blockchain_utilities/blockchain_dump.cpp index 23619eaeb..626900d42 100644 --- a/src/blockchain_utilities/blockchain_dump.cpp +++ b/src/blockchain_utilities/blockchain_dump.cpp @@ -27,8 +27,8 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "cryptonote_core/cryptonote_basic.h" -#include "cryptonote_core/blockchain_storage.h" #include "cryptonote_core/blockchain.h" +#include "cryptonote_core/tx_pool.h" #include "blockchain_db/blockchain_db.h" #include "blockchain_db/lmdb/db_lmdb.h" #ifdef BERKELEY_DB @@ -103,7 +103,6 @@ int main(int argc, char* argv[]) const command_line::arg_descriptor<std::string> arg_output_file = {"output-file", "Specify output file", "", true}; const command_line::arg_descriptor<uint32_t> arg_log_level = {"log-level", "", log_level}; const command_line::arg_descriptor<uint64_t> arg_block_stop = {"block-stop", "Stop at block number", block_stop}; -#if SOURCE_DB != DB_MEMORY const command_line::arg_descriptor<std::string> arg_db_type = { "db-type" , "Specify database type" @@ -114,7 +113,6 @@ int main(int argc, char* argv[]) , "Include data that is only in a database version." , false }; -#endif const command_line::arg_descriptor<bool> arg_testnet_on = { "testnet" , "Run on testnet." @@ -125,10 +123,8 @@ int main(int argc, char* argv[]) command_line::add_arg(desc_cmd_sett, command_line::arg_data_dir, default_data_path.string()); command_line::add_arg(desc_cmd_sett, command_line::arg_testnet_data_dir, default_testnet_data_path.string()); command_line::add_arg(desc_cmd_sett, arg_output_file); -#if SOURCE_DB != DB_MEMORY command_line::add_arg(desc_cmd_sett, arg_db_type); command_line::add_arg(desc_cmd_sett, arg_include_db_only_data); -#endif command_line::add_arg(desc_cmd_sett, arg_testnet_on); command_line::add_arg(desc_cmd_sett, arg_log_level); command_line::add_arg(desc_cmd_sett, arg_block_stop); @@ -164,9 +160,7 @@ int main(int argc, char* argv[]) LOG_PRINT_L0("Setting log level = " << log_level); bool opt_testnet = command_line::get_arg(vm, arg_testnet_on); -#if SOURCE_DB != DB_MEMORY bool opt_include_db_only_data = command_line::get_arg(vm, arg_include_db_only_data); -#endif std::string m_config_folder; @@ -208,15 +202,6 @@ int main(int argc, char* argv[]) // If we wanted to use the memory pool, we would set up a fake_core. -#if SOURCE_DB == DB_MEMORY - // blockchain_storage* core_storage = NULL; - // tx_memory_pool m_mempool(*core_storage); // is this fake anyway? just passing in NULL! so m_mempool can't be used anyway, right? - // core_storage = new blockchain_storage(&m_mempool); - - blockchain_storage* core_storage = new blockchain_storage(NULL); - LOG_PRINT_L0("Initializing source blockchain (in-memory database)"); - r = core_storage->init(m_config_folder, opt_testnet); -#else // Use Blockchain instead of lower-level BlockchainDB for two reasons: // 1. Blockchain has the init() method for easy setup // 2. exporter needs to use get_current_blockchain_height(), get_block_id_by_height(), get_block_by_hash() @@ -267,7 +252,6 @@ int main(int argc, char* argv[]) return 1; } r = core_storage->init(db, opt_testnet); -#endif CHECK_AND_ASSERT_MES(r, false, "Failed to initialize source blockchain storage"); LOG_PRINT_L0("Source blockchain storage initialized OK"); @@ -340,7 +324,6 @@ int main(int argc, char* argv[]) write_pod(d,key_images[n]); } end_compound(d); -#if SOURCE_DB != DB_MEMORY if (opt_include_db_only_data) { start_struct(d, "block_timestamps", true); @@ -420,7 +403,6 @@ int main(int argc, char* argv[]) write_pod(d, boost::lexical_cast<std::string>(h), (unsigned int)db->get_hard_fork_version(h)); end_compound(d); } -#endif end_compound(d); CHECK_AND_ASSERT_MES(r, false, "Failed to dump blockchain"); diff --git a/src/blockchain_utilities/blockchain_export.cpp b/src/blockchain_utilities/blockchain_export.cpp index 964c610cd..231bea337 100644 --- a/src/blockchain_utilities/blockchain_export.cpp +++ b/src/blockchain_utilities/blockchain_export.cpp @@ -29,6 +29,7 @@ #include "bootstrap_file.h" #include "blocksdat_file.h" #include "common/command_line.h" +#include "cryptonote_core/tx_pool.h" #include "blockchain_db/blockchain_db.h" #include "blockchain_db/lmdb/db_lmdb.h" #if defined(BERKELEY_DB) @@ -53,11 +54,7 @@ std::string join_set_strings(const std::unordered_set<std::string>& db_types_all int main(int argc, char* argv[]) { -#if defined(BLOCKCHAIN_DB) && (BLOCKCHAIN_DB == DB_MEMORY) - std::string default_db_type = "memory"; -#else std::string default_db_type = "lmdb"; -#endif std::unordered_set<std::string> db_types_all = cryptonote::blockchain_db_types; db_types_all.insert("memory"); @@ -160,15 +157,6 @@ int main(int argc, char* argv[]) // If we wanted to use the memory pool, we would set up a fake_core. -#if SOURCE_DB == DB_MEMORY - // blockchain_storage* core_storage = NULL; - // tx_memory_pool m_mempool(*core_storage); // is this fake anyway? just passing in NULL! so m_mempool can't be used anyway, right? - // core_storage = new blockchain_storage(&m_mempool); - - blockchain_storage* core_storage = new blockchain_storage(NULL); - LOG_PRINT_L0("Initializing source blockchain (in-memory database)"); - r = core_storage->init(m_config_folder, opt_testnet); -#else // Use Blockchain instead of lower-level BlockchainDB for two reasons: // 1. Blockchain has the init() method for easy setup // 2. exporter needs to use get_current_blockchain_height(), get_block_id_by_height(), get_block_by_hash() @@ -217,7 +205,6 @@ int main(int argc, char* argv[]) return 1; } r = core_storage->init(db, opt_testnet); -#endif CHECK_AND_ASSERT_MES(r, false, "Failed to initialize source blockchain storage"); LOG_PRINT_L0("Source blockchain storage initialized OK"); diff --git a/src/blockchain_utilities/blockchain_import.cpp b/src/blockchain_utilities/blockchain_import.cpp index 1aaf2bddc..27c9050f8 100644 --- a/src/blockchain_utilities/blockchain_import.cpp +++ b/src/blockchain_utilities/blockchain_import.cpp @@ -222,12 +222,8 @@ int pop_blocks(FakeCore& simple_core, int num_blocks) std::vector<transaction> popped_txs; for (int i=0; i < num_blocks; ++i) { -#if defined(BLOCKCHAIN_DB) && (BLOCKCHAIN_DB == DB_MEMORY) - simple_core.m_storage.debug_pop_block_from_blockchain(); -#else // simple_core.m_storage.pop_block_from_blockchain() is private, so call directly through db simple_core.m_storage.get_db().pop_block(popped_block, popped_txs); -#endif quit = 1; } @@ -244,9 +240,7 @@ int pop_blocks(FakeCore& simple_core, int num_blocks) { simple_core.batch_stop(); } -#if !defined(BLOCKCHAIN_DB) || (BLOCKCHAIN_DB == DB_LMDB) simple_core.m_storage.get_db().show_stats(); -#endif } return num_blocks; @@ -255,11 +249,6 @@ int pop_blocks(FakeCore& simple_core, int num_blocks) template <typename FakeCore> int import_from_file(FakeCore& simple_core, const std::string& import_file_path, uint64_t block_stop=0) { -#if !defined(BLOCKCHAIN_DB) - static_assert(std::is_same<fake_core_memory, FakeCore>::value || std::is_same<fake_core_db, FakeCore>::value, - "FakeCore constraint error"); -#endif -#if !defined(BLOCKCHAIN_DB) || (BLOCKCHAIN_DB == DB_LMDB) if (std::is_same<fake_core_db, FakeCore>::value) { // Reset stats, in case we're using newly created db, accumulating stats @@ -267,7 +256,6 @@ int import_from_file(FakeCore& simple_core, const std::string& import_file_path, // This aligns internal db counts with importer counts. simple_core.m_storage.get_db().reset_stats(); } -#endif boost::filesystem::path fs_import_file_path(import_file_path); boost::system::error_code ec; if (!boost::filesystem::exists(fs_import_file_path, ec)) @@ -567,9 +555,7 @@ int import_from_file(FakeCore& simple_core, const std::string& import_file_path, simple_core.batch_stop(); simple_core.batch_start(db_batch_size); std::cout << ENDL; -#if !defined(BLOCKCHAIN_DB) || (BLOCKCHAIN_DB == DB_LMDB) simple_core.m_storage.get_db().show_stats(); -#endif } } } @@ -595,9 +581,7 @@ int import_from_file(FakeCore& simple_core, const std::string& import_file_path, { simple_core.batch_stop(); } -#if !defined(BLOCKCHAIN_DB) || (BLOCKCHAIN_DB == DB_LMDB) simple_core.m_storage.get_db().show_stats(); -#endif LOG_PRINT_L0("Number of blocks imported: " << num_imported); if (h > 0) // TODO: if there was an error, the last added block is probably at zero-based height h-2 @@ -609,13 +593,8 @@ int import_from_file(FakeCore& simple_core, const std::string& import_file_path, int main(int argc, char* argv[]) { -#if defined(BLOCKCHAIN_DB) && (BLOCKCHAIN_DB == DB_MEMORY) - std::string default_db_type = "memory"; - std::string default_db_engine_compiled = "memory"; -#else std::string default_db_type = "lmdb"; std::string default_db_engine_compiled = "blockchain_db"; -#endif std::unordered_set<std::string> db_types_all = cryptonote::blockchain_db_types; db_types_all.insert("memory"); @@ -817,17 +796,11 @@ int main(int argc, char* argv[]) // circumstance. // for multi_db_runtime: -#if !defined(BLOCKCHAIN_DB) if (db_type == "lmdb" || db_type == "berkeley") { fake_core_db simple_core(m_config_folder, opt_testnet, opt_batch, db_type, db_flags); import_from_file(simple_core, import_file_path, block_stop); } - else if (db_type == "memory") - { - fake_core_memory simple_core(m_config_folder, opt_testnet); - import_from_file(simple_core, import_file_path, block_stop); - } else { std::cerr << "database type unrecognized" << ENDL; @@ -835,17 +808,7 @@ int main(int argc, char* argv[]) } // for multi_db_compile: -#else - if (db_engine_compiled != default_db_engine_compiled) - { - std::cerr << "Invalid database type for compiled version: " << db_type << std::endl; - return 1; - } -#if BLOCKCHAIN_DB == DB_LMDB fake_core_db simple_core(m_config_folder, opt_testnet, opt_batch, db_type, db_flags); -#else - fake_core_memory simple_core(m_config_folder, opt_testnet); -#endif if (! vm["pop-blocks"].defaulted()) { @@ -856,17 +819,14 @@ int main(int argc, char* argv[]) return 0; } -#if !defined(BLOCKCHAIN_DB) || (BLOCKCHAIN_DB == DB_LMDB) if (! vm["drop-hard-fork"].defaulted()) { LOG_PRINT_L0("Dropping hard fork tables..."); simple_core.m_storage.get_db().drop_hard_fork_info(); return 0; } -#endif import_from_file(simple_core, import_file_path, block_stop); -#endif } catch (const DB_ERROR& e) diff --git a/src/blockchain_utilities/blockchain_utilities.h b/src/blockchain_utilities/blockchain_utilities.h index 51c17a9b6..6ddda65ec 100644 --- a/src/blockchain_utilities/blockchain_utilities.h +++ b/src/blockchain_utilities/blockchain_utilities.h @@ -31,21 +31,6 @@ #include "version.h" -// CONFIG: choose one of the three #define's -// -// DB_MEMORY was a sensible default for users migrating to LMDB, as it allowed -// the exporter to use the in-memory blockchain while the other binaries -// work with LMDB, without recompiling anything. -// -// Now that the majority of users are on 0.9.2, and the converter has largely -// become a generalised database tool, the default has changed to DB_LMDB. -// -// #define SOURCE_DB DB_MEMORY -#define SOURCE_DB DB_LMDB -// to use global compile-time setting (DB_MEMORY or DB_LMDB): -// #define SOURCE_DB BLOCKCHAIN_DB - - // bounds checking is done before writing to buffer, but buffer size // should be a sensible maximum #define BUFFER_SIZE 1000000 diff --git a/src/blockchain_utilities/blocksdat_file.cpp b/src/blockchain_utilities/blocksdat_file.cpp index 81b56d55e..926562ba2 100644 --- a/src/blockchain_utilities/blocksdat_file.cpp +++ b/src/blockchain_utilities/blocksdat_file.cpp @@ -114,11 +114,7 @@ bool BlocksdatFile::close() } -#if SOURCE_DB == DB_MEMORY -bool BlocksdatFile::store_blockchain_raw(blockchain_storage* _blockchain_storage, tx_memory_pool* _tx_pool, boost::filesystem::path& output_file, uint64_t requested_block_stop) -#else bool BlocksdatFile::store_blockchain_raw(Blockchain* _blockchain_storage, tx_memory_pool* _tx_pool, boost::filesystem::path& output_file, uint64_t requested_block_stop) -#endif { uint64_t num_blocks_written = 0; m_blockchain_storage = _blockchain_storage; diff --git a/src/blockchain_utilities/blocksdat_file.h b/src/blockchain_utilities/blocksdat_file.h index 92bae9fcb..ed821cd6d 100644 --- a/src/blockchain_utilities/blocksdat_file.h +++ b/src/blockchain_utilities/blocksdat_file.h @@ -36,7 +36,6 @@ #include "cryptonote_core/cryptonote_basic.h" #include "cryptonote_core/cryptonote_boost_serialization.h" -#include "cryptonote_core/blockchain_storage.h" #include "cryptonote_core/blockchain.h" #include "blockchain_db/blockchain_db.h" #include "blockchain_db/lmdb/db_lmdb.h" @@ -60,21 +59,12 @@ class BlocksdatFile { public: -#if SOURCE_DB == DB_MEMORY - bool store_blockchain_raw(cryptonote::blockchain_storage* cs, cryptonote::tx_memory_pool* txp, - boost::filesystem::path& output_file, uint64_t use_block_height=0); -#else bool store_blockchain_raw(cryptonote::Blockchain* cs, cryptonote::tx_memory_pool* txp, boost::filesystem::path& output_file, uint64_t use_block_height=0); -#endif protected: -#if SOURCE_DB == DB_MEMORY - blockchain_storage* m_blockchain_storage; -#else Blockchain* m_blockchain_storage; -#endif std::ofstream * m_raw_data_file; diff --git a/src/blockchain_utilities/bootstrap_file.cpp b/src/blockchain_utilities/bootstrap_file.cpp index da3b44593..61bd35a6f 100644 --- a/src/blockchain_utilities/bootstrap_file.cpp +++ b/src/blockchain_utilities/bootstrap_file.cpp @@ -223,31 +223,9 @@ void BootstrapFile::write_block(block& block) { throw std::runtime_error("Aborting: tx == null_hash"); } -#if SOURCE_DB == DB_MEMORY - const transaction* tx = m_blockchain_storage->get_tx(tx_id); -#else transaction tx = m_blockchain_storage->get_db().get_tx(tx_id); -#endif -#if SOURCE_DB == DB_MEMORY - if(tx == NULL) - { - if (! m_tx_pool) - throw std::runtime_error("Aborting: tx == NULL, so memory pool required to get tx, but memory pool isn't enabled"); - else - { - transaction tx; - if(m_tx_pool->get_transaction(tx_id, tx)) - txs.push_back(tx); - else - throw std::runtime_error("Aborting: tx not found in pool"); - } - } - else - txs.push_back(*tx); -#else txs.push_back(tx); -#endif } // these non-coinbase txs will be serialized using this structure @@ -257,15 +235,9 @@ void BootstrapFile::write_block(block& block) bool include_extra_block_data = true; if (include_extra_block_data) { -#if SOURCE_DB == DB_MEMORY - size_t block_size = m_blockchain_storage->get_block_size(block_height); - difficulty_type cumulative_difficulty = m_blockchain_storage->get_block_cumulative_difficulty(block_height); - uint64_t coins_generated = m_blockchain_storage->get_block_coins_generated(block_height); -#else size_t block_size = m_blockchain_storage->get_db().get_block_size(block_height); difficulty_type cumulative_difficulty = m_blockchain_storage->get_db().get_block_cumulative_difficulty(block_height); uint64_t coins_generated = m_blockchain_storage->get_db().get_block_already_generated_coins(block_height); -#endif bp.block_size = block_size; bp.cumulative_difficulty = cumulative_difficulty; @@ -288,11 +260,7 @@ bool BootstrapFile::close() } -#if SOURCE_DB == DB_MEMORY -bool BootstrapFile::store_blockchain_raw(blockchain_storage* _blockchain_storage, tx_memory_pool* _tx_pool, boost::filesystem::path& output_file, uint64_t requested_block_stop) -#else bool BootstrapFile::store_blockchain_raw(Blockchain* _blockchain_storage, tx_memory_pool* _tx_pool, boost::filesystem::path& output_file, uint64_t requested_block_stop) -#endif { uint64_t num_blocks_written = 0; m_max_chunk = 0; diff --git a/src/blockchain_utilities/bootstrap_file.h b/src/blockchain_utilities/bootstrap_file.h index dd134f7ae..0bbe28f17 100644 --- a/src/blockchain_utilities/bootstrap_file.h +++ b/src/blockchain_utilities/bootstrap_file.h @@ -35,7 +35,6 @@ #include <boost/iostreams/filtering_streambuf.hpp> #include "cryptonote_core/cryptonote_basic.h" -#include "cryptonote_core/blockchain_storage.h" #include "cryptonote_core/blockchain.h" #include <algorithm> @@ -60,21 +59,12 @@ public: uint64_t count_blocks(const std::string& dir_path); uint64_t seek_to_first_chunk(std::ifstream& import_file); -#if SOURCE_DB == DB_MEMORY - bool store_blockchain_raw(cryptonote::blockchain_storage* cs, cryptonote::tx_memory_pool* txp, - boost::filesystem::path& output_file, uint64_t use_block_height=0); -#else bool store_blockchain_raw(cryptonote::Blockchain* cs, cryptonote::tx_memory_pool* txp, boost::filesystem::path& output_file, uint64_t use_block_height=0); -#endif protected: -#if SOURCE_DB == DB_MEMORY - blockchain_storage* m_blockchain_storage; -#else Blockchain* m_blockchain_storage; -#endif tx_memory_pool* m_tx_pool; typedef std::vector<char> buffer_type; diff --git a/src/blockchain_utilities/fake_core.h b/src/blockchain_utilities/fake_core.h index 2fb5031d3..ba1c2ed72 100644 --- a/src/blockchain_utilities/fake_core.h +++ b/src/blockchain_utilities/fake_core.h @@ -30,7 +30,7 @@ #include <boost/filesystem.hpp> #include "cryptonote_core/blockchain.h" // BlockchainDB -#include "cryptonote_core/blockchain_storage.h" // in-memory DB +#include "cryptonote_core/tx_pool.h" #include "blockchain_db/blockchain_db.h" #include "blockchain_db/lmdb/db_lmdb.h" #if defined(BERKELEY_DB) @@ -48,8 +48,6 @@ namespace } -#if !defined(BLOCKCHAIN_DB) || BLOCKCHAIN_DB == DB_LMDB - struct fake_core_db { Blockchain m_storage; @@ -59,12 +57,7 @@ struct fake_core_db bool support_add_block; // for multi_db_runtime: -#if !defined(BLOCKCHAIN_DB) - fake_core_db(const boost::filesystem::path &path, const bool use_testnet=false, const bool do_batch=true, const std::string& db_type="lmdb", const int db_flags=0) : m_pool(&m_storage), m_storage(m_pool) - // for multi_db_compile: -#else fake_core_db(const boost::filesystem::path &path, const bool use_testnet=false, const bool do_batch=true, const std::string& db_type="lmdb", const int db_flags=0) : m_pool(m_storage), m_storage(m_pool) -#endif { m_pool.init(path.string()); @@ -137,59 +130,4 @@ struct fake_core_db } }; -#endif - -#if !defined(BLOCKCHAIN_DB) || BLOCKCHAIN_DB == DB_MEMORY - -struct fake_core_memory -{ - blockchain_storage m_storage; - tx_memory_pool m_pool; - bool support_batch; - bool support_add_block; - - // for multi_db_runtime: -#if !defined(BLOCKCHAIN_DB) - fake_core_memory(const boost::filesystem::path &path, const bool use_testnet=false) : m_pool(&m_storage), m_storage(m_pool) -#else - // for multi_db_compile: - fake_core_memory(const boost::filesystem::path &path, const bool use_testnet=false) : m_pool(m_storage), m_storage(&m_pool) -#endif - { - m_pool.init(path.string()); - m_storage.init(path.string(), use_testnet); - support_batch = false; - support_add_block = false; - } - ~fake_core_memory() - { - LOG_PRINT_L3("fake_core_memory() destructor called - want to see it ripple down"); - m_storage.deinit(); - } - - uint64_t add_block(const block& blk - , const size_t& block_size - , const difficulty_type& cumulative_difficulty - , const uint64_t& coins_generated - , const std::vector<transaction>& txs - ) - { - // TODO: - // would need to refactor handle_block_to_main_chain() to have a direct add_block() method like Blockchain class - throw std::runtime_error("direct add_block() method not implemented for in-memory db"); - return 2; - } - - void batch_start(uint64_t batch_num_blocks = 0) - { - LOG_PRINT_L0("WARNING: [batch_start] opt_batch set, but this database doesn't support/need transactions - ignoring"); - } - - void batch_stop() - { - LOG_PRINT_L0("WARNING: [batch_stop] opt_batch set, but this database doesn't support/need transactions - ignoring"); - } - -}; -#endif diff --git a/src/common/json_util.h b/src/common/json_util.h index 6f8b4c18f..45046a4fb 100644 --- a/src/common/json_util.h +++ b/src/common/json_util.h @@ -28,8 +28,8 @@ #pragma once -#define GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, name, type, jtype, mandatory) \ - type field_##name; \ +#define GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, name, type, jtype, mandatory, def) \ + type field_##name = def; \ bool field_##name##_found = false; \ (void)field_##name##_found; \ do if (json.HasMember(#name)) \ diff --git a/src/crypto/crypto-ops.c b/src/crypto/crypto-ops.c index a9b659a6b..1b390e402 100644 --- a/src/crypto/crypto-ops.c +++ b/src/crypto/crypto-ops.c @@ -40,17 +40,15 @@ DISABLE_VS_WARNINGS(4146 4244) static void fe_mul(fe, const fe, const fe); static void fe_sq(fe, const fe); -static void fe_tobytes(unsigned char *, const fe); static void ge_madd(ge_p1p1 *, const ge_p3 *, const ge_precomp *); static void ge_msub(ge_p1p1 *, const ge_p3 *, const ge_precomp *); static void ge_p2_0(ge_p2 *); static void ge_p3_dbl(ge_p1p1 *, const ge_p3 *); -static void ge_sub(ge_p1p1 *, const ge_p3 *, const ge_cached *); static void fe_divpowm1(fe, const fe, const fe); /* Common functions */ -static uint64_t load_3(const unsigned char *in) { +uint64_t load_3(const unsigned char *in) { uint64_t result; result = (uint64_t) in[0]; result |= ((uint64_t) in[1]) << 8; @@ -58,7 +56,7 @@ static uint64_t load_3(const unsigned char *in) { return result; } -static uint64_t load_4(const unsigned char *in) +uint64_t load_4(const unsigned char *in) { uint64_t result; result = (uint64_t) in[0]; @@ -120,7 +118,7 @@ Postconditions: |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. */ -static void fe_add(fe h, const fe f, const fe g) { +void fe_add(fe h, const fe f, const fe g) { int32_t f0 = f[0]; int32_t f1 = f[1]; int32_t f2 = f[2]; @@ -258,7 +256,7 @@ static void fe_copy(fe h, const fe f) { /* From fe_invert.c */ -static void fe_invert(fe out, const fe z) { +void fe_invert(fe out, const fe z) { fe t0; fe t1; fe t2; @@ -1031,7 +1029,7 @@ Proof: so floor(2^(-255)(h + 19 2^(-25) h9 + 2^(-1))) = q. */ -static void fe_tobytes(unsigned char *s, const fe h) { +void fe_tobytes(unsigned char *s, const fe h) { int32_t h0 = h[0]; int32_t h1 = h[1]; int32_t h2 = h[2]; @@ -1591,7 +1589,7 @@ void ge_scalarmult_base(ge_p3 *h, const unsigned char *a) { r = p - q */ -static void ge_sub(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q) { +void ge_sub(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q) { fe t0; fe_add(r->X, p->Y, p->X); fe_sub(r->Y, p->Y, p->X); diff --git a/src/crypto/crypto-ops.h b/src/crypto/crypto-ops.h index cdc5ac1ee..4986499f4 100644 --- a/src/crypto/crypto-ops.h +++ b/src/crypto/crypto-ops.h @@ -143,3 +143,11 @@ void sc_sub(unsigned char *, const unsigned char *, const unsigned char *); void sc_mulsub(unsigned char *, const unsigned char *, const unsigned char *, const unsigned char *); int sc_check(const unsigned char *); int sc_isnonzero(const unsigned char *); /* Doesn't normalize */ + +// internal +uint64_t load_3(const unsigned char *in); +uint64_t load_4(const unsigned char *in); +void ge_sub(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q); +void fe_add(fe h, const fe f, const fe g); +void fe_tobytes(unsigned char *, const fe); +void fe_invert(fe out, const fe z); diff --git a/src/crypto/crypto.cpp b/src/crypto/crypto.cpp index f5e655274..250779ac3 100644 --- a/src/crypto/crypto.cpp +++ b/src/crypto/crypto.cpp @@ -151,7 +151,7 @@ namespace crypto { return true; } - static void derivation_to_scalar(const key_derivation &derivation, size_t output_index, ec_scalar &res) { + void crypto_ops::derivation_to_scalar(const key_derivation &derivation, size_t output_index, ec_scalar &res) { struct { key_derivation derivation; char output_index[(sizeof(size_t) * 8 + 6) / 7]; @@ -230,7 +230,7 @@ namespace crypto { buf.h = prefix_hash; buf.key = pub; if (ge_frombytes_vartime(&tmp3, &pub) != 0) { - abort(); + return false; } if (sc_check(&sig.c) != 0 || sc_check(&sig.r) != 0) { return false; @@ -364,7 +364,7 @@ POP_WARNINGS return false; } if (ge_frombytes_vartime(&tmp3, &*pubs[i]) != 0) { - abort(); + return false; } ge_double_scalarmult_base_vartime(&tmp2, &sig[i].c, &tmp3, &sig[i].r); ge_tobytes(&buf->ab[i].a, &tmp2); diff --git a/src/crypto/crypto.h b/src/crypto/crypto.h index fa55c2aab..b396fc7db 100644 --- a/src/crypto/crypto.h +++ b/src/crypto/crypto.h @@ -64,6 +64,22 @@ namespace crypto { friend class crypto_ops; }; + POD_CLASS public_keyV { + std::vector<public_key> keys; + int rows; + }; + + POD_CLASS secret_keyV { + std::vector<secret_key> keys; + int rows; + }; + + POD_CLASS public_keyM { + int cols; + int rows; + std::vector<secret_keyV> column_vectors; + }; + POD_CLASS key_derivation: ec_point { friend class crypto_ops; }; @@ -97,6 +113,8 @@ namespace crypto { friend bool secret_key_to_public_key(const secret_key &, public_key &); static bool generate_key_derivation(const public_key &, const secret_key &, key_derivation &); friend bool generate_key_derivation(const public_key &, const secret_key &, key_derivation &); + static void derivation_to_scalar(const key_derivation &derivation, size_t output_index, ec_scalar &res); + friend void derivation_to_scalar(const key_derivation &derivation, size_t output_index, ec_scalar &res); static bool derive_public_key(const key_derivation &, std::size_t, const public_key &, public_key &); friend bool derive_public_key(const key_derivation &, std::size_t, const public_key &, public_key &); static void derive_secret_key(const key_derivation &, std::size_t, const secret_key &, secret_key &); @@ -165,6 +183,9 @@ namespace crypto { const public_key &base, public_key &derived_key) { return crypto_ops::derive_public_key(derivation, output_index, base, derived_key); } + inline void derivation_to_scalar(const key_derivation &derivation, size_t output_index, ec_scalar &res) { + return crypto_ops::derivation_to_scalar(derivation, output_index, res); + } inline void derive_secret_key(const key_derivation &derivation, std::size_t output_index, const secret_key &base, secret_key &derived_key) { crypto_ops::derive_secret_key(derivation, output_index, base, derived_key); diff --git a/src/crypto/keccak.c b/src/crypto/keccak.c index 3ee2a887c..090d563a2 100644 --- a/src/crypto/keccak.c +++ b/src/crypto/keccak.c @@ -73,11 +73,11 @@ void keccakf(uint64_t st[25], int rounds) // compute a keccak hash (md) of given byte length from "in" typedef uint64_t state_t[25]; -int keccak(const uint8_t *in, int inlen, uint8_t *md, int mdlen) +int keccak(const uint8_t *in, size_t inlen, uint8_t *md, int mdlen) { state_t st; uint8_t temp[144]; - int i, rsiz, rsizw; + size_t i, rsiz, rsizw; rsiz = sizeof(state_t) == mdlen ? HASH_DATA_AREA : 200 - 2 * mdlen; rsizw = rsiz / 8; @@ -106,7 +106,7 @@ int keccak(const uint8_t *in, int inlen, uint8_t *md, int mdlen) return 0; } -void keccak1600(const uint8_t *in, int inlen, uint8_t *md) +void keccak1600(const uint8_t *in, size_t inlen, uint8_t *md) { keccak(in, inlen, md, sizeof(state_t)); } diff --git a/src/crypto/keccak.h b/src/crypto/keccak.h index 4f7f85729..fbd8e1904 100644 --- a/src/crypto/keccak.h +++ b/src/crypto/keccak.h @@ -16,11 +16,11 @@ #endif // compute a keccak hash (md) of given byte length from "in" -int keccak(const uint8_t *in, int inlen, uint8_t *md, int mdlen); +int keccak(const uint8_t *in, size_t inlen, uint8_t *md, int mdlen); // update the state void keccakf(uint64_t st[25], int norounds); -void keccak1600(const uint8_t *in, int inlen, uint8_t *md); +void keccak1600(const uint8_t *in, size_t inlen, uint8_t *md); #endif diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 1a093b501..50f61e346 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -41,7 +41,7 @@ #define CRYPTONOTE_MAX_TX_SIZE 1000000000 #define CRYPTONOTE_PUBLIC_ADDRESS_TEXTBLOB_VER 0 #define CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW 60 -#define CURRENT_TRANSACTION_VERSION 1 +#define CURRENT_TRANSACTION_VERSION 2 #define CURRENT_BLOCK_MAJOR_VERSION 1 #define CURRENT_BLOCK_MINOR_VERSION 0 #define CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT 60*60*2 diff --git a/src/cryptonote_core/CMakeLists.txt b/src/cryptonote_core/CMakeLists.txt index 205356797..4ebfd01e3 100644 --- a/src/cryptonote_core/CMakeLists.txt +++ b/src/cryptonote_core/CMakeLists.txt @@ -28,7 +28,6 @@ set(cryptonote_core_sources account.cpp - blockchain_storage.cpp blockchain.cpp checkpoints.cpp cryptonote_basic_impl.cpp @@ -44,7 +43,6 @@ set(cryptonote_core_headers) set(cryptonote_core_private_headers account.h account_boost_serialization.h - blockchain_storage.h blockchain_storage_boost_serialization.h blockchain.h checkpoints.h @@ -80,6 +78,7 @@ target_link_libraries(cryptonote_core crypto otshell_utils blockchain_db + ringct ${Boost_DATE_TIME_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_SERIALIZATION_LIBRARY} diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index cc6b48b6b..ef72dac59 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -51,6 +51,7 @@ #include "crypto/hash.h" #include "cryptonote_core/checkpoints.h" #include "cryptonote_core/cryptonote_core.h" +#include "ringct/rctSigs.h" #if defined(PER_BLOCK_CHECKPOINT) #include "blocks/blocks.h" #endif @@ -98,6 +99,10 @@ static const struct { // version 2 starts from block 624634, which is on or around the 23rd of November, 2015. Fork time finalised on 2015-11-20. No fork voting occurs for the v2 fork. { 2, 624634, 0, 1445355000 }, + + { 3, 800500, 0, 1472415034 }, + { 4, 801220, 0, 1472415035 }, + { 5, 802660, 0, 1472415036 }, }; static const uint64_t testnet_hard_fork_version_1_till = 624633; @@ -127,7 +132,7 @@ bool Blockchain::have_tx_keyimg_as_spent(const crypto::key_image &key_im) const // and collects the public key for each from the transaction it was included in // via the visitor passed to it. template <class visitor_t> -bool Blockchain::scan_outputkeys_for_indexes(const txin_to_key& tx_in_to_key, visitor_t &vis, const crypto::hash &tx_prefix_hash, uint64_t* pmax_related_block_height) const +bool Blockchain::scan_outputkeys_for_indexes(size_t tx_version, const txin_to_key& tx_in_to_key, visitor_t &vis, const crypto::hash &tx_prefix_hash, uint64_t* pmax_related_block_height) const { LOG_PRINT_L3("Blockchain::" << __func__); @@ -207,7 +212,7 @@ bool Blockchain::scan_outputkeys_for_indexes(const txin_to_key& tx_in_to_key, vi output_index = m_db->get_output_key(tx_in_to_key.amount, i); // call to the passed boost visitor to grab the public key for the output - if (!vis.handle_output(output_index.unlock_time, output_index.pubkey)) + if (!vis.handle_output(output_index.unlock_time, output_index.pubkey, output_index.commitment)) { LOG_PRINT_L0("Failed to handle_output for output no = " << count << ", with absolute offset " << i); return false; @@ -958,7 +963,7 @@ bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_bl money_in_use += o.amount; partial_block_reward = false; - if (version >= 3) { + if (version == 3) { for (auto &o: b.miner_tx.vout) { if (!is_valid_decomposed_amount(o.amount)) { LOG_PRINT_L1("miner tx output " << print_money(o.amount) << " is not a valid decomposed amount"); @@ -969,7 +974,7 @@ bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_bl std::vector<size_t> last_blocks_sizes; get_last_n_blocks_sizes(last_blocks_sizes, CRYPTONOTE_REWARD_BLOCKS_WINDOW); - if (!get_block_reward(epee::misc_utils::median(last_blocks_sizes), cumulative_block_size, already_generated_coins, base_reward, get_current_hard_fork_version())) + if (!get_block_reward(epee::misc_utils::median(last_blocks_sizes), cumulative_block_size, already_generated_coins, base_reward, version)) { LOG_PRINT_L1("block size " << cumulative_block_size << " is bigger than allowed for this blockchain"); return false; @@ -1086,14 +1091,24 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m { LOG_ERROR("Creating block template: error: invalid transaction size"); } - uint64_t inputs_amount; - if (!get_inputs_money_amount(cur_tx.tx, inputs_amount)) + if (cur_tx.tx.version == 1) { - LOG_ERROR("Creating block template: error: cannot get inputs amount"); + uint64_t inputs_amount; + if (!get_inputs_money_amount(cur_tx.tx, inputs_amount)) + { + LOG_ERROR("Creating block template: error: cannot get inputs amount"); + } + else if (cur_tx.fee != inputs_amount - get_outs_money_amount(cur_tx.tx)) + { + LOG_ERROR("Creating block template: error: invalid fee"); + } } - else if (cur_tx.fee != inputs_amount - get_outs_money_amount(cur_tx.tx)) + else { - LOG_ERROR("Creating block template: error: invalid fee"); + if (cur_tx.fee != cur_tx.tx.txnFee) + { + LOG_ERROR("Creating block template: error: invalid fee"); + } } } if (txs_size != real_txs_size) @@ -1117,7 +1132,9 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m block size, so first miner transaction generated with fake amount of money, and with phase we know think we know expected block size */ //make blocks coin-base tx looks close to real coinbase tx to get truthful blob size - bool r = construct_miner_tx(height, median_size, already_generated_coins, txs_size, fee, miner_address, b.miner_tx, ex_nonce, 11, m_hardfork->get_current_version()); + uint8_t hf_version = m_hardfork->get_current_version(); + size_t max_outs = hf_version >= 4 ? 1 : 11; + bool r = construct_miner_tx(height, median_size, already_generated_coins, txs_size, fee, miner_address, b.miner_tx, ex_nonce, max_outs, hf_version); CHECK_AND_ASSERT_MES(r, false, "Failed to construc miner tx, first chance"); size_t cumulative_size = txs_size + get_object_blobsize(b.miner_tx); #if defined(DEBUG_CREATE_BLOCK_TEMPLATE) @@ -1126,7 +1143,7 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m #endif for (size_t try_count = 0; try_count != 10; ++try_count) { - r = construct_miner_tx(height, median_size, already_generated_coins, cumulative_size, fee, miner_address, b.miner_tx, ex_nonce, 11, m_hardfork->get_current_version()); + r = construct_miner_tx(height, median_size, already_generated_coins, cumulative_size, fee, miner_address, b.miner_tx, ex_nonce, max_outs, hf_version); CHECK_AND_ASSERT_MES(r, false, "Failed to construc miner tx, second chance"); size_t coinbase_blob_size = get_object_blobsize(b.miner_tx); @@ -1597,6 +1614,139 @@ bool Blockchain::get_random_outs_for_amounts(const COMMAND_RPC_GET_RANDOM_OUTPUT return true; } //------------------------------------------------------------------ +// This function adds the ringct output at index i to the list +// unlocked and other such checks should be done by here. +void Blockchain::add_out_to_get_rct_random_outs(std::list<COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::out_entry>& outs, uint64_t amount, size_t i) const +{ + LOG_PRINT_L3("Blockchain::" << __func__); + CRITICAL_REGION_LOCAL(m_blockchain_lock); + + COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::out_entry& oen = *outs.insert(outs.end(), COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::out_entry()); + oen.amount = amount; + oen.global_amount_index = i; + output_data_t data = m_db->get_output_key(amount, i); + oen.out_key = data.pubkey; + oen.commitment = data.commitment; +} +//------------------------------------------------------------------ +// This function takes an RPC request for mixins and creates an RPC response +// with the requested mixins. +// TODO: figure out why this returns boolean / if we should be returning false +// in some cases +bool Blockchain::get_random_rct_outs(const COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::request& req, COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::response& res) const +{ + LOG_PRINT_L3("Blockchain::" << __func__); + CRITICAL_REGION_LOCAL(m_blockchain_lock); + + // for each amount that we need to get mixins for, get <n> random outputs + // from BlockchainDB where <n> is req.outs_count (number of mixins). + auto num_outs = m_db->get_num_outputs(0); + // ensure we don't include outputs that aren't yet eligible to be used + // outpouts are sorted by height + while (num_outs > 0) + { + const tx_out_index toi = m_db->get_output_tx_and_index(0, num_outs - 1); + const uint64_t height = m_db->get_tx_block_height(toi.first); + if (height + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE <= m_db->height()) + break; + --num_outs; + } + + std::unordered_set<uint64_t> seen_indices; + + // if there aren't enough outputs to mix with (or just enough), + // use all of them. Eventually this should become impossible. + if (num_outs <= req.outs_count) + { + for (uint64_t i = 0; i < num_outs; i++) + { + // get tx_hash, tx_out_index from DB + tx_out_index toi = m_db->get_output_tx_and_index(0, i); + + // if tx is unlocked, add output to result_outs + if (is_tx_spendtime_unlocked(m_db->get_tx_unlock_time(toi.first))) + { + add_out_to_get_rct_random_outs(res.outs, 0, i); + } + } + } + else + { + // while we still need more mixins + while (res.outs.size() < req.outs_count) + { + // if we've gone through every possible output, we've gotten all we can + if (seen_indices.size() == num_outs) + { + break; + } + + // get a random output index from the DB. If we've already seen it, + // return to the top of the loop and try again, otherwise add it to the + // list of output indices we've seen. + + // triangular distribution over [a,b) with a=0, mode c=b=up_index_limit + uint64_t r = crypto::rand<uint64_t>() % ((uint64_t)1 << 53); + double frac = std::sqrt((double)r / ((uint64_t)1 << 53)); + uint64_t i = (uint64_t)(frac*num_outs); + // just in case rounding up to 1 occurs after sqrt + if (i == num_outs) + --i; + + if (seen_indices.count(i)) + { + continue; + } + seen_indices.emplace(i); + + // get tx_hash, tx_out_index from DB + tx_out_index toi = m_db->get_output_tx_and_index(0, i); + + // if the output's transaction is unlocked, add the output's index to + // our list. + if (is_tx_spendtime_unlocked(m_db->get_tx_unlock_time(toi.first))) + { + add_out_to_get_rct_random_outs(res.outs, 0, i); + } + } + } + + if (res.outs.size() < req.outs_count) + return false; +#if 0 + // if we do not have enough RCT inputs, we can pick from the non RCT ones + // which will have a zero mask + if (res.outs.size() < req.outs_count) + { + LOG_PRINT_L0("Out of RCT inputs (" << res.outs.size() << "/" << req.outs_count << "), using regular ones"); + + // TODO: arbitrary selection, needs better + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request req2 = AUTO_VAL_INIT(req2); + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response res2 = AUTO_VAL_INIT(res2); + req2.outs_count = req.outs_count - res.outs.size(); + static const uint64_t amounts[] = {1, 10, 20, 50, 100, 200, 500, 1000, 10000}; + for (uint64_t a: amounts) + req2.amounts.push_back(a); + if (!get_random_outs_for_amounts(req2, res2)) + return false; + + // pick random ones from there + while (res.outs.size() < req.outs_count) + { + int list_idx = rand() % (sizeof(amounts)/sizeof(amounts[0])); + if (!res2.outs[list_idx].outs.empty()) + { + const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry oe = res2.outs[list_idx].outs.back(); + res2.outs[list_idx].outs.pop_back(); + add_out_to_get_rct_random_outs(res.outs, res2.outs[list_idx].amount, oe.global_amount_index); + } + } + } +#endif + + return true; +} +//------------------------------------------------------------------ bool Blockchain::get_outs(const COMMAND_RPC_GET_OUTPUTS::request& req, COMMAND_RPC_GET_OUTPUTS::response& res) const { LOG_PRINT_L3("Blockchain::" << __func__); @@ -1607,11 +1757,11 @@ bool Blockchain::get_outs(const COMMAND_RPC_GET_OUTPUTS::request& req, COMMAND_R for (const auto &i: req.outputs) { // get tx_hash, tx_out_index from DB - crypto::public_key key = m_db->get_output_key(i.amount, i.index).pubkey; + const output_data_t &od = m_db->get_output_key(i.amount, i.index); tx_out_index toi = m_db->get_output_tx_and_index(i.amount, i.index); bool unlocked = is_tx_spendtime_unlocked(m_db->get_tx_unlock_time(toi.first)); - res.outs.push_back({key, unlocked}); + res.outs.push_back({od.pubkey, od.commitment, unlocked}); } return true; } @@ -2012,7 +2162,7 @@ bool Blockchain::get_tx_outputs_gindexs(const crypto::hash& tx_id, std::vector<u // This function overloads its sister function with // an extra value (hash of highest block that holds an output used as input) // as a return-by-reference. -bool Blockchain::check_tx_inputs(const transaction& tx, uint64_t& max_used_block_height, crypto::hash& max_used_block_id, tx_verification_context &tvc, bool kept_by_block) +bool Blockchain::check_tx_inputs(transaction& tx, uint64_t& max_used_block_height, crypto::hash& max_used_block_id, tx_verification_context &tvc, bool kept_by_block) { LOG_PRINT_L3("Blockchain::" << __func__); CRITICAL_REGION_LOCAL(m_blockchain_lock); @@ -2055,9 +2205,24 @@ bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context // from hard fork 2, we forbid dust and compound outputs if (m_hardfork->get_current_version() >= 2) { for (auto &o: tx.vout) { - if (!is_valid_decomposed_amount(o.amount)) { - tvc.m_invalid_output = true; - return false; + if (tx.version == 1) + { + if (!is_valid_decomposed_amount(o.amount)) { + tvc.m_invalid_output = true; + return false; + } + } + } + } + + // in a v2 tx, all outputs must have 0 amount + if (m_hardfork->get_current_version() >= 3) { + if (tx.version >= 2) { + for (auto &o: tx.vout) { + if (o.amount != 0) { + tvc.m_invalid_output = true; + return false; + } } } } @@ -2076,13 +2241,83 @@ bool Blockchain::have_tx_keyimges_as_spent(const transaction &tx) const } return false; } +bool Blockchain::expand_transaction_2(transaction &tx, const crypto::hash &tx_prefix_hash, const std::vector<std::vector<rct::ctkey>> &pubkeys) +{ + CHECK_AND_ASSERT_MES(tx.version == 2, false, "Transaction version is not 2"); + + rct::rctSig &rv = tx.rct_signatures; + + // message - hash of the transaction prefix + rv.message = rct::hash2rct(tx_prefix_hash); + + // mixRing - full and simple store it in opposite ways + if (rv.type == rct::RCTTypeFull) + { + rv.mixRing.resize(pubkeys[0].size()); + for (size_t m = 0; m < pubkeys[0].size(); ++m) + rv.mixRing[m].clear(); + for (size_t n = 0; n < pubkeys.size(); ++n) + { + CHECK_AND_ASSERT_MES(pubkeys[n].size() <= pubkeys[0].size(), false, "More inputs that first ring"); + for (size_t m = 0; m < pubkeys[n].size(); ++m) + { + rv.mixRing[m].push_back(pubkeys[n][m]); + } + } + } + else if (rv.type == rct::RCTTypeSimple) + { + rv.mixRing.resize(pubkeys.size()); + for (size_t n = 0; n < pubkeys.size(); ++n) + { + rv.mixRing[n].clear(); + for (size_t m = 0; m < pubkeys[n].size(); ++m) + { + rv.mixRing[n].push_back(pubkeys[n][m]); + } + } + } + else + { + CHECK_AND_ASSERT_MES(false, false, "Unsupported rct tx type: " + boost::lexical_cast<std::string>(rv.type)); + } + + // II + if (rv.type == rct::RCTTypeFull) + { + rv.p.MGs.resize(1); + rv.p.MGs[0].II.resize(tx.vin.size()); + for (size_t n = 0; n < tx.vin.size(); ++n) + rv.p.MGs[0].II[n] = rct::ki2rct(boost::get<txin_to_key>(tx.vin[n]).k_image); + } + else if (rv.type == rct::RCTTypeSimple) + { + CHECK_AND_ASSERT_MES(rv.p.MGs.size() == tx.vin.size(), false, "Bad MGs size"); + for (size_t n = 0; n < tx.vin.size(); ++n) + { + rv.p.MGs[n].II.resize(1); + rv.p.MGs[n].II[0] = rct::ki2rct(boost::get<txin_to_key>(tx.vin[n]).k_image); + } + } + else + { + CHECK_AND_ASSERT_MES(false, false, "Unsupported rct tx type: " + boost::lexical_cast<std::string>(rv.type)); + } + + // outPk + CHECK_AND_ASSERT_MES(rv.outPk.size() == tx.vout.size(), false, "Bad outPk size"); + for (size_t n = 0; n < tx.rct_signatures.outPk.size(); ++n) + rv.outPk[n].dest = rct::pk2rct(boost::get<txout_to_key>(tx.vout[n].target).key); + + return true; +} //------------------------------------------------------------------ // This function validates transaction inputs and their keys. // FIXME: consider moving functionality specific to one input into // check_tx_input() rather than here, and use this function simply // to iterate the inputs as necessary (splitting the task // using threads, etc.) -bool Blockchain::check_tx_inputs(const transaction& tx, tx_verification_context &tvc, uint64_t* pmax_used_block_height) +bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, uint64_t* pmax_used_block_height) { LOG_PRINT_L3("Blockchain::" << __func__); size_t sig_index = 0; @@ -2091,30 +2326,44 @@ bool Blockchain::check_tx_inputs(const transaction& tx, tx_verification_context crypto::hash tx_prefix_hash = get_transaction_prefix_hash(tx); + const uint8_t hf_version = m_hardfork->get_current_version(); + // from hard fork 2, we require mixin at least 2 unless one output cannot mix with 2 others // if one output cannot mix with 2 others, we accept at most 1 output that can mix - if (m_hardfork->get_current_version() >= 2) + if (hf_version >= 2) { size_t n_unmixable = 0, n_mixable = 0; size_t mixin = std::numeric_limits<size_t>::max(); + const size_t min_mixin = hf_version >= 5 ? 4 : 2; for (const auto& txin : tx.vin) { // non txin_to_key inputs will be rejected below if (txin.type() == typeid(txin_to_key)) { const txin_to_key& in_to_key = boost::get<txin_to_key>(txin); - uint64_t n_outputs = m_db->get_num_outputs(in_to_key.amount); - LOG_PRINT_L2("output size " << print_money(in_to_key.amount) << ": " << n_outputs << " available"); - // n_outputs includes the output we're considering - if (n_outputs <= 2) - ++n_unmixable; - else + if (in_to_key.amount == 0) + { + // always consider rct inputs mixable. Even if there's not enough rct + // inputs on the chain to mix with, this is going to be the case for + // just a few blocks right after the fork at most ++n_mixable; + } + else + { + uint64_t n_outputs = m_db->get_num_outputs(in_to_key.amount); + LOG_PRINT_L2("output size " << print_money(in_to_key.amount) << ": " << n_outputs << " available"); + // n_outputs includes the output we're considering + if (n_outputs <= min_mixin) + ++n_unmixable; + else + ++n_mixable; + } if (in_to_key.key_offsets.size() - 1 < mixin) mixin = in_to_key.key_offsets.size() - 1; } } - if (mixin < 2) + + if (mixin < min_mixin) { if (n_unmixable == 0) { @@ -2129,6 +2378,22 @@ bool Blockchain::check_tx_inputs(const transaction& tx, tx_verification_context return false; } } + + // min/max tx version based on HF, and we accept v1 txes if having a non mixable + const size_t max_tx_version = (hf_version <= 3) ? 1 : 2; + if (tx.version > max_tx_version) + { + LOG_PRINT_L1("transaction version " << (unsigned)tx.version << " is higher than max accepted version " << max_tx_version); + tvc.m_verifivation_failed = true; + return false; + } + const size_t min_tx_version = (n_unmixable > 0 ? 1 : (hf_version >= 5) ? 2 : 1); + if (tx.version < min_tx_version) + { + LOG_PRINT_L1("transaction version " << (unsigned)tx.version << " is lower than min accepted version " << min_tx_version); + tvc.m_verifivation_failed = true; + return false; + } } auto it = m_check_txin_table.find(tx_prefix_hash); @@ -2140,7 +2405,7 @@ bool Blockchain::check_tx_inputs(const transaction& tx, tx_verification_context } uint64_t t_t1 = 0; - std::vector<std::vector<crypto::public_key>> pubkeys(tx.vin.size()); + std::vector<std::vector<rct::ctkey>> pubkeys(tx.vin.size()); std::vector < uint64_t > results; results.resize(tx.vin.size(), 0); @@ -2189,28 +2454,31 @@ bool Blockchain::check_tx_inputs(const transaction& tx, tx_verification_context return false; } - // basically, make sure number of inputs == number of signatures - CHECK_AND_ASSERT_MES(sig_index < tx.signatures.size(), false, "wrong transaction: not signature entry for input with index= " << sig_index); + if (tx.version == 1) + { + // basically, make sure number of inputs == number of signatures + CHECK_AND_ASSERT_MES(sig_index < tx.signatures.size(), false, "wrong transaction: not signature entry for input with index= " << sig_index); #if defined(CACHE_VIN_RESULTS) - auto itk = it->second.find(in_to_key.k_image); - if(itk != it->second.end()) - { - if(!itk->second) + auto itk = it->second.find(in_to_key.k_image); + if(itk != it->second.end()) { - LOG_PRINT_L1("Failed ring signature for tx " << get_transaction_hash(tx) << " vin key with k_image: " << in_to_key.k_image << " sig_index: " << sig_index); - return false; - } + if(!itk->second) + { + LOG_PRINT_L1("Failed ring signature for tx " << get_transaction_hash(tx) << " vin key with k_image: " << in_to_key.k_image << " sig_index: " << sig_index); + return false; + } - // txin has been verified already, skip - sig_index++; - continue; - } + // txin has been verified already, skip + sig_index++; + continue; + } #endif + } // make sure that output being spent matches up correctly with the // signature spending it. - if (!check_tx_input(in_to_key, tx_prefix_hash, tx.signatures[sig_index], pubkeys[sig_index], pmax_used_block_height)) + if (!check_tx_input(tx.version, in_to_key, tx_prefix_hash, tx.version == 1 ? tx.signatures[sig_index] : std::vector<crypto::signature>(), tx.rct_signatures, pubkeys[sig_index], pmax_used_block_height)) { it->second[in_to_key.k_image] = false; LOG_PRINT_L1("Failed to check ring signature for tx " << get_transaction_hash(tx) << " vin key with k_image: " << in_to_key.k_image << " sig_index: " << sig_index); @@ -2222,28 +2490,31 @@ bool Blockchain::check_tx_inputs(const transaction& tx, tx_verification_context return false; } - if (threads > 1) + if (tx.version == 1) { - // ND: Speedup - // 1. Thread ring signature verification if possible. - ioservice.dispatch(boost::bind(&Blockchain::check_ring_signature, this, std::cref(tx_prefix_hash), std::cref(in_to_key.k_image), std::cref(pubkeys[sig_index]), std::cref(tx.signatures[sig_index]), std::ref(results[sig_index]))); - } - else - { - check_ring_signature(tx_prefix_hash, in_to_key.k_image, pubkeys[sig_index], tx.signatures[sig_index], results[sig_index]); - if (!results[sig_index]) + if (threads > 1) { - it->second[in_to_key.k_image] = false; - LOG_PRINT_L1("Failed to check ring signature for tx " << get_transaction_hash(tx) << " vin key with k_image: " << in_to_key.k_image << " sig_index: " << sig_index); - - if (pmax_used_block_height) // a default value of NULL is used when called from Blockchain::handle_block_to_main_chain() + // ND: Speedup + // 1. Thread ring signature verification if possible. + ioservice.dispatch(boost::bind(&Blockchain::check_ring_signature, this, std::cref(tx_prefix_hash), std::cref(in_to_key.k_image), std::cref(pubkeys[sig_index]), std::cref(tx.signatures[sig_index]), std::ref(results[sig_index]))); + } + else + { + check_ring_signature(tx_prefix_hash, in_to_key.k_image, pubkeys[sig_index], tx.signatures[sig_index], results[sig_index]); + if (!results[sig_index]) { - LOG_PRINT_L1("*pmax_used_block_height: " << *pmax_used_block_height); - } + it->second[in_to_key.k_image] = false; + LOG_PRINT_L1("Failed to check ring signature for tx " << get_transaction_hash(tx) << " vin key with k_image: " << in_to_key.k_image << " sig_index: " << sig_index); - return false; + if (pmax_used_block_height) // a default value of NULL is used when called from Blockchain::handle_block_to_main_chain() + { + LOG_PRINT_L1("*pmax_used_block_height: " << *pmax_used_block_height); + } + + return false; + } + it->second[in_to_key.k_image] = true; } - it->second[in_to_key.k_image] = true; } sig_index++; @@ -2251,30 +2522,170 @@ bool Blockchain::check_tx_inputs(const transaction& tx, tx_verification_context KILL_IOSERVICE(); - if (threads > 1) + if (tx.version == 1) + { + if (threads > 1) + { + // save results to table, passed or otherwise + bool failed = false; + for (size_t i = 0; i < tx.vin.size(); i++) + { + const txin_to_key& in_to_key = boost::get<txin_to_key>(tx.vin[i]); + it->second[in_to_key.k_image] = results[i]; + if(!failed && !results[i]) + failed = true; + } + + if (failed) + { + LOG_PRINT_L1("Failed to check ring signatures!, t_loop: " << t_t1); + return false; + } + } + } + else { - // save results to table, passed or otherwise - bool failed = false; - for (size_t i = 0; i < tx.vin.size(); i++) + if (!expand_transaction_2(tx, tx_prefix_hash, pubkeys)) { - const txin_to_key& in_to_key = boost::get<txin_to_key>(tx.vin[i]); - it->second[in_to_key.k_image] = results[i]; - if(!failed && !results[i]) - failed = true; + LOG_PRINT_L1("Failed to expand rct signatures!"); + return false; } - if (failed) + // from version 2, check ringct signatures + // obviously, the original and simple rct APIs use a mixRing that's indexes + // in opposite orders, because it'd be too simple otherwise... + const rct::rctSig &rv = tx.rct_signatures; + switch (rv.type) { - LOG_PRINT_L1("Failed to check ring signatures!, t_loop: " << t_t1); + case rct::RCTTypeNull: { + // we only accept no signatures for coinbase txes + LOG_PRINT_L1("Null rct signature on non-coinbase tx"); + return false; + } + case rct::RCTTypeSimple: { + // check all this, either recontructed (so should really pass), or not + { + if (pubkeys.size() != rv.mixRing.size()) + { + LOG_PRINT_L1("Failed to check ringct signatures: mismatched pubkeys/mixRing size"); + return false; + } + for (size_t i = 0; i < pubkeys.size(); ++i) + { + if (pubkeys[i].size() != rv.mixRing[i].size()) + { + LOG_PRINT_L1("Failed to check ringct signatures: mismatched pubkeys/mixRing size"); + return false; + } + } + + for (size_t n = 0; n < pubkeys.size(); ++n) + { + for (size_t m = 0; m < pubkeys[n].size(); ++m) + { + if (pubkeys[n][m].dest != rct::rct2pk(rv.mixRing[n][m].dest)) + { + LOG_PRINT_L1("Failed to check ringct signatures: mismatched pubkey at vin " << n << ", index " << m); + return false; + } + if (pubkeys[n][m].mask != rct::rct2pk(rv.mixRing[n][m].mask)) + { + LOG_PRINT_L1("Failed to check ringct signatures: mismatched commitment at vin " << n << ", index " << m); + return false; + } + } + } + } + + if (rv.p.MGs.size() != tx.vin.size()) + { + LOG_PRINT_L1("Failed to check ringct signatures: mismatched MGs/vin sizes"); + return false; + } + for (size_t n = 0; n < tx.vin.size(); ++n) + { + if (memcmp(&boost::get<txin_to_key>(tx.vin[n]).k_image, &rv.p.MGs[n].II[0], 32)) + { + LOG_PRINT_L1("Failed to check ringct signatures: mismatched key image"); + return false; + } + } + + if (!rct::verRctSimple(rv)) + { + LOG_PRINT_L1("Failed to check ringct signatures!"); + return false; + } + break; + } + case rct::RCTTypeFull: { + // check all this, either recontructed (so should really pass), or not + { + bool size_matches = true; + for (size_t i = 0; i < pubkeys.size(); ++i) + size_matches &= pubkeys[i].size() == rv.mixRing.size(); + for (size_t i = 0; i < rv.mixRing.size(); ++i) + size_matches &= pubkeys.size() == rv.mixRing[i].size(); + if (!size_matches) + { + LOG_PRINT_L1("Failed to check ringct signatures: mismatched pubkeys/mixRing size"); + return false; + } + + for (size_t n = 0; n < pubkeys.size(); ++n) + { + for (size_t m = 0; m < pubkeys[n].size(); ++m) + { + if (pubkeys[n][m].dest != rct::rct2pk(rv.mixRing[m][n].dest)) + { + LOG_PRINT_L1("Failed to check ringct signatures: mismatched pubkey at vin " << n << ", index " << m); + return false; + } + if (pubkeys[n][m].mask != rct::rct2pk(rv.mixRing[m][n].mask)) + { + LOG_PRINT_L1("Failed to check ringct signatures: mismatched commitment at vin " << n << ", index " << m); + return false; + } + } + } + } + + if (rv.p.MGs.size() != 1) + { + LOG_PRINT_L1("Failed to check ringct signatures: Bad MGs size"); + return false; + } + if (rv.p.MGs[0].II.size() != tx.vin.size()) + { + LOG_PRINT_L1("Failed to check ringct signatures: mismatched II/vin sizes"); + return false; + } + for (size_t n = 0; n < tx.vin.size(); ++n) + { + if (memcmp(&boost::get<txin_to_key>(tx.vin[n]).k_image, &rv.p.MGs[0].II[n], 32)) + { + LOG_PRINT_L1("Failed to check ringct signatures: mismatched II/vin sizes"); + return false; + } + } + + if (!rct::verRct(rv)) + { + LOG_PRINT_L1("Failed to check ringct signatures!"); + return false; + } + break; + } + default: + LOG_PRINT_L1("Unsupported rct type: " << rv.type); return false; } } - LOG_PRINT_L1("t_loop: " << t_t1); return true; } //------------------------------------------------------------------ -void Blockchain::check_ring_signature(const crypto::hash &tx_prefix_hash, const crypto::key_image &key_image, const std::vector<crypto::public_key> &pubkeys, const std::vector<crypto::signature>& sig, uint64_t &result) +void Blockchain::check_ring_signature(const crypto::hash &tx_prefix_hash, const crypto::key_image &key_image, const std::vector<rct::ctkey> &pubkeys, const std::vector<crypto::signature>& sig, uint64_t &result) { if (m_is_in_checkpoint_zone) { @@ -2285,7 +2696,8 @@ void Blockchain::check_ring_signature(const crypto::hash &tx_prefix_hash, const std::vector<const crypto::public_key *> p_output_keys; for (auto &key : pubkeys) { - p_output_keys.push_back(&key); + // rct::key and crypto::public_key have the same structure, avoid object ctor/memcpy + p_output_keys.push_back(&(const crypto::public_key&)key.dest); } result = crypto::check_ring_signature(tx_prefix_hash, key_image, p_output_keys, sig.data()) ? 1 : 0; @@ -2321,7 +2733,7 @@ bool Blockchain::is_tx_spendtime_unlocked(uint64_t unlock_time) const // This function locates all outputs associated with a given input (mixins) // and validates that they exist and are usable. It also checks the ring // signature for each input. -bool Blockchain::check_tx_input(const txin_to_key& txin, const crypto::hash& tx_prefix_hash, const std::vector<crypto::signature>& sig, std::vector<crypto::public_key> &output_keys, uint64_t* pmax_related_block_height) +bool Blockchain::check_tx_input(size_t tx_version, const txin_to_key& txin, const crypto::hash& tx_prefix_hash, const std::vector<crypto::signature>& sig, const rct::rctSig &rct_signatures, std::vector<rct::ctkey> &output_keys, uint64_t* pmax_related_block_height) { LOG_PRINT_L3("Blockchain::" << __func__); @@ -2331,13 +2743,13 @@ bool Blockchain::check_tx_input(const txin_to_key& txin, const crypto::hash& tx_ struct outputs_visitor { - std::vector<crypto::public_key >& m_output_keys; + std::vector<rct::ctkey >& m_output_keys; const Blockchain& m_bch; - outputs_visitor(std::vector<crypto::public_key>& output_keys, const Blockchain& bch) : + outputs_visitor(std::vector<rct::ctkey>& output_keys, const Blockchain& bch) : m_output_keys(output_keys), m_bch(bch) { } - bool handle_output(uint64_t unlock_time, const crypto::public_key &pubkey) + bool handle_output(uint64_t unlock_time, const crypto::public_key &pubkey, const rct::key &commitment) { //check tx unlock time if (!m_bch.is_tx_spendtime_unlocked(unlock_time)) @@ -2351,7 +2763,7 @@ bool Blockchain::check_tx_input(const txin_to_key& txin, const crypto::hash& tx_ // but only txout_to_key outputs are stored in the DB in the first place, done in // Blockchain*::add_output - m_output_keys.push_back(pubkey); + m_output_keys.push_back(rct::ctkey({rct::pk2rct(pubkey), commitment})); return true; } }; @@ -2360,7 +2772,7 @@ bool Blockchain::check_tx_input(const txin_to_key& txin, const crypto::hash& tx_ // collect output keys outputs_visitor vi(output_keys, *this); - if (!scan_outputkeys_for_indexes(txin, vi, tx_prefix_hash, pmax_related_block_height)) + if (!scan_outputkeys_for_indexes(tx_version, txin, vi, tx_prefix_hash, pmax_related_block_height)) { LOG_PRINT_L1("Failed to get output keys for tx with amount = " << print_money(txin.amount) << " and count indexes " << txin.key_offsets.size()); return false; @@ -2371,7 +2783,10 @@ bool Blockchain::check_tx_input(const txin_to_key& txin, const crypto::hash& tx_ LOG_PRINT_L1("Output keys for tx with amount = " << txin.amount << " and count indexes " << txin.key_offsets.size() << " returned wrong keys count " << output_keys.size()); return false; } - CHECK_AND_ASSERT_MES(sig.size() == output_keys.size(), false, "internal error: tx signatures count=" << sig.size() << " mismatch with outputs keys count for inputs=" << output_keys.size()); + if (tx_version == 1) { + CHECK_AND_ASSERT_MES(sig.size() == output_keys.size(), false, "internal error: tx signatures count=" << sig.size() << " mismatch with outputs keys count for inputs=" << output_keys.size()); + } + // rct_signatures will be expanded after this return true; } //------------------------------------------------------------------ @@ -2453,6 +2868,8 @@ void Blockchain::return_tx_to_pool(const std::vector<transaction> &txs) //------------------------------------------------------------------ bool Blockchain::flush_txes_from_pool(const std::list<crypto::hash> &txids) { + CRITICAL_REGION_LOCAL(m_tx_pool); + bool res = true; for (const auto &txid: txids) { @@ -2461,7 +2878,7 @@ bool Blockchain::flush_txes_from_pool(const std::list<crypto::hash> &txids) uint64_t fee; bool relayed; LOG_PRINT_L1("Removing txid " << txid << " from the pool"); - if(!m_tx_pool.take_tx(txid, tx, blob_size, fee, relayed)) + if(m_tx_pool.have_tx(txid) && !m_tx_pool.take_tx(txid, tx, blob_size, fee, relayed)) { LOG_PRINT_L0("Failed to remove txid " << txid << " from the pool"); res = false; diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 1524693b8..94701608e 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -455,6 +455,23 @@ namespace cryptonote bool get_outs(const COMMAND_RPC_GET_OUTPUTS::request& req, COMMAND_RPC_GET_OUTPUTS::response& res) const; /** + * @brief gets random ringct outputs to mix with + * + * This function takes an RPC request for outputs to mix with + * and creates an RPC response with the resultant output indices + * and the matching keys. + * + * Outputs to mix with are randomly selected from the utxo set + * for each output amount in the request. + * + * @param req the output amounts and number of mixins to select + * @param res return-by-reference the resultant output indices + * + * @return true + */ + bool get_random_rct_outs(const COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::request& req, COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::response& res) const; + + /** * @brief gets the global indices for outputs from a given transaction * * This function gets the global indices for all outputs belonging @@ -483,6 +500,7 @@ namespace cryptonote * validates a transaction's inputs as correctly used and not previously * spent. also returns the hash and height of the most recent block * which contains an output that was used as an input to the transaction. + * The transaction's rct signatures, if any, are expanded. * * @param tx the transaction to validate * @param pmax_used_block_height return-by-reference block height of most recent input @@ -492,7 +510,7 @@ namespace cryptonote * * @return false if any input is invalid, otherwise true */ - bool check_tx_inputs(const transaction& tx, uint64_t& pmax_used_block_height, crypto::hash& max_used_block_id, tx_verification_context &tvc, bool kept_by_block = false); + bool check_tx_inputs(transaction& tx, uint64_t& pmax_used_block_height, crypto::hash& max_used_block_id, tx_verification_context &tvc, bool kept_by_block = false); /** * @brief check that a transaction's outputs conform to current standards @@ -867,11 +885,12 @@ namespace cryptonote * @param vis an instance of the visitor to use * @param tx_prefix_hash the hash of the associated transaction_prefix * @param pmax_related_block_height return-by-pointer the height of the most recent block in the input set + * @param tx_version version of the tx, if > 1 we also get commitments * * @return false if any keys are not found or any inputs are not unlocked, otherwise true */ template<class visitor_t> - inline bool scan_outputkeys_for_indexes(const txin_to_key& tx_in_to_key, visitor_t &vis, const crypto::hash &tx_prefix_hash, uint64_t* pmax_related_block_height = NULL) const; + inline bool scan_outputkeys_for_indexes(size_t tx_version, const txin_to_key& tx_in_to_key, visitor_t &vis, const crypto::hash &tx_prefix_hash, uint64_t* pmax_related_block_height = NULL) const; /** * @brief collect output public keys of a transaction input set @@ -883,15 +902,17 @@ namespace cryptonote * If pmax_related_block_height is not NULL, its value is set to the height * of the most recent block which contains an output used in the input set * + * @param tx_version the transaction version * @param txin the transaction input * @param tx_prefix_hash the transaction prefix hash, for caching organization * @param sig the input signature * @param output_keys return-by-reference the public keys of the outputs in the input set + * @param rct_signatures the ringCT signatures, which are only valid if tx version > 1 * @param pmax_related_block_height return-by-pointer the height of the most recent block in the input set * * @return false if any output is not yet unlocked, or is missing, otherwise true */ - bool check_tx_input(const txin_to_key& txin, const crypto::hash& tx_prefix_hash, const std::vector<crypto::signature>& sig, std::vector<crypto::public_key> &output_keys, uint64_t* pmax_related_block_height); + bool check_tx_input(size_t tx_version,const txin_to_key& txin, const crypto::hash& tx_prefix_hash, const std::vector<crypto::signature>& sig, const rct::rctSig &rct_signatures, std::vector<rct::ctkey> &output_keys, uint64_t* pmax_related_block_height); /** * @brief validate a transaction's inputs and their keys @@ -899,6 +920,7 @@ namespace cryptonote * This function validates transaction inputs and their keys. Previously * it also performed double spend checking, but that has been moved to its * own function. + * The transaction's rct signatures, if any, are expanded. * * If pmax_related_block_height is not NULL, its value is set to the height * of the most recent block which contains an output used in any input set @@ -912,7 +934,7 @@ namespace cryptonote * * @return false if any validation step fails, otherwise true */ - bool check_tx_inputs(const transaction& tx, tx_verification_context &tvc, uint64_t* pmax_used_block_height = NULL); + bool check_tx_inputs(transaction& tx, tx_verification_context &tvc, uint64_t* pmax_used_block_height = NULL); /** * @brief performs a blockchain reorganization according to the longest chain rule @@ -1054,6 +1076,15 @@ namespace cryptonote void add_out_to_get_random_outs(COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& result_outs, uint64_t amount, size_t i) const; /** + * @brief adds the given output to the requested set of random ringct outputs + * + * @param outs return-by-reference the set the output is to be added to + * @param amount the output amount (0 for rct inputs) + * @param i the rct output index + */ + void add_out_to_get_rct_random_outs(std::list<COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::out_entry>& outs, uint64_t amount, size_t i) const; + + /** * @brief checks if a transaction is unlocked (its outputs spendable) * * This function checks to see if a transaction is unlocked. @@ -1172,7 +1203,7 @@ namespace cryptonote * @param result false if the ring signature is invalid, otherwise true */ void check_ring_signature(const crypto::hash &tx_prefix_hash, const crypto::key_image &key_image, - const std::vector<crypto::public_key> &pubkeys, const std::vector<crypto::signature> &sig, uint64_t &result); + const std::vector<rct::ctkey> &pubkeys, const std::vector<crypto::signature> &sig, uint64_t &result); /** * @brief loads block hashes from compiled-in data set @@ -1182,5 +1213,14 @@ namespace cryptonote * a useful state. */ void load_compiled_in_block_hashes(); + + /** + * @brief expands v2 transaction data from blockchain + * + * RingCT transactions do not transmit some of their data if it + * can be reconstituted by the receiver. This function expands + * that implicit data. + */ + bool expand_transaction_2(transaction &tx, const crypto::hash &tx_prefix_hash, const std::vector<std::vector<rct::ctkey>> &pubkeys); }; } // namespace cryptonote diff --git a/src/cryptonote_core/blockchain_storage.cpp b/src/cryptonote_core/blockchain_storage.cpp deleted file mode 100644 index a829b7cbe..000000000 --- a/src/cryptonote_core/blockchain_storage.cpp +++ /dev/null @@ -1,1952 +0,0 @@ -// Copyright (c) 2014-2016, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers - -#include <algorithm> -#include <cstdio> -#include <cmath> -#include <boost/archive/binary_oarchive.hpp> -#include <boost/archive/binary_iarchive.hpp> - -#include "include_base_utils.h" -#include "cryptonote_basic_impl.h" -#include "blockchain_storage.h" -#include "cryptonote_format_utils.h" -#include "cryptonote_boost_serialization.h" -#include "blockchain_storage_boost_serialization.h" -#include "cryptonote_config.h" -#include "miner.h" -#include "misc_language.h" -#include "profile_tools.h" -#include "file_io_utils.h" -#include "common/boost_serialization_helper.h" -#include "warnings.h" -#include "crypto/hash.h" -#include "cryptonote_core/checkpoints.h" -//#include "serialization/json_archive.h" -#include "../../contrib/otshell_utils/utils.hpp" -#include "../../src/p2p/data_logger.hpp" - -using namespace cryptonote; - -DISABLE_VS_WARNINGS(4267) - -//------------------------------------------------------------------ -bool blockchain_storage::have_tx(const crypto::hash &id) const -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - return m_transactions.find(id) != m_transactions.end(); -} -//------------------------------------------------------------------ -bool blockchain_storage::have_tx_keyimg_as_spent(const crypto::key_image &key_im) const -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - return m_spent_keys.find(key_im) != m_spent_keys.end(); -} -//------------------------------------------------------------------ -const transaction *blockchain_storage::get_tx(const crypto::hash &id) const -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - auto it = m_transactions.find(id); - if (it == m_transactions.end()) - return NULL; - - return &it->second.tx; -} -//------------------------------------------------------------------ -uint64_t blockchain_storage::get_current_blockchain_height() const -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - return m_blocks.size(); -} -//------------------------------------------------------------------ -bool blockchain_storage::init(const std::string& config_folder, bool testnet) -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - m_config_folder = config_folder; - m_testnet = testnet; - LOG_PRINT_L0("Loading blockchain..."); - const std::string filename = m_config_folder + "/" CRYPTONOTE_BLOCKCHAINDATA_FILENAME; - if(tools::unserialize_obj_from_file(*this, filename)) - { - - // checkpoints - - // mainchain - for (size_t height=0; height < m_blocks.size(); ++height) - { - CHECK_AND_ASSERT_MES((!m_checkpoints.is_in_checkpoint_zone(height)) || m_checkpoints.check_block(height,get_block_hash(m_blocks[height].bl)),false,"checkpoint fail, blockchain.bin invalid"); - } - - // check alt chains - #if 0 - // doesn't work when a checkpoint is added and there are already alt chains. However, the rest of the blockchain code suffers from the same issue, so ignore for now - // see issue #118 - BOOST_FOREACH(blocks_ext_by_hash::value_type& alt_block, m_alternative_chains) - { - CHECK_AND_ASSERT_MES(m_checkpoints.is_alternative_block_allowed(m_blocks.size()-1,alt_block.second.height),false,"stored alternative block not allowed, blockchain.bin invalid"); - } - #endif - } - else - { - LOG_PRINT_L0("Can't load blockchain storage from file, generating genesis block."); - if (!store_genesis_block(testnet, true)) - return false; - } - if(!m_blocks.size()) - { - LOG_PRINT_L0("Blockchain not loaded, generating genesis block."); - - if (!store_genesis_block(testnet, false)) { - return false; - } - } else { - cryptonote::block b; - - if (testnet) - { - generate_genesis_block(b, config::testnet::GENESIS_TX, config::testnet::GENESIS_NONCE); - } - else - { - generate_genesis_block(b, config::GENESIS_TX, config::GENESIS_NONCE); - } - - crypto::hash genesis_hash = get_block_hash(m_blocks[0].bl); - crypto::hash testnet_genesis_hash = get_block_hash(b); - if (genesis_hash != testnet_genesis_hash) { - LOG_ERROR("Failed to init: genesis block mismatch. Probably you set --testnet flag with data dir with non-test blockchain or another network."); - return false; - } - } - uint64_t timestamp_diff = time(NULL) - m_blocks.back().bl.timestamp; - if(!m_blocks.back().bl.timestamp) - timestamp_diff = time(NULL) - 1341378000; - LOG_PRINT_GREEN("Blockchain initialized. last block: " << m_blocks.size() - 1 << ", " << epee::misc_utils::get_time_interval_string(timestamp_diff) << " time ago, current difficulty: " << get_difficulty_for_next_block(), LOG_LEVEL_0); - return true; -} -//------------------------------------------------------------------ -bool blockchain_storage::store_genesis_block(bool testnet, bool check_added) { - block bl = ::boost::value_initialized<block>(); - block_verification_context bvc = boost::value_initialized<block_verification_context>(); - - if (testnet) - { - generate_genesis_block(bl, config::testnet::GENESIS_TX, config::testnet::GENESIS_NONCE); - } - else - { - generate_genesis_block(bl, config::GENESIS_TX, config::GENESIS_NONCE); - } - - add_new_block(bl, bvc); - CHECK_AND_ASSERT_MES(!bvc.m_verifivation_failed && (bvc.m_added_to_main_chain || !check_added), false, "Failed to add genesis block to blockchain"); - return true; -} -//------------------------------------------------------------------ -bool blockchain_storage::store_blockchain() -{ - m_is_blockchain_storing = true; - epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){m_is_blockchain_storing=false;}); - - LOG_PRINT_L0("Storing blockchain..."); - if (!tools::create_directories_if_necessary(m_config_folder)) - { - LOG_PRINT_L0("Failed to create data directory: " << m_config_folder); - return false; - } - - const std::string temp_filename = m_config_folder + "/" CRYPTONOTE_BLOCKCHAINDATA_TEMP_FILENAME; - // There is a chance that temp_filename and filename are hardlinks to the same file - std::remove(temp_filename.c_str()); - if(!tools::serialize_obj_to_file(*this, temp_filename)) - { - //achtung! - LOG_ERROR("Failed to save blockchain data to file: " << temp_filename); - return false; - } - const std::string filename = m_config_folder + "/" CRYPTONOTE_BLOCKCHAINDATA_FILENAME; - std::error_code ec = tools::replace_file(temp_filename, filename); - if (ec) - { - LOG_ERROR("Failed to rename blockchain data file " << temp_filename << " to " << filename << ": " << ec.message() << ':' << ec.value()); - return false; - } - LOG_PRINT_L0("Blockchain stored OK."); - return true; -} -//------------------------------------------------------------------ -bool blockchain_storage::deinit() -{ - return store_blockchain(); -} -//------------------------------------------------------------------ -bool blockchain_storage::pop_block_from_blockchain() -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - - CHECK_AND_ASSERT_MES(m_blocks.size() > 1, false, "pop_block_from_blockchain: can't pop from blockchain with size = " << m_blocks.size()); - size_t h = m_blocks.size()-1; - block_extended_info& bei = m_blocks[h]; - //crypto::hash id = get_block_hash(bei.bl); - bool r = purge_block_data_from_blockchain(bei.bl, bei.bl.tx_hashes.size()); - CHECK_AND_ASSERT_MES(r, false, "Failed to purge_block_data_from_blockchain for block " << get_block_hash(bei.bl) << " on height " << h); - - //remove from index - auto bl_ind = m_blocks_index.find(get_block_hash(bei.bl)); - CHECK_AND_ASSERT_MES(bl_ind != m_blocks_index.end(), false, "pop_block_from_blockchain: blockchain id not found in index"); - m_blocks_index.erase(bl_ind); - //pop block from core - m_blocks.pop_back(); - m_tx_pool->on_blockchain_dec(m_blocks.size()-1, get_tail_id()); - return true; -} -//------------------------------------------------------------------ -bool blockchain_storage::reset_and_set_genesis_block(const block& b) -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - m_transactions.clear(); - m_spent_keys.clear(); - m_blocks.clear(); - m_blocks_index.clear(); - m_alternative_chains.clear(); - m_outputs.clear(); - - block_verification_context bvc = boost::value_initialized<block_verification_context>(); - add_new_block(b, bvc); - return bvc.m_added_to_main_chain && !bvc.m_verifivation_failed; -} -//------------------------------------------------------------------ -bool blockchain_storage::purge_transaction_keyimages_from_blockchain(const transaction& tx, bool strict_check) -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - struct purge_transaction_visitor: public boost::static_visitor<bool> - { - key_images_container& m_spent_keys; - bool m_strict_check; - purge_transaction_visitor(key_images_container& spent_keys, bool strict_check):m_spent_keys(spent_keys), m_strict_check(strict_check){} - - bool operator()(const txin_to_key& inp) const - { - //const crypto::key_image& ki = inp.k_image; - auto r = m_spent_keys.find(inp.k_image); - if(r != m_spent_keys.end()) - { - m_spent_keys.erase(r); - }else - { - CHECK_AND_ASSERT_MES(!m_strict_check, false, "purge_block_data_from_blockchain: key image in transaction not found"); - } - return true; - } - bool operator()(const txin_gen& inp) const - { - return true; - } - bool operator()(const txin_to_script& tx) const - { - return false; - } - - bool operator()(const txin_to_scripthash& tx) const - { - return false; - } - }; - - BOOST_FOREACH(const txin_v& in, tx.vin) - { - bool r = boost::apply_visitor(purge_transaction_visitor(m_spent_keys, strict_check), in); - CHECK_AND_ASSERT_MES(!strict_check || r, false, "failed to process purge_transaction_visitor"); - } - return true; -} -//------------------------------------------------------------------ -bool blockchain_storage::purge_transaction_from_blockchain(const crypto::hash& tx_id) -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - auto tx_index_it = m_transactions.find(tx_id); - CHECK_AND_ASSERT_MES(tx_index_it != m_transactions.end(), false, "purge_block_data_from_blockchain: transaction not found in blockchain index!!"); - transaction& tx = tx_index_it->second.tx; - - purge_transaction_keyimages_from_blockchain(tx, true); - - if(!is_coinbase(tx)) - { - cryptonote::tx_verification_context tvc = AUTO_VAL_INIT(tvc); - bool r = m_tx_pool->add_tx(tx, tvc, true, true, 1); - CHECK_AND_ASSERT_MES(r, false, "purge_block_data_from_blockchain: failed to add transaction to transaction pool"); - } - - bool res = pop_transaction_from_global_index(tx, tx_id); - m_transactions.erase(tx_index_it); - LOG_PRINT_L1("Removed transaction from blockchain history:" << tx_id << ENDL); - return res; -} -//------------------------------------------------------------------ -bool blockchain_storage::purge_block_data_from_blockchain(const block& bl, size_t processed_tx_count) -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - - bool res = true; - CHECK_AND_ASSERT_MES(processed_tx_count <= bl.tx_hashes.size(), false, "wrong processed_tx_count in purge_block_data_from_blockchain"); - for(size_t count = 0; count != processed_tx_count; count++) - { - res = purge_transaction_from_blockchain(bl.tx_hashes[(processed_tx_count -1)- count]) && res; - } - - res = purge_transaction_from_blockchain(get_transaction_hash(bl.miner_tx)) && res; - - return res; -} -//------------------------------------------------------------------ -crypto::hash blockchain_storage::get_tail_id(uint64_t& height) const -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - height = get_current_blockchain_height()-1; - return get_tail_id(); -} -//------------------------------------------------------------------ -crypto::hash blockchain_storage::get_tail_id() const -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - crypto::hash id = null_hash; - if(m_blocks.size()) - { - get_block_hash(m_blocks.back().bl, id); - } - return id; -} -//------------------------------------------------------------------ -bool blockchain_storage::get_short_chain_history(std::list<crypto::hash>& ids) const -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - size_t i = 0; - size_t current_multiplier = 1; - size_t sz = m_blocks.size(); - if(!sz) - return true; - size_t current_back_offset = 1; - bool genesis_included = false; - while(current_back_offset < sz) - { - ids.push_back(get_block_hash(m_blocks[sz-current_back_offset].bl)); - if(sz-current_back_offset == 0) - genesis_included = true; - if(i < 10) - { - ++current_back_offset; - }else - { - current_back_offset += current_multiplier *= 2; - } - ++i; - } - if(!genesis_included) - ids.push_back(get_block_hash(m_blocks[0].bl)); - - return true; -} -//------------------------------------------------------------------ -crypto::hash blockchain_storage::get_block_id_by_height(uint64_t height) const -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - if(height >= m_blocks.size()) - return null_hash; - - return get_block_hash(m_blocks[height].bl); -} -//------------------------------------------------------------------ -bool blockchain_storage::get_block_by_hash(const crypto::hash &h, block &blk) const { - CRITICAL_REGION_LOCAL(m_blockchain_lock); - - // try to find block in main chain - blocks_by_id_index::const_iterator it = m_blocks_index.find(h); - if (m_blocks_index.end() != it) { - blk = m_blocks[it->second].bl; - return true; - } - - // try to find block in alternative chain - blocks_ext_by_hash::const_iterator it_alt = m_alternative_chains.find(h); - if (m_alternative_chains.end() != it_alt) { - blk = it_alt->second.bl; - return true; - } - - return false; -} -//------------------------------------------------------------------ -void blockchain_storage::get_all_known_block_ids(std::list<crypto::hash> &main, std::list<crypto::hash> &alt, std::list<crypto::hash> &invalid) const { - CRITICAL_REGION_LOCAL(m_blockchain_lock); - - BOOST_FOREACH(const blocks_by_id_index::value_type &v, m_blocks_index) - main.push_back(v.first); - - BOOST_FOREACH(const blocks_ext_by_hash::value_type &v, m_alternative_chains) - alt.push_back(v.first); - - BOOST_FOREACH(const blocks_ext_by_hash::value_type &v, m_invalid_blocks) - invalid.push_back(v.first); -} -//------------------------------------------------------------------ -difficulty_type blockchain_storage::get_difficulty_for_next_block() const -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - std::vector<uint64_t> timestamps; - std::vector<difficulty_type> commulative_difficulties; - size_t offset = m_blocks.size() - std::min(m_blocks.size(), static_cast<size_t>(DIFFICULTY_BLOCKS_COUNT)); - if(!offset) - ++offset;//skip genesis block - for(; offset < m_blocks.size(); offset++) - { - timestamps.push_back(m_blocks[offset].bl.timestamp); - commulative_difficulties.push_back(m_blocks[offset].cumulative_difficulty); - } - return next_difficulty(timestamps, commulative_difficulties,DIFFICULTY_TARGET_V1); -} -//------------------------------------------------------------------ -bool blockchain_storage::rollback_blockchain_switching(std::list<block>& original_chain, size_t rollback_height) -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - - // fail if rollback_height passed is too high - if (rollback_height > m_blocks.size()) - { - return true; - } - - //remove failed subchain - for(size_t i = m_blocks.size()-1; i >=rollback_height; i--) - { - bool r = pop_block_from_blockchain(); - CHECK_AND_ASSERT_MES(r, false, "PANIC! failed to remove block while chain switching during the rollback!"); - } - //return back original chain - BOOST_FOREACH(auto& bl, original_chain) - { - block_verification_context bvc = boost::value_initialized<block_verification_context>(); - bool r = handle_block_to_main_chain(bl, bvc); - CHECK_AND_ASSERT_MES(r && bvc.m_added_to_main_chain, false, "PANIC! failed to add (again) block while chain switching during the rollback!"); - } - - LOG_PRINT_L1("Rollback success."); - return true; -} -//------------------------------------------------------------------ -bool blockchain_storage::switch_to_alternative_blockchain(std::list<blocks_ext_by_hash::iterator>& alt_chain, bool discard_disconnected_chain) -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - CHECK_AND_ASSERT_MES(alt_chain.size(), false, "switch_to_alternative_blockchain: empty chain passed"); - - size_t split_height = alt_chain.front()->second.height; - CHECK_AND_ASSERT_MES(m_blocks.size() > split_height, false, "switch_to_alternative_blockchain: blockchain size is lower than split height"); - - //disconnecting old chain - std::list<block> disconnected_chain; - for(size_t i = m_blocks.size()-1; i >=split_height; i--) - { - block b = m_blocks[i].bl; - bool r = pop_block_from_blockchain(); - CHECK_AND_ASSERT_MES(r, false, "failed to remove block on chain switching"); - disconnected_chain.push_front(b); - } - - //connecting new alternative chain - for(auto alt_ch_iter = alt_chain.begin(); alt_ch_iter != alt_chain.end(); alt_ch_iter++) - { - auto ch_ent = *alt_ch_iter; - block_verification_context bvc = boost::value_initialized<block_verification_context>(); - bool r = handle_block_to_main_chain(ch_ent->second.bl, bvc); - if(!r || !bvc.m_added_to_main_chain) - { - LOG_PRINT_L1("Failed to switch to alternative blockchain"); - rollback_blockchain_switching(disconnected_chain, split_height); - add_block_as_invalid(ch_ent->second, get_block_hash(ch_ent->second.bl)); - LOG_PRINT_L1("The block was inserted as invalid while connecting new alternative chain, block_id: " << get_block_hash(ch_ent->second.bl)); - m_alternative_chains.erase(ch_ent); - - for(auto alt_ch_to_orph_iter = ++alt_ch_iter; alt_ch_to_orph_iter != alt_chain.end(); alt_ch_to_orph_iter++) - { - //block_verification_context bvc = boost::value_initialized<block_verification_context>(); - add_block_as_invalid((*alt_ch_iter)->second, (*alt_ch_iter)->first); - m_alternative_chains.erase(*alt_ch_to_orph_iter); - } - return false; - } - } - - if(!discard_disconnected_chain) - { - //pushing old chain as alternative chain - BOOST_FOREACH(auto& old_ch_ent, disconnected_chain) - { - block_verification_context bvc = boost::value_initialized<block_verification_context>(); - bool r = handle_alternative_block(old_ch_ent, get_block_hash(old_ch_ent), bvc); - if(!r) - { - LOG_PRINT_L1("Failed to push ex-main chain blocks to alternative chain "); - rollback_blockchain_switching(disconnected_chain, split_height); - return false; - } - } - } - - //removing all_chain entries from alternative chain - BOOST_FOREACH(auto ch_ent, alt_chain) - { - m_alternative_chains.erase(ch_ent); - } - - LOG_PRINT_GREEN("REORGANIZE SUCCESS! on height: " << split_height << ", new blockchain size: " << m_blocks.size(), LOG_LEVEL_0); - return true; -} -//------------------------------------------------------------------ -difficulty_type blockchain_storage::get_next_difficulty_for_alternative_chain(const std::list<blocks_ext_by_hash::iterator>& alt_chain, block_extended_info& bei) const -{ - std::vector<uint64_t> timestamps; - std::vector<difficulty_type> commulative_difficulties; - if(alt_chain.size()< DIFFICULTY_BLOCKS_COUNT) - { - CRITICAL_REGION_LOCAL(m_blockchain_lock); - size_t main_chain_stop_offset = alt_chain.size() ? alt_chain.front()->second.height : bei.height; - size_t main_chain_count = DIFFICULTY_BLOCKS_COUNT - std::min(static_cast<size_t>(DIFFICULTY_BLOCKS_COUNT), alt_chain.size()); - main_chain_count = std::min(main_chain_count, main_chain_stop_offset); - size_t main_chain_start_offset = main_chain_stop_offset - main_chain_count; - - if(!main_chain_start_offset) - ++main_chain_start_offset; //skip genesis block - for(; main_chain_start_offset < main_chain_stop_offset; ++main_chain_start_offset) - { - timestamps.push_back(m_blocks[main_chain_start_offset].bl.timestamp); - commulative_difficulties.push_back(m_blocks[main_chain_start_offset].cumulative_difficulty); - } - - CHECK_AND_ASSERT_MES((alt_chain.size() + timestamps.size()) <= DIFFICULTY_BLOCKS_COUNT, false, "Internal error, alt_chain.size()["<< alt_chain.size() - << "] + vtimestampsec.size()[" << timestamps.size() << "] NOT <= DIFFICULTY_WINDOW[]" << DIFFICULTY_BLOCKS_COUNT ); - BOOST_FOREACH(auto it, alt_chain) - { - timestamps.push_back(it->second.bl.timestamp); - commulative_difficulties.push_back(it->second.cumulative_difficulty); - } - }else - { - timestamps.resize(std::min(alt_chain.size(), static_cast<size_t>(DIFFICULTY_BLOCKS_COUNT))); - commulative_difficulties.resize(std::min(alt_chain.size(), static_cast<size_t>(DIFFICULTY_BLOCKS_COUNT))); - size_t count = 0; - size_t max_i = timestamps.size()-1; - BOOST_REVERSE_FOREACH(auto it, alt_chain) - { - timestamps[max_i - count] = it->second.bl.timestamp; - commulative_difficulties[max_i - count] = it->second.cumulative_difficulty; - count++; - if(count >= DIFFICULTY_BLOCKS_COUNT) - break; - } - } - return next_difficulty(timestamps, commulative_difficulties,DIFFICULTY_TARGET_V1); -} -//------------------------------------------------------------------ -bool blockchain_storage::prevalidate_miner_transaction(const block& b, uint64_t height) const -{ - CHECK_AND_ASSERT_MES(b.miner_tx.vin.size() == 1, false, "coinbase transaction in the block has no inputs"); - CHECK_AND_ASSERT_MES(b.miner_tx.vin[0].type() == typeid(txin_gen), false, "coinbase transaction in the block has the wrong type"); - if(boost::get<txin_gen>(b.miner_tx.vin[0]).height != height) - { - LOG_PRINT_RED_L1("The miner transaction in block has invalid height: " << boost::get<txin_gen>(b.miner_tx.vin[0]).height << ", expected: " << height); - return false; - } - CHECK_AND_ASSERT_MES(b.miner_tx.unlock_time == height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, - false, - "coinbase transaction transaction has the wrong unlock time=" << b.miner_tx.unlock_time << ", expected " << height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW); - - //check outs overflow - if(!check_outs_overflow(b.miner_tx)) - { - LOG_PRINT_RED_L1("miner transaction has money overflow in block " << get_block_hash(b)); - return false; - } - - return true; -} -//------------------------------------------------------------------ -bool blockchain_storage::validate_miner_transaction(const block& b, size_t cumulative_block_size, uint64_t fee, uint64_t& base_reward, uint64_t already_generated_coins) const -{ - //validate reward - uint64_t money_in_use = 0; - BOOST_FOREACH(auto& o, b.miner_tx.vout) - money_in_use += o.amount; - - std::vector<size_t> last_blocks_sizes; - get_last_n_blocks_sizes(last_blocks_sizes, CRYPTONOTE_REWARD_BLOCKS_WINDOW); - if (!get_block_reward(epee::misc_utils::median(last_blocks_sizes), cumulative_block_size, already_generated_coins, base_reward,0)) { - LOG_PRINT_L1("block size " << cumulative_block_size << " is bigger than allowed for this blockchain"); - return false; - } - if(base_reward + fee < money_in_use) - { - LOG_PRINT_L1("coinbase transaction spend too much money (" << print_money(money_in_use) << "). Block reward is " << print_money(base_reward + fee) << "(" << print_money(base_reward) << "+" << print_money(fee) << ")"); - return false; - } - if(base_reward + fee != money_in_use) - { - LOG_PRINT_L1("coinbase transaction doesn't use full amount of block reward: spent: " - << print_money(money_in_use) << ", block reward " << print_money(base_reward + fee) << "(" << print_money(base_reward) << "+" << print_money(fee) << ")"); - return false; - } - return true; -} -//------------------------------------------------------------------ -bool blockchain_storage::get_backward_blocks_sizes(size_t from_height, std::vector<size_t>& sz, size_t count) const -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - CHECK_AND_ASSERT_MES(from_height < m_blocks.size(), false, "Internal error: get_backward_blocks_sizes called with from_height=" << from_height << ", blockchain height = " << m_blocks.size()); - - size_t start_offset = (from_height+1) - std::min((from_height+1), count); - for(size_t i = start_offset; i != from_height+1; i++) - sz.push_back(m_blocks[i].block_cumulative_size); - - return true; -} -//------------------------------------------------------------------ -bool blockchain_storage::get_last_n_blocks_sizes(std::vector<size_t>& sz, size_t count) const -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - if(!m_blocks.size()) - return true; - return get_backward_blocks_sizes(m_blocks.size() -1, sz, count); -} -//------------------------------------------------------------------ -uint64_t blockchain_storage::get_current_cumulative_blocksize_limit() const -{ - return m_current_block_cumul_sz_limit; -} -//------------------------------------------------------------------ -bool blockchain_storage::create_block_template(block& b, const account_public_address& miner_address, difficulty_type& diffic, uint64_t& height, const blobdata& ex_nonce) const -{ - size_t median_size; - uint64_t already_generated_coins; - - CRITICAL_REGION_BEGIN(m_blockchain_lock); - b.major_version = CURRENT_BLOCK_MAJOR_VERSION; - b.minor_version = CURRENT_BLOCK_MINOR_VERSION; - b.prev_id = get_tail_id(); - b.timestamp = time(NULL); - height = m_blocks.size(); - diffic = get_difficulty_for_next_block(); - CHECK_AND_ASSERT_MES(diffic, false, "difficulty overhead."); - - median_size = m_current_block_cumul_sz_limit / 2; - already_generated_coins = m_blocks.back().already_generated_coins; - - CRITICAL_REGION_END(); - - size_t txs_size; - uint64_t fee; - if (!m_tx_pool->fill_block_template(b, median_size, already_generated_coins, txs_size, fee)) { - return false; - } -#if defined(DEBUG_CREATE_BLOCK_TEMPLATE) - size_t real_txs_size = 0; - uint64_t real_fee = 0; - CRITICAL_REGION_BEGIN(m_tx_pool->m_transactions_lock); - BOOST_FOREACH(crypto::hash &cur_hash, b.tx_hashes) { - auto cur_res = m_tx_pool->m_transactions.find(cur_hash); - if (cur_res == m_tx_pool->m_transactions.end()) { - LOG_ERROR("Creating block template: error: transaction not found"); - continue; - } - tx_memory_pool::tx_details &cur_tx = cur_res->second; - real_txs_size += cur_tx.blob_size; - real_fee += cur_tx.fee; - if (cur_tx.blob_size != get_object_blobsize(cur_tx.tx)) { - LOG_ERROR("Creating block template: error: invalid transaction size"); - } - uint64_t inputs_amount; - if (!get_inputs_money_amount(cur_tx.tx, inputs_amount)) { - LOG_ERROR("Creating block template: error: cannot get inputs amount"); - } else if (cur_tx.fee != inputs_amount - get_outs_money_amount(cur_tx.tx)) { - LOG_ERROR("Creating block template: error: invalid fee"); - } - } - if (txs_size != real_txs_size) { - LOG_ERROR("Creating block template: error: wrongly calculated transaction size"); - } - if (fee != real_fee) { - LOG_ERROR("Creating block template: error: wrongly calculated fee"); - } - CRITICAL_REGION_END(); - LOG_PRINT_L1("Creating block template: height " << height << - ", median size " << median_size << - ", already generated coins " << already_generated_coins << - ", transaction size " << txs_size << - ", fee " << fee); -#endif - - /* - two-phase miner transaction generation: we don't know exact block size until we prepare block, but we don't know reward until we know - block size, so first miner transaction generated with fake amount of money, and with phase we know think we know expected block size - */ - //make blocks coin-base tx looks close to real coinbase tx to get truthful blob size - bool r = construct_miner_tx(height, median_size, already_generated_coins, txs_size, fee, miner_address, b.miner_tx, ex_nonce, 11); - CHECK_AND_ASSERT_MES(r, false, "Failed to construc miner tx, first chance"); - size_t cumulative_size = txs_size + get_object_blobsize(b.miner_tx); -#if defined(DEBUG_CREATE_BLOCK_TEMPLATE) - LOG_PRINT_L1("Creating block template: miner tx size " << get_object_blobsize(b.miner_tx) << - ", cumulative size " << cumulative_size); -#endif - for (size_t try_count = 0; try_count != 10; ++try_count) { - r = construct_miner_tx(height, median_size, already_generated_coins, cumulative_size, fee, miner_address, b.miner_tx, ex_nonce, 11); - - CHECK_AND_ASSERT_MES(r, false, "Failed to construc miner tx, second chance"); - size_t coinbase_blob_size = get_object_blobsize(b.miner_tx); - if (coinbase_blob_size > cumulative_size - txs_size) { - cumulative_size = txs_size + coinbase_blob_size; -#if defined(DEBUG_CREATE_BLOCK_TEMPLATE) - LOG_PRINT_L1("Creating block template: miner tx size " << coinbase_blob_size << - ", cumulative size " << cumulative_size << " is greater then before"); -#endif - continue; - } - - if (coinbase_blob_size < cumulative_size - txs_size) { - size_t delta = cumulative_size - txs_size - coinbase_blob_size; -#if defined(DEBUG_CREATE_BLOCK_TEMPLATE) - LOG_PRINT_L1("Creating block template: miner tx size " << coinbase_blob_size << - ", cumulative size " << txs_size + coinbase_blob_size << - " is less then before, adding " << delta << " zero bytes"); -#endif - b.miner_tx.extra.insert(b.miner_tx.extra.end(), delta, 0); - //here could be 1 byte difference, because of extra field counter is varint, and it can become from 1-byte len to 2-bytes len. - if (cumulative_size != txs_size + get_object_blobsize(b.miner_tx)) { - CHECK_AND_ASSERT_MES(cumulative_size + 1 == txs_size + get_object_blobsize(b.miner_tx), false, "unexpected case: cumulative_size=" << cumulative_size << " + 1 is not equal txs_cumulative_size=" << txs_size << " + get_object_blobsize(b.miner_tx)=" << get_object_blobsize(b.miner_tx)); - b.miner_tx.extra.resize(b.miner_tx.extra.size() - 1); - if (cumulative_size != txs_size + get_object_blobsize(b.miner_tx)) { - //fuck, not lucky, -1 makes varint-counter size smaller, in that case we continue to grow with cumulative_size - LOG_PRINT_RED("Miner tx creation has no luck with delta_extra size = " << delta << " and " << delta - 1 , LOG_LEVEL_2); - cumulative_size += delta - 1; - continue; - } - LOG_PRINT_GREEN("Setting extra for block: " << b.miner_tx.extra.size() << ", try_count=" << try_count, LOG_LEVEL_1); - } - } - CHECK_AND_ASSERT_MES(cumulative_size == txs_size + get_object_blobsize(b.miner_tx), false, "unexpected case: cumulative_size=" << cumulative_size << " is not equal txs_cumulative_size=" << txs_size << " + get_object_blobsize(b.miner_tx)=" << get_object_blobsize(b.miner_tx)); -#if defined(DEBUG_CREATE_BLOCK_TEMPLATE) - LOG_PRINT_L1("Creating block template: miner tx size " << coinbase_blob_size << - ", cumulative size " << cumulative_size << " is now good"); -#endif - return true; - } - LOG_ERROR("Failed to create_block_template with " << 10 << " tries"); - return false; -} -//------------------------------------------------------------------ -bool blockchain_storage::complete_timestamps_vector(uint64_t start_top_height, std::vector<uint64_t>& timestamps) const -{ - - if(timestamps.size() >= BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW) - return true; - - CRITICAL_REGION_LOCAL(m_blockchain_lock); - size_t need_elements = BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW - timestamps.size(); - CHECK_AND_ASSERT_MES(start_top_height < m_blocks.size(), false, "internal error: passed start_height = " << start_top_height << " not less then m_blocks.size()=" << m_blocks.size()); - size_t stop_offset = start_top_height > need_elements ? start_top_height - need_elements:0; - do - { - timestamps.push_back(m_blocks[start_top_height].bl.timestamp); - if(start_top_height == 0) - break; - --start_top_height; - }while(start_top_height != stop_offset); - return true; -} -//------------------------------------------------------------------ -bool blockchain_storage::handle_alternative_block(const block& b, const crypto::hash& id, block_verification_context& bvc) -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - - uint64_t block_height = get_block_height(b); - if(0 == block_height) - { - LOG_PRINT_L1("Block with id: " << epee::string_tools::pod_to_hex(id) << " (as alternative) has wrong miner transaction"); - bvc.m_verifivation_failed = true; - return false; - } - if (!m_checkpoints.is_alternative_block_allowed(get_current_blockchain_height(), block_height)) - { - LOG_PRINT_RED_L1("Block with id: " << id - << ENDL << " can't be accepted for alternative chain, block height: " << block_height - << ENDL << " blockchain height: " << get_current_blockchain_height()); - bvc.m_verifivation_failed = true; - return false; - } - - //Block is not related with head of main chain - //first of all - look in alternative chains container - auto it_main_prev = m_blocks_index.find(b.prev_id); - auto it_prev = m_alternative_chains.find(b.prev_id); - if(it_prev != m_alternative_chains.end() || it_main_prev != m_blocks_index.end()) - { - //we have new block in alternative chain - - //build alternative subchain, front -> mainchain, back -> alternative head - blocks_ext_by_hash::iterator alt_it = it_prev; //m_alternative_chains.find() - std::list<blocks_ext_by_hash::iterator> alt_chain; - std::vector<uint64_t> timestamps; - while(alt_it != m_alternative_chains.end()) - { - alt_chain.push_front(alt_it); - timestamps.push_back(alt_it->second.bl.timestamp); - alt_it = m_alternative_chains.find(alt_it->second.bl.prev_id); - } - - if(alt_chain.size()) - { - //make sure that it has right connection to main chain - CHECK_AND_ASSERT_MES(m_blocks.size() > alt_chain.front()->second.height, false, "main blockchain wrong height"); - crypto::hash h = null_hash; - get_block_hash(m_blocks[alt_chain.front()->second.height - 1].bl, h); - CHECK_AND_ASSERT_MES(h == alt_chain.front()->second.bl.prev_id, false, "alternative chain has wrong connection to main chain"); - complete_timestamps_vector(alt_chain.front()->second.height - 1, timestamps); - }else - { - CHECK_AND_ASSERT_MES(it_main_prev != m_blocks_index.end(), false, "internal error: broken imperative condition it_main_prev != m_blocks_index.end()"); - complete_timestamps_vector(it_main_prev->second, timestamps); - } - //check timestamp correct - if(!check_block_timestamp(timestamps, b)) - { - LOG_PRINT_RED_L1("Block with id: " << id - << ENDL << " for alternative chain, has invalid timestamp: " << b.timestamp); - //add_block_as_invalid(b, id);//do not add blocks to invalid storage before proof of work check was passed - bvc.m_verifivation_failed = true; - return false; - } - - block_extended_info bei = boost::value_initialized<block_extended_info>(); - bei.bl = b; - bei.height = alt_chain.size() ? it_prev->second.height + 1 : it_main_prev->second + 1; - - bool is_a_checkpoint; - if(!m_checkpoints.check_block(bei.height, id, is_a_checkpoint)) - { - LOG_ERROR("CHECKPOINT VALIDATION FAILED"); - bvc.m_verifivation_failed = true; - return false; - } - - // Always check PoW for alternative blocks - m_is_in_checkpoint_zone = false; - difficulty_type current_diff = get_next_difficulty_for_alternative_chain(alt_chain, bei); - CHECK_AND_ASSERT_MES(current_diff, false, "!!!!!!! DIFFICULTY OVERHEAD !!!!!!!"); - crypto::hash proof_of_work = null_hash; - get_block_longhash(bei.bl, proof_of_work, bei.height); - if(!check_hash(proof_of_work, current_diff)) - { - LOG_PRINT_RED_L1("Block with id: " << id - << ENDL << " for alternative chain, does not have enough proof of work: " << proof_of_work - << ENDL << " expected difficulty: " << current_diff); - bvc.m_verifivation_failed = true; - return false; - } - - if(!prevalidate_miner_transaction(b, bei.height)) - { - LOG_PRINT_RED_L1("Block with id: " << epee::string_tools::pod_to_hex(id) - << " (as alternative) has incorrect miner transaction."); - bvc.m_verifivation_failed = true; - return false; - - } - - bei.cumulative_difficulty = alt_chain.size() ? it_prev->second.cumulative_difficulty: m_blocks[it_main_prev->second].cumulative_difficulty; - bei.cumulative_difficulty += current_diff; - -#ifdef _DEBUG - auto i_dres = m_alternative_chains.find(id); - CHECK_AND_ASSERT_MES(i_dres == m_alternative_chains.end(), false, "insertion of new alternative block returned as it already exist"); -#endif - auto i_res = m_alternative_chains.insert(blocks_ext_by_hash::value_type(id, bei)); - CHECK_AND_ASSERT_MES(i_res.second, false, "insertion of new alternative block returned as it already exist"); - alt_chain.push_back(i_res.first); - - if(is_a_checkpoint) - { - //do reorganize! - LOG_PRINT_GREEN("###### REORGANIZE on height: " << alt_chain.front()->second.height << " of " << m_blocks.size() - 1 << - ", checkpoint is found in alternative chain on height " << bei.height, LOG_LEVEL_0); - bool r = switch_to_alternative_blockchain(alt_chain, true); - if(r) bvc.m_added_to_main_chain = true; - else bvc.m_verifivation_failed = true; - return r; - }else if(m_blocks.back().cumulative_difficulty < bei.cumulative_difficulty) //check if difficulty bigger then in main chain - { - //do reorganize! - LOG_PRINT_GREEN("###### REORGANIZE on height: " << alt_chain.front()->second.height << " of " << m_blocks.size() - 1 << " with cum_difficulty " << m_blocks.back().cumulative_difficulty - << ENDL << " alternative blockchain size: " << alt_chain.size() << " with cum_difficulty " << bei.cumulative_difficulty, LOG_LEVEL_0); - bool r = switch_to_alternative_blockchain(alt_chain, false); - if(r) bvc.m_added_to_main_chain = true; - else bvc.m_verifivation_failed = true; - return r; - }else - { - LOG_PRINT_BLUE("----- BLOCK ADDED AS ALTERNATIVE ON HEIGHT " << bei.height - << ENDL << "id:\t" << id - << ENDL << "PoW:\t" << proof_of_work - << ENDL << "difficulty:\t" << current_diff, LOG_LEVEL_0); - return true; - } - }else - { - //block orphaned - bvc.m_marked_as_orphaned = true; - LOG_PRINT_RED_L1("Block recognized as orphaned and rejected, id = " << id); - } - - return true; -} -//------------------------------------------------------------------ -bool blockchain_storage::get_blocks(uint64_t start_offset, size_t count, std::list<block>& blocks, std::list<transaction>& txs) const -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - if(start_offset >= m_blocks.size()) - return false; - for(size_t i = start_offset; i < start_offset + count && i < m_blocks.size();i++) - { - blocks.push_back(m_blocks[i].bl); - std::list<crypto::hash> missed_ids; - get_transactions(m_blocks[i].bl.tx_hashes, txs, missed_ids); - CHECK_AND_ASSERT_MES(!missed_ids.size(), false, "has missed transactions in own block in main blockchain"); - } - - return true; -} -//------------------------------------------------------------------ -bool blockchain_storage::get_blocks(uint64_t start_offset, size_t count, std::list<block>& blocks) const -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - if(start_offset >= m_blocks.size()) - return false; - - for(size_t i = start_offset; i < start_offset + count && i < m_blocks.size();i++) - blocks.push_back(m_blocks[i].bl); - return true; -} -//------------------------------------------------------------------ -bool blockchain_storage::handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, NOTIFY_RESPONSE_GET_OBJECTS::request& rsp) -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - rsp.current_blockchain_height = get_current_blockchain_height(); - std::list<block> blocks; - get_blocks(arg.blocks, blocks, rsp.missed_ids); - - BOOST_FOREACH(const auto& bl, blocks) - { - std::list<crypto::hash> missed_tx_id; - std::list<transaction> txs; - get_transactions(bl.tx_hashes, txs, rsp.missed_ids); - CHECK_AND_ASSERT_MES(!missed_tx_id.size(), false, "Internal error: has missed missed_tx_id.size()=" << missed_tx_id.size() - << ENDL << "for block id = " << get_block_hash(bl)); - rsp.blocks.push_back(block_complete_entry()); - block_complete_entry& e = rsp.blocks.back(); - //pack block - e.block = t_serializable_object_to_blob(bl); - //pack transactions - BOOST_FOREACH(transaction& tx, txs) - e.txs.push_back(t_serializable_object_to_blob(tx)); - - } - //get another transactions, if need - std::list<transaction> txs; - get_transactions(arg.txs, txs, rsp.missed_ids); - //pack aside transactions - BOOST_FOREACH(const auto& tx, txs) - rsp.txs.push_back(t_serializable_object_to_blob(tx)); - - return true; -} -//------------------------------------------------------------------ -bool blockchain_storage::get_alternative_blocks(std::list<block>& blocks) const -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - - BOOST_FOREACH(const auto& alt_bl, m_alternative_chains) - { - blocks.push_back(alt_bl.second.bl); - } - return true; -} -//------------------------------------------------------------------ -size_t blockchain_storage::get_alternative_blocks_count() const -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - return m_alternative_chains.size(); -} -//------------------------------------------------------------------ -bool blockchain_storage::add_out_to_get_random_outs(const std::vector<std::pair<crypto::hash, size_t> >& amount_outs, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& result_outs, uint64_t amount, size_t i) const -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - transactions_container::const_iterator tx_it = m_transactions.find(amount_outs[i].first); - CHECK_AND_ASSERT_MES(tx_it != m_transactions.end(), false, "internal error: transaction with id " << amount_outs[i].first << ENDL << - ", used in mounts global index for amount=" << amount << ": i=" << i << "not found in transactions index"); - CHECK_AND_ASSERT_MES(tx_it->second.tx.vout.size() > amount_outs[i].second, false, "internal error: in global outs index, transaction out index=" - << amount_outs[i].second << " more than transaction outputs = " << tx_it->second.tx.vout.size() << ", for tx id = " << amount_outs[i].first); - const transaction& tx = tx_it->second.tx; - CHECK_AND_ASSERT_MES(tx.vout[amount_outs[i].second].target.type() == typeid(txout_to_key), false, "unknown tx out type"); - - //check if transaction is unlocked - if(!is_tx_spendtime_unlocked(tx.unlock_time)) - return false; - - COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry& oen = *result_outs.outs.insert(result_outs.outs.end(), COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry()); - oen.global_amount_index = i; - oen.out_key = boost::get<txout_to_key>(tx.vout[amount_outs[i].second].target).key; - return true; -} -//------------------------------------------------------------------ -size_t blockchain_storage::find_end_of_allowed_index(const std::vector<std::pair<crypto::hash, size_t> >& amount_outs) const -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - if(!amount_outs.size()) - return 0; - size_t i = amount_outs.size(); - do - { - --i; - transactions_container::const_iterator it = m_transactions.find(amount_outs[i].first); - CHECK_AND_ASSERT_MES(it != m_transactions.end(), 0, "internal error: failed to find transaction from outputs index with tx_id=" << amount_outs[i].first); - if(it->second.m_keeper_block_height + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE <= get_current_blockchain_height() ) - return i+1; - } while (i != 0); - return 0; -} -//------------------------------------------------------------------ -bool blockchain_storage::get_random_outs_for_amounts(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request& req, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response& res) const -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - BOOST_FOREACH(uint64_t amount, req.amounts) - { - COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& result_outs = *res.outs.insert(res.outs.end(), COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount()); - result_outs.amount = amount; - auto it = m_outputs.find(amount); - if(it == m_outputs.end()) - { - LOG_PRINT_L1("COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS: not outs for amount " << amount << ", wallet should use some real outs when it lookup for some mix, so, at least one out for this amount should exist"); - continue;//actually this is strange situation, wallet should use some real outs when it lookup for some mix, so, at least one out for this amount should exist - } - const std::vector<std::pair<crypto::hash, size_t> >& amount_outs = it->second; - //it is not good idea to use top fresh outs, because it increases possibility of transaction canceling on split - //lets find upper bound of not fresh outs - size_t up_index_limit = find_end_of_allowed_index(amount_outs); - CHECK_AND_ASSERT_MES(up_index_limit <= amount_outs.size(), false, "internal error: find_end_of_allowed_index returned wrong index=" << up_index_limit << ", with amount_outs.size = " << amount_outs.size()); - if(amount_outs.size() > req.outs_count) - { - std::set<size_t> used; - size_t try_count = 0; - for(uint64_t j = 0; j != req.outs_count && try_count < up_index_limit;) - { - // triangular distribution over [a,b) with a=0, mode c=b=up_index_limit - uint64_t r = crypto::rand<uint64_t>() % ((uint64_t)1 << 53); - double frac = std::sqrt((double)r / ((uint64_t)1 << 53)); - size_t i = (size_t)(frac*up_index_limit); - // just in case rounding up to 1 occurs after sqrt - if (i == up_index_limit) - --i; - if(used.count(i)) - continue; - bool added = add_out_to_get_random_outs(amount_outs, result_outs, amount, i); - used.insert(i); - if(added) - ++j; - ++try_count; - } - }else - { - for(size_t i = 0; i != up_index_limit; i++) - add_out_to_get_random_outs(amount_outs, result_outs, amount, i); - } - } - return true; -} -//------------------------------------------------------------------ -bool blockchain_storage::find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, uint64_t& starter_offset) const -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - - if(!qblock_ids.size() /*|| !req.m_total_height*/) - { - LOG_PRINT_L1("Client sent wrong NOTIFY_REQUEST_CHAIN: m_block_ids.size()=" << qblock_ids.size() << /*", m_height=" << req.m_total_height <<*/ ", dropping connection"); - return false; - } - //check genesis match - if(qblock_ids.back() != get_block_hash(m_blocks[0].bl)) - { - LOG_PRINT_L1("Client sent wrong NOTIFY_REQUEST_CHAIN: genesis block missmatch: " << ENDL << "id: " - << qblock_ids.back() << ", " << ENDL << "expected: " << get_block_hash(m_blocks[0].bl) - << "," << ENDL << " dropping connection"); - return false; - } - - /* Figure out what blocks we should request to get state_normal */ - size_t i = 0; - auto bl_it = qblock_ids.begin(); - auto block_index_it = m_blocks_index.find(*bl_it); - for(; bl_it != qblock_ids.end(); bl_it++, i++) - { - block_index_it = m_blocks_index.find(*bl_it); - if(block_index_it != m_blocks_index.end()) - break; - } - - if(bl_it == qblock_ids.end()) - { - LOG_PRINT_L1("Internal error handling connection, can't find split point"); - return false; - } - - if(block_index_it == m_blocks_index.end()) - { - //this should NEVER happen, but, dose of paranoia in such cases is not too bad - LOG_PRINT_L1("Internal error handling connection, can't find split point"); - return false; - } - - //we start to put block ids INCLUDING last known id, just to make other side be sure - starter_offset = block_index_it->second; - return true; -} -//------------------------------------------------------------------ -uint64_t blockchain_storage::block_difficulty(size_t i) const -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - CHECK_AND_ASSERT_MES(i < m_blocks.size(), false, "wrong block index i = " << i << " at blockchain_storage::block_difficulty()"); - if(i == 0) - return m_blocks[i].cumulative_difficulty; - - return m_blocks[i].cumulative_difficulty - m_blocks[i-1].cumulative_difficulty; -} -//------------------------------------------------------------------ -double blockchain_storage::get_avg_block_size( size_t count) const -{ - if (count > get_current_blockchain_height()) return 500; - - double average = 0; - _dbg1_c("net/blksize", "HEIGHT: " << get_current_blockchain_height()); - _dbg1_c("net/blksize", "BLOCK ID BY HEIGHT: " << get_block_id_by_height(get_current_blockchain_height()) ); - _dbg1_c("net/blksize", "BLOCK TAIL ID: " << get_tail_id() ); - std::vector<size_t> size_vector; - - get_backward_blocks_sizes(get_current_blockchain_height() - count, size_vector, count); - - std::vector<size_t>::iterator it; - it = size_vector.begin(); - while (it != size_vector.end()) { - average += *it; - _dbg2_c("net/blksize", "VECTOR ELEMENT: " << (*it) ); - it++; - } - average = average / count; - _dbg1_c("net/blksize", "VECTOR SIZE: " << size_vector.size() << " average=" << average); - - return average; -} -//------------------------------------------------------------------ -void blockchain_storage::print_blockchain(uint64_t start_index, uint64_t end_index) const -{ - std::stringstream ss; - CRITICAL_REGION_LOCAL(m_blockchain_lock); - if(start_index >=m_blocks.size()) - { - LOG_PRINT_L1("Wrong starter index set: " << start_index << ", expected max index " << m_blocks.size()-1); - return; - } - - for(size_t i = start_index; i != m_blocks.size() && i != end_index; i++) - { - ss << "height " << i << ", timestamp " << m_blocks[i].bl.timestamp << ", cumul_dif " << m_blocks[i].cumulative_difficulty << ", cumul_size " << m_blocks[i].block_cumulative_size - << "\nid\t\t" << get_block_hash(m_blocks[i].bl) - << "\ndifficulty\t\t" << block_difficulty(i) << ", nonce " << m_blocks[i].bl.nonce << ", tx_count " << m_blocks[i].bl.tx_hashes.size() << ENDL; - } - LOG_PRINT_L1("Current blockchain:" << ENDL << ss.str()); - LOG_PRINT_L0("Blockchain printed with log level 1"); -} -//------------------------------------------------------------------ -void blockchain_storage::print_blockchain_index() const -{ - std::stringstream ss; - CRITICAL_REGION_LOCAL(m_blockchain_lock); - BOOST_FOREACH(const blocks_by_id_index::value_type& v, m_blocks_index) - ss << "id\t\t" << v.first << " height" << v.second << ENDL << ""; - - LOG_PRINT_L0("Current blockchain index:" << ENDL << ss.str()); -} -//------------------------------------------------------------------ -void blockchain_storage::print_blockchain_outs(const std::string& file) const -{ - std::stringstream ss; - CRITICAL_REGION_LOCAL(m_blockchain_lock); - BOOST_FOREACH(const outputs_container::value_type& v, m_outputs) - { - const std::vector<std::pair<crypto::hash, size_t> >& vals = v.second; - if(vals.size()) - { - ss << "amount: " << v.first << ENDL; - for(size_t i = 0; i != vals.size(); i++) - ss << "\t" << vals[i].first << ": " << vals[i].second << ENDL; - } - } - if(epee::file_io_utils::save_string_to_file(file, ss.str())) - { - LOG_PRINT_L0("Current outputs index writen to file: " << file); - }else - { - LOG_PRINT_L0("Failed to write current outputs index to file: " << file); - } -} -//------------------------------------------------------------------ -bool blockchain_storage::find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) const -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - if(!find_blockchain_supplement(qblock_ids, resp.start_height)) - return false; - - resp.total_height = get_current_blockchain_height(); - size_t count = 0; - for(size_t i = resp.start_height; i != m_blocks.size() && count < BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT; i++, count++) - resp.m_block_ids.push_back(get_block_hash(m_blocks[i].bl)); - return true; -} -//------------------------------------------------------------------ -bool blockchain_storage::find_blockchain_supplement(const uint64_t req_start_block, const std::list<crypto::hash>& qblock_ids, std::list<std::pair<block, std::list<transaction> > >& blocks, uint64_t& total_height, uint64_t& start_height, size_t max_count) const -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - if(req_start_block > 0) { - start_height = req_start_block; - } else { - if(!find_blockchain_supplement(qblock_ids, start_height)) - return false; - } - - total_height = get_current_blockchain_height(); - size_t count = 0; - for(size_t i = start_height; i != m_blocks.size() && count < max_count; i++, count++) - { - blocks.resize(blocks.size()+1); - blocks.back().first = m_blocks[i].bl; - std::list<crypto::hash> mis; - get_transactions(m_blocks[i].bl.tx_hashes, blocks.back().second, mis); - CHECK_AND_ASSERT_MES(!mis.size(), false, "internal error, transaction from block not found"); - } - return true; -} -//------------------------------------------------------------------ -bool blockchain_storage::add_block_as_invalid(const block& bl, const crypto::hash& h) -{ - block_extended_info bei = AUTO_VAL_INIT(bei); - bei.bl = bl; - return add_block_as_invalid(bei, h); -} -//------------------------------------------------------------------ -bool blockchain_storage::add_block_as_invalid(const block_extended_info& bei, const crypto::hash& h) -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - auto i_res = m_invalid_blocks.insert(std::map<crypto::hash, block_extended_info>::value_type(h, bei)); - CHECK_AND_ASSERT_MES(i_res.second, false, "at insertion invalid by tx returned status existed"); - LOG_PRINT_L1("BLOCK ADDED AS INVALID: " << h << ENDL << ", prev_id=" << bei.bl.prev_id << ", m_invalid_blocks count=" << m_invalid_blocks.size()); - return true; -} -//------------------------------------------------------------------ -bool blockchain_storage::have_block(const crypto::hash& id) const -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - if(m_blocks_index.count(id)) - return true; - if(m_alternative_chains.count(id)) - return true; - /*if(m_orphaned_blocks.get<by_id>().count(id)) - return true;*/ - - /*if(m_orphaned_by_tx.count(id)) - return true;*/ - if(m_invalid_blocks.count(id)) - return true; - - return false; -} -//------------------------------------------------------------------ -bool blockchain_storage::handle_block_to_main_chain(const block& bl, block_verification_context& bvc) -{ - crypto::hash id = get_block_hash(bl); - return handle_block_to_main_chain(bl, id, bvc); -} -//------------------------------------------------------------------ -bool blockchain_storage::push_transaction_to_global_outs_index(const transaction& tx, const crypto::hash& tx_id, std::vector<uint64_t>& global_indexes) -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - size_t i = 0; - BOOST_FOREACH(const auto& ot, tx.vout) - { - outputs_container::mapped_type& amount_index = m_outputs[ot.amount]; - amount_index.push_back(std::pair<crypto::hash, size_t>(tx_id, i)); - global_indexes.push_back(amount_index.size()-1); - ++i; - } - return true; -} -//------------------------------------------------------------------ -size_t blockchain_storage::get_total_transactions() const -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - return m_transactions.size(); -} -//------------------------------------------------------------------ -bool blockchain_storage::get_outs(uint64_t amount, std::list<crypto::public_key>& pkeys) const -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - auto it = m_outputs.find(amount); - if(it == m_outputs.end()) - return true; - - BOOST_FOREACH(const auto& out_entry, it->second) - { - auto tx_it = m_transactions.find(out_entry.first); - CHECK_AND_ASSERT_MES(tx_it != m_transactions.end(), false, "transactions outs global index consistency broken: wrong tx id in index"); - CHECK_AND_ASSERT_MES(tx_it->second.tx.vout.size() > out_entry.second, false, "transactions outs global index consistency broken: index in tx_outx more then size"); - CHECK_AND_ASSERT_MES(tx_it->second.tx.vout[out_entry.second].target.type() == typeid(txout_to_key), false, "transactions outs global index consistency broken: index in tx_outx more then size"); - pkeys.push_back(boost::get<txout_to_key>(tx_it->second.tx.vout[out_entry.second].target).key); - } - - return true; -} -//------------------------------------------------------------------ -bool blockchain_storage::pop_transaction_from_global_index(const transaction& tx, const crypto::hash& tx_id) -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - size_t i = tx.vout.size()-1; - BOOST_REVERSE_FOREACH(const auto& ot, tx.vout) - { - auto it = m_outputs.find(ot.amount); - CHECK_AND_ASSERT_MES(it != m_outputs.end(), false, "transactions outs global index consistency broken"); - CHECK_AND_ASSERT_MES(it->second.size(), false, "transactions outs global index: empty index for amount: " << ot.amount); - CHECK_AND_ASSERT_MES(it->second.back().first == tx_id , false, "transactions outs global index consistency broken: tx id missmatch"); - CHECK_AND_ASSERT_MES(it->second.back().second == i, false, "transactions outs global index consistency broken: in transaction index missmatch"); - it->second.pop_back(); - --i; - } - return true; -} -//------------------------------------------------------------------ -bool blockchain_storage::add_transaction_from_block(const transaction& tx, const crypto::hash& tx_id, const crypto::hash& bl_id, uint64_t bl_height, size_t blob_size) -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - struct add_transaction_input_visitor: public boost::static_visitor<bool> - { - key_images_container& m_spent_keys; - const crypto::hash& m_tx_id; - const crypto::hash& m_bl_id; - add_transaction_input_visitor(key_images_container& spent_keys, const crypto::hash& tx_id, const crypto::hash& bl_id):m_spent_keys(spent_keys), m_tx_id(tx_id), m_bl_id(bl_id) - {} - bool operator()(const txin_to_key& in) const - { - const crypto::key_image& ki = in.k_image; - auto r = m_spent_keys.insert(ki); - if(!r.second) - { - //double spend detected - LOG_PRINT_L1("tx with id: " << m_tx_id << " in block id: " << m_bl_id << " has input marked as spent with key image: " << ki << ", block declined"); - return false; - } - return true; - } - - bool operator()(const txin_gen& tx) const{return true;} - bool operator()(const txin_to_script& tx) const{return false;} - bool operator()(const txin_to_scripthash& tx) const{return false;} - }; - - BOOST_FOREACH(const txin_v& in, tx.vin) - { - if(!boost::apply_visitor(add_transaction_input_visitor(m_spent_keys, tx_id, bl_id), in)) - { - LOG_PRINT_L1("critical internal error: add_transaction_input_visitor failed. but here key_images should be checked"); - purge_transaction_keyimages_from_blockchain(tx, false); - return false; - } - } - transaction_chain_entry ch_e; - ch_e.m_keeper_block_height = bl_height; - ch_e.m_blob_size = blob_size; - ch_e.tx = tx; - auto i_r = m_transactions.insert(std::pair<crypto::hash, transaction_chain_entry>(tx_id, ch_e)); - if(!i_r.second) - { - LOG_PRINT_L1("tx with id: " << tx_id << " in block id: " << bl_id << " already in blockchain"); - return false; - } - bool r = push_transaction_to_global_outs_index(tx, tx_id, i_r.first->second.m_global_output_indexes); - CHECK_AND_ASSERT_MES(r, false, "failed to return push_transaction_to_global_outs_index tx id " << tx_id); - LOG_PRINT_L2("Added transaction to blockchain history:" << ENDL - << "tx_id: " << tx_id << ENDL - << "inputs: " << tx.vin.size() << ", outs: " << tx.vout.size() << ", spend money: " << print_money(get_outs_money_amount(tx)) << "(fee: " << (is_coinbase(tx) ? "0[coinbase]" : print_money(get_tx_fee(tx))) << ")"); - return true; -} -//------------------------------------------------------------------ -bool blockchain_storage::get_tx_outputs_gindexs(const crypto::hash& tx_id, std::vector<uint64_t>& indexs) const -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - auto it = m_transactions.find(tx_id); - if(it == m_transactions.end()) - { - LOG_PRINT_RED_L1("warning: get_tx_outputs_gindexs failed to find transaction with id = " << tx_id); - return false; - } - - CHECK_AND_ASSERT_MES(it->second.m_global_output_indexes.size(), false, "internal error: global indexes for transaction " << tx_id << " is empty"); - indexs = it->second.m_global_output_indexes; - return true; -} -//------------------------------------------------------------------ -bool blockchain_storage::check_tx_inputs(const transaction& tx, uint64_t& max_used_block_height, crypto::hash& max_used_block_id) const -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - bool res = check_tx_inputs(tx, &max_used_block_height); - if(!res) return false; - CHECK_AND_ASSERT_MES(max_used_block_height < m_blocks.size(), false, "internal error: max used block index=" << max_used_block_height << " is not less then blockchain size = " << m_blocks.size()); - get_block_hash(m_blocks[max_used_block_height].bl, max_used_block_id); - return true; -} -//------------------------------------------------------------------ -bool blockchain_storage::have_tx_keyimges_as_spent(const transaction &tx) const -{ - BOOST_FOREACH(const txin_v& in, tx.vin) - { - CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, in_to_key, true); - if(have_tx_keyimg_as_spent(in_to_key.k_image)) - return true; - } - return false; -} -//------------------------------------------------------------------ -bool blockchain_storage::check_tx_inputs(const transaction& tx, uint64_t* pmax_used_block_height) const -{ - crypto::hash tx_prefix_hash = get_transaction_prefix_hash(tx); - return check_tx_inputs(tx, tx_prefix_hash, pmax_used_block_height); -} -//------------------------------------------------------------------ -bool blockchain_storage::check_tx_inputs(const transaction& tx, const crypto::hash& tx_prefix_hash, uint64_t* pmax_used_block_height) const -{ - size_t sig_index = 0; - if(pmax_used_block_height) - *pmax_used_block_height = 0; - - BOOST_FOREACH(const auto& txin, tx.vin) - { - CHECK_AND_ASSERT_MES(txin.type() == typeid(txin_to_key), false, "wrong type id in tx input at blockchain_storage::check_tx_inputs"); - const txin_to_key& in_to_key = boost::get<txin_to_key>(txin); - - CHECK_AND_ASSERT_MES(in_to_key.key_offsets.size(), false, "empty in_to_key.key_offsets in transaction with id " << get_transaction_hash(tx)); - - if(have_tx_keyimg_as_spent(in_to_key.k_image)) - { - LOG_PRINT_L1("Key image already spent in blockchain: " << epee::string_tools::pod_to_hex(in_to_key.k_image)); - return false; - } - - CHECK_AND_ASSERT_MES(sig_index < tx.signatures.size(), false, "wrong transaction: not signature entry for input with index= " << sig_index); - if(!check_tx_input(in_to_key, tx_prefix_hash, tx.signatures[sig_index], pmax_used_block_height)) - { - LOG_PRINT_L1("Failed to check ring signature for tx " << get_transaction_hash(tx)); - return false; - } - - sig_index++; - } - - return true; -} -//------------------------------------------------------------------ -bool blockchain_storage::is_tx_spendtime_unlocked(uint64_t unlock_time) const -{ - if(unlock_time < CRYPTONOTE_MAX_BLOCK_NUMBER) - { - //interpret as block index - if(get_current_blockchain_height()-1 + CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS >= unlock_time) - return true; - else - return false; - }else - { - //interpret as time - uint64_t current_time = static_cast<uint64_t>(time(NULL)); - if(current_time + CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS_V1 >= unlock_time) - return true; - else - return false; - } - return false; -} -//------------------------------------------------------------------ -bool blockchain_storage::check_tx_input(const txin_to_key& txin, const crypto::hash& tx_prefix_hash, const std::vector<crypto::signature>& sig, uint64_t* pmax_related_block_height) const -{ - CRITICAL_REGION_LOCAL(m_blockchain_lock); - - struct outputs_visitor - { - std::vector<const crypto::public_key *>& m_results_collector; - const blockchain_storage& m_bch; - outputs_visitor(std::vector<const crypto::public_key *>& results_collector, const blockchain_storage& bch):m_results_collector(results_collector), m_bch(bch) - {} - bool handle_output(const transaction& tx, const tx_out& out) - { - //check tx unlock time - if(!m_bch.is_tx_spendtime_unlocked(tx.unlock_time)) - { - LOG_PRINT_L1("One of outputs for one of inputs has wrong tx.unlock_time = " << tx.unlock_time); - return false; - } - - if(out.target.type() != typeid(txout_to_key)) - { - LOG_PRINT_L1("Output has wrong type id, which=" << out.target.which()); - return false; - } - - m_results_collector.push_back(&boost::get<txout_to_key>(out.target).key); - return true; - } - }; - - //check ring signature - std::vector<const crypto::public_key *> output_keys; - outputs_visitor vi(output_keys, *this); - if(!scan_outputkeys_for_indexes(txin, vi, pmax_related_block_height)) - { - LOG_PRINT_L1("Failed to get output keys for tx with amount = " << print_money(txin.amount) << " and count indexes " << txin.key_offsets.size()); - return false; - } - - if(txin.key_offsets.size() != output_keys.size()) - { - LOG_PRINT_L1("Output keys for tx with amount = " << txin.amount << " and count indexes " << txin.key_offsets.size() << " returned wrong keys count " << output_keys.size()); - return false; - } - CHECK_AND_ASSERT_MES(sig.size() == output_keys.size(), false, "internal error: tx signatures count=" << sig.size() << " mismatch with outputs keys count for inputs=" << output_keys.size()); - if(m_is_in_checkpoint_zone) - return true; - return crypto::check_ring_signature(tx_prefix_hash, txin.k_image, output_keys, sig.data()); -} -//------------------------------------------------------------------ -uint64_t blockchain_storage::get_adjusted_time() const -{ - //TODO: add collecting median time - return time(NULL); -} -//------------------------------------------------------------------ -bool blockchain_storage::check_block_timestamp_main(const block& b) const -{ - if(b.timestamp > get_adjusted_time() + CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT) - { - LOG_PRINT_L1("Timestamp of block with id: " << get_block_hash(b) << ", " << b.timestamp << ", bigger than adjusted time + 2 hours"); - return false; - } - - std::vector<uint64_t> timestamps; - size_t offset = m_blocks.size() <= BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW ? 0: m_blocks.size()- BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW; - for(;offset!= m_blocks.size(); ++offset) - timestamps.push_back(m_blocks[offset].bl.timestamp); - - return check_block_timestamp(std::move(timestamps), b); -} -//------------------------------------------------------------------ -bool blockchain_storage::check_block_timestamp(std::vector<uint64_t> timestamps, const block& b) const -{ - if(timestamps.size() < BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW) - return true; - - uint64_t median_ts = epee::misc_utils::median(timestamps); - - if(b.timestamp < median_ts) - { - LOG_PRINT_L1("Timestamp of block with id: " << get_block_hash(b) << ", " << b.timestamp << ", less than median of last " << BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW << " blocks, " << median_ts); - return false; - } - - return true; -} -//------------------------------------------------------------------ -bool blockchain_storage::handle_block_to_main_chain(const block& bl, const crypto::hash& id, block_verification_context& bvc) -{ - TIME_MEASURE_START(block_processing_time); - CRITICAL_REGION_LOCAL(m_blockchain_lock); - if(bl.prev_id != get_tail_id()) - { - LOG_PRINT_L1("Block with id: " << id << ENDL - << "has wrong prev_id: " << bl.prev_id << ENDL - << "expected: " << get_tail_id()); - return false; - } - - if(!check_block_timestamp_main(bl)) - { - LOG_PRINT_L1("Block with id: " << id << ENDL - << "has invalid timestamp: " << bl.timestamp); - //add_block_as_invalid(bl, id);//do not add blocks to invalid storage befor proof of work check was passed - bvc.m_verifivation_failed = true; - return false; - } - - //check proof of work - TIME_MEASURE_START(target_calculating_time); - difficulty_type current_diffic = get_difficulty_for_next_block(); - CHECK_AND_ASSERT_MES(current_diffic, false, "!!!!!!!!! difficulty overhead !!!!!!!!!"); - TIME_MEASURE_FINISH(target_calculating_time); - TIME_MEASURE_START(longhash_calculating_time); - crypto::hash proof_of_work = null_hash; - - // Formerly the code below contained an if loop with the following condition - // !m_checkpoints.is_in_checkpoint_zone(get_current_blockchain_height()) - // however, this caused the daemon to not bother checking PoW for blocks - // before checkpoints, which is very dangerous behaviour. We moved the PoW - // validation out of the next chunk of code to make sure that we correctly - // check PoW now. - proof_of_work = get_block_longhash(bl, m_blocks.size()); - - if(!check_hash(proof_of_work, current_diffic)) - { - LOG_PRINT_L1("Block with id: " << id << ENDL - << "does not have enough proof of work: " << proof_of_work << ENDL - << "unexpected difficulty: " << current_diffic ); - bvc.m_verifivation_failed = true; - return false; - } - - // If we're at a checkpoint, ensure that our hardcoded checkpoint hash - // is correct. - if(m_checkpoints.is_in_checkpoint_zone(get_current_blockchain_height())) - { - if(!m_checkpoints.check_block(get_current_blockchain_height(), id)) - { - LOG_ERROR("CHECKPOINT VALIDATION FAILED"); - bvc.m_verifivation_failed = true; - return false; - } - } - - TIME_MEASURE_FINISH(longhash_calculating_time); - - if(!prevalidate_miner_transaction(bl, m_blocks.size())) - { - LOG_PRINT_L1("Block with id: " << id - << " failed to pass prevalidation"); - bvc.m_verifivation_failed = true; - return false; - } - crypto::hash coinbase_hash = null_hash; - size_t coinbase_blob_size = 0; - get_transaction_hash(bl.miner_tx, coinbase_hash, coinbase_blob_size); - size_t cumulative_block_size = coinbase_blob_size; - //process transactions - if(!add_transaction_from_block(bl.miner_tx, coinbase_hash, id, get_current_blockchain_height(), coinbase_blob_size)) - { - LOG_PRINT_L1("Block with id: " << id << " failed to add transaction to blockchain storage"); - bvc.m_verifivation_failed = true; - return false; - } - size_t tx_processed_count = 0; - uint64_t fee_summary = 0; - BOOST_FOREACH(const crypto::hash& tx_id, bl.tx_hashes) - { - transaction tx; - size_t blob_size = 0; - uint64_t fee = 0; - bool relayed = false; - if(!m_tx_pool->take_tx(tx_id, tx, blob_size, fee, relayed)) - { - LOG_PRINT_L1("Block with id: " << id << "has at least one unknown transaction with id: " << tx_id); - purge_block_data_from_blockchain(bl, tx_processed_count); - //add_block_as_invalid(bl, id); - bvc.m_verifivation_failed = true; - return false; - } - if(!check_tx_inputs(tx)) - { - LOG_PRINT_L1("Block with id: " << id << "has at least one transaction (id: " << tx_id << ") with wrong inputs."); - cryptonote::tx_verification_context tvc = AUTO_VAL_INIT(tvc); - bool add_res = m_tx_pool->add_tx(tx, tvc, true, relayed, 1); - CHECK_AND_ASSERT_MES2(add_res, "handle_block_to_main_chain: failed to add transaction back to transaction pool"); - purge_block_data_from_blockchain(bl, tx_processed_count); - add_block_as_invalid(bl, id); - LOG_PRINT_L1("Block with id " << id << " added as invalid becouse of wrong inputs in transactions"); - bvc.m_verifivation_failed = true; - return false; - } - - if(!add_transaction_from_block(tx, tx_id, id, get_current_blockchain_height(), blob_size)) - { - LOG_PRINT_L1("Block with id: " << id << " failed to add transaction to blockchain storage"); - cryptonote::tx_verification_context tvc = AUTO_VAL_INIT(tvc); - bool add_res = m_tx_pool->add_tx(tx, tvc, true, relayed, 1); - CHECK_AND_ASSERT_MES2(add_res, "handle_block_to_main_chain: failed to add transaction back to transaction pool"); - purge_block_data_from_blockchain(bl, tx_processed_count); - bvc.m_verifivation_failed = true; - return false; - } - fee_summary += fee; - cumulative_block_size += blob_size; - ++tx_processed_count; - } - uint64_t base_reward = 0; - uint64_t already_generated_coins = m_blocks.size() ? m_blocks.back().already_generated_coins:0; - if(!validate_miner_transaction(bl, cumulative_block_size, fee_summary, base_reward, already_generated_coins)) - { - LOG_PRINT_L1("Block with id: " << id - << " has incorrect miner transaction"); - purge_block_data_from_blockchain(bl, tx_processed_count); - bvc.m_verifivation_failed = true; - return false; - } - - - block_extended_info bei = boost::value_initialized<block_extended_info>(); - bei.bl = bl; - bei.block_cumulative_size = cumulative_block_size; - bei.cumulative_difficulty = current_diffic; - - // In the "tail" state when the minimum subsidy (implemented in get_block_reward) is in effect, the number of - // coins will eventually exceed MONEY_SUPPLY and overflow a uint64. To prevent overflow, cap already_generated_coins - // at MONEY_SUPPLY. already_generated_coins is only used to compute the block subsidy and MONEY_SUPPLY yields a - // subsidy of 0 under the base formula and therefore the minimum subsidy >0 in the tail state. - - bei.already_generated_coins = base_reward < (MONEY_SUPPLY-already_generated_coins) ? already_generated_coins + base_reward : MONEY_SUPPLY; - - if(m_blocks.size()) - bei.cumulative_difficulty += m_blocks.back().cumulative_difficulty; - - bei.height = m_blocks.size(); - - auto ind_res = m_blocks_index.insert(std::pair<crypto::hash, size_t>(id, bei.height)); - if(!ind_res.second) - { - LOG_PRINT_L1("block with id: " << id << " already in block indexes"); - purge_block_data_from_blockchain(bl, tx_processed_count); - bvc.m_verifivation_failed = true; - return false; - } - - m_blocks.push_back(bei); - update_next_comulative_size_limit(); - TIME_MEASURE_FINISH(block_processing_time); - LOG_PRINT_L1("+++++ BLOCK SUCCESSFULLY ADDED" << ENDL << "id:\t" << id - << ENDL << "PoW:\t" << proof_of_work - << ENDL << "HEIGHT " << bei.height << ", difficulty:\t" << current_diffic - << ENDL << "block reward: " << print_money(fee_summary + base_reward) << "(" << print_money(base_reward) << " + " << print_money(fee_summary) - << "), coinbase_blob_size: " << coinbase_blob_size << ", cumulative size: " << cumulative_block_size - << ", " << block_processing_time << "("<< target_calculating_time << "/" << longhash_calculating_time << ")ms"); - - epee::net_utils::data_logger::get_instance().add_data("blockchain_processing_time", block_processing_time); - - bvc.m_added_to_main_chain = true; - /*if(!m_orphanes_reorganize_in_work) - review_orphaned_blocks_with_new_block_id(id, true);*/ - - m_tx_pool->on_blockchain_inc(bei.height, id); - //LOG_PRINT_L0("BLOCK: " << ENDL << "" << dump_obj_as_json(bei.bl)); - return true; -} -//------------------------------------------------------------------ -bool blockchain_storage::update_next_comulative_size_limit() -{ - std::vector<size_t> sz; - get_last_n_blocks_sizes(sz, CRYPTONOTE_REWARD_BLOCKS_WINDOW); - - uint64_t median = epee::misc_utils::median(sz); - if(median <= CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V1) - median = CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V1; - - m_current_block_cumul_sz_limit = median*2; - return true; -} -//------------------------------------------------------------------ -bool blockchain_storage::add_new_block(const block& bl_, block_verification_context& bvc) -{ - //copy block here to let modify block.target - block bl = bl_; - crypto::hash id = get_block_hash(bl); - CRITICAL_REGION_LOCAL(*m_tx_pool);//to avoid deadlock lets lock tx_pool for whole add/reorganize process - CRITICAL_REGION_LOCAL1(m_blockchain_lock); - if(have_block(id)) - { - LOG_PRINT_L3("block with id = " << id << " already exists"); - bvc.m_already_exists = true; - return false; - } - - //check that block refers to chain tail - if(!(bl.prev_id == get_tail_id())) - { - //chain switching or wrong block - bvc.m_added_to_main_chain = false; - return handle_alternative_block(bl, id, bvc); - //never relay alternative blocks - } - - return handle_block_to_main_chain(bl, id, bvc); -} -//------------------------------------------------------------------ -void blockchain_storage::check_against_checkpoints(const checkpoints& points, bool enforce) -{ - const auto& pts = points.get_points(); - - for (const auto& pt : pts) - { - // if the checkpoint is for a block we don't have yet, move on - if (pt.first >= m_blocks.size()) - { - continue; - } - - if (!points.check_block(pt.first, get_block_hash(m_blocks[pt.first].bl))) - { - // if asked to enforce checkpoints, roll back to a couple of blocks before the checkpoint - if (enforce) - { - LOG_ERROR("Local blockchain failed to pass a checkpoint, rolling back!"); - std::list<block> empty; - rollback_blockchain_switching(empty, pt.first - 2); - } - else - { - LOG_ERROR("WARNING: local blockchain failed to pass a MoneroPulse checkpoint, and you could be on a fork. You should either sync up from scratch, OR download a fresh blockchain bootstrap, OR enable checkpoint enforcing with the --enforce-dns-checkpointing command-line option"); - } - } - } -} -//------------------------------------------------------------------ -// returns false if any of the checkpoints loading returns false. -// That should happen only if a checkpoint is added that conflicts -// with an existing checkpoint. -bool blockchain_storage::update_checkpoints(const std::string& file_path, bool check_dns) -{ - if (!m_checkpoints.load_checkpoints_from_json(file_path)) - { - return false; - } - - // if we're checking both dns and json, load checkpoints from dns. - // if we're not hard-enforcing dns checkpoints, handle accordingly - if (m_enforce_dns_checkpoints && check_dns) - { - if (!m_checkpoints.load_checkpoints_from_dns()) - { - return false; - } - } - else if (check_dns) - { - checkpoints dns_points; - dns_points.load_checkpoints_from_dns(m_testnet); - if (m_checkpoints.check_for_conflicts(dns_points)) - { - check_against_checkpoints(dns_points, false); - } - else - { - LOG_PRINT_L0("One or more checkpoints fetched from DNS conflicted with existing checkpoints!"); - } - } - - check_against_checkpoints(m_checkpoints, true); - - return true; -} -//------------------------------------------------------------------ -void blockchain_storage::set_enforce_dns_checkpoints(bool enforce_checkpoints) -{ - m_enforce_dns_checkpoints = enforce_checkpoints; -} -//------------------------------------------------------------------ -bool blockchain_storage::flush_txes_from_pool(const std::list<crypto::hash> &txids) -{ - bool res = true; - for (const auto &txid: txids) - { - cryptonote::transaction tx; - size_t blob_size; - uint64_t fee; - bool relayed; - LOG_PRINT_L1("Removing txid " << txid << " from the pool"); - if(!m_tx_pool->take_tx(txid, tx, blob_size, fee, relayed)) - { - LOG_PRINT_L0("Failed to remove txid " << txid << " from the pool"); - res = false; - } - } - return res; -} -//------------------------------------------------------------------ -bool blockchain_storage::for_all_key_images(std::function<bool(const crypto::key_image&)> f) const -{ - for (key_images_container::const_iterator i = m_spent_keys.begin(); i != m_spent_keys.end(); ++i) { - if (!f(*i)) - return false; - } - return true; -} -//------------------------------------------------------------------ -bool blockchain_storage::for_all_blocks(std::function<bool(uint64_t, const crypto::hash&, const block&)> f) const -{ - for (blocks_container::const_iterator i = m_blocks.begin(); i != m_blocks.end(); ++i) { - crypto::hash hash; - get_block_hash (i->bl, hash); - if (!f(i->height, hash, i->bl)) - return false; - } - return true; -} -//------------------------------------------------------------------ -bool blockchain_storage::for_all_transactions(std::function<bool(const crypto::hash&, const transaction&)> f) const -{ - for (transactions_container::const_iterator i = m_transactions.begin(); i != m_transactions.end(); ++i) { - if (!f(i->first, i->second.tx)) - return false; - } - return true; -} -//------------------------------------------------------------------ -bool blockchain_storage::for_all_outputs(std::function<bool(uint64_t, const crypto::hash&, size_t)> f) const -{ - for (outputs_container::const_iterator i = m_outputs.begin(); i != m_outputs.end(); ++i) { - for (size_t n = 0; n < i->second.size(); ++n) { - if (!f(i->first, i->second[n].first, i->second[n].second)) - return false; - } - } - return true; -} diff --git a/src/cryptonote_core/blockchain_storage.h b/src/cryptonote_core/blockchain_storage.h deleted file mode 100644 index 878202cf1..000000000 --- a/src/cryptonote_core/blockchain_storage.h +++ /dev/null @@ -1,377 +0,0 @@ -// Copyright (c) 2014-2016, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers - -#pragma once -#include <boost/serialization/serialization.hpp> -#include <boost/serialization/version.hpp> -#include <boost/serialization/list.hpp> -#include <boost/multi_index_container.hpp> -#include <boost/multi_index/global_fun.hpp> -#include <boost/multi_index/hashed_index.hpp> -#include <boost/multi_index/member.hpp> -#include <boost/foreach.hpp> -#include <atomic> - -#include "syncobj.h" -#include "string_tools.h" -#include "tx_pool.h" -#include "cryptonote_basic.h" -#include "common/util.h" -#include "cryptonote_protocol/cryptonote_protocol_defs.h" -#include "rpc/core_rpc_server_commands_defs.h" -#include "difficulty.h" -#include "cryptonote_core/cryptonote_format_utils.h" -#include "verification_context.h" -#include "crypto/hash.h" -#include "checkpoints.h" - -namespace cryptonote -{ - - /************************************************************************/ - /* */ - /************************************************************************/ - class blockchain_storage - { - public: - struct transaction_chain_entry - { - transaction tx; - uint64_t m_keeper_block_height; - size_t m_blob_size; - std::vector<uint64_t> m_global_output_indexes; - }; - - struct block_extended_info - { - block bl; - uint64_t height; - size_t block_cumulative_size; - difficulty_type cumulative_difficulty; - uint64_t already_generated_coins; - }; - - blockchain_storage(tx_memory_pool* tx_pool):m_tx_pool(tx_pool), m_current_block_cumul_sz_limit(0), m_is_in_checkpoint_zone(false), m_is_blockchain_storing(false), m_enforce_dns_checkpoints(false) - {}; - - bool init() { return init(tools::get_default_data_dir(), true); } - bool init(const std::string& config_folder, bool testnet = false); - bool deinit(); - - void set_checkpoints(checkpoints&& chk_pts) { m_checkpoints = chk_pts; } - - //bool push_new_block(); - bool get_blocks(uint64_t start_offset, size_t count, std::list<block>& blocks, std::list<transaction>& txs) const; - bool get_blocks(uint64_t start_offset, size_t count, std::list<block>& blocks) const; - bool get_alternative_blocks(std::list<block>& blocks) const; - size_t get_alternative_blocks_count() const; - crypto::hash get_block_id_by_height(uint64_t height) const; - bool get_block_by_hash(const crypto::hash &h, block &blk) const; - void get_all_known_block_ids(std::list<crypto::hash> &main, std::list<crypto::hash> &alt, std::list<crypto::hash> &invalid) const; - - template<class archive_t> - void serialize(archive_t & ar, const unsigned int version); - - bool have_tx(const crypto::hash &id) const; - bool have_tx_keyimges_as_spent(const transaction &tx) const; - bool have_tx_keyimg_as_spent(const crypto::key_image &key_im) const; - const transaction *get_tx(const crypto::hash &id) const; - - uint64_t get_current_blockchain_height() const; - crypto::hash get_tail_id() const; - crypto::hash get_tail_id(uint64_t& height) const; - difficulty_type get_difficulty_for_next_block() const; - bool add_new_block(const block& bl_, block_verification_context& bvc); - bool reset_and_set_genesis_block(const block& b); - bool create_block_template(block& b, const account_public_address& miner_address, difficulty_type& di, uint64_t& height, const blobdata& ex_nonce) const; - bool have_block(const crypto::hash& id) const; - size_t get_total_transactions() const; - bool get_outs(uint64_t amount, std::list<crypto::public_key>& pkeys) const; - bool get_short_chain_history(std::list<crypto::hash>& ids) const; - bool find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) const; - bool find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, uint64_t& starter_offset) const; - bool find_blockchain_supplement(const uint64_t req_start_block, const std::list<crypto::hash>& qblock_ids, std::list<std::pair<block, std::list<transaction> > >& blocks, uint64_t& total_height, uint64_t& start_height, size_t max_count) const; - bool handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, NOTIFY_RESPONSE_GET_OBJECTS::request& rsp); - bool handle_get_objects(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request& req, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response& res); - bool get_random_outs_for_amounts(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request& req, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response& res) const; - bool get_backward_blocks_sizes(size_t from_height, std::vector<size_t>& sz, size_t count) const; - bool get_tx_outputs_gindexs(const crypto::hash& tx_id, std::vector<uint64_t>& indexs) const; - bool store_blockchain(); - - bool check_tx_inputs(const transaction& tx, uint64_t& pmax_used_block_height, crypto::hash& max_used_block_id) const; - uint64_t get_current_cumulative_blocksize_limit() const; - bool is_storing_blockchain()const{return m_is_blockchain_storing;} - uint64_t block_difficulty(size_t i) const; - double get_avg_block_size( size_t count) const; - bool flush_txes_from_pool(const std::list<crypto::hash> &txids); - - template<class t_ids_container, class t_blocks_container, class t_missed_container> - bool get_blocks(const t_ids_container& block_ids, t_blocks_container& blocks, t_missed_container& missed_bs) const - { - CRITICAL_REGION_LOCAL(m_blockchain_lock); - - BOOST_FOREACH(const auto& bl_id, block_ids) - { - auto it = m_blocks_index.find(bl_id); - if(it == m_blocks_index.end()) - missed_bs.push_back(bl_id); - else - { - CHECK_AND_ASSERT_MES(it->second < m_blocks.size(), false, "Internal error: bl_id=" << epee::string_tools::pod_to_hex(bl_id) - << " have index record with offset="<<it->second<< ", bigger then m_blocks.size()=" << m_blocks.size()); - blocks.push_back(m_blocks[it->second].bl); - } - } - return true; - } - - template<class t_ids_container, class t_tx_container, class t_missed_container> - bool get_transactions(const t_ids_container& txs_ids, t_tx_container& txs, t_missed_container& missed_txs) const - { - CRITICAL_REGION_LOCAL(m_blockchain_lock); - - BOOST_FOREACH(const auto& tx_id, txs_ids) - { - auto it = m_transactions.find(tx_id); - if(it == m_transactions.end()) - { - transaction tx; - if(!m_tx_pool->get_transaction(tx_id, tx)) - missed_txs.push_back(tx_id); - else - txs.push_back(tx); - } - else - txs.push_back(it->second.tx); - } - return true; - } - //debug functions - void print_blockchain(uint64_t start_index, uint64_t end_index) const; - void print_blockchain_index() const; - void print_blockchain_outs(const std::string& file) const; - void check_against_checkpoints(const checkpoints& points, bool enforce); - bool update_checkpoints(const std::string& file_path, bool check_dns); - void set_enforce_dns_checkpoints(bool enforce_checkpoints); - - block get_block(uint64_t height) const { return m_blocks[height].bl; } - size_t get_block_size(uint64_t height) const { return m_blocks[height].block_cumulative_size; } - difficulty_type get_block_cumulative_difficulty(uint64_t height) const { return m_blocks[height].cumulative_difficulty; } - uint64_t get_block_coins_generated(uint64_t height) const { return m_blocks[height].already_generated_coins; } - - bool for_all_key_images(std::function<bool(const crypto::key_image&)>) const; - bool for_all_blocks(std::function<bool(uint64_t height, const crypto::hash&, const block&)>) const; - bool for_all_transactions(std::function<bool(const crypto::hash&, const transaction&)>) const; - bool for_all_outputs(std::function<bool(uint64_t amount, const crypto::hash &tx_hash, size_t tx_idx)>) const; - - // use for testing only - bool debug_pop_block_from_blockchain() { return pop_block_from_blockchain(); } - - private: - typedef std::unordered_map<crypto::hash, size_t> blocks_by_id_index; - typedef std::unordered_map<crypto::hash, transaction_chain_entry> transactions_container; - typedef std::unordered_set<crypto::key_image> key_images_container; - typedef std::vector<block_extended_info> blocks_container; - typedef std::unordered_map<crypto::hash, block_extended_info> blocks_ext_by_hash; - typedef std::unordered_map<crypto::hash, block> blocks_by_hash; - typedef std::map<uint64_t, std::vector<std::pair<crypto::hash, size_t>>> outputs_container; //crypto::hash - tx hash, size_t - index of out in transaction - - tx_memory_pool* m_tx_pool; - mutable epee::critical_section m_blockchain_lock; // TODO: add here reader/writer lock - - // main chain - blocks_container m_blocks; // height -> block_extended_info - blocks_by_id_index m_blocks_index; // crypto::hash -> height - transactions_container m_transactions; - key_images_container m_spent_keys; - size_t m_current_block_cumul_sz_limit; - - - // all alternative chains - blocks_ext_by_hash m_alternative_chains; // crypto::hash -> block_extended_info - - // some invalid blocks - blocks_ext_by_hash m_invalid_blocks; // crypto::hash -> block_extended_info - outputs_container m_outputs; - - - std::string m_config_folder; - checkpoints m_checkpoints; - std::atomic<bool> m_is_in_checkpoint_zone; - std::atomic<bool> m_is_blockchain_storing; - - bool m_enforce_dns_checkpoints; - bool m_testnet; - - // made private for consistency with blockchain.h - template<class visitor_t> - bool scan_outputkeys_for_indexes(const txin_to_key& tx_in_to_key, visitor_t& vis, uint64_t* pmax_related_block_height = NULL) const; - bool check_tx_input(const txin_to_key& txin, const crypto::hash& tx_prefix_hash, const std::vector<crypto::signature>& sig, uint64_t* pmax_related_block_height = NULL) const; - bool check_tx_inputs(const transaction& tx, const crypto::hash& tx_prefix_hash, uint64_t* pmax_used_block_height = NULL) const; - bool check_tx_inputs(const transaction& tx, uint64_t* pmax_used_block_height = NULL) const; - - bool switch_to_alternative_blockchain(std::list<blocks_ext_by_hash::iterator>& alt_chain, bool discard_disconnected_chain); - bool pop_block_from_blockchain(); - bool purge_block_data_from_blockchain(const block& b, size_t processed_tx_count); - bool purge_transaction_from_blockchain(const crypto::hash& tx_id); - bool purge_transaction_keyimages_from_blockchain(const transaction& tx, bool strict_check); - - bool handle_block_to_main_chain(const block& bl, block_verification_context& bvc); - bool handle_block_to_main_chain(const block& bl, const crypto::hash& id, block_verification_context& bvc); - bool handle_alternative_block(const block& b, const crypto::hash& id, block_verification_context& bvc); - difficulty_type get_next_difficulty_for_alternative_chain(const std::list<blocks_ext_by_hash::iterator>& alt_chain, block_extended_info& bei) const; - bool prevalidate_miner_transaction(const block& b, uint64_t height) const; - bool validate_miner_transaction(const block& b, size_t cumulative_block_size, uint64_t fee, uint64_t& base_reward, uint64_t already_generated_coins) const; - bool validate_transaction(const block& b, uint64_t height, const transaction& tx) const; - bool rollback_blockchain_switching(std::list<block>& original_chain, size_t rollback_height); - bool add_transaction_from_block(const transaction& tx, const crypto::hash& tx_id, const crypto::hash& bl_id, uint64_t bl_height, size_t blob_size); - bool push_transaction_to_global_outs_index(const transaction& tx, const crypto::hash& tx_id, std::vector<uint64_t>& global_indexes); - bool pop_transaction_from_global_index(const transaction& tx, const crypto::hash& tx_id); - bool get_last_n_blocks_sizes(std::vector<size_t>& sz, size_t count) const; - bool add_out_to_get_random_outs(const std::vector<std::pair<crypto::hash, size_t> >& amount_outs, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& result_outs, uint64_t amount, size_t i) const; - bool is_tx_spendtime_unlocked(uint64_t unlock_time) const; - bool add_block_as_invalid(const block& bl, const crypto::hash& h); - bool add_block_as_invalid(const block_extended_info& bei, const crypto::hash& h); - size_t find_end_of_allowed_index(const std::vector<std::pair<crypto::hash, size_t> >& amount_outs) const; - bool check_block_timestamp_main(const block& b) const; - bool check_block_timestamp(std::vector<uint64_t> timestamps, const block& b) const; - uint64_t get_adjusted_time() const; - bool complete_timestamps_vector(uint64_t start_height, std::vector<uint64_t>& timestamps) const; - bool update_next_comulative_size_limit(); - bool store_genesis_block(bool testnet, bool check_added = false); - }; - - - /************************************************************************/ - /* */ - /************************************************************************/ - - #define CURRENT_BLOCKCHAIN_STORAGE_ARCHIVE_VER 12 - - template<class archive_t> - void blockchain_storage::serialize(archive_t & ar, const unsigned int version) - { - if(version < 11) - return; - CRITICAL_REGION_LOCAL(m_blockchain_lock); - ar & m_blocks; - ar & m_blocks_index; - ar & m_transactions; - ar & m_spent_keys; - ar & m_alternative_chains; - ar & m_outputs; - ar & m_invalid_blocks; - ar & m_current_block_cumul_sz_limit; - /*serialization bug workaround*/ - if(version > 11) - { - uint64_t total_check_count = m_blocks.size() + m_blocks_index.size() + m_transactions.size() + m_spent_keys.size() + m_alternative_chains.size() + m_outputs.size() + m_invalid_blocks.size() + m_current_block_cumul_sz_limit; - if(archive_t::is_saving::value) - { - ar & total_check_count; - }else - { - uint64_t total_check_count_loaded = 0; - ar & total_check_count_loaded; - if(total_check_count != total_check_count_loaded) - { - LOG_ERROR("Blockchain storage data corruption detected. total_count loaded from file = " << total_check_count_loaded << ", expected = " << total_check_count); - - LOG_PRINT_L0("Blockchain storage:" << ENDL << - "m_blocks: " << m_blocks.size() << ENDL << - "m_blocks_index: " << m_blocks_index.size() << ENDL << - "m_transactions: " << m_transactions.size() << ENDL << - "m_spent_keys: " << m_spent_keys.size() << ENDL << - "m_alternative_chains: " << m_alternative_chains.size() << ENDL << - "m_outputs: " << m_outputs.size() << ENDL << - "m_invalid_blocks: " << m_invalid_blocks.size() << ENDL << - "m_current_block_cumul_sz_limit: " << m_current_block_cumul_sz_limit); - - throw std::runtime_error("Blockchain data corruption"); - } - } - } - - - LOG_PRINT_L2("Blockchain storage:" << ENDL << - "m_blocks: " << m_blocks.size() << ENDL << - "m_blocks_index: " << m_blocks_index.size() << ENDL << - "m_transactions: " << m_transactions.size() << ENDL << - "m_spent_keys: " << m_spent_keys.size() << ENDL << - "m_alternative_chains: " << m_alternative_chains.size() << ENDL << - "m_outputs: " << m_outputs.size() << ENDL << - "m_invalid_blocks: " << m_invalid_blocks.size() << ENDL << - "m_current_block_cumul_sz_limit: " << m_current_block_cumul_sz_limit); - } - - //------------------------------------------------------------------ - template<class visitor_t> - bool blockchain_storage::scan_outputkeys_for_indexes(const txin_to_key& tx_in_to_key, visitor_t& vis, uint64_t* pmax_related_block_height) const - { - CRITICAL_REGION_LOCAL(m_blockchain_lock); - auto it = m_outputs.find(tx_in_to_key.amount); - if(it == m_outputs.end() || !tx_in_to_key.key_offsets.size()) - return false; - - std::vector<uint64_t> absolute_offsets = relative_output_offsets_to_absolute(tx_in_to_key.key_offsets); - - - const std::vector<std::pair<crypto::hash, size_t> >& amount_outs_vec = it->second; - size_t count = 0; - BOOST_FOREACH(uint64_t i, absolute_offsets) - { - if(i >= amount_outs_vec.size() ) - { - LOG_PRINT_L0("Wrong index in transaction inputs: " << i << ", expected maximum " << amount_outs_vec.size() - 1); - return false; - } - transactions_container::const_iterator tx_it = m_transactions.find(amount_outs_vec[i].first); - CHECK_AND_ASSERT_MES(tx_it != m_transactions.end(), false, "Wrong transaction id in output indexes: " << epee::string_tools::pod_to_hex(amount_outs_vec[i].first)); - CHECK_AND_ASSERT_MES(amount_outs_vec[i].second < tx_it->second.tx.vout.size(), false, - "Wrong index in transaction outputs: " << amount_outs_vec[i].second << ", expected less then " << tx_it->second.tx.vout.size()); - if(!vis.handle_output(tx_it->second.tx, tx_it->second.tx.vout[amount_outs_vec[i].second])) - { - LOG_PRINT_L0("Failed to handle_output for output no = " << count << ", with absolute offset " << i); - return false; - } - if(count++ == absolute_offsets.size()-1 && pmax_related_block_height) - { - if(*pmax_related_block_height < tx_it->second.m_keeper_block_height) - *pmax_related_block_height = tx_it->second.m_keeper_block_height; - } - } - - return true; - } -} - - - -BOOST_CLASS_VERSION(cryptonote::blockchain_storage, CURRENT_BLOCKCHAIN_STORAGE_ARCHIVE_VER) diff --git a/src/cryptonote_core/cryptonote_basic.h b/src/cryptonote_core/cryptonote_basic.h index ed62e26f4..f54b8c2b3 100644 --- a/src/cryptonote_core/cryptonote_basic.h +++ b/src/cryptonote_core/cryptonote_basic.h @@ -49,9 +49,7 @@ #include "crypto/hash.h" #include "misc_language.h" #include "tx_extra.h" - -#define DB_MEMORY 1 -#define DB_LMDB 2 +#include "ringct/rctTypes.h" namespace cryptonote { @@ -175,14 +173,14 @@ namespace cryptonote BEGIN_SERIALIZE() VARINT_FIELD(version) - if(CURRENT_TRANSACTION_VERSION < version) return false; + if(version == 0 || CURRENT_TRANSACTION_VERSION < version) return false; VARINT_FIELD(unlock_time) FIELD(vin) FIELD(vout) FIELD(extra) END_SERIALIZE() - protected: + public: transaction_prefix(){} }; @@ -190,6 +188,7 @@ namespace cryptonote { public: std::vector<std::vector<crypto::signature> > signatures; //count signatures always the same as inputs count + rct::rctSig rct_signatures; transaction(); virtual ~transaction(); @@ -198,34 +197,59 @@ namespace cryptonote BEGIN_SERIALIZE_OBJECT() FIELDS(*static_cast<transaction_prefix *>(this)) - ar.tag("signatures"); - ar.begin_array(); - PREPARE_CUSTOM_VECTOR_SERIALIZATION(vin.size(), signatures); - bool signatures_not_expected = signatures.empty(); - if (!signatures_not_expected && vin.size() != signatures.size()) - return false; - - for (size_t i = 0; i < vin.size(); ++i) + if (version == 1) { - size_t signature_size = get_signature_size(vin[i]); - if (signatures_not_expected) + ar.tag("signatures"); + ar.begin_array(); + PREPARE_CUSTOM_VECTOR_SERIALIZATION(vin.size(), signatures); + bool signatures_not_expected = signatures.empty(); + if (!signatures_not_expected && vin.size() != signatures.size()) + return false; + + for (size_t i = 0; i < vin.size(); ++i) { - if (0 == signature_size) - continue; - else + size_t signature_size = get_signature_size(vin[i]); + if (signatures_not_expected) + { + if (0 == signature_size) + continue; + else + return false; + } + + PREPARE_CUSTOM_VECTOR_SERIALIZATION(signature_size, signatures[i]); + if (signature_size != signatures[i].size()) return false; - } - - PREPARE_CUSTOM_VECTOR_SERIALIZATION(signature_size, signatures[i]); - if (signature_size != signatures[i].size()) - return false; - FIELDS(signatures[i]); + FIELDS(signatures[i]); - if (vin.size() - i > 1) - ar.delimit_array(); + if (vin.size() - i > 1) + ar.delimit_array(); + } + ar.end_array(); + } + else + { + FIELD(rct_signatures) + switch (rct_signatures.type) + { + case rct::RCTTypeNull: + break; + case rct::RCTTypeSimple: + if (rct_signatures.mixRing.size() && rct_signatures.mixRing.size() != vin.size()) + return false; + break; + case rct::RCTTypeFull: + for (size_t i = 0; i < rct_signatures.mixRing.size(); ++i) + { + if (rct_signatures.mixRing[i].size() != vin.size()) + return false; + } + break; + default: + return false; + } } - ar.end_array(); END_SERIALIZE() private: @@ -248,12 +272,13 @@ namespace cryptonote inline void transaction::set_null() { - version = 0; + version = 1; unlock_time = 0; vin.clear(); vout.clear(); extra.clear(); signatures.clear(); + rct_signatures.type = rct::RCTTypeNull; } inline diff --git a/src/cryptonote_core/cryptonote_boost_serialization.h b/src/cryptonote_core/cryptonote_boost_serialization.h index 79ceec5bc..c91f78c58 100644 --- a/src/cryptonote_core/cryptonote_boost_serialization.h +++ b/src/cryptonote_core/cryptonote_boost_serialization.h @@ -37,9 +37,13 @@ #include <boost/serialization/map.hpp> #include <boost/foreach.hpp> #include <boost/serialization/is_bitwise_serializable.hpp> +#include <boost/archive/binary_iarchive.hpp> +#include <boost/archive/binary_oarchive.hpp> #include "cryptonote_basic.h" #include "common/unordered_containers_boost_serialization.h" #include "crypto/crypto.h" +#include "ringct/rctTypes.h" +#include "ringct/rctOps.h" //namespace cryptonote { namespace boost @@ -140,6 +144,16 @@ namespace boost template <class Archive> + inline void serialize(Archive &a, cryptonote::transaction_prefix &x, const boost::serialization::version_type ver) + { + a & x.version; + a & x.unlock_time; + a & x.vin; + a & x.vout; + a & x.extra; + } + + template <class Archive> inline void serialize(Archive &a, cryptonote::transaction &x, const boost::serialization::version_type ver) { a & x.version; @@ -147,7 +161,10 @@ namespace boost a & x.vin; a & x.vout; a & x.extra; - a & x.signatures; + if (x.version == 1) + a & x.signatures; + else + a & x.rct_signatures; } @@ -163,6 +180,107 @@ namespace boost a & b.miner_tx; a & b.tx_hashes; } + + template <class Archive> + inline void serialize(Archive &a, rct::key &x, const boost::serialization::version_type ver) + { + a & reinterpret_cast<char (&)[sizeof(rct::key)]>(x); + } + + template <class Archive> + inline void serialize(Archive &a, rct::ctkey &x, const boost::serialization::version_type ver) + { + a & x.dest; + a & x.mask; + } + + template <class Archive> + inline void serialize(Archive &a, rct::rangeSig &x, const boost::serialization::version_type ver) + { + a & x.asig; + a & x.Ci; + } + + template <class Archive> + inline void serialize(Archive &a, rct::asnlSig &x, const boost::serialization::version_type ver) + { + a & x.L1; + a & x.s2; + a & x.s; + } + + template <class Archive> + inline void serialize(Archive &a, rct::mgSig &x, const boost::serialization::version_type ver) + { + a & x.ss; + a & x.cc; + // a & x.II; // not serialized, we can recover it from the tx vin + } + + template <class Archive> + inline void serialize(Archive &a, rct::ecdhTuple &x, const boost::serialization::version_type ver) + { + a & x.mask; + a & x.amount; + // a & x.senderPk; // not serialized, as we do not use it in monero currently + } + + inline void serializeOutPk(boost::archive::binary_iarchive &a, rct::ctkeyV &outPk_, const boost::serialization::version_type ver) + { + rct::keyV outPk; + a & outPk; + outPk_.resize(outPk.size()); + for (size_t n = 0; n < outPk_.size(); ++n) + { + outPk_[n].dest = rct::identity(); + outPk_[n].mask = outPk[n]; + } + } + + inline void serializeOutPk(boost::archive::binary_oarchive &a, rct::ctkeyV &outPk_, const boost::serialization::version_type ver) + { + rct::keyV outPk(outPk_.size()); + for (size_t n = 0; n < outPk_.size(); ++n) + outPk[n] = outPk_[n].mask; + a & outPk; + } + + template <class Archive> + inline void serialize(Archive &a, rct::rctSigBase &x, const boost::serialization::version_type ver) + { + a & x.type; + if (x.type == rct::RCTTypeNull) + return; + if (x.type != rct::RCTTypeFull && x.type != rct::RCTTypeSimple) + throw boost::archive::archive_exception(boost::archive::archive_exception::other_exception, "Unsupported rct type"); + // a & x.message; message is not serialized, as it can be reconstructed from the tx data + // a & x.mixRing; mixRing is not serialized, as it can be reconstructed from the offsets + if (x.type == rct::RCTTypeSimple) + a & x.pseudoOuts; + a & x.ecdhInfo; + serializeOutPk(a, x.outPk, ver); + a & x.txnFee; + } + + template <class Archive> + inline void serialize(Archive &a, rct::rctSig &x, const boost::serialization::version_type ver) + { + a & x.type; + if (x.type == rct::RCTTypeNull) + return; + if (x.type != rct::RCTTypeFull && x.type != rct::RCTTypeSimple) + throw boost::archive::archive_exception(boost::archive::archive_exception::other_exception, "Unsupported rct type"); + // a & x.message; message is not serialized, as it can be reconstructed from the tx data + // a & x.mixRing; mixRing is not serialized, as it can be reconstructed from the offsets + if (x.type == rct::RCTTypeSimple) + a & x.pseudoOuts; + a & x.ecdhInfo; + serializeOutPk(a, x.outPk, ver); + a & x.txnFee; + //-------------- + a & x.p.rangeSigs; + a & x.p.MGs; + } } } diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 26748aceb..73edde1b7 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -43,6 +43,7 @@ using namespace epee; #include "misc_language.h" #include <csignal> #include "cryptonote_core/checkpoints.h" +#include "ringct/rctTypes.h" #include "blockchain_db/blockchain_db.h" #include "blockchain_db/lmdb/db_lmdb.h" #if defined(BERKELEY_DB) @@ -57,11 +58,7 @@ namespace cryptonote //----------------------------------------------------------------------------------------------- core::core(i_cryptonote_protocol* pprotocol): m_mempool(m_blockchain_storage), -#if BLOCKCHAIN_DB == DB_LMDB m_blockchain_storage(m_mempool), -#else - m_blockchain_storage(&m_mempool), -#endif m_miner(this), m_miner_address(boost::value_initialized<account_public_address>()), m_starter_message_showed(false), @@ -257,7 +254,6 @@ namespace cryptonote r = m_mempool.init(m_fakechain ? std::string() : m_config_folder); CHECK_AND_ASSERT_MES(r, false, "Failed to initialize memory pool"); -#if BLOCKCHAIN_DB == DB_LMDB std::string db_type = command_line::get_arg(vm, command_line::arg_db_type); std::string db_sync_mode = command_line::get_arg(vm, command_line::arg_db_sync_mode); bool fast_sync = command_line::get_arg(vm, command_line::arg_fast_block_sync) != 0; @@ -293,20 +289,23 @@ namespace cryptonote catch (...) { } BlockchainDB* db = nullptr; - uint64_t BDB_FAST_MODE = 0; - uint64_t BDB_FASTEST_MODE = 0; - uint64_t BDB_SAFE_MODE = 0; + uint64_t DBS_FAST_MODE = 0; + uint64_t DBS_FASTEST_MODE = 0; + uint64_t DBS_SAFE_MODE = 0; if (db_type == "lmdb") { db = new BlockchainLMDB(); + DBS_SAFE_MODE = MDB_NORDAHEAD; + DBS_FAST_MODE = MDB_NORDAHEAD | MDB_NOSYNC; + DBS_FASTEST_MODE = MDB_NORDAHEAD | MDB_NOSYNC | MDB_WRITEMAP | MDB_MAPASYNC; } else if (db_type == "berkeley") { #if defined(BERKELEY_DB) db = new BlockchainBDB(); - BDB_FAST_MODE = DB_TXN_WRITE_NOSYNC; - BDB_FASTEST_MODE = DB_TXN_NOSYNC; - BDB_SAFE_MODE = DB_TXN_SYNC; + DBS_FAST_MODE = DB_TXN_WRITE_NOSYNC; + DBS_FASTEST_MODE = DB_TXN_NOSYNC; + DBS_SAFE_MODE = DB_TXN_SYNC; #else LOG_ERROR("BerkeleyDB support disabled."); return false; @@ -329,7 +328,6 @@ namespace cryptonote try { uint64_t db_flags = 0; - bool islmdb = db_type == "lmdb"; std::vector<std::string> options; boost::trim(db_sync_mode); @@ -338,9 +336,8 @@ namespace cryptonote for(const auto &option : options) LOG_PRINT_L0("option: " << option); - // temporarily default to fastest:async:1000 - uint64_t DEFAULT_FLAGS = islmdb ? MDB_WRITEMAP | MDB_MAPASYNC | MDB_NORDAHEAD | MDB_NOMETASYNC | MDB_NOSYNC : - BDB_FASTEST_MODE; + // default to fast:async:1000 + uint64_t DEFAULT_FLAGS = DBS_FAST_MODE; if(options.size() == 0) { @@ -354,13 +351,13 @@ namespace cryptonote if(options[0] == "safe") { safemode = true; - db_flags = islmdb ? MDB_NORDAHEAD : BDB_SAFE_MODE; + db_flags = DBS_SAFE_MODE; sync_mode = db_nosync; } else if(options[0] == "fast") - db_flags = islmdb ? MDB_NOMETASYNC | MDB_NOSYNC | MDB_NORDAHEAD : BDB_FAST_MODE; + db_flags = DBS_FAST_MODE; else if(options[0] == "fastest") - db_flags = islmdb ? MDB_WRITEMAP | MDB_MAPASYNC | MDB_NORDAHEAD | MDB_NOMETASYNC | MDB_NOSYNC : BDB_FASTEST_MODE; + db_flags = DBS_FASTEST_MODE; else db_flags = DEFAULT_FLAGS; } @@ -405,9 +402,6 @@ namespace cryptonote bool show_time_stats = command_line::get_arg(vm, command_line::arg_show_time_stats) != 0; m_blockchain_storage.set_show_time_stats(show_time_stats); -#else - r = m_blockchain_storage.init(m_config_folder, m_testnet); -#endif CHECK_AND_ASSERT_MES(r, false, "Failed to initialize blockchain storage"); // load json & DNS checkpoints, and verify them @@ -505,6 +499,15 @@ namespace cryptonote } //std::cout << "!"<< tx.vin.size() << std::endl; + uint8_t version = m_blockchain_storage.get_current_hard_fork_version(); + const size_t max_tx_version = version == 1 ? 1 : 2; + if (tx.version == 0 || tx.version > max_tx_version) + { + // v2 is the latest one we know + tvc.m_verifivation_failed = true; + return false; + } + if(!check_tx_syntax(tx)) { LOG_PRINT_L1("WRONG TRANSACTION BLOB, Failed to check tx " << tx_hash << " syntax, rejected"); @@ -560,6 +563,14 @@ namespace cryptonote LOG_PRINT_RED_L1("tx with invalid outputs, rejected for tx id= " << get_transaction_hash(tx)); return false; } + if (tx.version > 1) + { + if (tx.rct_signatures.outPk.size() != tx.vout.size()) + { + LOG_PRINT_RED_L1("tx with mismatched vout/outPk count, rejected for tx id= " << get_transaction_hash(tx)); + return false; + } + } if(!check_money_overflow(tx)) { @@ -567,15 +578,19 @@ namespace cryptonote return false; } - uint64_t amount_in = 0; - get_inputs_money_amount(tx, amount_in); - uint64_t amount_out = get_outs_money_amount(tx); - - if(amount_in <= amount_out) + if (tx.version == 1) { - LOG_PRINT_RED_L1("tx with wrong amounts: ins " << amount_in << ", outs " << amount_out << ", rejected for tx id= " << get_transaction_hash(tx)); - return false; + uint64_t amount_in = 0; + get_inputs_money_amount(tx, amount_in); + uint64_t amount_out = get_outs_money_amount(tx); + + if(amount_in <= amount_out) + { + LOG_PRINT_RED_L1("tx with wrong amounts: ins " << amount_in << ", outs " << amount_out << ", rejected for tx id= " << get_transaction_hash(tx)); + return false; + } } + // for version > 1, ringct signatures check verifies amounts match if(!keeped_by_block && get_object_blobsize(tx) >= m_blockchain_storage.get_current_cumulative_blocksize_limit() - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE) { @@ -714,6 +729,11 @@ namespace cryptonote return m_blockchain_storage.get_outs(req, res); } //----------------------------------------------------------------------------------------------- + bool core::get_random_rct_outs(const COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::request& req, COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::response& res) const + { + return m_blockchain_storage.get_random_rct_outs(req, res); + } + //----------------------------------------------------------------------------------------------- bool core::get_tx_outputs_gindexs(const crypto::hash& tx_id, std::vector<uint64_t>& indexs) const { return m_blockchain_storage.get_tx_outputs_gindexs(tx_id, indexs); @@ -780,18 +800,14 @@ namespace cryptonote //----------------------------------------------------------------------------------------------- bool core::prepare_handle_incoming_blocks(const std::list<block_complete_entry> &blocks) { -#if BLOCKCHAIN_DB == DB_LMDB m_blockchain_storage.prepare_handle_incoming_blocks(blocks); -#endif return true; } //----------------------------------------------------------------------------------------------- bool core::cleanup_handle_incoming_blocks(bool force_sync) { -#if BLOCKCHAIN_DB == DB_LMDB m_blockchain_storage.cleanup_handle_incoming_blocks(force_sync); -#endif return true; } @@ -918,11 +934,6 @@ namespace cryptonote m_starter_message_showed = true; } -#if BLOCKCHAIN_DB == DB_LMDB - // m_store_blockchain_interval.do_call(boost::bind(&Blockchain::store_blockchain, &m_blockchain_storage)); -#else - m_store_blockchain_interval.do_call(boost::bind(&blockchain_storage::store_blockchain, &m_blockchain_storage)); -#endif m_fork_moaner.do_call(boost::bind(&core::check_fork_time, this)); m_txpool_auto_relayer.do_call(boost::bind(&core::relay_txpool_transactions, this)); m_miner.on_idle(); @@ -932,7 +943,6 @@ namespace cryptonote //----------------------------------------------------------------------------------------------- bool core::check_fork_time() { -#if BLOCKCHAIN_DB == DB_LMDB HardFork::State state = m_blockchain_storage.get_hard_fork_state(); switch (state) { case HardFork::LikelyForked: @@ -951,7 +961,6 @@ namespace cryptonote default: break; } -#endif return true; } //----------------------------------------------------------------------------------------------- diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 63969bc23..d16bd6553 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -40,11 +40,7 @@ #include "cryptonote_protocol/cryptonote_protocol_handler_common.h" #include "storages/portable_storage_template_helper.h" #include "tx_pool.h" -#if BLOCKCHAIN_DB == DB_LMDB #include "blockchain.h" -#else -#include "blockchain_storage.h" -#endif #include "miner.h" #include "connection_context.h" #include "cryptonote_core/cryptonote_stat_info.h" @@ -483,6 +479,14 @@ namespace cryptonote */ bool get_outs(const COMMAND_RPC_GET_OUTPUTS::request& req, COMMAND_RPC_GET_OUTPUTS::response& res) const; + /** + * + * @copydoc Blockchain::get_random_rct_outs + * + * @note see Blockchain::get_random_rct_outs + */ + bool get_random_rct_outs(const COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::request& req, COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::response& res) const; + /** * @copydoc miner::pause @@ -498,7 +502,6 @@ namespace cryptonote */ void resume_mine(); -#if BLOCKCHAIN_DB == DB_LMDB /** * @brief gets the Blockchain instance * @@ -512,10 +515,6 @@ namespace cryptonote * @return a const reference to the Blockchain instance */ const Blockchain& get_blockchain_storage()const{return m_blockchain_storage;} -#else - blockchain_storage& get_blockchain_storage(){return m_blockchain_storage;} - const blockchain_storage& get_blockchain_storage()const{return m_blockchain_storage;} -#endif /** * @copydoc Blockchain::print_blockchain @@ -765,11 +764,7 @@ namespace cryptonote uint64_t m_test_drop_download_height = 0; //!< height under which to drop incoming blocks, if doing so tx_memory_pool m_mempool; //!< transaction pool instance -#if BLOCKCHAIN_DB == DB_LMDB Blockchain m_blockchain_storage; //!< Blockchain instance -#else - blockchain_storage m_blockchain_storage; -#endif i_cryptonote_protocol* m_pprotocol; //!< cryptonote protocol instance diff --git a/src/cryptonote_core/cryptonote_format_utils.cpp b/src/cryptonote_core/cryptonote_format_utils.cpp index ff752ae47..32b96f1fc 100644 --- a/src/cryptonote_core/cryptonote_format_utils.cpp +++ b/src/cryptonote_core/cryptonote_format_utils.cpp @@ -37,6 +37,7 @@ using namespace epee; #include "miner.h" #include "crypto/crypto.h" #include "crypto/hash.h" +#include "ringct/rctSigs.h" #define ENCRYPTED_PAYMENT_ID_TAIL 0x8d @@ -100,7 +101,7 @@ namespace cryptonote CHECK_AND_ASSERT_MES(r, false, "Failed to parse transaction from blob"); //TODO: validate tx - crypto::cn_fast_hash(tx_blob.data(), tx_blob.size(), tx_hash); + get_transaction_hash(tx, tx_hash); get_transaction_prefix_hash(tx, tx_prefix_hash); return true; } @@ -135,7 +136,10 @@ namespace cryptonote // from hard fork 2, we cut out the low significant digits. This makes the tx smaller, and // keeps the paid amount almost the same. The unpaid remainder gets pushed back to the // emission schedule - if (hard_fork_version >= 2) { + // from hard fork 4, we use a single "dusty" output. This makes the tx even smaller, + // and avoids the quantization. These outputs will be added as rct outputs with identity + // masks, to they can be used as rct inputs. + if (hard_fork_version >= 2 && hard_fork_version < 4) { block_reward = block_reward - block_reward % ::config::BASE_REWARD_CLAMP_THRESHOLD; } @@ -145,12 +149,16 @@ namespace cryptonote [&out_amounts](uint64_t a_dust) { out_amounts.push_back(a_dust); }); CHECK_AND_ASSERT_MES(1 <= max_outs, false, "max_out must be non-zero"); - if (height == 0) + if (height == 0 || hard_fork_version >= 4) { // the genesis block was not decomposed, for unknown reasons while (max_outs < out_amounts.size()) { - out_amounts[out_amounts.size() - 2] += out_amounts.back(); + //out_amounts[out_amounts.size() - 2] += out_amounts.back(); + //out_amounts.resize(out_amounts.size() - 1); + out_amounts[1] += out_amounts[0]; + for (size_t n = 1; n < out_amounts.size(); ++n) + out_amounts[n - 1] = out_amounts[n]; out_amounts.resize(out_amounts.size() - 1); } } @@ -181,7 +189,11 @@ namespace cryptonote CHECK_AND_ASSERT_MES(summary_amounts == block_reward, false, "Failed to construct miner tx, summary_amounts = " << summary_amounts << " not equal block_reward = " << block_reward); - tx.version = CURRENT_TRANSACTION_VERSION; + if (hard_fork_version >= 4) + tx.version = 2; + else + tx.version = 1; + //lock tx.unlock_time = height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW; tx.vin.push_back(in); @@ -252,6 +264,11 @@ namespace cryptonote //--------------------------------------------------------------- bool get_tx_fee(const transaction& tx, uint64_t & fee) { + if (tx.version > 1) + { + fee = tx.rct_signatures.txnFee; + return true; + } uint64_t amount_in = 0; uint64_t amount_out = 0; BOOST_FOREACH(auto& in, tx.vin) @@ -315,6 +332,11 @@ namespace cryptonote return pub_key_field.pub_key; } //--------------------------------------------------------------- + crypto::public_key get_tx_pub_key_from_extra(const transaction_prefix& tx_prefix) + { + return get_tx_pub_key_from_extra(tx_prefix.extra); + } + //--------------------------------------------------------------- crypto::public_key get_tx_pub_key_from_extra(const transaction& tx) { return get_tx_pub_key_from_extra(tx.extra); @@ -447,13 +469,16 @@ namespace cryptonote return encrypt_payment_id(payment_id, public_key, secret_key); } //--------------------------------------------------------------- - bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key) + bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, bool rct) { + std::vector<crypto::secret_key> amount_keys; tx.vin.clear(); tx.vout.clear(); tx.signatures.clear(); + tx.rct_signatures = rct::rctSig(); + amount_keys.clear(); - tx.version = CURRENT_TRANSACTION_VERSION; + tx.version = rct ? 2 : 1; tx.unlock_time = unlock_time; tx.extra = extra; @@ -509,7 +534,6 @@ namespace cryptonote }; std::vector<input_generation_context_data> in_contexts; - uint64_t summary_inputs_money = 0; //fill inputs BOOST_FOREACH(const tx_source_entry& src_entr, sources) @@ -529,7 +553,7 @@ namespace cryptonote return false; //check that derivated key is equal with real output key - if( !(in_ephemeral.pub == src_entr.outputs[src_entr.real_output].second) ) + if( !(in_ephemeral.pub == src_entr.outputs[src_entr.real_output].second.dest) ) { LOG_ERROR("derived public key missmatch with output public key! "<< ENDL << "derived_key:" << string_tools::pod_to_hex(in_ephemeral.pub) << ENDL << "real output_public_key:" @@ -559,12 +583,18 @@ namespace cryptonote size_t output_index = 0; BOOST_FOREACH(const tx_destination_entry& dst_entr, shuffled_dsts) { - CHECK_AND_ASSERT_MES(dst_entr.amount > 0, false, "Destination with wrong amount: " << dst_entr.amount); + CHECK_AND_ASSERT_MES(dst_entr.amount > 0 || tx.version > 1, false, "Destination with wrong amount: " << dst_entr.amount); crypto::key_derivation derivation; crypto::public_key out_eph_public_key; bool r = crypto::generate_key_derivation(dst_entr.addr.m_view_public_key, txkey.sec, derivation); CHECK_AND_ASSERT_MES(r, false, "at creation outs: failed to generate_key_derivation(" << dst_entr.addr.m_view_public_key << ", " << txkey.sec << ")"); + if (tx.version > 1) + { + crypto::secret_key scalar1; + crypto::derivation_to_scalar(derivation, output_index, scalar1); + amount_keys.push_back(scalar1); + } r = crypto::derive_public_key(derivation, output_index, dst_entr.addr.m_spend_public_key, out_eph_public_key); CHECK_AND_ASSERT_MES(r, false, "at creation outs: failed to derive_public_key(" << derivation << ", " << output_index << ", "<< dst_entr.addr.m_spend_public_key << ")"); @@ -586,33 +616,148 @@ namespace cryptonote } - //generate ring signatures - crypto::hash tx_prefix_hash; - get_transaction_prefix_hash(tx, tx_prefix_hash); - - std::stringstream ss_ring_s; - size_t i = 0; - BOOST_FOREACH(const tx_source_entry& src_entr, sources) + if (tx.version == 1) { - ss_ring_s << "pub_keys:" << ENDL; - std::vector<const crypto::public_key*> keys_ptrs; - BOOST_FOREACH(const tx_source_entry::output_entry& o, src_entr.outputs) + //generate ring signatures + crypto::hash tx_prefix_hash; + get_transaction_prefix_hash(tx, tx_prefix_hash); + + std::stringstream ss_ring_s; + size_t i = 0; + BOOST_FOREACH(const tx_source_entry& src_entr, sources) { - keys_ptrs.push_back(&o.second); - ss_ring_s << o.second << ENDL; + ss_ring_s << "pub_keys:" << ENDL; + std::vector<const crypto::public_key*> keys_ptrs; + std::vector<crypto::public_key> keys(src_entr.outputs.size()); + size_t ii = 0; + BOOST_FOREACH(const tx_source_entry::output_entry& o, src_entr.outputs) + { + keys[ii] = rct2pk(o.second.dest); + keys_ptrs.push_back(&keys[ii]); + ss_ring_s << o.second.dest << ENDL; + ++ii; + } + + tx.signatures.push_back(std::vector<crypto::signature>()); + std::vector<crypto::signature>& sigs = tx.signatures.back(); + sigs.resize(src_entr.outputs.size()); + crypto::generate_ring_signature(tx_prefix_hash, boost::get<txin_to_key>(tx.vin[i]).k_image, keys_ptrs, in_contexts[i].in_ephemeral.sec, src_entr.real_output, sigs.data()); + ss_ring_s << "signatures:" << ENDL; + std::for_each(sigs.begin(), sigs.end(), [&](const crypto::signature& s){ss_ring_s << s << ENDL;}); + ss_ring_s << "prefix_hash:" << tx_prefix_hash << ENDL << "in_ephemeral_key: " << in_contexts[i].in_ephemeral.sec << ENDL << "real_output: " << src_entr.real_output; + i++; } - tx.signatures.push_back(std::vector<crypto::signature>()); - std::vector<crypto::signature>& sigs = tx.signatures.back(); - sigs.resize(src_entr.outputs.size()); - crypto::generate_ring_signature(tx_prefix_hash, boost::get<txin_to_key>(tx.vin[i]).k_image, keys_ptrs, in_contexts[i].in_ephemeral.sec, src_entr.real_output, sigs.data()); - ss_ring_s << "signatures:" << ENDL; - std::for_each(sigs.begin(), sigs.end(), [&](const crypto::signature& s){ss_ring_s << s << ENDL;}); - ss_ring_s << "prefix_hash:" << tx_prefix_hash << ENDL << "in_ephemeral_key: " << in_contexts[i].in_ephemeral.sec << ENDL << "real_output: " << src_entr.real_output; - i++; + LOG_PRINT2("construct_tx.log", "transaction_created: " << get_transaction_hash(tx) << ENDL << obj_to_json_str(tx) << ENDL << ss_ring_s.str() , LOG_LEVEL_3); } + else + { + bool all_rct_inputs = true; + size_t n_total_outs = sources[0].outputs.size(); // only for non-simple rct + BOOST_FOREACH(const tx_source_entry& src_entr, sources) + all_rct_inputs &= !(src_entr.mask == rct::identity()); - LOG_PRINT2("construct_tx.log", "transaction_created: " << get_transaction_hash(tx) << ENDL << obj_to_json_str(tx) << ENDL << ss_ring_s.str() , LOG_LEVEL_3); + // the non-simple version is slightly smaller, but assumes all real inputs + // are on the same index, so can only be used if there just one ring. + bool use_simple_rct = sources.size() > 1; + + if (!use_simple_rct) + { + // non simple ringct requires all real inputs to be at the same index for all inputs + BOOST_FOREACH(const tx_source_entry& src_entr, sources) + { + if(src_entr.real_output != sources.begin()->real_output) + { + LOG_ERROR("All inputs must have the same index for non-simple ringct"); + return false; + } + } + + // enforce same mixin for all outputs + for (size_t i = 1; i < sources.size(); ++i) { + if (n_total_outs != sources[i].outputs.size()) { + LOG_ERROR("Non-simple ringct transaction has varying mixin"); + return false; + } + } + } + + uint64_t amount_in = 0, amount_out = 0; + rct::ctkeyV inSk; + // mixRing indexing is done the other way round for simple + rct::ctkeyM mixRing(use_simple_rct ? sources.size() : n_total_outs); + rct::keyV destinations; + std::vector<uint64_t> inamounts, outamounts; + std::vector<unsigned int> index; + for (size_t i = 0; i < sources.size(); ++i) + { + rct::ctkey ctkey; + amount_in += sources[i].amount; + inamounts.push_back(sources[i].amount); + index.push_back(sources[i].real_output); + // inSk: (secret key, mask) + ctkey.dest = rct::sk2rct(in_contexts[i].in_ephemeral.sec); + ctkey.mask = sources[i].mask; + inSk.push_back(ctkey); + // inPk: (public key, commitment) + // will be done when filling in mixRing + } + for (size_t i = 0; i < tx.vout.size(); ++i) + { + destinations.push_back(rct::pk2rct(boost::get<txout_to_key>(tx.vout[i].target).key)); + outamounts.push_back(tx.vout[i].amount); + amount_out += tx.vout[i].amount; + } + + if (use_simple_rct) + { + // mixRing indexing is done the other way round for simple + for (size_t i = 0; i < sources.size(); ++i) + { + mixRing[i].resize(sources[i].outputs.size()); + for (size_t n = 0; n < sources[i].outputs.size(); ++n) + { + mixRing[i][n] = sources[i].outputs[n].second; + } + } + } + else + { + for (size_t i = 0; i < n_total_outs; ++i) // same index assumption + { + mixRing[i].resize(sources.size()); + for (size_t n = 0; n < sources.size(); ++n) + { + mixRing[i][n] = sources[n].outputs[i].second; + } + } + } + + // fee + if (!use_simple_rct && amount_in > amount_out) + outamounts.push_back(amount_in - amount_out); + + // zero out all amounts to mask rct outputs, real amounts are now encrypted + for (size_t i = 0; i < tx.vin.size(); ++i) + { + if (sources[i].rct) + boost::get<txin_to_key>(tx.vin[i]).amount = 0; + } + for (size_t i = 0; i < tx.vout.size(); ++i) + tx.vout[i].amount = 0; + + crypto::hash tx_prefix_hash; + get_transaction_prefix_hash(tx, tx_prefix_hash); + rct::ctkeyV outSk; + if (use_simple_rct) + tx.rct_signatures = rct::genRctSimple(rct::hash2rct(tx_prefix_hash), inSk, destinations, inamounts, outamounts, amount_in - amount_out, mixRing, (const rct::keyV&)amount_keys, index, outSk); + else + tx.rct_signatures = rct::genRct(rct::hash2rct(tx_prefix_hash), inSk, destinations, outamounts, mixRing, (const rct::keyV&)amount_keys, sources[0].real_output, outSk); // same index assumption + + CHECK_AND_ASSERT_MES(tx.vout.size() == outSk.size(), false, "outSk size does not match vout"); + + LOG_PRINT2("construct_tx.log", "transaction_created: " << get_transaction_hash(tx) << ENDL << obj_to_json_str(tx) << ENDL, LOG_LEVEL_3); + } return true; } @@ -661,7 +806,10 @@ namespace cryptonote << out.target.type().name() << ", expected " << typeid(txout_to_key).name() << ", in transaction id=" << get_transaction_hash(tx)); - CHECK_AND_NO_ASSERT_MES(0 < out.amount, false, "zero amount ouput in transaction id=" << get_transaction_hash(tx)); + if (tx.version == 1) + { + CHECK_AND_NO_ASSERT_MES(0 < out.amount, false, "zero amount output in transaction id=" << get_transaction_hash(tx)); + } if(!check_key(boost::get<txout_to_key>(out.target).key)) return false; @@ -776,20 +924,49 @@ namespace cryptonote crypto::hash get_transaction_hash(const transaction& t) { crypto::hash h = null_hash; - size_t blob_size = 0; - get_object_hash(t, h, blob_size); + get_transaction_hash(t, h, NULL); return h; } //--------------------------------------------------------------- bool get_transaction_hash(const transaction& t, crypto::hash& res) { - size_t blob_size = 0; - return get_object_hash(t, res, blob_size); + return get_transaction_hash(t, res, NULL); + } + //--------------------------------------------------------------- + bool get_transaction_hash(const transaction& t, crypto::hash& res, size_t* blob_size) + { + // v1 transactions hash the entire blob + if (t.version == 1) + { + size_t ignored_blob_size, &blob_size_ref = blob_size ? *blob_size : ignored_blob_size; + return get_object_hash(t, res, blob_size_ref); + } + + // v2 transactions hash different parts together, than hash the set of those hashes + crypto::hash hashes[3]; + + // prefix + get_transaction_prefix_hash(t, hashes[0]); + + // base rct data + get_blob_hash(t_serializable_object_to_blob((const rct::rctSigBase&)t.rct_signatures), hashes[1]); + + // prunable rct data + get_blob_hash(t_serializable_object_to_blob(t.rct_signatures.p), hashes[2]); + + // the tx hash is the hash of the 3 hashes + res = cn_fast_hash(hashes, sizeof(hashes)); + + // we still need the size + if (blob_size) + *blob_size = get_object_blobsize(t); + + return true; } //--------------------------------------------------------------- bool get_transaction_hash(const transaction& t, crypto::hash& res, size_t& blob_size) { - return get_object_hash(t, res, blob_size); + return get_transaction_hash(t, res, &blob_size); } //--------------------------------------------------------------- blobdata get_block_hashing_blob(const block& b) diff --git a/src/cryptonote_core/cryptonote_format_utils.h b/src/cryptonote_core/cryptonote_format_utils.h index 056940e98..e6a3bfba4 100644 --- a/src/cryptonote_core/cryptonote_format_utils.h +++ b/src/cryptonote_core/cryptonote_format_utils.h @@ -35,6 +35,7 @@ #include "include_base_utils.h" #include "crypto/crypto.h" #include "crypto/hash.h" +#include "ringct/rctOps.h" namespace cryptonote @@ -50,13 +51,17 @@ namespace cryptonote struct tx_source_entry { - typedef std::pair<uint64_t, crypto::public_key> output_entry; + typedef std::pair<uint64_t, rct::ctkey> output_entry; - std::vector<output_entry> outputs; //index + key + std::vector<output_entry> outputs; //index + key + optional ringct commitment size_t real_output; //index in outputs vector of real output_entry crypto::public_key real_out_tx_key; //incoming real tx public key size_t real_output_in_tx_index; //index in transaction outputs vector uint64_t amount; //money + bool rct; //true if the output is rct + rct::key mask; //ringct amount mask + + void push_output(uint64_t idx, const crypto::public_key &k, uint64_t amount) { outputs.push_back(std::make_pair(idx, rct::ctkey({rct::pk2rct(k), rct::zeroCommit(amount)}))); } }; struct tx_destination_entry @@ -70,7 +75,7 @@ namespace cryptonote //--------------------------------------------------------------- bool construct_tx(const account_keys& sender_account_keys, const std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time); - bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &txkey); + bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, bool rct = false); template<typename T> bool find_tx_extra_field_by_type(const std::vector<tx_extra_field>& tx_extra_fields, T& field) @@ -85,6 +90,7 @@ namespace cryptonote bool parse_tx_extra(const std::vector<uint8_t>& tx_extra, std::vector<tx_extra_field>& tx_extra_fields); crypto::public_key get_tx_pub_key_from_extra(const std::vector<uint8_t>& tx_extra); + crypto::public_key get_tx_pub_key_from_extra(const transaction_prefix& tx); crypto::public_key get_tx_pub_key_from_extra(const transaction& tx); bool add_tx_pub_key_to_extra(transaction& tx, const crypto::public_key& tx_pub_key); bool add_extra_nonce_to_tx_extra(std::vector<uint8_t>& tx_extra, const blobdata& extra_nonce); @@ -106,6 +112,7 @@ namespace cryptonote crypto::hash get_transaction_hash(const transaction& t); bool get_transaction_hash(const transaction& t, crypto::hash& res); bool get_transaction_hash(const transaction& t, crypto::hash& res, size_t& blob_size); + bool get_transaction_hash(const transaction& t, crypto::hash& res, size_t* blob_size); blobdata get_block_hashing_blob(const block& b); bool get_block_hash(const block& b, crypto::hash& res); crypto::hash get_block_hash(const block& b); diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index 3d5ab86e1..46fab4dcf 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -37,11 +37,7 @@ #include "cryptonote_format_utils.h" #include "cryptonote_boost_serialization.h" #include "cryptonote_config.h" -#if BLOCKCHAIN_DB == DB_LMDB #include "blockchain.h" -#else -#include "blockchain_storage.h" -#endif #include "common/boost_serialization_helper.h" #include "common/int-util.h" #include "misc_language.h" @@ -74,21 +70,22 @@ namespace cryptonote } } //--------------------------------------------------------------------------------- -#if BLOCKCHAIN_DB == DB_LMDB //--------------------------------------------------------------------------------- tx_memory_pool::tx_memory_pool(Blockchain& bchs): m_blockchain(bchs) { } -#else - tx_memory_pool::tx_memory_pool(blockchain_storage& bchs): m_blockchain(bchs) - { - - } -#endif //--------------------------------------------------------------------------------- bool tx_memory_pool::add_tx(const transaction &tx, /*const crypto::hash& tx_prefix_hash,*/ const crypto::hash &id, size_t blob_size, tx_verification_context& tvc, bool kept_by_block, bool relayed, uint8_t version) { + if (tx.version == 0) + { + // v0 never accepted + LOG_PRINT_L1("transaction version 0 is invalid"); + tvc.m_verifivation_failed = true; + return false; + } + // we do not accept transactions that timed out before, unless they're // kept_by_block if (!kept_by_block && m_timed_out_transactions.find(id) != m_timed_out_transactions.end()) @@ -106,25 +103,34 @@ namespace cryptonote return false; } - uint64_t inputs_amount = 0; - if(!get_inputs_money_amount(tx, inputs_amount)) + // fee per kilobyte, size rounded up. + uint64_t fee; + + if (tx.version == 1) { - tvc.m_verifivation_failed = true; - return false; - } + uint64_t inputs_amount = 0; + if(!get_inputs_money_amount(tx, inputs_amount)) + { + tvc.m_verifivation_failed = true; + return false; + } - uint64_t outputs_amount = get_outs_money_amount(tx); + uint64_t outputs_amount = get_outs_money_amount(tx); + if(outputs_amount >= inputs_amount) + { + LOG_PRINT_L1("transaction use more money then it has: use " << print_money(outputs_amount) << ", have " << print_money(inputs_amount)); + tvc.m_verifivation_failed = true; + tvc.m_overspend = true; + return false; + } - if(outputs_amount >= inputs_amount) + fee = inputs_amount - outputs_amount; + } + else { - LOG_PRINT_L1("transaction use more money then it has: use " << print_money(outputs_amount) << ", have " << print_money(inputs_amount)); - tvc.m_verifivation_failed = true; - tvc.m_overspend = true; - return false; + fee = tx.rct_signatures.txnFee; } - // fee per kilobyte, size rounded up. - uint64_t fee = inputs_amount - outputs_amount; uint64_t needed_fee = blob_size / 1024; needed_fee += (blob_size % 1024) ? 1 : 0; needed_fee *= FEE_PER_KB; @@ -161,7 +167,7 @@ namespace cryptonote if (!m_blockchain.check_tx_outputs(tx, tvc)) { - LOG_PRINT_L1("Transaction with id= "<< id << " has at least one invalid outout"); + LOG_PRINT_L1("Transaction with id= "<< id << " has at least one invalid output"); tvc.m_verifivation_failed = true; tvc.m_invalid_output = true; return false; @@ -169,11 +175,9 @@ namespace cryptonote crypto::hash max_used_block_id = null_hash; uint64_t max_used_block_height = 0; -#if BLOCKCHAIN_DB == DB_LMDB - bool ch_inp_res = m_blockchain.check_tx_inputs(tx, max_used_block_height, max_used_block_id, tvc, kept_by_block); -#else - bool ch_inp_res = m_blockchain.check_tx_inputs(tx, max_used_block_height, max_used_block_id); -#endif + tx_details txd; + txd.tx = tx; + bool ch_inp_res = m_blockchain.check_tx_inputs(txd.tx, max_used_block_height, max_used_block_id, tvc, kept_by_block); CRITICAL_REGION_LOCAL(m_transactions_lock); if(!ch_inp_res) { @@ -181,11 +185,10 @@ namespace cryptonote // may become valid again, so ignore the failed inputs check. if(kept_by_block) { - auto txd_p = m_transactions.insert(transactions_container::value_type(id, tx_details())); + auto txd_p = m_transactions.insert(transactions_container::value_type(id, txd)); CHECK_AND_ASSERT_MES(txd_p.second, false, "transaction already exists at inserting in memory pool"); txd_p.first->second.blob_size = blob_size; - txd_p.first->second.tx = tx; - txd_p.first->second.fee = inputs_amount - outputs_amount; + txd_p.first->second.fee = fee; txd_p.first->second.max_used_block_id = null_hash; txd_p.first->second.max_used_block_height = 0; txd_p.first->second.kept_by_block = kept_by_block; @@ -203,12 +206,11 @@ namespace cryptonote }else { //update transactions container - auto txd_p = m_transactions.insert(transactions_container::value_type(id, tx_details())); + auto txd_p = m_transactions.insert(transactions_container::value_type(id, txd)); CHECK_AND_ASSERT_MES(txd_p.second, false, "intrnal error: transaction already exists at inserting in memorypool"); txd_p.first->second.blob_size = blob_size; - txd_p.first->second.tx = tx; txd_p.first->second.kept_by_block = kept_by_block; - txd_p.first->second.fee = inputs_amount - outputs_amount; + txd_p.first->second.fee = fee; txd_p.first->second.max_used_block_id = max_used_block_id; txd_p.first->second.max_used_block_height = max_used_block_height; txd_p.first->second.last_failed_height = 0; @@ -518,12 +520,8 @@ namespace cryptonote if(txd.last_failed_id == m_blockchain.get_block_id_by_height(txd.last_failed_height)) return false; //check ring signature again, it is possible (with very small chance) that this transaction become again valid -#if BLOCKCHAIN_DB == DB_LMDB tx_verification_context tvc; if(!m_blockchain.check_tx_inputs(txd.tx, txd.max_used_block_height, txd.max_used_block_id, tvc)) -#else - if(!m_blockchain.check_tx_inputs(txd.tx, txd.max_used_block_height, txd.max_used_block_id)) -#endif { txd.last_failed_height = m_blockchain.get_current_blockchain_height()-1; txd.last_failed_id = m_blockchain.get_block_id_by_height(txd.last_failed_height); @@ -649,6 +647,7 @@ namespace cryptonote //--------------------------------------------------------------------------------- size_t tx_memory_pool::validate(uint8_t version) { + CRITICAL_REGION_LOCAL(m_transactions_lock); size_t n_removed = 0; size_t tx_size_limit = (version < 2 ? TRANSACTION_SIZE_LIMIT_V1 : TRANSACTION_SIZE_LIMIT_V2); for (auto it = m_transactions.begin(); it != m_transactions.end(); ) { diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h index c7aab7f08..0e280872d 100644 --- a/src/cryptonote_core/tx_pool.h +++ b/src/cryptonote_core/tx_pool.h @@ -48,11 +48,7 @@ namespace cryptonote { -#if BLOCKCHAIN_DB == DB_LMDB class Blockchain; -#else - class blockchain_storage; -#endif /************************************************************************/ /* */ /************************************************************************/ @@ -93,16 +89,12 @@ namespace cryptonote class tx_memory_pool: boost::noncopyable { public: -#if BLOCKCHAIN_DB == DB_LMDB /** * @brief Constructor * * @param bchs a Blockchain class instance, for getting chain info */ tx_memory_pool(Blockchain& bchs); -#else - tx_memory_pool(blockchain_storage& bchs); -#endif /** @@ -492,18 +484,7 @@ namespace cryptonote std::unordered_set<crypto::hash> m_timed_out_transactions; std::string m_config_folder; //!< the folder to save state to -#if BLOCKCHAIN_DB == DB_LMDB Blockchain& m_blockchain; //!< reference to the Blockchain object -#else - blockchain_storage& m_blockchain; -#endif - -#if BLOCKCHAIN_DB == DB_LMDB -#else -#if defined(DEBUG_CREATE_BLOCK_TEMPLATE) - friend class blockchain_storage; -#endif -#endif }; } diff --git a/src/cryptonote_core/verification_context.h b/src/cryptonote_core/verification_context.h index e58291ea9..0bb635e84 100644 --- a/src/cryptonote_core/verification_context.h +++ b/src/cryptonote_core/verification_context.h @@ -47,6 +47,7 @@ namespace cryptonote bool m_too_big; bool m_overspend; bool m_fee_too_low; + bool m_not_rct; }; struct block_verification_context diff --git a/src/ringct/CMakeLists.txt b/src/ringct/CMakeLists.txt new file mode 100644 index 000000000..078199bb0 --- /dev/null +++ b/src/ringct/CMakeLists.txt @@ -0,0 +1,59 @@ +# Copyright (c) 2016, The Monero Project +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are +# permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of +# conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list +# of conditions and the following disclaimer in the documentation and/or other +# materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be +# used to endorse or promote products derived from this software without specific +# prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +set(ringct_sources + rctOps.cpp + rctSigs.cpp + rctTypes.cpp + rctCryptoOps.c) + +set(ringct_headers) + +set(ringct_private_headers + rctOps.h + rctSigs.h + rctTypes.h) + +bitmonero_private_headers(ringct + ${crypto_private_headers}) +bitmonero_add_library(ringct + ${ringct_sources} + ${ringct_headers} + ${ringct_private_headers}) +target_link_libraries(ringct + LINK_PUBLIC + common + crypto + ${Boost_DATE_TIME_LIBRARY} + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_SERIALIZATION_LIBRARY} + LINK_PRIVATE + ${Boost_FILESYSTEM_LIBRARY} + ${Boost_SYSTEM_LIBRARY} + ${Boost_THREAD_LIBRARY} + ${EXTRA_LIBRARIES}) diff --git a/src/ringct/rctCryptoOps.c b/src/ringct/rctCryptoOps.c new file mode 100644 index 000000000..9bb9a6891 --- /dev/null +++ b/src/ringct/rctCryptoOps.c @@ -0,0 +1,221 @@ +// Copyright (c) 2014-2016, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#include <assert.h> +#include <stdint.h> + +#include "crypto/crypto-ops.h" + +//DISABLE_VS_WARNINGS(4146 4244) + +void sc_reduce32copy(unsigned char * scopy, const unsigned char *s) { + int64_t s0 = 2097151 & load_3(s); + int64_t s1 = 2097151 & (load_4(s + 2) >> 5); + int64_t s2 = 2097151 & (load_3(s + 5) >> 2); + int64_t s3 = 2097151 & (load_4(s + 7) >> 7); + int64_t s4 = 2097151 & (load_4(s + 10) >> 4); + int64_t s5 = 2097151 & (load_3(s + 13) >> 1); + int64_t s6 = 2097151 & (load_4(s + 15) >> 6); + int64_t s7 = 2097151 & (load_3(s + 18) >> 3); + int64_t s8 = 2097151 & load_3(s + 21); + int64_t s9 = 2097151 & (load_4(s + 23) >> 5); + int64_t s10 = 2097151 & (load_3(s + 26) >> 2); + int64_t s11 = (load_4(s + 28) >> 7); + int64_t s12 = 0; + int64_t carry0; + int64_t carry1; + int64_t carry2; + int64_t carry3; + int64_t carry4; + int64_t carry5; + int64_t carry6; + int64_t carry7; + int64_t carry8; + int64_t carry9; + int64_t carry10; + int64_t carry11; + + carry0 = (s0 + (1<<20)) >> 21; + s1 += carry0; + s0 -= carry0 << 21; + carry2 = (s2 + (1<<20)) >> 21; + s3 += carry2; + s2 -= carry2 << 21; + carry4 = (s4 + (1<<20)) >> 21; + s5 += carry4; + s4 -= carry4 << 21; + carry6 = (s6 + (1<<20)) >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry8 = (s8 + (1<<20)) >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry10 = (s10 + (1<<20)) >> 21; + s11 += carry10; + s10 -= carry10 << 21; + + carry1 = (s1 + (1<<20)) >> 21; + s2 += carry1; + s1 -= carry1 << 21; + carry3 = (s3 + (1<<20)) >> 21; + s4 += carry3; + s3 -= carry3 << 21; + carry5 = (s5 + (1<<20)) >> 21; + s6 += carry5; + s5 -= carry5 << 21; + carry7 = (s7 + (1<<20)) >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry9 = (s9 + (1<<20)) >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry11 = (s11 + (1<<20)) >> 21; + s12 += carry11; + s11 -= carry11 << 21; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + + carry0 = s0 >> 21; + s1 += carry0; + s0 -= carry0 << 21; + carry1 = s1 >> 21; + s2 += carry1; + s1 -= carry1 << 21; + carry2 = s2 >> 21; + s3 += carry2; + s2 -= carry2 << 21; + carry3 = s3 >> 21; + s4 += carry3; + s3 -= carry3 << 21; + carry4 = s4 >> 21; + s5 += carry4; + s4 -= carry4 << 21; + carry5 = s5 >> 21; + s6 += carry5; + s5 -= carry5 << 21; + carry6 = s6 >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry7 = s7 >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry8 = s8 >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry9 = s9 >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry10 = s10 >> 21; + s11 += carry10; + s10 -= carry10 << 21; + carry11 = s11 >> 21; + s12 += carry11; + s11 -= carry11 << 21; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + + carry0 = s0 >> 21; + s1 += carry0; + s0 -= carry0 << 21; + carry1 = s1 >> 21; + s2 += carry1; + s1 -= carry1 << 21; + carry2 = s2 >> 21; + s3 += carry2; + s2 -= carry2 << 21; + carry3 = s3 >> 21; + s4 += carry3; + s3 -= carry3 << 21; + carry4 = s4 >> 21; + s5 += carry4; + s4 -= carry4 << 21; + carry5 = s5 >> 21; + s6 += carry5; + s5 -= carry5 << 21; + carry6 = s6 >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry7 = s7 >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry8 = s8 >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry9 = s9 >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry10 = s10 >> 21; + s11 += carry10; + s10 -= carry10 << 21; + + scopy[0] = s0 >> 0; + scopy[1] = s0 >> 8; + scopy[2] = (s0 >> 16) | (s1 << 5); + scopy[3] = s1 >> 3; + scopy[4] = s1 >> 11; + scopy[5] = (s1 >> 19) | (s2 << 2); + scopy[6] = s2 >> 6; + scopy[7] = (s2 >> 14) | (s3 << 7); + scopy[8] = s3 >> 1; + scopy[9] = s3 >> 9; + scopy[10] = (s3 >> 17) | (s4 << 4); + scopy[11] = s4 >> 4; + scopy[12] = s4 >> 12; + scopy[13] = (s4 >> 20) | (s5 << 1); + scopy[14] = s5 >> 7; + scopy[15] = (s5 >> 15) | (s6 << 6); + scopy[16] = s6 >> 2; + scopy[17] = s6 >> 10; + scopy[18] = (s6 >> 18) | (s7 << 3); + scopy[19] = s7 >> 5; + scopy[20] = s7 >> 13; + scopy[21] = s8 >> 0; + scopy[22] = s8 >> 8; + scopy[23] = (s8 >> 16) | (s9 << 5); + scopy[24] = s9 >> 3; + scopy[25] = s9 >> 11; + scopy[26] = (s9 >> 19) | (s10 << 2); + scopy[27] = s10 >> 6; + scopy[28] = (s10 >> 14) | (s11 << 7); + scopy[29] = s11 >> 1; + scopy[30] = s11 >> 9; + scopy[31] = s11 >> 17; +} diff --git a/src/ringct/rctCryptoOps.h b/src/ringct/rctCryptoOps.h new file mode 100644 index 000000000..58c6964d8 --- /dev/null +++ b/src/ringct/rctCryptoOps.h @@ -0,0 +1,37 @@ +// Copyright (c) 2014-2016, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once + +extern "C" { +#include "crypto/crypto-ops.h" +} + +void sc_reduce32copy(unsigned char * scopy, const unsigned char *s); diff --git a/src/ringct/rctOps.cpp b/src/ringct/rctOps.cpp new file mode 100644 index 000000000..488e47ca0 --- /dev/null +++ b/src/ringct/rctOps.cpp @@ -0,0 +1,475 @@ +// Copyright (c) 2016, Monero Research Labs +// +// Author: Shen Noether <shen.noether@gmx.com> +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "misc_log_ex.h" +#include "rctOps.h" +using namespace crypto; +using namespace std; + +namespace rct { + + //Various key initialization functions + + //Creates a zero scalar + void zero(key &zero) { + memset(&zero, 0, 32); + } + + //Creates a zero scalar + key zero() { + static const key z = { {0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; + return z; + } + + //Creates a zero elliptic curve point + void identity(key &Id) { + Id[0] = (unsigned char)(0x01); + memset(Id.bytes+1, 0, 31); + } + + //Creates a zero elliptic curve point + key identity() { + key Id; + Id[0] = (unsigned char)(0x01); + memset(Id.bytes+1, 0, 31); + return Id; + } + + //copies a scalar or point + void copy(key &AA, const key &A) { + memcpy(&AA, &A, 32); + } + + //copies a scalar or point + key copy(const key &A) { + key AA; + memcpy(&AA, &A, 32); + return AA; + } + + + //initializes a key matrix; + //first parameter is rows, + //second is columns + keyM keyMInit(int rows, int cols) { + keyM rv(cols); + int i = 0; + for (i = 0 ; i < cols ; i++) { + rv[i] = keyV(rows); + } + return rv; + } + + + + + //Various key generation functions + + //generates a random scalar which can be used as a secret key or mask + void skGen(key &sk) { + sk = crypto::rand<key>(); + sc_reduce32(sk.bytes); + } + + //generates a random scalar which can be used as a secret key or mask + key skGen() { + key sk = crypto::rand<key>(); + sc_reduce32(sk.bytes); + return sk; + } + + //Generates a vector of secret key + //Mainly used in testing + keyV skvGen(int rows ) { + keyV rv(rows); + int i = 0; + for (i = 0 ; i < rows ; i++) { + skGen(rv[i]); + } + return rv; + } + + //generates a random curve point (for testing) + key pkGen() { + key sk = skGen(); + key pk = scalarmultBase(sk); + return pk; + } + + //generates a random secret and corresponding public key + void skpkGen(key &sk, key &pk) { + skGen(sk); + scalarmultBase(pk, sk); + } + + //generates a random secret and corresponding public key + tuple<key, key> skpkGen() { + key sk = skGen(); + key pk = scalarmultBase(sk); + return make_tuple(sk, pk); + } + + //generates C =aG + bH from b, a is given.. + void genC(key & C, const key & a, xmr_amount amount) { + key bH = scalarmultH(d2h(amount)); + addKeys1(C, a, bH); + } + + //generates a <secret , public> / Pedersen commitment to the amount + tuple<ctkey, ctkey> ctskpkGen(xmr_amount amount) { + ctkey sk, pk; + skpkGen(sk.dest, pk.dest); + skpkGen(sk.mask, pk.mask); + key am = d2h(amount); + key bH = scalarmultH(am); + addKeys(pk.mask, pk.mask, bH); + return make_tuple(sk, pk); + } + + + //generates a <secret , public> / Pedersen commitment but takes bH as input + tuple<ctkey, ctkey> ctskpkGen(key bH) { + ctkey sk, pk; + skpkGen(sk.dest, pk.dest); + skpkGen(sk.mask, pk.mask); + addKeys(pk.mask, pk.mask, bH); + return make_tuple(sk, pk); + } + + key zeroCommit(xmr_amount amount) { + key mask = identity(); + mask = scalarmultBase(mask); + key am = d2h(amount); + key bH = scalarmultH(am); + addKeys(mask, mask, bH); + return mask; + } + + key commit(xmr_amount amount, key mask) { + mask = scalarmultBase(mask); + key am = d2h(amount); + key bH = scalarmultH(am); + addKeys(mask, mask, bH); + return mask; + } + + //generates a random uint long long (for testing) + xmr_amount randXmrAmount(xmr_amount upperlimit) { + return h2d(skGen()) % (upperlimit); + } + + //Scalar multiplications of curve points + + //does a * G where a is a scalar and G is the curve basepoint + void scalarmultBase(key &aG,const key &a) { + ge_p3 point; + sc_reduce32copy(aG.bytes, a.bytes); //do this beforehand! + ge_scalarmult_base(&point, aG.bytes); + ge_p3_tobytes(aG.bytes, &point); + } + + //does a * G where a is a scalar and G is the curve basepoint + key scalarmultBase(const key & a) { + ge_p3 point; + key aG; + sc_reduce32copy(aG.bytes, a.bytes); //do this beforehand + ge_scalarmult_base(&point, aG.bytes); + ge_p3_tobytes(aG.bytes, &point); + return aG; + } + + //does a * P where a is a scalar and P is an arbitrary point + void scalarmultKey(key & aP, const key &P, const key &a) { + ge_p3 A; + ge_p2 R; + CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&A, P.bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__)); + ge_scalarmult(&R, a.bytes, &A); + ge_tobytes(aP.bytes, &R); + } + + //does a * P where a is a scalar and P is an arbitrary point + key scalarmultKey(const key & P, const key & a) { + ge_p3 A; + ge_p2 R; + CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&A, P.bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__)); + ge_scalarmult(&R, a.bytes, &A); + key aP; + ge_tobytes(aP.bytes, &R); + return aP; + } + + + //Computes aH where H= toPoint(cn_fast_hash(G)), G the basepoint + key scalarmultH(const key & a) { + ge_p3 A; + ge_p2 R; + CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&A, H.bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__)); + ge_scalarmult(&R, a.bytes, &A); + key aP; + ge_tobytes(aP.bytes, &R); + return aP; + } + + //Curve addition / subtractions + + //for curve points: AB = A + B + void addKeys(key &AB, const key &A, const key &B) { + ge_p3 B2, A2; + CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&B2, B.bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__)); + CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&A2, A.bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__)); + ge_cached tmp2; + ge_p3_to_cached(&tmp2, &B2); + ge_p1p1 tmp3; + ge_add(&tmp3, &A2, &tmp2); + ge_p1p1_to_p3(&A2, &tmp3); + ge_p3_tobytes(AB.bytes, &A2); + } + + + //addKeys1 + //aGB = aG + B where a is a scalar, G is the basepoint, and B is a point + void addKeys1(key &aGB, const key &a, const key & B) { + key aG = scalarmultBase(a); + addKeys(aGB, aG, B); + } + + //addKeys2 + //aGbB = aG + bB where a, b are scalars, G is the basepoint and B is a point + void addKeys2(key &aGbB, const key &a, const key &b, const key & B) { + ge_p2 rv; + ge_p3 B2; + CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&B2, B.bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__)); + ge_double_scalarmult_base_vartime(&rv, b.bytes, &B2, a.bytes); + ge_tobytes(aGbB.bytes, &rv); + } + + //Does some precomputation to make addKeys3 more efficient + // input B a curve point and output a ge_dsmp which has precomputation applied + void precomp(ge_dsmp rv, const key & B) { + ge_p3 B2; + CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&B2, B.bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__)); + ge_dsm_precomp(rv, &B2); + } + + //addKeys3 + //aAbB = a*A + b*B where a, b are scalars, A, B are curve points + //B must be input after applying "precomp" + void addKeys3(key &aAbB, const key &a, const key &A, const key &b, const ge_dsmp B) { + ge_p2 rv; + ge_p3 A2; + CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&A2, A.bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__)); + ge_double_scalarmult_precomp_vartime(&rv, a.bytes, &A2, b.bytes, B); + ge_tobytes(aAbB.bytes, &rv); + } + + + //subtract Keys (subtracts curve points) + //AB = A - B where A, B are curve points + void subKeys(key & AB, const key &A, const key &B) { + ge_p3 B2, A2; + CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&B2, B.bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__)); + CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&A2, A.bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__)); + ge_cached tmp2; + ge_p3_to_cached(&tmp2, &B2); + ge_p1p1 tmp3; + ge_sub(&tmp3, &A2, &tmp2); + ge_p1p1_to_p3(&A2, &tmp3); + ge_p3_tobytes(AB.bytes, &A2); + } + + //checks if A, B are equal as curve points + //without doing curve operations + bool equalKeys(const key & a, const key & b) { + bool rv = true; + for (int i = 0; i < 32; ++i) { + if (a.bytes[i] != b.bytes[i]) { + rv = false; + } + } + return rv; + } + + //Hashing - cn_fast_hash + //be careful these are also in crypto namespace + //cn_fast_hash for arbitrary multiples of 32 bytes + void cn_fast_hash(key &hash, const void * data, const std::size_t l) { + keccak((uint8_t *)data, l, hash.bytes, 32); + } + + void hash_to_scalar(key &hash, const void * data, const std::size_t l) { + cn_fast_hash(hash, data, l); + sc_reduce32(hash.bytes); + } + + //cn_fast_hash for a 32 byte key + void cn_fast_hash(key & hash, const key & in) { + keccak((uint8_t *)in.bytes, 32, hash.bytes, 32); + } + + void hash_to_scalar(key & hash, const key & in) { + cn_fast_hash(hash, in); + sc_reduce32(hash.bytes); + } + + //cn_fast_hash for a 32 byte key + key cn_fast_hash(const key & in) { + key hash; + keccak((uint8_t *)in.bytes, 32, hash.bytes, 32); + return hash; + } + + key hash_to_scalar(const key & in) { + key hash = cn_fast_hash(in); + sc_reduce32(hash.bytes); + return hash; + } + + //cn_fast_hash for a 128 byte unsigned char + key cn_fast_hash128(const void * in) { + key hash; + keccak((uint8_t *)in, 128, hash.bytes, 32); + return hash; + } + + key hash_to_scalar128(const void * in) { + key hash = cn_fast_hash128(in); + sc_reduce32(hash.bytes); + return hash; + } + + //cn_fast_hash for multisig purpose + //This takes the outputs and commitments + //and hashes them into a 32 byte sized key + key cn_fast_hash(ctkeyV PC) { + key rv = identity(); + std::size_t l = (std::size_t)PC.size(); + size_t i = 0, j = 0; + vector<char> m(l * 64); + for (i = 0 ; i < l ; i++) { + memcpy(&m[i * 64], &PC[i].dest, 32); + memcpy(&m[i * 64 + 32], &PC[i].mask, 32); + } + cn_fast_hash(rv, &m[0], 64*l); + return rv; + } + + key hash_to_scalar(ctkeyV PC) { + key rv = cn_fast_hash(PC); + sc_reduce32(rv.bytes); + return rv; + } + + //cn_fast_hash for a key-vector of arbitrary length + //this is useful since you take a number of keys + //put them in the key vector and it concatenates them + //and then hashes them + key cn_fast_hash(const keyV &keys) { + size_t l = keys.size(); + vector<unsigned char> m(l * 32); + size_t i; + for (i = 0 ; i < l ; i++) { + memcpy(&m[i * 32], keys[i].bytes, 32); + } + key rv; + cn_fast_hash(rv, &m[0], 32 * l); + //dp(rv); + return rv; + } + + key hash_to_scalar(const keyV &keys) { + key rv = cn_fast_hash(keys); + sc_reduce32(rv.bytes); + return rv; + } + + key hashToPointSimple(const key & hh) { + key pointk; + ge_p1p1 point2; + ge_p2 point; + ge_p3 res; + key h = cn_fast_hash(hh); + CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&res, h.bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__)); + ge_p3_to_p2(&point, &res); + ge_mul8(&point2, &point); + ge_p1p1_to_p3(&res, &point2); + ge_p3_tobytes(pointk.bytes, &res); + return pointk; + } + + key hashToPoint(const key & hh) { + key pointk; + ge_p2 point; + ge_p1p1 point2; + ge_p3 res; + key h = cn_fast_hash(hh); + ge_fromfe_frombytes_vartime(&point, h.bytes); + ge_mul8(&point2, &point); + ge_p1p1_to_p3(&res, &point2); + ge_p3_tobytes(pointk.bytes, &res); + return pointk; + } + + void hashToPoint(key & pointk, const key & hh) { + ge_p2 point; + ge_p1p1 point2; + ge_p3 res; + key h = cn_fast_hash(hh); + ge_fromfe_frombytes_vartime(&point, h.bytes); + ge_mul8(&point2, &point); + ge_p1p1_to_p3(&res, &point2); + ge_p3_tobytes(pointk.bytes, &res); + } + + //sums a vector of curve points (for scalars use sc_add) + void sumKeys(key & Csum, const keyV & Cis) { + identity(Csum); + size_t i = 0; + for (i = 0; i < Cis.size(); i++) { + addKeys(Csum, Csum, Cis[i]); + } + } + + //Elliptic Curve Diffie Helman: encodes and decodes the amount b and mask a + // where C= aG + bH + void ecdhEncode(ecdhTuple & unmasked, const key & sharedSec) { + key sharedSec1 = hash_to_scalar(sharedSec); + key sharedSec2 = hash_to_scalar(sharedSec1); + //encode + sc_add(unmasked.mask.bytes, unmasked.mask.bytes, sharedSec1.bytes); + sc_add(unmasked.amount.bytes, unmasked.amount.bytes, sharedSec2.bytes); + } + void ecdhDecode(ecdhTuple & masked, const key & sharedSec) { + key sharedSec1 = hash_to_scalar(sharedSec); + key sharedSec2 = hash_to_scalar(sharedSec1); + //decode + sc_sub(masked.mask.bytes, masked.mask.bytes, sharedSec1.bytes); + sc_sub(masked.amount.bytes, masked.amount.bytes, sharedSec2.bytes); + } +} diff --git a/src/ringct/rctOps.h b/src/ringct/rctOps.h new file mode 100644 index 000000000..1e71c645d --- /dev/null +++ b/src/ringct/rctOps.h @@ -0,0 +1,171 @@ +//#define DBG +// Copyright (c) 2016, Monero Research Labs +// +// Author: Shen Noether <shen.noether@gmx.com> +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#ifndef RCTOPS_H +#define RCTOPS_H + +#include <cstddef> +#include <mutex> +#include <vector> +#include <tuple> + +#include "crypto/generic-ops.h" + +extern "C" { +#include "crypto/random.h" +#include "crypto/keccak.h" +#include "rctCryptoOps.h" +} +#include "crypto/crypto.h" + +#include "rctTypes.h" + +//Define this flag when debugging to get additional info on the console +#ifdef DBG +#define DP(x) dp(x) +#else +#define DP(x) +#endif + +using namespace std; +using namespace crypto; + +namespace rct { + + //Various key initialization functions + + //Creates a zero scalar + key zero(); + void zero(key &z); + //Creates a zero elliptic curve point + key identity(); + void identity(key &Id); + //copies a scalar or point + void copy(key &AA, const key &A); + key copy(const key & AA); + //initializes a key matrix; + //first parameter is rows, + //second is columns + keyM keyMInit(int, int); + + //Various key generation functions + + //generates a random scalar which can be used as a secret key or mask + key skGen(); + void skGen(key &); + + //generates a vector of secret keys of size "int" + keyV skvGen(int ); + + //generates a random curve point (for testing) + key pkGen(); + //generates a random secret and corresponding public key + void skpkGen(key &sk, key &pk); + tuple<key, key> skpkGen(); + //generates a <secret , public> / Pedersen commitment to the amount + tuple<ctkey, ctkey> ctskpkGen(xmr_amount amount); + //generates C =aG + bH from b, a is random + void genC(key & C, const key & a, xmr_amount amount); + //this one is mainly for testing, can take arbitrary amounts.. + tuple<ctkey, ctkey> ctskpkGen(key bH); + // make a pedersen commitment with given key + key commit(xmr_amount amount, key mask); + // make a pedersen commitment with zero key + key zeroCommit(xmr_amount amount); + //generates a random uint long long + xmr_amount randXmrAmount(xmr_amount upperlimit); + + //Scalar multiplications of curve points + + //does a * G where a is a scalar and G is the curve basepoint + void scalarmultBase(key & aG, const key &a); + key scalarmultBase(const key & a); + //does a * P where a is a scalar and P is an arbitrary point + void scalarmultKey(key &aP, const key &P, const key &a); + key scalarmultKey(const key &P, const key &a); + //Computes aH where H= toPoint(cn_fast_hash(G)), G the basepoint + key scalarmultH(const key & a); + + //Curve addition / subtractions + + //for curve points: AB = A + B + void addKeys(key &AB, const key &A, const key &B); + //aGB = aG + B where a is a scalar, G is the basepoint, and B is a point + void addKeys1(key &aGB, const key &a, const key & B); + //aGbB = aG + bB where a, b are scalars, G is the basepoint and B is a point + void addKeys2(key &aGbB, const key &a, const key &b, const key &B); + //Does some precomputation to make addKeys3 more efficient + // input B a curve point and output a ge_dsmp which has precomputation applied + void precomp(ge_dsmp rv, const key &B); + //aAbB = a*A + b*B where a, b are scalars, A, B are curve points + //B must be input after applying "precomp" + void addKeys3(key &aAbB, const key &a, const key &A, const key &b, const ge_dsmp B); + //AB = A - B where A, B are curve points + void subKeys(key &AB, const key &A, const key &B); + //checks if A, B are equal as curve points + bool equalKeys(const key & A, const key & B); + + //Hashing - cn_fast_hash + //be careful these are also in crypto namespace + //cn_fast_hash for arbitrary l multiples of 32 bytes + void cn_fast_hash(key &hash, const void * data, const size_t l); + void hash_to_scalar(key &hash, const void * data, const size_t l); + //cn_fast_hash for a 32 byte key + void cn_fast_hash(key &hash, const key &in); + void hash_to_scalar(key &hash, const key &in); + //cn_fast_hash for a 32 byte key + key cn_fast_hash(const key &in); + key hash_to_scalar(const key &in); + //for mg sigs + key cn_fast_hash128(const void * in); + key hash_to_scalar128(const void * in); + key cn_fast_hash(ctkeyV PC); + key hash_to_scalar(ctkeyV PC); + //for mg sigs + key cn_fast_hash(const keyV &keys); + key hash_to_scalar(const keyV &keys); + + //returns hashToPoint as described in https://github.com/ShenNoether/ge_fromfe_writeup + key hashToPointSimple(const key &in); + key hashToPoint(const key &in); + void hashToPoint(key &out, const key &in); + + //sums a vector of curve points (for scalars use sc_add) + void sumKeys(key & Csum, const key &Cis); + + //Elliptic Curve Diffie Helman: encodes and decodes the amount b and mask a + // where C= aG + bH + void ecdhEncode(ecdhTuple & unmasked, const key & sharedSec); + void ecdhDecode(ecdhTuple & masked, const key & sharedSec); +} +#endif /* RCTOPS_H */ diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp new file mode 100644 index 000000000..f4dbd65c5 --- /dev/null +++ b/src/ringct/rctSigs.cpp @@ -0,0 +1,875 @@ +// Copyright (c) 2016, Monero Research Labs +// +// Author: Shen Noether <shen.noether@gmx.com> +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "misc_log_ex.h" +#include "rctSigs.h" +#include "cryptonote_core/cryptonote_format_utils.h" + +using namespace crypto; +using namespace std; + +namespace rct { + + //Schnorr Non-linkable + //Gen Gives a signature (L1, s1, s2) proving that the sender knows "x" such that xG = one of P1 or P2 + //Ver Verifies that signer knows an "x" such that xG = one of P1 or P2 + //These are called in the below ASNL sig generation + + void GenSchnorrNonLinkable(key & L1, key & s1, key & s2, const key & x, const key & P1, const key & P2, int index) { + key c1, c2, L2; + key a = skGen(); + if (index == 0) { + scalarmultBase(L1, a); + hash_to_scalar(c2, L1); + skGen(s2); + addKeys2(L2, s2, c2, P2); + hash_to_scalar(c1, L2); + //s1 = a - x * c1 + sc_mulsub(s1.bytes, x.bytes, c1.bytes, a.bytes); + } + else if (index == 1) { + scalarmultBase(L2, a); + hash_to_scalar(c1, L2); + skGen(s1); + addKeys2(L1, s1, c1, P1); + hash_to_scalar(c2, L1); + sc_mulsub(s2.bytes, x.bytes, c2.bytes, a.bytes); + } + else { + throw std::runtime_error("GenSchnorrNonLinkable: invalid index (should be 0 or 1)"); + } + } + + //Schnorr Non-linkable + //Gen Gives a signature (L1, s1, s2) proving that the sender knows "x" such that xG = one of P1 or P2 + //Ver Verifies that signer knows an "x" such that xG = one of P1 or P2 + //These are called in the below ASNL sig generation + bool VerSchnorrNonLinkable(const key & P1, const key & P2, const key & L1, const key & s1, const key & s2) { + key c2, L2, c1, L1p; + hash_to_scalar(c2, L1); + addKeys2(L2, s2, c2, P2); + hash_to_scalar(c1, L2); + addKeys2(L1p, s1, c1, P1); + + return equalKeys(L1, L1p); + } + + //Aggregate Schnorr Non-linkable Ring Signature (ASNL) + // c.f. http://eprint.iacr.org/2015/1098 section 5. + // These are used in range proofs (alternatively Borromean could be used) + // Gen gives a signature which proves the signer knows, for each i, + // an x[i] such that x[i]G = one of P1[i] or P2[i] + // Ver Verifies the signer knows a key for one of P1[i], P2[i] at each i + asnlSig GenASNL(key64 x, key64 P1, key64 P2, bits indices) { + DP("Generating Aggregate Schnorr Non-linkable Ring Signature\n"); + key64 s1; + int j = 0; + asnlSig rv; + rv.s = zero(); + for (j = 0; j < ATOMS; j++) { + GenSchnorrNonLinkable(rv.L1[j], s1[j], rv.s2[j], x[j], P1[j], P2[j], (int)indices[j]); + sc_add(rv.s.bytes, rv.s.bytes, s1[j].bytes); + } + return rv; + } + + //Aggregate Schnorr Non-linkable Ring Signature (ASNL) + // c.f. http://eprint.iacr.org/2015/1098 section 5. + // These are used in range proofs (alternatively Borromean could be used) + // Gen gives a signature which proves the signer knows, for each i, + // an x[i] such that x[i]G = one of P1[i] or P2[i] + // Ver Verifies the signer knows a key for one of P1[i], P2[i] at each i + bool VerASNL(const key64 P1, const key64 P2, const asnlSig &as) { + DP("Verifying Aggregate Schnorr Non-linkable Ring Signature\n"); + key LHS = identity(); + key RHS = scalarmultBase(as.s); + key c2, L2, c1; + int j = 0; + for (j = 0; j < ATOMS; j++) { + hash_to_scalar(c2, as.L1[j]); + addKeys2(L2, as.s2[j], c2, P2[j]); + addKeys(LHS, LHS, as.L1[j]); + hash_to_scalar(c1, L2); + addKeys(RHS, RHS, scalarmultKey(P1[j], c1)); + } + key cc; + sc_sub(cc.bytes, LHS.bytes, RHS.bytes); + return sc_isnonzero(cc.bytes) == 0; + } + + //Multilayered Spontaneous Anonymous Group Signatures (MLSAG signatures) + //These are aka MG signatutes in earlier drafts of the ring ct paper + // c.f. http://eprint.iacr.org/2015/1098 section 2. + // keyImageV just does I[i] = xx[i] * Hash(xx[i] * G) for each i + // Gen creates a signature which proves that for some column in the keymatrix "pk" + // the signer knows a secret key for each row in that column + // Ver verifies that the MG sig was created correctly + keyV keyImageV(const keyV &xx) { + keyV II(xx.size()); + size_t i = 0; + for (i = 0; i < xx.size(); i++) { + II[i] = scalarmultKey(hashToPoint(scalarmultBase(xx[i])), xx[i]); + } + return II; + } + + + //Multilayered Spontaneous Anonymous Group Signatures (MLSAG signatures) + //This is a just slghtly more efficient version than the ones described below + //(will be explained in more detail in Ring Multisig paper + //These are aka MG signatutes in earlier drafts of the ring ct paper + // c.f. http://eprint.iacr.org/2015/1098 section 2. + // keyImageV just does I[i] = xx[i] * Hash(xx[i] * G) for each i + // Gen creates a signature which proves that for some column in the keymatrix "pk" + // the signer knows a secret key for each row in that column + // Ver verifies that the MG sig was created correctly + mgSig MLSAG_Gen(key message, const keyM & pk, const keyV & xx, const unsigned int index, size_t dsRows) { + mgSig rv; + size_t cols = pk.size(); + CHECK_AND_ASSERT_THROW_MES(cols >= 2, "Error! What is c if cols = 1!"); + CHECK_AND_ASSERT_THROW_MES(index < cols, "Index out of range"); + size_t rows = pk[0].size(); + CHECK_AND_ASSERT_THROW_MES(rows >= 1, "Empty pk"); + for (size_t i = 1; i < cols; ++i) { + CHECK_AND_ASSERT_THROW_MES(pk[i].size() == rows, "pk is not rectangular"); + } + CHECK_AND_ASSERT_THROW_MES(xx.size() == rows, "Bad xx size"); + CHECK_AND_ASSERT_THROW_MES(dsRows <= rows, "Bad dsRows size"); + + size_t i = 0, j = 0, ii = 0; + key c, c_old, L, R, Hi; + sc_0(c_old.bytes); + vector<geDsmp> Ip(dsRows); + rv.II = keyV(dsRows); + keyV alpha(rows); + keyV aG(rows); + rv.ss = keyM(cols, aG); + keyV aHP(dsRows); + keyV toHash(1 + 3 * dsRows + 2 * (rows - dsRows)); + toHash[0] = message; + DP("here1"); + for (i = 0; i < dsRows; i++) { + skpkGen(alpha[i], aG[i]); //need to save alphas for later.. + Hi = hashToPoint(pk[index][i]); + aHP[i] = scalarmultKey(Hi, alpha[i]); + toHash[3 * i + 1] = pk[index][i]; + toHash[3 * i + 2] = aG[i]; + toHash[3 * i + 3] = aHP[i]; + rv.II[i] = scalarmultKey(Hi, xx[i]); + precomp(Ip[i].k, rv.II[i]); + } + size_t ndsRows = 3 * dsRows; //non Double Spendable Rows (see identity chains paper) + for (i = dsRows, ii = 0 ; i < rows ; i++, ii++) { + skpkGen(alpha[i], aG[i]); //need to save alphas for later.. + toHash[ndsRows + 2 * ii + 1] = pk[index][i]; + toHash[ndsRows + 2 * ii + 2] = aG[i]; + } + + c_old = hash_to_scalar(toHash); + + + i = (index + 1) % cols; + if (i == 0) { + copy(rv.cc, c_old); + } + while (i != index) { + + rv.ss[i] = skvGen(rows); + sc_0(c.bytes); + for (j = 0; j < dsRows; j++) { + addKeys2(L, rv.ss[i][j], c_old, pk[i][j]); + hashToPoint(Hi, pk[i][j]); + addKeys3(R, rv.ss[i][j], Hi, c_old, Ip[j].k); + toHash[3 * j + 1] = pk[i][j]; + toHash[3 * j + 2] = L; + toHash[3 * j + 3] = R; + } + for (j = dsRows, ii = 0; j < rows; j++, ii++) { + addKeys2(L, rv.ss[i][j], c_old, pk[i][j]); + toHash[ndsRows + 2 * ii + 1] = pk[i][j]; + toHash[ndsRows + 2 * ii + 2] = L; + } + c = hash_to_scalar(toHash); + copy(c_old, c); + i = (i + 1) % cols; + + if (i == 0) { + copy(rv.cc, c_old); + } + } + for (j = 0; j < rows; j++) { + sc_mulsub(rv.ss[index][j].bytes, c.bytes, xx[j].bytes, alpha[j].bytes); + } + return rv; + } + + //Multilayered Spontaneous Anonymous Group Signatures (MLSAG signatures) + //This is a just slghtly more efficient version than the ones described below + //(will be explained in more detail in Ring Multisig paper + //These are aka MG signatutes in earlier drafts of the ring ct paper + // c.f. http://eprint.iacr.org/2015/1098 section 2. + // keyImageV just does I[i] = xx[i] * Hash(xx[i] * G) for each i + // Gen creates a signature which proves that for some column in the keymatrix "pk" + // the signer knows a secret key for each row in that column + // Ver verifies that the MG sig was created correctly + bool MLSAG_Ver(key message, const keyM & pk, const mgSig & rv, size_t dsRows) { + + size_t cols = pk.size(); + CHECK_AND_ASSERT_MES(cols >= 2, false, "Error! What is c if cols = 1!"); + size_t rows = pk[0].size(); + CHECK_AND_ASSERT_MES(rows >= 1, false, "Empty pk"); + for (size_t i = 1; i < cols; ++i) { + CHECK_AND_ASSERT_MES(pk[i].size() == rows, false, "pk is not rectangular"); + } + CHECK_AND_ASSERT_MES(rv.II.size() == dsRows, false, "Bad II size"); + CHECK_AND_ASSERT_MES(rv.ss.size() == cols, false, "Bad rv.ss size"); + for (size_t i = 0; i < cols; ++i) { + CHECK_AND_ASSERT_MES(rv.ss[i].size() == rows, false, "rv.ss is not rectangular"); + } + CHECK_AND_ASSERT_MES(dsRows <= rows, false, "Bad dsRows value"); + + size_t i = 0, j = 0, ii = 0; + key c, L, R, Hi; + key c_old = copy(rv.cc); + vector<geDsmp> Ip(dsRows); + for (i = 0 ; i < dsRows ; i++) { + precomp(Ip[i].k, rv.II[i]); + } + size_t ndsRows = 3 * dsRows; //non Double Spendable Rows (see identity chains paper + keyV toHash(1 + 3 * dsRows + 2 * (rows - dsRows)); + toHash[0] = message; + i = 0; + while (i < cols) { + sc_0(c.bytes); + for (j = 0; j < dsRows; j++) { + addKeys2(L, rv.ss[i][j], c_old, pk[i][j]); + hashToPoint(Hi, pk[i][j]); + addKeys3(R, rv.ss[i][j], Hi, c_old, Ip[j].k); + toHash[3 * j + 1] = pk[i][j]; + toHash[3 * j + 2] = L; + toHash[3 * j + 3] = R; + } + for (j = dsRows, ii = 0 ; j < rows ; j++, ii++) { + addKeys2(L, rv.ss[i][j], c_old, pk[i][j]); + toHash[ndsRows + 2 * ii + 1] = pk[i][j]; + toHash[ndsRows + 2 * ii + 2] = L; + } + c = hash_to_scalar(toHash); + copy(c_old, c); + i = (i + 1); + } + sc_sub(c.bytes, c_old.bytes, rv.cc.bytes); + return sc_isnonzero(c.bytes) == 0; + } + + + + //proveRange and verRange + //proveRange gives C, and mask such that \sumCi = C + // c.f. http://eprint.iacr.org/2015/1098 section 5.1 + // and Ci is a commitment to either 0 or 2^i, i=0,...,63 + // thus this proves that "amount" is in [0, 2^64] + // mask is a such that C = aG + bH, and b = amount + //verRange verifies that \sum Ci = C and that each Ci is a commitment to 0 or 2^i + rangeSig proveRange(key & C, key & mask, const xmr_amount & amount) { + sc_0(mask.bytes); + identity(C); + bits b; + d2b(b, amount); + rangeSig sig; + key64 ai; + key64 CiH; + int i = 0; + for (i = 0; i < ATOMS; i++) { + skGen(ai[i]); + if (b[i] == 0) { + scalarmultBase(sig.Ci[i], ai[i]); + } + if (b[i] == 1) { + addKeys1(sig.Ci[i], ai[i], H2[i]); + } + subKeys(CiH[i], sig.Ci[i], H2[i]); + sc_add(mask.bytes, mask.bytes, ai[i].bytes); + addKeys(C, C, sig.Ci[i]); + } + sig.asig = GenASNL(ai, sig.Ci, CiH, b); + return sig; + } + + //proveRange and verRange + //proveRange gives C, and mask such that \sumCi = C + // c.f. http://eprint.iacr.org/2015/1098 section 5.1 + // and Ci is a commitment to either 0 or 2^i, i=0,...,63 + // thus this proves that "amount" is in [0, 2^64] + // mask is a such that C = aG + bH, and b = amount + //verRange verifies that \sum Ci = C and that each Ci is a commitment to 0 or 2^i + bool verRange(const key & C, const rangeSig & as) { + key64 CiH; + int i = 0; + key Ctmp = identity(); + for (i = 0; i < 64; i++) { + subKeys(CiH[i], as.Ci[i], H2[i]); + addKeys(Ctmp, Ctmp, as.Ci[i]); + } + if (!equalKeys(C, Ctmp)) + return false; + if (!VerASNL(as.Ci, CiH, as.asig)) + return false; + return true; + } + + key get_pre_mlsag_hash(const rctSig &rv) + { + keyV hashes; + hashes.push_back(rv.message); + crypto::hash h; + cryptonote::get_blob_hash(cryptonote::t_serializable_object_to_blob((const rctSigBase&)rv), h); + hashes.push_back(hash2rct(h)); + keyV kv; + for (auto r: rv.p.rangeSigs) + { + for (size_t n = 0; n < 64; ++n) + kv.push_back(r.asig.L1[n]); + for (size_t n = 0; n < 64; ++n) + kv.push_back(r.asig.s2[n]); + kv.push_back(r.asig.s); + for (size_t n = 0; n < 64; ++n) + kv.push_back(r.Ci[n]); + } + hashes.push_back(cn_fast_hash(kv)); + + return cn_fast_hash(hashes); + } + + //Ring-ct MG sigs + //Prove: + // c.f. http://eprint.iacr.org/2015/1098 section 4. definition 10. + // This does the MG sig on the "dest" part of the given key matrix, and + // the last row is the sum of input commitments from that column - sum output commitments + // this shows that sum inputs = sum outputs + //Ver: + // verifies the above sig is created corretly + mgSig proveRctMG(const key &message, const ctkeyM & pubs, const ctkeyV & inSk, const ctkeyV &outSk, const ctkeyV & outPk, unsigned int index, key txnFeeKey) { + mgSig mg; + //setup vars + size_t cols = pubs.size(); + CHECK_AND_ASSERT_THROW_MES(cols >= 1, "Empty pubs"); + size_t rows = pubs[0].size(); + CHECK_AND_ASSERT_THROW_MES(rows >= 1, "Empty pubs"); + for (size_t i = 1; i < cols; ++i) { + CHECK_AND_ASSERT_THROW_MES(pubs[i].size() == rows, "pubs is not rectangular"); + } + CHECK_AND_ASSERT_THROW_MES(inSk.size() == rows, "Bad inSk size"); + CHECK_AND_ASSERT_THROW_MES(outSk.size() == outPk.size(), "Bad outSk/outPk size"); + + keyV sk(rows + 1); + keyV tmp(rows + 1); + size_t i = 0, j = 0; + for (i = 0; i < rows + 1; i++) { + sc_0(sk[i].bytes); + identity(tmp[i]); + } + keyM M(cols, tmp); + //create the matrix to mg sig + for (i = 0; i < cols; i++) { + M[i][rows] = identity(); + for (j = 0; j < rows; j++) { + M[i][j] = pubs[i][j].dest; + addKeys(M[i][rows], M[i][rows], pubs[i][j].mask); //add input commitments in last row + } + } + sc_0(sk[rows].bytes); + for (j = 0; j < rows; j++) { + sk[j] = copy(inSk[j].dest); + sc_add(sk[rows].bytes, sk[rows].bytes, inSk[j].mask.bytes); //add masks in last row + } + for (i = 0; i < cols; i++) { + for (size_t j = 0; j < outPk.size(); j++) { + subKeys(M[i][rows], M[i][rows], outPk[j].mask); //subtract output Ci's in last row + } + //subtract txn fee output in last row + subKeys(M[i][rows], M[i][rows], txnFeeKey); + } + for (size_t j = 0; j < outPk.size(); j++) { + sc_sub(sk[rows].bytes, sk[rows].bytes, outSk[j].mask.bytes); //subtract output masks in last row.. + } + return MLSAG_Gen(message, M, sk, index, rows); + } + + + //Ring-ct MG sigs Simple + // Simple version for when we assume only + // post rct inputs + // here pubs is a vector of (P, C) length mixin + // inSk is x, a_in corresponding to signing index + // a_out, Cout is for the output commitment + // index is the signing index.. + mgSig proveRctMGSimple(const key &message, const ctkeyV & pubs, const ctkey & inSk, const key &a , const key &Cout, unsigned int index) { + mgSig mg; + //setup vars + size_t rows = 1; + size_t cols = pubs.size(); + CHECK_AND_ASSERT_THROW_MES(cols >= 1, "Empty pubs"); + keyV tmp(rows + 1); + keyV sk(rows + 1); + size_t i; + keyM M(cols, tmp); + for (i = 0; i < cols; i++) { + M[i][0] = pubs[i].dest; + subKeys(M[i][1], pubs[i].mask, Cout); + sk[0] = copy(inSk.dest); + sc_sub(sk[1].bytes, inSk.mask.bytes, a.bytes); + } + return MLSAG_Gen(message, M, sk, index, rows); + } + + + //Ring-ct MG sigs + //Prove: + // c.f. http://eprint.iacr.org/2015/1098 section 4. definition 10. + // This does the MG sig on the "dest" part of the given key matrix, and + // the last row is the sum of input commitments from that column - sum output commitments + // this shows that sum inputs = sum outputs + //Ver: + // verifies the above sig is created corretly + bool verRctMG(const mgSig &mg, const ctkeyM & pubs, const ctkeyV & outPk, key txnFeeKey, const key &message) { + //setup vars + size_t cols = pubs.size(); + CHECK_AND_ASSERT_MES(cols >= 1, false, "Empty pubs"); + size_t rows = pubs[0].size(); + CHECK_AND_ASSERT_MES(rows >= 1, false, "Empty pubs"); + for (size_t i = 1; i < cols; ++i) { + CHECK_AND_ASSERT_MES(pubs[i].size() == rows, false, "pubs is not rectangular"); + } + + keyV tmp(rows + 1); + size_t i = 0, j = 0; + for (i = 0; i < rows + 1; i++) { + identity(tmp[i]); + } + keyM M(cols, tmp); + + //create the matrix to mg sig + for (j = 0; j < rows; j++) { + for (i = 0; i < cols; i++) { + M[i][j] = pubs[i][j].dest; + addKeys(M[i][rows], M[i][rows], pubs[i][j].mask); //add Ci in last row + } + } + for (i = 0; i < cols; i++) { + for (j = 0; j < outPk.size(); j++) { + subKeys(M[i][rows], M[i][rows], outPk[j].mask); //subtract output Ci's in last row + } + //subtract txn fee output in last row + subKeys(M[i][rows], M[i][rows], txnFeeKey); + } + return MLSAG_Ver(message, M, mg, rows); + } + + //Ring-ct Simple MG sigs + //Ver: + //This does a simplified version, assuming only post Rct + //inputs + bool verRctMGSimple(const key &message, const mgSig &mg, const ctkeyV & pubs, const key & C) { + //setup vars + size_t rows = 1; + size_t cols = pubs.size(); + CHECK_AND_ASSERT_MES(cols >= 1, false, "Empty pubs"); + keyV tmp(rows + 1); + size_t i; + keyM M(cols, tmp); + //create the matrix to mg sig + for (i = 0; i < cols; i++) { + M[i][0] = pubs[i].dest; + subKeys(M[i][1], pubs[i].mask, C); + } + //DP(C); + return MLSAG_Ver(message, M, mg, rows); + } + + //These functions get keys from blockchain + //replace these when connecting blockchain + //getKeyFromBlockchain grabs a key from the blockchain at "reference_index" to mix with + //populateFromBlockchain creates a keymatrix with "mixin" columns and one of the columns is inPk + // the return value are the key matrix, and the index where inPk was put (random). + void getKeyFromBlockchain(ctkey & a, size_t reference_index) { + a.mask = pkGen(); + a.dest = pkGen(); + } + + //These functions get keys from blockchain + //replace these when connecting blockchain + //getKeyFromBlockchain grabs a key from the blockchain at "reference_index" to mix with + //populateFromBlockchain creates a keymatrix with "mixin" + 1 columns and one of the columns is inPk + // the return value are the key matrix, and the index where inPk was put (random). + tuple<ctkeyM, xmr_amount> populateFromBlockchain(ctkeyV inPk, int mixin) { + int rows = inPk.size(); + ctkeyM rv(mixin + 1, inPk); + int index = randXmrAmount(mixin); + int i = 0, j = 0; + for (i = 0; i <= mixin; i++) { + if (i != index) { + for (j = 0; j < rows; j++) { + getKeyFromBlockchain(rv[i][j], (size_t)randXmrAmount); + } + } + } + return make_tuple(rv, index); + } + + //These functions get keys from blockchain + //replace these when connecting blockchain + //getKeyFromBlockchain grabs a key from the blockchain at "reference_index" to mix with + //populateFromBlockchain creates a keymatrix with "mixin" columns and one of the columns is inPk + // the return value are the key matrix, and the index where inPk was put (random). + xmr_amount populateFromBlockchainSimple(ctkeyV & mixRing, const ctkey & inPk, int mixin) { + int index = randXmrAmount(mixin); + int i = 0; + for (i = 0; i <= mixin; i++) { + if (i != index) { + getKeyFromBlockchain(mixRing[i], (size_t)randXmrAmount(1000)); + } else { + mixRing[i] = inPk; + } + } + return index; + } + + //RingCT protocol + //genRct: + // creates an rctSig with all data necessary to verify the rangeProofs and that the signer owns one of the + // columns that are claimed as inputs, and that the sum of inputs = sum of outputs. + // Also contains masked "amount" and "mask" so the receiver can see how much they received + //verRct: + // verifies that all signatures (rangeProogs, MG sig, sum inputs = outputs) are correct + //decodeRct: (c.f. http://eprint.iacr.org/2015/1098 section 5.1.1) + // uses the attached ecdh info to find the amounts represented by each output commitment + // must know the destination private key to find the correct amount, else will return a random number + // Note: For txn fees, the last index in the amounts vector should contain that + // Thus the amounts vector will be "one" longer than the destinations vectort + rctSig genRct(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> & amounts, const ctkeyM &mixRing, const keyV &amount_keys, unsigned int index, ctkeyV &outSk) { + CHECK_AND_ASSERT_THROW_MES(amounts.size() == destinations.size() || amounts.size() == destinations.size() + 1, "Different number of amounts/destinations"); + CHECK_AND_ASSERT_THROW_MES(index < mixRing.size(), "Bad index into mixRing"); + for (size_t n = 0; n < mixRing.size(); ++n) { + CHECK_AND_ASSERT_THROW_MES(mixRing[n].size() == inSk.size(), "Bad mixRing size"); + } + + rctSig rv; + rv.type = RCTTypeFull; + rv.message = message; + rv.outPk.resize(destinations.size()); + rv.p.rangeSigs.resize(destinations.size()); + rv.ecdhInfo.resize(destinations.size()); + + size_t i = 0; + keyV masks(destinations.size()); //sk mask.. + outSk.resize(destinations.size()); + for (i = 0; i < destinations.size(); i++) { + //add destination to sig + rv.outPk[i].dest = copy(destinations[i]); + //compute range proof + rv.p.rangeSigs[i] = proveRange(rv.outPk[i].mask, outSk[i].mask, amounts[i]); + #ifdef DBG + CHECK_AND_ASSERT_THROW_MES(verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]), "verRange failed on newly created proof"); + #endif + + //mask amount and mask + rv.ecdhInfo[i].mask = copy(outSk[i].mask); + rv.ecdhInfo[i].amount = d2h(amounts[i]); + ecdhEncode(rv.ecdhInfo[i], amount_keys[i]); + + } + + //set txn fee + if (amounts.size() > destinations.size()) + { + rv.txnFee = amounts[destinations.size()]; + } + else + { + rv.txnFee = 0; + } + key txnFeeKey = scalarmultH(d2h(rv.txnFee)); + + rv.mixRing = mixRing; + rv.p.MGs.push_back(proveRctMG(get_pre_mlsag_hash(rv), rv.mixRing, inSk, outSk, rv.outPk, index, txnFeeKey)); + return rv; + } + + rctSig genRct(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> & amounts, const keyV &amount_keys, const int mixin) { + unsigned int index; + ctkeyM mixRing; + ctkeyV outSk; + tie(mixRing, index) = populateFromBlockchain(inPk, mixin); + return genRct(message, inSk, destinations, amounts, mixRing, amount_keys, index, outSk); + } + + //RCT simple + //for post-rct only + rctSig genRctSimple(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> &inamounts, const vector<xmr_amount> &outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector<unsigned int> & index, ctkeyV &outSk) { + CHECK_AND_ASSERT_THROW_MES(inamounts.size() > 0, "Empty inamounts"); + CHECK_AND_ASSERT_THROW_MES(inamounts.size() == inSk.size(), "Different number of inamounts/inSk"); + CHECK_AND_ASSERT_THROW_MES(outamounts.size() == destinations.size(), "Different number of amounts/destinations"); + CHECK_AND_ASSERT_THROW_MES(index.size() == inSk.size(), "Different number of index/inSk"); + CHECK_AND_ASSERT_THROW_MES(mixRing.size() == inSk.size(), "Different number of mixRing/inSk"); + for (size_t n = 0; n < mixRing.size(); ++n) { + CHECK_AND_ASSERT_THROW_MES(index[n] < mixRing[n].size(), "Bad index into mixRing"); + } + + rctSig rv; + rv.type = RCTTypeSimple; + rv.message = message; + rv.outPk.resize(destinations.size()); + rv.p.rangeSigs.resize(destinations.size()); + rv.ecdhInfo.resize(destinations.size()); + + size_t i; + keyV masks(destinations.size()); //sk mask.. + outSk.resize(destinations.size()); + key sumout = zero(); + for (i = 0; i < destinations.size(); i++) { + + //add destination to sig + rv.outPk[i].dest = copy(destinations[i]); + //compute range proof + rv.p.rangeSigs[i] = proveRange(rv.outPk[i].mask, outSk[i].mask, outamounts[i]); + #ifdef DBG + verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]); + #endif + + sc_add(sumout.bytes, outSk[i].mask.bytes, sumout.bytes); + + //mask amount and mask + rv.ecdhInfo[i].mask = copy(outSk[i].mask); + rv.ecdhInfo[i].amount = d2h(outamounts[i]); + ecdhEncode(rv.ecdhInfo[i], amount_keys[i]); + } + + //set txn fee + rv.txnFee = txnFee; +// TODO: unused ?? +// key txnFeeKey = scalarmultH(d2h(rv.txnFee)); + rv.mixRing = mixRing; + rv.pseudoOuts.resize(inamounts.size()); + rv.p.MGs.resize(inamounts.size()); + key sumpouts = zero(); //sum pseudoOut masks + keyV a(inamounts.size()); + for (i = 0 ; i < inamounts.size() - 1; i++) { + skGen(a[i]); + sc_add(sumpouts.bytes, a[i].bytes, sumpouts.bytes); + genC(rv.pseudoOuts[i], a[i], inamounts[i]); + } + rv.mixRing = mixRing; + sc_sub(a[i].bytes, sumout.bytes, sumpouts.bytes); + genC(rv.pseudoOuts[i], a[i], inamounts[i]); + DP(rv.pseudoOuts[i]); + + key full_message = get_pre_mlsag_hash(rv); + for (i = 0 ; i < inamounts.size(); i++) { + rv.p.MGs[i] = proveRctMGSimple(full_message, rv.mixRing[i], inSk[i], a[i], rv.pseudoOuts[i], index[i]); + } + return rv; + } + + rctSig genRctSimple(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> &inamounts, const vector<xmr_amount> &outamounts, const keyV &amount_keys, xmr_amount txnFee, unsigned int mixin) { + std::vector<unsigned int> index; + index.resize(inPk.size()); + ctkeyM mixRing; + ctkeyV outSk; + mixRing.resize(inPk.size()); + for (size_t i = 0; i < inPk.size(); ++i) { + mixRing[i].resize(mixin+1); + index[i] = populateFromBlockchainSimple(mixRing[i], inPk[i], mixin); + } + return genRctSimple(message, inSk, destinations, inamounts, outamounts, txnFee, mixRing, amount_keys, index, outSk); + } + + //RingCT protocol + //genRct: + // creates an rctSig with all data necessary to verify the rangeProofs and that the signer owns one of the + // columns that are claimed as inputs, and that the sum of inputs = sum of outputs. + // Also contains masked "amount" and "mask" so the receiver can see how much they received + //verRct: + // verifies that all signatures (rangeProogs, MG sig, sum inputs = outputs) are correct + //decodeRct: (c.f. http://eprint.iacr.org/2015/1098 section 5.1.1) + // uses the attached ecdh info to find the amounts represented by each output commitment + // must know the destination private key to find the correct amount, else will return a random number + bool verRct(const rctSig & rv) { + CHECK_AND_ASSERT_MES(rv.type == RCTTypeFull, false, "verRct called on non-full rctSig"); + CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.p.rangeSigs.size(), false, "Mismatched sizes of outPk and rv.p.rangeSigs"); + CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.ecdhInfo.size(), false, "Mismatched sizes of outPk and rv.ecdhInfo"); + CHECK_AND_ASSERT_MES(rv.p.MGs.size() == 1, false, "full rctSig has not one MG"); + + // some rct ops can throw + try + { + size_t i = 0; + bool tmp; + DP("range proofs verified?"); + for (i = 0; i < rv.outPk.size(); i++) { + tmp = verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]); + DP(tmp); + if (!tmp) { + LOG_ERROR("Range proof verification failed for input " << i); + return false; + } + } + //compute txn fee + key txnFeeKey = scalarmultH(d2h(rv.txnFee)); + bool mgVerd = verRctMG(rv.p.MGs[0], rv.mixRing, rv.outPk, txnFeeKey, get_pre_mlsag_hash(rv)); + DP("mg sig verified?"); + DP(mgVerd); + if (!mgVerd) { + LOG_ERROR("MG signature verification failed"); + return false; + } + + return true; + } + catch(...) + { + return false; + } + } + + //ver RingCT simple + //assumes only post-rct style inputs (at least for max anonymity) + bool verRctSimple(const rctSig & rv) { + size_t i = 0; + + CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple, false, "verRctSimple called on non simple rctSig"); + CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.p.rangeSigs.size(), false, "Mismatched sizes of outPk and rv.p.rangeSigs"); + CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.ecdhInfo.size(), false, "Mismatched sizes of outPk and rv.ecdhInfo"); + CHECK_AND_ASSERT_MES(rv.pseudoOuts.size() == rv.p.MGs.size(), false, "Mismatched sizes of rv.pseudoOuts and rv.p.MGs"); + CHECK_AND_ASSERT_MES(rv.pseudoOuts.size() == rv.mixRing.size(), false, "Mismatched sizes of rv.pseudoOuts and mixRing"); + + key sumOutpks = identity(); + for (i = 0; i < rv.outPk.size(); i++) { + if (!verRange(rv.outPk[i].mask, rv.p.rangeSigs[i])) { + LOG_ERROR("Range proof verified failed for input " << i); + return false; + } + addKeys(sumOutpks, sumOutpks, rv.outPk[i].mask); + } + DP(sumOutpks); + key txnFeeKey = scalarmultH(d2h(rv.txnFee)); + addKeys(sumOutpks, txnFeeKey, sumOutpks); + + bool tmpb = false; + key message = get_pre_mlsag_hash(rv); + key sumPseudoOuts = identity(); + for (i = 0 ; i < rv.mixRing.size() ; i++) { + tmpb = verRctMGSimple(message, rv.p.MGs[i], rv.mixRing[i], rv.pseudoOuts[i]); + addKeys(sumPseudoOuts, sumPseudoOuts, rv.pseudoOuts[i]); + DP(tmpb); + if (!tmpb) { + LOG_ERROR("verRctMGSimple failed for input " << i); + return false; + } + } + DP(sumPseudoOuts); + + //check pseudoOuts vs Outs.. + if (!equalKeys(sumPseudoOuts, sumOutpks)) { + LOG_ERROR("Sum check failed"); + return false; + } + + return true; + } + + //RingCT protocol + //genRct: + // creates an rctSig with all data necessary to verify the rangeProofs and that the signer owns one of the + // columns that are claimed as inputs, and that the sum of inputs = sum of outputs. + // Also contains masked "amount" and "mask" so the receiver can see how much they received + //verRct: + // verifies that all signatures (rangeProogs, MG sig, sum inputs = outputs) are correct + //decodeRct: (c.f. http://eprint.iacr.org/2015/1098 section 5.1.1) + // uses the attached ecdh info to find the amounts represented by each output commitment + // must know the destination private key to find the correct amount, else will return a random number + xmr_amount decodeRct(const rctSig & rv, const key & sk, unsigned int i, key & mask) { + CHECK_AND_ASSERT_MES(rv.type == RCTTypeFull, false, "decodeRct called on non-full rctSig"); + CHECK_AND_ASSERT_THROW_MES(rv.p.rangeSigs.size() > 0, "Empty rv.p.rangeSigs"); + CHECK_AND_ASSERT_THROW_MES(rv.outPk.size() == rv.p.rangeSigs.size(), "Mismatched sizes of rv.outPk and rv.p.rangeSigs"); + CHECK_AND_ASSERT_THROW_MES(i < rv.ecdhInfo.size(), "Bad index"); + + //mask amount and mask + ecdhTuple ecdh_info = rv.ecdhInfo[i]; + ecdhDecode(ecdh_info, sk); + mask = ecdh_info.mask; + key amount = ecdh_info.amount; + key C = rv.outPk[i].mask; + DP("C"); + DP(C); + key Ctmp; + addKeys2(Ctmp, mask, amount, H); + DP("Ctmp"); + DP(Ctmp); + if (equalKeys(C, Ctmp) == false) { + CHECK_AND_ASSERT_THROW_MES(false, "warning, amount decoded incorrectly, will be unable to spend"); + } + return h2d(amount); + } + + xmr_amount decodeRct(const rctSig & rv, const key & sk, unsigned int i) { + key mask; + return decodeRct(rv, sk, i, mask); + } + + xmr_amount decodeRctSimple(const rctSig & rv, const key & sk, unsigned int i, key &mask) { + CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple, false, "decodeRct called on non simple rctSig"); + CHECK_AND_ASSERT_THROW_MES(rv.p.rangeSigs.size() > 0, "Empty rv.p.rangeSigs"); + CHECK_AND_ASSERT_THROW_MES(rv.outPk.size() == rv.p.rangeSigs.size(), "Mismatched sizes of rv.outPk and rv.p.rangeSigs"); + CHECK_AND_ASSERT_THROW_MES(i < rv.ecdhInfo.size(), "Bad index"); + + //mask amount and mask + ecdhTuple ecdh_info = rv.ecdhInfo[i]; + ecdhDecode(ecdh_info, sk); + mask = ecdh_info.mask; + key amount = ecdh_info.amount; + key C = rv.outPk[i].mask; + DP("C"); + DP(C); + key Ctmp; + addKeys2(Ctmp, mask, amount, H); + DP("Ctmp"); + DP(Ctmp); + if (equalKeys(C, Ctmp) == false) { + CHECK_AND_ASSERT_THROW_MES(false, "warning, amount decoded incorrectly, will be unable to spend"); + } + return h2d(amount); + } + + xmr_amount decodeRctSimple(const rctSig & rv, const key & sk, unsigned int i) { + key mask; + return decodeRctSimple(rv, sk, i, mask); + } +} diff --git a/src/ringct/rctSigs.h b/src/ringct/rctSigs.h new file mode 100644 index 000000000..f1c906d5e --- /dev/null +++ b/src/ringct/rctSigs.h @@ -0,0 +1,150 @@ +// Copyright (c) 2016, Monero Research Labs +// +// Author: Shen Noether <shen.noether@gmx.com> +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +//#define DBG + +#ifndef RCTSIGS_H +#define RCTSIGS_H + +#include <cstddef> +#include <mutex> +#include <vector> +#include <tuple> + +#include "crypto/generic-ops.h" + +extern "C" { +#include "crypto/random.h" +#include "crypto/keccak.h" +} +#include "crypto/crypto.h" + + +#include "rctTypes.h" +#include "rctOps.h" + +//Define this flag when debugging to get additional info on the console +#ifdef DBG +#define DP(x) dp(x) +#else +#define DP(x) +#endif + + + +using namespace std; +using namespace crypto; + +namespace rct { + + //Schnorr Non-linkable + //Gen Gives a signature (L1, s1, s2) proving that the sender knows "x" such that xG = one of P1 or P2 + //Ver Verifies that signer knows an "x" such that xG = one of P1 or P2 + //These are called in the below ASNL sig generation + void GenSchnorrNonLinkable(key & L1, key & s1, key & s2, const key & x, const key & P1, const key & P2, int index); + bool VerSchnorrNonLinkable(const key & P1, const key & P2, const key & L1, const key & s1, const key & s2); + + //Aggregate Schnorr Non-linkable Ring Signature (ASNL) + // c.f. http://eprint.iacr.org/2015/1098 section 5. + // These are used in range proofs (alternatively Borromean could be used) + // Gen gives a signature which proves the signer knows, for each i, + // an x[i] such that x[i]G = one of P1[i] or P2[i] + // Ver Verifies the signer knows a key for one of P1[i], P2[i] at each i + asnlSig GenASNL(key64 x, key64 P1, key64 P2, bits indices); + bool VerASNL(const key64 P1, const key64 P2, const asnlSig &as); + + //Multilayered Spontaneous Anonymous Group Signatures (MLSAG signatures) + //These are aka MG signatutes in earlier drafts of the ring ct paper + // c.f. http://eprint.iacr.org/2015/1098 section 2. + // keyImageV just does I[i] = xx[i] * HashToPoint(xx[i] * G) for each i + // Gen creates a signature which proves that for some column in the keymatrix "pk" + // the signer knows a secret key for each row in that column + // Ver verifies that the MG sig was created correctly + keyV keyImageV(const keyV &xx); + mgSig MLSAG_Gen(key message, const keyM & pk, const keyV & xx, const unsigned int index, size_t dsRows); + bool MLSAG_Ver(key message, const keyM &pk, const mgSig &sig, size_t dsRows); + //mgSig MLSAG_Gen_Old(const keyM & pk, const keyV & xx, const int index); + + //proveRange and verRange + //proveRange gives C, and mask such that \sumCi = C + // c.f. http://eprint.iacr.org/2015/1098 section 5.1 + // and Ci is a commitment to either 0 or 2^i, i=0,...,63 + // thus this proves that "amount" is in [0, 2^64] + // mask is a such that C = aG + bH, and b = amount + //verRange verifies that \sum Ci = C and that each Ci is a commitment to 0 or 2^i + rangeSig proveRange(key & C, key & mask, const xmr_amount & amount); + bool verRange(const key & C, const rangeSig & as); + + //Ring-ct MG sigs + //Prove: + // c.f. http://eprint.iacr.org/2015/1098 section 4. definition 10. + // This does the MG sig on the "dest" part of the given key matrix, and + // the last row is the sum of input commitments from that column - sum output commitments + // this shows that sum inputs = sum outputs + //Ver: + // verifies the above sig is created corretly + mgSig proveRctMG(const ctkeyM & pubs, const ctkeyV & inSk, const keyV &outMasks, const ctkeyV & outPk, unsigned int index, key txnFee, const key &message); + mgSig proveRctMGSimple(const key & message, const ctkeyV & pubs, const ctkey & inSk, const key &a , const key &Cout, unsigned int index); + bool verRctMG(const mgSig &mg, const ctkeyM & pubs, const ctkeyV & outPk, key txnFee, const key &message); + bool verRctMGSimple(const key &message, const mgSig &mg, const ctkeyV & pubs, const key & C); + + //These functions get keys from blockchain + //replace these when connecting blockchain + //getKeyFromBlockchain grabs a key from the blockchain at "reference_index" to mix with + //populateFromBlockchain creates a keymatrix with "mixin" columns and one of the columns is inPk + // the return value are the key matrix, and the index where inPk was put (random). + void getKeyFromBlockchain(ctkey & a, size_t reference_index); + tuple<ctkeyM, xmr_amount> populateFromBlockchain(ctkeyV inPk, int mixin); + + //RingCT protocol + //genRct: + // creates an rctSig with all data necessary to verify the rangeProofs and that the signer owns one of the + // columns that are claimed as inputs, and that the sum of inputs = sum of outputs. + // Also contains masked "amount" and "mask" so the receiver can see how much they received + //verRct: + // verifies that all signatures (rangeProogs, MG sig, sum inputs = outputs) are correct + //decodeRct: (c.f. http://eprint.iacr.org/2015/1098 section 5.1.1) + // uses the attached ecdh info to find the amounts represented by each output commitment + // must know the destination private key to find the correct amount, else will return a random number + rctSig genRct(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> & amounts, const ctkeyM &mixRing, const keyV &amount_keys, unsigned int index, ctkeyV &outSk); + rctSig genRct(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> & amounts, const keyV &amount_keys, const int mixin); + rctSig genRctSimple(const key & message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> & inamounts, const vector<xmr_amount> & outamounts, const keyV &amount_keys, xmr_amount txnFee, unsigned int mixin); + rctSig genRctSimple(const key & message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> & inamounts, const vector<xmr_amount> & outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector<unsigned int> & index, ctkeyV &outSk); + bool verRct(const rctSig & rv); + bool verRctSimple(const rctSig & rv); + xmr_amount decodeRct(const rctSig & rv, const key & sk, unsigned int i, key & mask); + xmr_amount decodeRct(const rctSig & rv, const key & sk, unsigned int i); + xmr_amount decodeRctSimple(const rctSig & rv, const key & sk, unsigned int i, key & mask); + xmr_amount decodeRctSimple(const rctSig & rv, const key & sk, unsigned int i); +} +#endif /* RCTSIGS_H */ + diff --git a/src/ringct/rctTypes.cpp b/src/ringct/rctTypes.cpp new file mode 100644 index 000000000..e773c6043 --- /dev/null +++ b/src/ringct/rctTypes.cpp @@ -0,0 +1,208 @@ +// Copyright (c) 2016, Monero Research Labs +// +// Author: Shen Noether <shen.noether@gmx.com> +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "rctTypes.h" +using namespace crypto; +using namespace std; + +namespace rct { + + //dp + //Debug printing for the above types + //Actually use DP(value) and #define DBG + + void dp(key a) { + int j = 0; + printf("\""); + for (j = 0; j < 32; j++) { + printf("%02x", (unsigned char)a.bytes[j]); + } + printf("\""); + printf("\n"); + } + + void dp(bool a) { + printf(" ... %s ... ", a ? "true" : "false"); + printf("\n"); + } + + void dp(const char * a, int l) { + int j = 0; + printf("\""); + for (j = 0; j < l; j++) { + printf("%02x", (unsigned char)a[j]); + } + printf("\""); + printf("\n"); + } + void dp(keyV a) { + size_t j = 0; + printf("["); + for (j = 0; j < a.size(); j++) { + dp(a[j]); + if (j < a.size() - 1) { + printf(","); + } + } + printf("]"); + printf("\n"); + } + void dp(keyM a) { + size_t j = 0; + printf("["); + for (j = 0; j < a.size(); j++) { + dp(a[j]); + if (j < a.size() - 1) { + printf(","); + } + } + printf("]"); + printf("\n"); + } + void dp(xmr_amount vali) { + printf("x: "); + std::cout << vali; + printf("\n\n"); + } + + void dp(int vali) { + printf("x: %d\n", vali); + printf("\n"); + } + void dp(bits amountb) { + for (int i = 0; i < 64; i++) { + printf("%d", amountb[i]); + } + printf("\n"); + + } + + void dp(const char * st) { + printf("%s\n", st); + } + + //Various Conversions + + //uint long long to 32 byte key + void d2h(key & amounth, const xmr_amount in) { + sc_0(amounth.bytes); + xmr_amount val = in; + int i = 0; + while (val != 0) { + amounth[i] = (unsigned char)(val & 0xFF); + i++; + val /= (xmr_amount)256; + } + } + + //uint long long to 32 byte key + key d2h(const xmr_amount in) { + key amounth; + sc_0(amounth.bytes); + xmr_amount val = in; + int i = 0; + while (val != 0) { + amounth[i] = (unsigned char)(val & 0xFF); + i++; + val /= (xmr_amount)256; + } + return amounth; + } + + //uint long long to int[64] + void d2b(bits amountb, xmr_amount val) { + int i = 0; + while (val != 0) { + amountb[i] = val & 1; + i++; + val >>= 1; + } + while (i < 64) { + amountb[i] = 0; + i++; + } + } + + //32 byte key to uint long long + // if the key holds a value > 2^64 + // then the value in the first 8 bytes is returned + xmr_amount h2d(const key & test) { + xmr_amount vali = 0; + int j = 0; + for (j = 7; j >= 0; j--) { + vali = (xmr_amount)(vali * 256 + (unsigned char)test.bytes[j]); + } + return vali; + } + + //32 byte key to int[64] + void h2b(bits amountb2, const key & test) { + int val = 0, i = 0, j = 0; + for (j = 0; j < 8; j++) { + val = (unsigned char)test.bytes[j]; + i = 8 * j; + while (val != 0) { + amountb2[i] = val & 1; + i++; + val >>= 1; + } + while (i < 8 * (j + 1)) { + amountb2[i] = 0; + } + } + } + + //int[64] to 32 byte key + void b2h(key & amountdh, const bits amountb2) { + int byte, i, j; + for (j = 0; j < 8; j++) { + byte = 0; + i = 8 * j; + for (i = 7; i > -1; i--) { + byte = byte * 2 + amountb2[8 * j + i]; + } + amountdh[j] = (unsigned char)byte; + } + for (j = 8; j < 32; j++) { + amountdh[j] = (unsigned char)(0x00); + } + } + + //int[64] to uint long long + xmr_amount b2d(bits amountb) { + xmr_amount vali = 0; + int j = 0; + for (j = 63; j >= 0; j--) { + vali = (xmr_amount)(vali * 2 + amountb[j]); + } + return vali; + } + +} diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h new file mode 100644 index 000000000..f231d30fb --- /dev/null +++ b/src/ringct/rctTypes.h @@ -0,0 +1,405 @@ +// Copyright (c) 2016, Monero Research Labs +// +// Author: Shen Noether <shen.noether@gmx.com> +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once +#ifndef RCT_TYPES_H +#define RCT_TYPES_H + +#include <cstddef> +#include <mutex> +#include <vector> +#include <tuple> +#include <iostream> +#include <cinttypes> + +extern "C" { +#include "crypto/generic-ops.h" +#include "crypto/crypto-ops.h" +#include "crypto/random.h" +#include "crypto/keccak.h" +} +#include "crypto/crypto.h" + +#include "serialization/serialization.h" +#include "serialization/debug_archive.h" +#include "serialization/binary_archive.h" +#include "serialization/json_archive.h" + + +//Define this flag when debugging to get additional info on the console +#ifdef DBG +#define DP(x) dp(x) +#else +#define DP(x) +#endif + +//atomic units of moneros +#define ATOMS 64 + +//for printing large ints + +using namespace std; +using namespace crypto; + +//Namespace specifically for ring ct code +namespace rct { + //basic ops containers + typedef unsigned char * Bytes; + + // Can contain a secret or public key + // similar to secret_key / public_key of crypto-ops, + // but uses unsigned chars, + // also includes an operator for accessing the i'th byte. + struct key { + unsigned char & operator[](int i) { + return bytes[i]; + } + unsigned char operator[](int i) const { + return bytes[i]; + } + bool operator==(const key &k) const { return !memcmp(bytes, k.bytes, sizeof(bytes)); } + unsigned char bytes[32]; + }; + typedef vector<key> keyV; //vector of keys + typedef vector<keyV> keyM; //matrix of keys (indexed by column first) + + //containers For CT operations + //if it's representing a private ctkey then "dest" contains the secret key of the address + // while "mask" contains a where C = aG + bH is CT pedersen commitment and b is the amount + // (store b, the amount, separately + //if it's representing a public ctkey, then "dest" = P the address, mask = C the commitment + struct ctkey { + key dest; + key mask; //C here if public + }; + typedef vector<ctkey> ctkeyV; + typedef vector<ctkeyV> ctkeyM; + + //data for passing the amount to the receiver secretly + // If the pedersen commitment to an amount is C = aG + bH, + // "mask" contains a 32 byte key a + // "amount" contains a hex representation (in 32 bytes) of a 64 bit number + // "senderPk" is not the senders actual public key, but a one-time public key generated for + // the purpose of the ECDH exchange + struct ecdhTuple { + key mask; + key amount; + key senderPk; + + BEGIN_SERIALIZE_OBJECT() + FIELD(mask) + FIELD(amount) + // FIELD(senderPk) // not serialized, as we do not use it in monero currently + END_SERIALIZE() + }; + + //containers for representing amounts + typedef uint64_t xmr_amount; + typedef unsigned int bits[ATOMS]; + typedef key key64[64]; + + //just contains the necessary keys to represent asnlSigs + //c.f. http://eprint.iacr.org/2015/1098 + struct asnlSig { + key64 L1; + key64 s2; + key s; + }; + + //Container for precomp + struct geDsmp { + ge_dsmp k; + }; + + //just contains the necessary keys to represent MLSAG sigs + //c.f. http://eprint.iacr.org/2015/1098 + struct mgSig { + keyM ss; + key cc; + keyV II; + + BEGIN_SERIALIZE_OBJECT() + FIELD(ss) + FIELD(cc) + // FIELD(II) - not serialized, it can be reconstructed + END_SERIALIZE() + }; + //contains the data for an asnl sig + // also contains the "Ci" values such that + // \sum Ci = C + // and the signature proves that each Ci is either + // a Pedersen commitment to 0 or to 2^i + //thus proving that C is in the range of [0, 2^64] + struct rangeSig { + asnlSig asig; + key64 Ci; + + BEGIN_SERIALIZE_OBJECT() + FIELD(asig) + FIELD(Ci) + END_SERIALIZE() + }; + //A container to hold all signatures necessary for RingCT + // rangeSigs holds all the rangeproof data of a transaction + // MG holds the MLSAG signature of a transaction + // mixRing holds all the public keypairs (P, C) for a transaction + // ecdhInfo holds an encoded mask / amount to be passed to each receiver + // outPk contains public keypairs which are destinations (P, C), + // P = address, C = commitment to amount + enum { + RCTTypeNull = 0, + RCTTypeFull = 1, + RCTTypeSimple = 2, + }; + struct rctSigBase { + uint8_t type; + key message; + ctkeyM mixRing; //the set of all pubkeys / copy + //pairs that you mix with + keyV pseudoOuts; //C - for simple rct + vector<ecdhTuple> ecdhInfo; + ctkeyV outPk; + xmr_amount txnFee; // contains b + + BEGIN_SERIALIZE() + FIELD(type) + if (type == RCTTypeNull) + return true; + // FIELD(message) - not serialized, it can be reconstructed + // FIELD(mixRing) - not serialized, it can be reconstructed + if (type == RCTTypeSimple) + FIELD(pseudoOuts) + FIELD(ecdhInfo) + if (typename Archive<W>::is_saving()) { + keyV outPk(this->outPk.size()); + for (size_t n = 0; n < outPk.size(); ++n) + outPk[n] = this->outPk[n].mask; + FIELD(outPk) + } + else { + keyV outPk; + FIELD(outPk) + this->outPk.resize(outPk.size()); + for (size_t n = 0; n < outPk.size(); ++n) + this->outPk[n].mask = outPk[n]; + } + VARINT_FIELD(txnFee) + END_SERIALIZE() + }; + struct rctSigPrunable { + vector<rangeSig> rangeSigs; + vector<mgSig> MGs; // simple rct has N, full has 1 + + BEGIN_SERIALIZE() + FIELD(rangeSigs) + FIELD(MGs) + END_SERIALIZE() + }; + struct rctSig: public rctSigBase { + rctSigPrunable p; + + BEGIN_SERIALIZE_OBJECT() + FIELDS(*static_cast<rctSigBase *>(this)) + if (type == RCTTypeNull) + return true; + FIELDS(p); + END_SERIALIZE() + }; + + //other basepoint H = toPoint(cn_fast_hash(G)), G the basepoint + static const key H = { {0x8b, 0x65, 0x59, 0x70, 0x15, 0x37, 0x99, 0xaf, 0x2a, 0xea, 0xdc, 0x9f, 0xf1, 0xad, 0xd0, 0xea, 0x6c, 0x72, 0x51, 0xd5, 0x41, 0x54, 0xcf, 0xa9, 0x2c, 0x17, 0x3a, 0x0d, 0xd3, 0x9c, 0x1f, 0x94} }; + + //H2 contains 2^i H in each index, i.e. H, 2H, 4H, 8H, ... + //This is used for the range proofG + //You can regenerate this by running python2 Test.py HPow2 in the MiniNero repo + static const key64 H2 = {{0x8b, 0x65, 0x59, 0x70, 0x15, 0x37, 0x99, 0xaf, 0x2a, 0xea, 0xdc, 0x9f, 0xf1, 0xad, 0xd0, 0xea, 0x6c, 0x72, 0x51, 0xd5, 0x41, 0x54, 0xcf, 0xa9, 0x2c, 0x17, 0x3a, 0x0d, 0xd3, 0x9c, 0x1f, 0x94}, + {0x8f, 0xaa, 0x44, 0x8a, 0xe4, 0xb3, 0xe2, 0xbb, 0x3d, 0x4d, 0x13, 0x09, 0x09, 0xf5, 0x5f, 0xcd, 0x79, 0x71, 0x1c, 0x1c, 0x83, 0xcd, 0xbc, 0xca, 0xdd, 0x42, 0xcb, 0xe1, 0x51, 0x5e, 0x87, 0x12}, + {0x12, 0xa7, 0xd6, 0x2c, 0x77, 0x91, 0x65, 0x4a, 0x57, 0xf3, 0xe6, 0x76, 0x94, 0xed, 0x50, 0xb4, 0x9a, 0x7d, 0x9e, 0x3f, 0xc1, 0xe4, 0xc7, 0xa0, 0xbd, 0xe2, 0x9d, 0x18, 0x7e, 0x9c, 0xc7, 0x1d}, + {0x78, 0x9a, 0xb9, 0x93, 0x4b, 0x49, 0xc4, 0xf9, 0xe6, 0x78, 0x5c, 0x6d, 0x57, 0xa4, 0x98, 0xb3, 0xea, 0xd4, 0x43, 0xf0, 0x4f, 0x13, 0xdf, 0x11, 0x0c, 0x54, 0x27, 0xb4, 0xf2, 0x14, 0xc7, 0x39}, + {0x77, 0x1e, 0x92, 0x99, 0xd9, 0x4f, 0x02, 0xac, 0x72, 0xe3, 0x8e, 0x44, 0xde, 0x56, 0x8a, 0xc1, 0xdc, 0xb2, 0xed, 0xc6, 0xed, 0xb6, 0x1f, 0x83, 0xca, 0x41, 0x8e, 0x10, 0x77, 0xce, 0x3d, 0xe8}, + {0x73, 0xb9, 0x6d, 0xb4, 0x30, 0x39, 0x81, 0x9b, 0xda, 0xf5, 0x68, 0x0e, 0x5c, 0x32, 0xd7, 0x41, 0x48, 0x88, 0x84, 0xd1, 0x8d, 0x93, 0x86, 0x6d, 0x40, 0x74, 0xa8, 0x49, 0x18, 0x2a, 0x8a, 0x64}, + {0x8d, 0x45, 0x8e, 0x1c, 0x2f, 0x68, 0xeb, 0xeb, 0xcc, 0xd2, 0xfd, 0x5d, 0x37, 0x9f, 0x5e, 0x58, 0xf8, 0x13, 0x4d, 0xf3, 0xe0, 0xe8, 0x8c, 0xad, 0x3d, 0x46, 0x70, 0x10, 0x63, 0xa8, 0xd4, 0x12}, + {0x09, 0x55, 0x1e, 0xdb, 0xe4, 0x94, 0x41, 0x8e, 0x81, 0x28, 0x44, 0x55, 0xd6, 0x4b, 0x35, 0xee, 0x8a, 0xc0, 0x93, 0x06, 0x8a, 0x5f, 0x16, 0x1f, 0xa6, 0x63, 0x75, 0x59, 0x17, 0x7e, 0xf4, 0x04}, + {0xd0, 0x5a, 0x88, 0x66, 0xf4, 0xdf, 0x8c, 0xee, 0x1e, 0x26, 0x8b, 0x1d, 0x23, 0xa4, 0xc5, 0x8c, 0x92, 0xe7, 0x60, 0x30, 0x97, 0x86, 0xcd, 0xac, 0x0f, 0xed, 0xa1, 0xd2, 0x47, 0xa9, 0xc9, 0xa7}, + {0x55, 0xcd, 0xaa, 0xd5, 0x18, 0xbd, 0x87, 0x1d, 0xd1, 0xeb, 0x7b, 0xc7, 0x02, 0x3e, 0x1d, 0xc0, 0xfd, 0xf3, 0x33, 0x98, 0x64, 0xf8, 0x8f, 0xdd, 0x2d, 0xe2, 0x69, 0xfe, 0x9e, 0xe1, 0x83, 0x2d}, + {0xe7, 0x69, 0x7e, 0x95, 0x1a, 0x98, 0xcf, 0xd5, 0x71, 0x2b, 0x84, 0xbb, 0xe5, 0xf3, 0x4e, 0xd7, 0x33, 0xe9, 0x47, 0x3f, 0xcb, 0x68, 0xed, 0xa6, 0x6e, 0x37, 0x88, 0xdf, 0x19, 0x58, 0xc3, 0x06}, + {0xf9, 0x2a, 0x97, 0x0b, 0xae, 0x72, 0x78, 0x29, 0x89, 0xbf, 0xc8, 0x3a, 0xdf, 0xaa, 0x92, 0xa4, 0xf4, 0x9c, 0x7e, 0x95, 0x91, 0x8b, 0x3b, 0xba, 0x3c, 0xdc, 0x7f, 0xe8, 0x8a, 0xcc, 0x8d, 0x47}, + {0x1f, 0x66, 0xc2, 0xd4, 0x91, 0xd7, 0x5a, 0xf9, 0x15, 0xc8, 0xdb, 0x6a, 0x6d, 0x1c, 0xb0, 0xcd, 0x4f, 0x7d, 0xdc, 0xd5, 0xe6, 0x3d, 0x3b, 0xa9, 0xb8, 0x3c, 0x86, 0x6c, 0x39, 0xef, 0x3a, 0x2b}, + {0x3e, 0xec, 0x98, 0x84, 0xb4, 0x3f, 0x58, 0xe9, 0x3e, 0xf8, 0xde, 0xea, 0x26, 0x00, 0x04, 0xef, 0xea, 0x2a, 0x46, 0x34, 0x4f, 0xc5, 0x96, 0x5b, 0x1a, 0x7d, 0xd5, 0xd1, 0x89, 0x97, 0xef, 0xa7}, + {0xb2, 0x9f, 0x8f, 0x0c, 0xcb, 0x96, 0x97, 0x7f, 0xe7, 0x77, 0xd4, 0x89, 0xd6, 0xbe, 0x9e, 0x7e, 0xbc, 0x19, 0xc4, 0x09, 0xb5, 0x10, 0x35, 0x68, 0xf2, 0x77, 0x61, 0x1d, 0x7e, 0xa8, 0x48, 0x94}, + {0x56, 0xb1, 0xf5, 0x12, 0x65, 0xb9, 0x55, 0x98, 0x76, 0xd5, 0x8d, 0x24, 0x9d, 0x0c, 0x14, 0x6d, 0x69, 0xa1, 0x03, 0x63, 0x66, 0x99, 0x87, 0x4d, 0x3f, 0x90, 0x47, 0x35, 0x50, 0xfe, 0x3f, 0x2c}, + {0x1d, 0x7a, 0x36, 0x57, 0x5e, 0x22, 0xf5, 0xd1, 0x39, 0xff, 0x9c, 0xc5, 0x10, 0xfa, 0x13, 0x85, 0x05, 0x57, 0x6b, 0x63, 0x81, 0x5a, 0x94, 0xe4, 0xb0, 0x12, 0xbf, 0xd4, 0x57, 0xca, 0xaa, 0xda}, + {0xd0, 0xac, 0x50, 0x7a, 0x86, 0x4e, 0xcd, 0x05, 0x93, 0xfa, 0x67, 0xbe, 0x7d, 0x23, 0x13, 0x43, 0x92, 0xd0, 0x0e, 0x40, 0x07, 0xe2, 0x53, 0x48, 0x78, 0xd9, 0xb2, 0x42, 0xe1, 0x0d, 0x76, 0x20}, + {0xf6, 0xc6, 0x84, 0x0b, 0x9c, 0xf1, 0x45, 0xbb, 0x2d, 0xcc, 0xf8, 0x6e, 0x94, 0x0b, 0xe0, 0xfc, 0x09, 0x8e, 0x32, 0xe3, 0x10, 0x99, 0xd5, 0x6f, 0x7f, 0xe0, 0x87, 0xbd, 0x5d, 0xeb, 0x50, 0x94}, + {0x28, 0x83, 0x1a, 0x33, 0x40, 0x07, 0x0e, 0xb1, 0xdb, 0x87, 0xc1, 0x2e, 0x05, 0x98, 0x0d, 0x5f, 0x33, 0xe9, 0xef, 0x90, 0xf8, 0x3a, 0x48, 0x17, 0xc9, 0xf4, 0xa0, 0xa3, 0x32, 0x27, 0xe1, 0x97}, + {0x87, 0x63, 0x22, 0x73, 0xd6, 0x29, 0xcc, 0xb7, 0xe1, 0xed, 0x1a, 0x76, 0x8f, 0xa2, 0xeb, 0xd5, 0x17, 0x60, 0xf3, 0x2e, 0x1c, 0x0b, 0x86, 0x7a, 0x5d, 0x36, 0x8d, 0x52, 0x71, 0x05, 0x5c, 0x6e}, + {0x5c, 0x7b, 0x29, 0x42, 0x43, 0x47, 0x96, 0x4d, 0x04, 0x27, 0x55, 0x17, 0xc5, 0xae, 0x14, 0xb6, 0xb5, 0xea, 0x27, 0x98, 0xb5, 0x73, 0xfc, 0x94, 0xe6, 0xe4, 0x4a, 0x53, 0x21, 0x60, 0x0c, 0xfb}, + {0xe6, 0x94, 0x50, 0x42, 0xd7, 0x8b, 0xc2, 0xc3, 0xbd, 0x6e, 0xc5, 0x8c, 0x51, 0x1a, 0x9f, 0xe8, 0x59, 0xc0, 0xad, 0x63, 0xfd, 0xe4, 0x94, 0xf5, 0x03, 0x9e, 0x0e, 0x82, 0x32, 0x61, 0x2b, 0xd5}, + {0x36, 0xd5, 0x69, 0x07, 0xe2, 0xec, 0x74, 0x5d, 0xb6, 0xe5, 0x4f, 0x0b, 0x2e, 0x1b, 0x23, 0x00, 0xab, 0xcb, 0x42, 0x2e, 0x71, 0x2d, 0xa5, 0x88, 0xa4, 0x0d, 0x3f, 0x1e, 0xbb, 0xbe, 0x02, 0xf6}, + {0x34, 0xdb, 0x6e, 0xe4, 0xd0, 0x60, 0x8e, 0x5f, 0x78, 0x36, 0x50, 0x49, 0x5a, 0x3b, 0x2f, 0x52, 0x73, 0xc5, 0x13, 0x4e, 0x52, 0x84, 0xe4, 0xfd, 0xf9, 0x66, 0x27, 0xbb, 0x16, 0xe3, 0x1e, 0x6b}, + {0x8e, 0x76, 0x59, 0xfb, 0x45, 0xa3, 0x78, 0x7d, 0x67, 0x4a, 0xe8, 0x67, 0x31, 0xfa, 0xa2, 0x53, 0x8e, 0xc0, 0xfd, 0xf4, 0x42, 0xab, 0x26, 0xe9, 0xc7, 0x91, 0xfa, 0xda, 0x08, 0x94, 0x67, 0xe9}, + {0x30, 0x06, 0xcf, 0x19, 0x8b, 0x24, 0xf3, 0x1b, 0xb4, 0xc7, 0xe6, 0x34, 0x60, 0x00, 0xab, 0xc7, 0x01, 0xe8, 0x27, 0xcf, 0xbb, 0x5d, 0xf5, 0x2d, 0xcf, 0xa4, 0x2e, 0x9c, 0xa9, 0xff, 0x08, 0x02}, + {0xf5, 0xfd, 0x40, 0x3c, 0xb6, 0xe8, 0xbe, 0x21, 0x47, 0x2e, 0x37, 0x7f, 0xfd, 0x80, 0x5a, 0x8c, 0x60, 0x83, 0xea, 0x48, 0x03, 0xb8, 0x48, 0x53, 0x89, 0xcc, 0x3e, 0xbc, 0x21, 0x5f, 0x00, 0x2a}, + {0x37, 0x31, 0xb2, 0x60, 0xeb, 0x3f, 0x94, 0x82, 0xe4, 0x5f, 0x1c, 0x3f, 0x3b, 0x9d, 0xcf, 0x83, 0x4b, 0x75, 0xe6, 0xee, 0xf8, 0xc4, 0x0f, 0x46, 0x1e, 0xa2, 0x7e, 0x8b, 0x6e, 0xd9, 0x47, 0x3d}, + {0x9f, 0x9d, 0xab, 0x09, 0xc3, 0xf5, 0xe4, 0x28, 0x55, 0xc2, 0xde, 0x97, 0x1b, 0x65, 0x93, 0x28, 0xa2, 0xdb, 0xc4, 0x54, 0x84, 0x5f, 0x39, 0x6f, 0xfc, 0x05, 0x3f, 0x0b, 0xb1, 0x92, 0xf8, 0xc3}, + {0x5e, 0x05, 0x5d, 0x25, 0xf8, 0x5f, 0xdb, 0x98, 0xf2, 0x73, 0xe4, 0xaf, 0xe0, 0x84, 0x64, 0xc0, 0x03, 0xb7, 0x0f, 0x1e, 0xf0, 0x67, 0x7b, 0xb5, 0xe2, 0x57, 0x06, 0x40, 0x0b, 0xe6, 0x20, 0xa5}, + {0x86, 0x8b, 0xcf, 0x36, 0x79, 0xcb, 0x6b, 0x50, 0x0b, 0x94, 0x41, 0x8c, 0x0b, 0x89, 0x25, 0xf9, 0x86, 0x55, 0x30, 0x30, 0x3a, 0xe4, 0xe4, 0xb2, 0x62, 0x59, 0x18, 0x65, 0x66, 0x6a, 0x45, 0x90}, + {0xb3, 0xdb, 0x6b, 0xd3, 0x89, 0x7a, 0xfb, 0xd1, 0xdf, 0x3f, 0x96, 0x44, 0xab, 0x21, 0xc8, 0x05, 0x0e, 0x1f, 0x00, 0x38, 0xa5, 0x2f, 0x7c, 0xa9, 0x5a, 0xc0, 0xc3, 0xde, 0x75, 0x58, 0xcb, 0x7a}, + {0x81, 0x19, 0xb3, 0xa0, 0x59, 0xff, 0x2c, 0xac, 0x48, 0x3e, 0x69, 0xbc, 0xd4, 0x1d, 0x6d, 0x27, 0x14, 0x94, 0x47, 0x91, 0x42, 0x88, 0xbb, 0xea, 0xee, 0x34, 0x13, 0xe6, 0xdc, 0xc6, 0xd1, 0xeb}, + {0x10, 0xfc, 0x58, 0xf3, 0x5f, 0xc7, 0xfe, 0x7a, 0xe8, 0x75, 0x52, 0x4b, 0xb5, 0x85, 0x00, 0x03, 0x00, 0x5b, 0x7f, 0x97, 0x8c, 0x0c, 0x65, 0xe2, 0xa9, 0x65, 0x46, 0x4b, 0x6d, 0x00, 0x81, 0x9c}, + {0x5a, 0xcd, 0x94, 0xeb, 0x3c, 0x57, 0x83, 0x79, 0xc1, 0xea, 0x58, 0xa3, 0x43, 0xec, 0x4f, 0xcf, 0xf9, 0x62, 0x77, 0x6f, 0xe3, 0x55, 0x21, 0xe4, 0x75, 0xa0, 0xe0, 0x6d, 0x88, 0x7b, 0x2d, 0xb9}, + {0x33, 0xda, 0xf3, 0xa2, 0x14, 0xd6, 0xe0, 0xd4, 0x2d, 0x23, 0x00, 0xa7, 0xb4, 0x4b, 0x39, 0x29, 0x0d, 0xb8, 0x98, 0x9b, 0x42, 0x79, 0x74, 0xcd, 0x86, 0x5d, 0xb0, 0x11, 0x05, 0x5a, 0x29, 0x01}, + {0xcf, 0xc6, 0x57, 0x2f, 0x29, 0xaf, 0xd1, 0x64, 0xa4, 0x94, 0xe6, 0x4e, 0x6f, 0x1a, 0xeb, 0x82, 0x0c, 0x3e, 0x7d, 0xa3, 0x55, 0x14, 0x4e, 0x51, 0x24, 0xa3, 0x91, 0xd0, 0x6e, 0x9f, 0x95, 0xea}, + {0xd5, 0x31, 0x2a, 0x4b, 0x0e, 0xf6, 0x15, 0xa3, 0x31, 0xf6, 0x35, 0x2c, 0x2e, 0xd2, 0x1d, 0xac, 0x9e, 0x7c, 0x36, 0x39, 0x8b, 0x93, 0x9a, 0xec, 0x90, 0x1c, 0x25, 0x7f, 0x6c, 0xbc, 0x9e, 0x8e}, + {0x55, 0x1d, 0x67, 0xfe, 0xfc, 0x7b, 0x5b, 0x9f, 0x9f, 0xdb, 0xf6, 0xaf, 0x57, 0xc9, 0x6c, 0x8a, 0x74, 0xd7, 0xe4, 0x5a, 0x00, 0x20, 0x78, 0xa7, 0xb5, 0xba, 0x45, 0xc6, 0xfd, 0xe9, 0x3e, 0x33}, + {0xd5, 0x0a, 0xc7, 0xbd, 0x5c, 0xa5, 0x93, 0xc6, 0x56, 0x92, 0x8f, 0x38, 0x42, 0x80, 0x17, 0xfc, 0x7b, 0xa5, 0x02, 0x85, 0x4c, 0x43, 0xd8, 0x41, 0x49, 0x50, 0xe9, 0x6e, 0xcb, 0x40, 0x5d, 0xc3}, + {0x07, 0x73, 0xe1, 0x8e, 0xa1, 0xbe, 0x44, 0xfe, 0x1a, 0x97, 0xe2, 0x39, 0x57, 0x3c, 0xfa, 0xe3, 0xe4, 0xe9, 0x5e, 0xf9, 0xaa, 0x9f, 0xaa, 0xbe, 0xac, 0x12, 0x74, 0xd3, 0xad, 0x26, 0x16, 0x04}, + {0xe9, 0xaf, 0x0e, 0x7c, 0xa8, 0x93, 0x30, 0xd2, 0xb8, 0x61, 0x5d, 0x1b, 0x41, 0x37, 0xca, 0x61, 0x7e, 0x21, 0x29, 0x7f, 0x2f, 0x0d, 0xed, 0x8e, 0x31, 0xb7, 0xd2, 0xea, 0xd8, 0x71, 0x46, 0x60}, + {0x7b, 0x12, 0x45, 0x83, 0x09, 0x7f, 0x10, 0x29, 0xa0, 0xc7, 0x41, 0x91, 0xfe, 0x73, 0x78, 0xc9, 0x10, 0x5a, 0xcc, 0x70, 0x66, 0x95, 0xed, 0x14, 0x93, 0xbb, 0x76, 0x03, 0x42, 0x26, 0xa5, 0x7b}, + {0xec, 0x40, 0x05, 0x7b, 0x99, 0x54, 0x76, 0x65, 0x0b, 0x3d, 0xb9, 0x8e, 0x9d, 0xb7, 0x57, 0x38, 0xa8, 0xcd, 0x2f, 0x94, 0xd8, 0x63, 0xb9, 0x06, 0x15, 0x0c, 0x56, 0xaa, 0xc1, 0x9c, 0xaa, 0x6b}, + {0x01, 0xd9, 0xff, 0x72, 0x9e, 0xfd, 0x39, 0xd8, 0x37, 0x84, 0xc0, 0xfe, 0x59, 0xc4, 0xae, 0x81, 0xa6, 0x70, 0x34, 0xcb, 0x53, 0xc9, 0x43, 0xfb, 0x81, 0x8b, 0x9d, 0x8a, 0xe7, 0xfc, 0x33, 0xe5}, + {0x00, 0xdf, 0xb3, 0xc6, 0x96, 0x32, 0x8c, 0x76, 0x42, 0x45, 0x19, 0xa7, 0xbe, 0xfe, 0x8e, 0x0f, 0x6c, 0x76, 0xf9, 0x47, 0xb5, 0x27, 0x67, 0x91, 0x6d, 0x24, 0x82, 0x3f, 0x73, 0x5b, 0xaf, 0x2e}, + {0x46, 0x1b, 0x79, 0x9b, 0x4d, 0x9c, 0xee, 0xa8, 0xd5, 0x80, 0xdc, 0xb7, 0x6d, 0x11, 0x15, 0x0d, 0x53, 0x5e, 0x16, 0x39, 0xd1, 0x60, 0x03, 0xc3, 0xfb, 0x7e, 0x9d, 0x1f, 0xd1, 0x30, 0x83, 0xa8}, + {0xee, 0x03, 0x03, 0x94, 0x79, 0xe5, 0x22, 0x8f, 0xdc, 0x55, 0x1c, 0xbd, 0xe7, 0x07, 0x9d, 0x34, 0x12, 0xea, 0x18, 0x6a, 0x51, 0x7c, 0xcc, 0x63, 0xe4, 0x6e, 0x9f, 0xcc, 0xe4, 0xfe, 0x3a, 0x6c}, + {0xa8, 0xcf, 0xb5, 0x43, 0x52, 0x4e, 0x7f, 0x02, 0xb9, 0xf0, 0x45, 0xac, 0xd5, 0x43, 0xc2, 0x1c, 0x37, 0x3b, 0x4c, 0x9b, 0x98, 0xac, 0x20, 0xce, 0xc4, 0x17, 0xa6, 0xdd, 0xb5, 0x74, 0x4e, 0x94}, + {0x93, 0x2b, 0x79, 0x4b, 0xf8, 0x9c, 0x6e, 0xda, 0xf5, 0xd0, 0x65, 0x0c, 0x7c, 0x4b, 0xad, 0x92, 0x42, 0xb2, 0x56, 0x26, 0xe3, 0x7e, 0xad, 0x5a, 0xa7, 0x5e, 0xc8, 0xc6, 0x4e, 0x09, 0xdd, 0x4f}, + {0x16, 0xb1, 0x0c, 0x77, 0x9c, 0xe5, 0xcf, 0xef, 0x59, 0xc7, 0x71, 0x0d, 0x2e, 0x68, 0x44, 0x1e, 0xa6, 0xfa, 0xcb, 0x68, 0xe9, 0xb5, 0xf7, 0xd5, 0x33, 0xae, 0x0b, 0xb7, 0x8e, 0x28, 0xbf, 0x57}, + {0x0f, 0x77, 0xc7, 0x67, 0x43, 0xe7, 0x39, 0x6f, 0x99, 0x10, 0x13, 0x9f, 0x49, 0x37, 0xd8, 0x37, 0xae, 0x54, 0xe2, 0x10, 0x38, 0xac, 0x5c, 0x0b, 0x3f, 0xd6, 0xef, 0x17, 0x1a, 0x28, 0xa7, 0xe4}, + {0xd7, 0xe5, 0x74, 0xb7, 0xb9, 0x52, 0xf2, 0x93, 0xe8, 0x0d, 0xde, 0x90, 0x5e, 0xb5, 0x09, 0x37, 0x3f, 0x3f, 0x6c, 0xd1, 0x09, 0xa0, 0x22, 0x08, 0xb3, 0xc1, 0xe9, 0x24, 0x08, 0x0a, 0x20, 0xca}, + {0x45, 0x66, 0x6f, 0x8c, 0x38, 0x1e, 0x3d, 0xa6, 0x75, 0x56, 0x3f, 0xf8, 0xba, 0x23, 0xf8, 0x3b, 0xfa, 0xc3, 0x0c, 0x34, 0xab, 0xdd, 0xe6, 0xe5, 0xc0, 0x97, 0x5e, 0xf9, 0xfd, 0x70, 0x0c, 0xb9}, + {0xb2, 0x46, 0x12, 0xe4, 0x54, 0x60, 0x7e, 0xb1, 0xab, 0xa4, 0x47, 0xf8, 0x16, 0xd1, 0xa4, 0x55, 0x1e, 0xf9, 0x5f, 0xa7, 0x24, 0x7f, 0xb7, 0xc1, 0xf5, 0x03, 0x02, 0x0a, 0x71, 0x77, 0xf0, 0xdd}, + {0x7e, 0x20, 0x88, 0x61, 0x85, 0x6d, 0xa4, 0x2c, 0x8b, 0xb4, 0x6a, 0x75, 0x67, 0xf8, 0x12, 0x13, 0x62, 0xd9, 0xfb, 0x24, 0x96, 0xf1, 0x31, 0xa4, 0xaa, 0x90, 0x17, 0xcf, 0x36, 0x6c, 0xdf, 0xce}, + {0x5b, 0x64, 0x6b, 0xff, 0x6a, 0xd1, 0x10, 0x01, 0x65, 0x03, 0x7a, 0x05, 0x56, 0x01, 0xea, 0x02, 0x35, 0x8c, 0x0f, 0x41, 0x05, 0x0f, 0x9d, 0xfe, 0x3c, 0x95, 0xdc, 0xcb, 0xd3, 0x08, 0x7b, 0xe0}, + {0x74, 0x6d, 0x1d, 0xcc, 0xfe, 0xd2, 0xf0, 0xff, 0x1e, 0x13, 0xc5, 0x1e, 0x2d, 0x50, 0xd5, 0x32, 0x43, 0x75, 0xfb, 0xd5, 0xbf, 0x7c, 0xa8, 0x2a, 0x89, 0x31, 0x82, 0x8d, 0x80, 0x1d, 0x43, 0xab}, + {0xcb, 0x98, 0x11, 0x0d, 0x4a, 0x6b, 0xb9, 0x7d, 0x22, 0xfe, 0xad, 0xbc, 0x6c, 0x0d, 0x89, 0x30, 0xc5, 0xf8, 0xfc, 0x50, 0x8b, 0x2f, 0xc5, 0xb3, 0x53, 0x28, 0xd2, 0x6b, 0x88, 0xdb, 0x19, 0xae}, + {0x60, 0xb6, 0x26, 0xa0, 0x33, 0xb5, 0x5f, 0x27, 0xd7, 0x67, 0x6c, 0x40, 0x95, 0xea, 0xba, 0xbc, 0x7a, 0x2c, 0x7e, 0xde, 0x26, 0x24, 0xb4, 0x72, 0xe9, 0x7f, 0x64, 0xf9, 0x6b, 0x8c, 0xfc, 0x0e}, + {0xe5, 0xb5, 0x2b, 0xc9, 0x27, 0x46, 0x8d, 0xf7, 0x18, 0x93, 0xeb, 0x81, 0x97, 0xef, 0x82, 0x0c, 0xf7, 0x6c, 0xb0, 0xaa, 0xf6, 0xe8, 0xe4, 0xfe, 0x93, 0xad, 0x62, 0xd8, 0x03, 0x98, 0x31, 0x04}, + {0x05, 0x65, 0x41, 0xae, 0x5d, 0xa9, 0x96, 0x1b, 0xe2, 0xb0, 0xa5, 0xe8, 0x95, 0xe5, 0xc5, 0xba, 0x15, 0x3c, 0xbb, 0x62, 0xdd, 0x56, 0x1a, 0x42, 0x7b, 0xad, 0x0f, 0xfd, 0x41, 0x92, 0x31, 0x99}, + {0xf8, 0xfe, 0xf0, 0x5a, 0x3f, 0xa5, 0xc9, 0xf3, 0xeb, 0xa4, 0x16, 0x38, 0xb2, 0x47, 0xb7, 0x11, 0xa9, 0x9f, 0x96, 0x0f, 0xe7, 0x3a, 0xa2, 0xf9, 0x01, 0x36, 0xae, 0xb2, 0x03, 0x29, 0xb8, 0x88}}; + + //Debug printing for the above types + //Actually use DP(value) and #define DBG + void dp(key a); + void dp(bool a); + void dp(const char * a, int l); + void dp(keyV a); + void dp(keyM a); + void dp(xmr_amount vali); + void dp(int vali); + void dp(bits amountb); + void dp(const char * st); + + //various conversions + + //uint long long to 32 byte key + void d2h(key & amounth, xmr_amount val); + key d2h(xmr_amount val); + //uint long long to int[64] + void d2b(bits amountb, xmr_amount val); + //32 byte key to uint long long + // if the key holds a value > 2^64 + // then the value in the first 8 bytes is returned + xmr_amount h2d(const key &test); + //32 byte key to int[64] + void h2b(bits amountb2, key & test); + //int[64] to 32 byte key + void b2h(key & amountdh, bits amountb2); + //int[64] to uint long long + xmr_amount b2d(bits amountb); + + static inline const rct::key pk2rct(const crypto::public_key &pk) { return (const rct::key&)pk; } + static inline const rct::key sk2rct(const crypto::secret_key &sk) { return (const rct::key&)sk; } + static inline const rct::key ki2rct(const crypto::key_image &ki) { return (const rct::key&)ki; } + static inline const rct::key hash2rct(const crypto::hash &h) { return (const rct::key&)h; } + static inline const crypto::public_key rct2pk(const rct::key &k) { return (const crypto::public_key&)k; } + static inline const crypto::secret_key rct2sk(const rct::key &k) { return (const crypto::secret_key&)k; } + static inline const crypto::key_image rct2ki(const rct::key &k) { return (const crypto::key_image&)k; } + static inline const crypto::hash rct2hash(const rct::key &k) { return (const crypto::hash&)k; } + static inline bool operator==(const rct::key &k0, const crypto::public_key &k1) { return !memcmp(&k0, &k1, 32); } + static inline bool operator!=(const rct::key &k0, const crypto::public_key &k1) { return memcmp(&k0, &k1, 32); } +} + + +namespace cryptonote { + static inline bool operator==(const crypto::public_key &k0, const rct::key &k1) { return !memcmp(&k0, &k1, 32); } + static inline bool operator!=(const crypto::public_key &k0, const rct::key &k1) { return memcmp(&k0, &k1, 32); } + static inline bool operator==(const crypto::secret_key &k0, const rct::key &k1) { return !memcmp(&k0, &k1, 32); } + static inline bool operator!=(const crypto::secret_key &k0, const rct::key &k1) { return memcmp(&k0, &k1, 32); } +} + +template<typename T> std::ostream &print256(std::ostream &o, const T &v); +inline std::ostream &operator <<(std::ostream &o, const rct::key &v) { return print256(o, v); } + + +BLOB_SERIALIZER(rct::key); +BLOB_SERIALIZER(rct::key64); +BLOB_SERIALIZER(rct::ctkey); +BLOB_SERIALIZER(rct::asnlSig); + +VARIANT_TAG(debug_archive, rct::key, "rct::key"); +VARIANT_TAG(debug_archive, rct::key64, "rct::key64"); +VARIANT_TAG(debug_archive, rct::keyV, "rct::keyV"); +VARIANT_TAG(debug_archive, rct::keyM, "rct::keyM"); +VARIANT_TAG(debug_archive, rct::ctkey, "rct::ctkey"); +VARIANT_TAG(debug_archive, rct::ctkeyV, "rct::ctkeyV"); +VARIANT_TAG(debug_archive, rct::ctkeyM, "rct::ctkeyM"); +VARIANT_TAG(debug_archive, rct::ecdhTuple, "rct::ecdhTuple"); +VARIANT_TAG(debug_archive, rct::mgSig, "rct::mgSig"); +VARIANT_TAG(debug_archive, rct::rangeSig, "rct::rangeSig"); +VARIANT_TAG(debug_archive, rct::asnlSig, "rct::asnlSig"); +VARIANT_TAG(debug_archive, rct::rctSig, "rct::rctSig"); + +VARIANT_TAG(binary_archive, rct::key, 0x90); +VARIANT_TAG(binary_archive, rct::key64, 0x91); +VARIANT_TAG(binary_archive, rct::keyV, 0x92); +VARIANT_TAG(binary_archive, rct::keyM, 0x93); +VARIANT_TAG(binary_archive, rct::ctkey, 0x94); +VARIANT_TAG(binary_archive, rct::ctkeyV, 0x95); +VARIANT_TAG(binary_archive, rct::ctkeyM, 0x96); +VARIANT_TAG(binary_archive, rct::ecdhTuple, 0x97); +VARIANT_TAG(binary_archive, rct::mgSig, 0x98); +VARIANT_TAG(binary_archive, rct::rangeSig, 0x99); +VARIANT_TAG(binary_archive, rct::asnlSig, 0x9a); +VARIANT_TAG(binary_archive, rct::rctSig, 0x9b); + +VARIANT_TAG(json_archive, rct::key, "rct_key"); +VARIANT_TAG(json_archive, rct::key64, "rct_key64"); +VARIANT_TAG(json_archive, rct::keyV, "rct_keyV"); +VARIANT_TAG(json_archive, rct::keyM, "rct_keyM"); +VARIANT_TAG(json_archive, rct::ctkey, "rct_ctkey"); +VARIANT_TAG(json_archive, rct::ctkeyV, "rct_ctkeyV"); +VARIANT_TAG(json_archive, rct::ctkeyM, "rct_ctkeyM"); +VARIANT_TAG(json_archive, rct::ecdhTuple, "rct_ecdhTuple"); +VARIANT_TAG(json_archive, rct::mgSig, "rct_mgSig"); +VARIANT_TAG(json_archive, rct::rangeSig, "rct_rangeSig"); +VARIANT_TAG(json_archive, rct::asnlSig, "rct_asnlSig"); +VARIANT_TAG(json_archive, rct::rctSig, "rct_rctSig"); + +#endif /* RCTTYPES_H */ diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index e6fc11060..5aa3591ab 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -159,9 +159,25 @@ namespace cryptonote { res.blocks.resize(res.blocks.size()+1); res.blocks.back().block = block_to_blob(b.first); + res.output_indices.push_back(COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices()); + res.output_indices.back().indices.push_back(COMMAND_RPC_GET_BLOCKS_FAST::tx_output_indices()); + bool r = m_core.get_tx_outputs_gindexs(get_transaction_hash(b.first.miner_tx), res.output_indices.back().indices.back().indices); + if (!r) + { + res.status = "Failed"; + return false; + } + size_t txidx = 0; BOOST_FOREACH(auto& t, b.second) { res.blocks.back().txs.push_back(tx_to_blob(t)); + res.output_indices.back().indices.push_back(COMMAND_RPC_GET_BLOCKS_FAST::tx_output_indices()); + bool r = m_core.get_tx_outputs_gindexs(b.first.tx_hashes[txidx++], res.output_indices.back().indices.back().indices); + if (!r) + { + res.status = "Failed"; + return false; + } } } @@ -250,6 +266,30 @@ namespace cryptonote return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::on_get_random_rct_outs(const COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::request& req, COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::response& res) + { + CHECK_CORE_BUSY(); + res.status = "Failed"; + if(!m_core.get_random_rct_outs(req, res)) + { + return true; + } + + res.status = CORE_RPC_STATUS_OK; + std::stringstream ss; + typedef COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::out_entry out_entry; + CHECK_AND_ASSERT_MES(res.outs.size(), true, "internal error: res.outs.size() is empty"); + std::for_each(res.outs.begin(), res.outs.end(), [&](out_entry& oe) + { + ss << oe.global_amount_index << " "; + }); + ss << ENDL; + std::string s = ss.str(); + LOG_PRINT_L2("COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS: " << ENDL << s); + res.status = CORE_RPC_STATUS_OK; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_indexes(const COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::request& req, COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::response& res) { CHECK_CORE_BUSY(); @@ -459,6 +499,8 @@ namespace cryptonote res.reason = "overspend"; if ((res.fee_too_low = tvc.m_fee_too_low)) res.reason = "fee too low"; + if ((res.not_rct = tvc.m_not_rct)) + res.reason = "tx is not ringct"; return true; } @@ -1023,7 +1065,6 @@ namespace cryptonote return false; } -#if BLOCKCHAIN_DB == DB_LMDB const Blockchain &blockchain = m_core.get_blockchain_storage(); uint8_t version = req.version > 0 ? req.version : blockchain.get_next_hard_fork_version(); res.version = blockchain.get_current_hard_fork_version(); @@ -1031,11 +1072,6 @@ namespace cryptonote res.state = blockchain.get_hard_fork_state(); res.status = CORE_RPC_STATUS_OK; return true; -#else - error_resp.code = CORE_RPC_ERROR_CODE_UNSUPPORTED_RPC; - error_resp.message = "Hard fork inoperative in memory mode."; - return false; -#endif } //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_bans(const COMMAND_RPC_GETBANS::request& req, COMMAND_RPC_GETBANS::response& res, epee::json_rpc::error& error_resp) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 6ebf41abc..9fcf3abf8 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -79,6 +79,7 @@ namespace cryptonote MAP_URI_AUTO_BIN2("/get_o_indexes.bin", on_get_indexes, COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES) MAP_URI_AUTO_BIN2("/getrandom_outs.bin", on_get_random_outs, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS) MAP_URI_AUTO_BIN2("/get_outs.bin", on_get_outs, COMMAND_RPC_GET_OUTPUTS) + MAP_URI_AUTO_BIN2("/getrandom_rctouts.bin", on_get_random_rct_outs, COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS) MAP_URI_AUTO_JON2("/gettransactions", on_get_transactions, COMMAND_RPC_GET_TRANSACTIONS) MAP_URI_AUTO_JON2("/is_key_image_spent", on_is_key_image_spent, COMMAND_RPC_IS_KEY_IMAGE_SPENT) MAP_URI_AUTO_JON2("/sendrawtransaction", on_send_raw_tx, COMMAND_RPC_SEND_RAW_TX) @@ -128,6 +129,7 @@ namespace cryptonote bool on_mining_status(const COMMAND_RPC_MINING_STATUS::request& req, COMMAND_RPC_MINING_STATUS::response& res); bool on_get_random_outs(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request& req, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response& res); bool on_get_outs(const COMMAND_RPC_GET_OUTPUTS::request& req, COMMAND_RPC_GET_OUTPUTS::response& res); + bool on_get_random_rct_outs(const COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::request& req, COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::response& res); bool on_get_info(const COMMAND_RPC_GET_INFO::request& req, COMMAND_RPC_GET_INFO::response& res); bool on_save_bc(const COMMAND_RPC_SAVE_BC::request& req, COMMAND_RPC_SAVE_BC::response& res); bool on_get_peer_list(const COMMAND_RPC_GET_PEER_LIST::request& req, COMMAND_RPC_GET_PEER_LIST::response& res); diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 22c3590e1..8cba53943 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -41,7 +41,7 @@ namespace cryptonote #define CORE_RPC_STATUS_BUSY "BUSY" #define CORE_RPC_STATUS_NOT_MINING "NOT MINING" -#define CORE_RPC_VERSION 2 +#define CORE_RPC_VERSION 3 struct COMMAND_RPC_GET_HEIGHT { @@ -76,18 +76,38 @@ namespace cryptonote END_KV_SERIALIZE_MAP() }; + struct tx_output_indices + { + std::vector<uint64_t> indices; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(indices) + END_KV_SERIALIZE_MAP() + }; + + struct block_output_indices + { + std::vector<tx_output_indices> indices; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(indices) + END_KV_SERIALIZE_MAP() + }; + struct response { std::list<block_complete_entry> blocks; uint64_t start_height; uint64_t current_height; std::string status; + std::vector<block_output_indices> output_indices; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(blocks) KV_SERIALIZE(start_height) KV_SERIALIZE(current_height) KV_SERIALIZE(status) + KV_SERIALIZE(output_indices) END_KV_SERIALIZE_MAP() }; }; @@ -296,10 +316,12 @@ namespace cryptonote struct outkey { crypto::public_key key; + rct::key mask; bool unlocked; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE_VAL_POD_AS_BLOB(key) + KV_SERIALIZE_VAL_POD_AS_BLOB(mask) KV_SERIALIZE(unlocked) END_KV_SERIALIZE_MAP() }; @@ -315,6 +337,37 @@ namespace cryptonote END_KV_SERIALIZE_MAP() }; }; + + struct COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS + { + struct request + { + uint64_t outs_count; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(outs_count) + END_KV_SERIALIZE_MAP() + }; + +#pragma pack (push, 1) + struct out_entry + { + uint64_t amount; + uint64_t global_amount_index; + crypto::public_key out_key; + rct::key commitment; + }; +#pragma pack(pop) + + struct response + { + std::list<out_entry> outs; + std::string status; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_CONTAINER_POD_AS_BLOB(outs) + KV_SERIALIZE(status) + END_KV_SERIALIZE_MAP() + }; + }; //----------------------------------------------- struct COMMAND_RPC_SEND_RAW_TX { @@ -345,6 +398,7 @@ namespace cryptonote bool too_big; bool overspend; bool fee_too_low; + bool not_rct; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(status) @@ -357,6 +411,7 @@ namespace cryptonote KV_SERIALIZE(too_big) KV_SERIALIZE(overspend) KV_SERIALIZE(fee_too_low) + KV_SERIALIZE(not_rct) END_KV_SERIALIZE_MAP() }; }; diff --git a/src/serialization/crypto.h b/src/serialization/crypto.h index 3644b0e03..9a7e89c49 100644 --- a/src/serialization/crypto.h +++ b/src/serialization/crypto.h @@ -92,3 +92,4 @@ VARIANT_TAG(debug_archive, crypto::secret_key, "secret_key"); VARIANT_TAG(debug_archive, crypto::key_derivation, "key_derivation"); VARIANT_TAG(debug_archive, crypto::key_image, "key_image"); VARIANT_TAG(debug_archive, crypto::signature, "signature"); + diff --git a/src/serialization/serialization.h b/src/serialization/serialization.h index 8e277a46a..177cdf33a 100644 --- a/src/serialization/serialization.h +++ b/src/serialization/serialization.h @@ -102,6 +102,12 @@ inline bool do_serialize(Archive &ar, T &v) { return ::serializer<Archive, T>::serialize(ar, v); } +template <class Archive> +inline bool do_serialize(Archive &ar, bool &v) +{ + ar.serialize_blob(&v, sizeof(v)); + return true; +} // Never used in the code base // #ifndef __GNUC__ diff --git a/src/serialization/vector.h b/src/serialization/vector.h index a2a69f841..7f2bc78ba 100644 --- a/src/serialization/vector.h +++ b/src/serialization/vector.h @@ -32,6 +32,11 @@ #include "serialization.h" +template <template <bool> class Archive, class T> +bool do_serialize(Archive<false> &ar, std::vector<T> &v); +template <template <bool> class Archive, class T> +bool do_serialize(Archive<true> &ar, std::vector<T> &v); + namespace serialization { namespace detail diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 1840e54c9..44b5b0c70 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -58,6 +58,7 @@ #include "mnemonics/electrum-words.h" #include "rapidjson/document.h" #include "common/json_util.h" +#include "ringct/rctSigs.h" #include <stdexcept> #if defined(WIN32) @@ -99,6 +100,11 @@ typedef cryptonote::simple_wallet sw; m_auto_refresh_enabled.store(auto_refresh_enabled, std::memory_order_relaxed); \ }) +enum TransferType { + TransferOriginal, + TransferNew, +}; + namespace { const command_line::arg_descriptor<std::string> arg_wallet_file = {"wallet-file", sw::tr("Use wallet <arg>"), ""}; @@ -642,8 +648,8 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("incoming_transfers", boost::bind(&simple_wallet::show_incoming_transfers, this, _1), tr("incoming_transfers [available|unavailable] - Show incoming transfers, all or filtered by availability")); m_cmd_binder.set_handler("payments", boost::bind(&simple_wallet::show_payments, this, _1), tr("payments <PID_1> [<PID_2> ... <PID_N>] - Show payments for given payment ID[s]")); m_cmd_binder.set_handler("bc_height", boost::bind(&simple_wallet::show_blockchain_height, this, _1), tr("Show blockchain height")); - m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::transfer, this, _1), tr("transfer [<mixin_count>] <addr_1> <amount_1> [<addr_2> <amount_2> ... <addr_N> <amount_N>] [payment_id] - Transfer <amount_1>,... <amount_N> to <address_1>,... <address_N>, respectively. <mixin_count> is the number of extra inputs to include for untraceability (from 0 to maximum available)")); - m_cmd_binder.set_handler("transfer_new", boost::bind(&simple_wallet::transfer_new, this, _1), tr("Same as transfer, but using a new transaction building algorithm")); + m_cmd_binder.set_handler("transfer_original", boost::bind(&simple_wallet::transfer, this, _1), tr("transfer [<mixin_count>] <addr_1> <amount_1> [<addr_2> <amount_2> ... <addr_N> <amount_N>] [payment_id] - Transfer <amount_1>,... <amount_N> to <address_1>,... <address_N>, respectively. <mixin_count> is the number of extra inputs to include for untraceability (from 0 to maximum available)")); + m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::transfer_new, this, _1), tr("Same as transfer_original, but using a new transaction building algorithm")); m_cmd_binder.set_handler("sweep_unmixable", boost::bind(&simple_wallet::sweep_unmixable, this, _1), tr("Send all unmixable outputs to yourself with mixin 0")); m_cmd_binder.set_handler("sweep_all", boost::bind(&simple_wallet::sweep_all, this, _1), tr("Send all unlocked balance an address")); m_cmd_binder.set_handler("set_log", boost::bind(&simple_wallet::set_log, this, _1), tr("set_log <level> - Change current log detail level, <0-4>")); @@ -973,22 +979,22 @@ bool simple_wallet::generate_from_json(const boost::program_options::variables_m return false; } - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, version, unsigned, Uint, true); + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, version, unsigned, Uint, true, 0); const int current_version = 1; if (field_version > current_version) { fail_msg_writer() << boost::format(tr("Version %u too new, we can only grok up to %u")) % field_version % current_version; return false; } - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, filename, std::string, String, true); + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, filename, std::string, String, true, std::string()); - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, scan_from_height, uint64_t, Uint64, false); + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, scan_from_height, uint64_t, Uint64, false, 0); bool recover = field_scan_from_height_found; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, password, std::string, String, false); + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, password, std::string, String, false, std::string()); password = field_password; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, viewkey, std::string, String, false); + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, viewkey, std::string, String, false, std::string()); crypto::secret_key viewkey; if (field_viewkey_found) { @@ -1006,7 +1012,7 @@ bool simple_wallet::generate_from_json(const boost::program_options::variables_m } } - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, spendkey, std::string, String, false); + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, spendkey, std::string, String, false, std::string()); crypto::secret_key spendkey; if (field_spendkey_found) { @@ -1024,7 +1030,7 @@ bool simple_wallet::generate_from_json(const boost::program_options::variables_m } } - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, seed, std::string, String, false); + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, seed, std::string, String, false, std::string()); std::string old_language; if (field_seed_found) { @@ -1037,7 +1043,7 @@ bool simple_wallet::generate_from_json(const boost::program_options::variables_m m_restore_deterministic_wallet = true; } - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, address, std::string, String, false); + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, address, std::string, String, false, std::string()); // compatibility checks if (!field_seed_found && !field_viewkey_found) @@ -1888,24 +1894,24 @@ void simple_wallet::on_new_block(uint64_t height, const cryptonote::block& block m_refresh_progress_reporter.update(height, false); } //---------------------------------------------------------------------------------------------------- -void simple_wallet::on_money_received(uint64_t height, const cryptonote::transaction& tx, size_t out_index) +void simple_wallet::on_money_received(uint64_t height, const cryptonote::transaction& tx, uint64_t amount) { message_writer(epee::log_space::console_color_green, false) << "\r" << tr("Height ") << height << ", " << tr("transaction ") << get_transaction_hash(tx) << ", " << - tr("received ") << print_money(tx.vout[out_index].amount); + tr("received ") << print_money(amount); if (m_auto_refresh_refreshing) m_cmd_binder.print_prompt(); else m_refresh_progress_reporter.update(height, true); } //---------------------------------------------------------------------------------------------------- -void simple_wallet::on_money_spent(uint64_t height, const cryptonote::transaction& in_tx, size_t out_index, const cryptonote::transaction& spend_tx) +void simple_wallet::on_money_spent(uint64_t height, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx) { message_writer(epee::log_space::console_color_magenta, false) << "\r" << tr("Height ") << height << ", " << tr("transaction ") << get_transaction_hash(spend_tx) << ", " << - tr("spent ") << print_money(in_tx.vout[out_index].amount); + tr("spent ") << print_money(amount); if (m_auto_refresh_refreshing) m_cmd_binder.print_prompt(); else @@ -2052,15 +2058,17 @@ bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& args { if (!transfers_found) { - message_writer() << boost::format("%21s%8s%16s%68s") % tr("amount") % tr("spent") % tr("global index") % tr("tx id"); + message_writer() << boost::format("%21s%8s%12s%8s%16s%68s") % tr("amount") % tr("spent") % tr("unlocked") % tr("ringct") % tr("global index") % tr("tx id"); transfers_found = true; } message_writer(td.m_spent ? epee::log_space::console_color_magenta : epee::log_space::console_color_green, false) << - boost::format("%21s%8s%16u%68s") % + boost::format("%21s%8s%12s%8s%16u%68s") % print_money(td.amount()) % (td.m_spent ? tr("T") : tr("F")) % + (m_wallet->is_transfer_unlocked(td) ? tr("unlocked") : tr("locked")) % + (td.is_rct() ? tr("RingCT") : tr("-")) % td.m_global_output_index % - get_transaction_hash (td.m_tx); + td.m_txid; } } @@ -2274,7 +2282,7 @@ bool simple_wallet::get_address_from_str(const std::string &str, cryptonote::acc return true; } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::transfer_main(bool new_algorithm, const std::vector<std::string> &args_) +bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::string> &args_) { if (!try_connect_to_daemon()) return true; @@ -2386,10 +2394,17 @@ bool simple_wallet::transfer_main(bool new_algorithm, const std::vector<std::str { // figure out what tx will be necessary std::vector<tools::wallet2::pending_tx> ptx_vector; - if (new_algorithm) - ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, 0 /* unlock_time */, 0 /* unused fee arg*/, extra, m_trusted_daemon); - else - ptx_vector = m_wallet->create_transactions(dsts, fake_outs_count, 0 /* unlock_time */, 0 /* unused fee arg*/, extra, m_trusted_daemon); + switch (transfer_type) + { + case TransferNew: + ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, 0 /* unlock_time */, 0 /* unused fee arg*/, extra, m_trusted_daemon); + break; + default: + LOG_ERROR("Unknown transfer method, using original"); + case TransferOriginal: + ptx_vector = m_wallet->create_transactions(dsts, fake_outs_count, 0 /* unlock_time */, 0 /* unused fee arg*/, extra, m_trusted_daemon); + break; + } // if more than one tx necessary, prompt user to confirm if (m_wallet->always_confirm_transfers() || ptx_vector.size() > 1) @@ -2531,14 +2546,13 @@ bool simple_wallet::transfer_main(bool new_algorithm, const std::vector<std::str //---------------------------------------------------------------------------------------------------- bool simple_wallet::transfer(const std::vector<std::string> &args_) { - return transfer_main(false, args_); + return transfer_main(TransferOriginal, args_); } //---------------------------------------------------------------------------------------------------- bool simple_wallet::transfer_new(const std::vector<std::string> &args_) { - return transfer_main(true, args_); + return transfer_main(TransferNew, args_); } - //---------------------------------------------------------------------------------------------------- bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_) { @@ -2944,15 +2958,15 @@ bool simple_wallet::get_tx_key(const std::vector<std::string> &args_) LOCK_IDLE_SCOPE(); crypto::secret_key tx_key; - bool r = m_wallet->get_tx_key(txid, tx_key); - if (r) + std::vector<crypto::secret_key> amount_keys; + if (m_wallet->get_tx_key(txid, tx_key)) { - success_msg_writer() << tr("Tx key: ") << tx_key; + success_msg_writer() << tr("Tx key: ") << epee::string_tools::pod_to_hex(tx_key); return true; } else { - fail_msg_writer() << tr("no tx key found for this txid"); + fail_msg_writer() << tr("no tx keys found for this txid"); return true; } } @@ -2979,13 +2993,19 @@ bool simple_wallet::check_tx_key(const std::vector<std::string> &args_) LOCK_IDLE_SCOPE(); + if (local_args[1].size() < 64 || local_args[1].size() % 64) + { + fail_msg_writer() << tr("failed to parse tx key"); + return true; + } + crypto::secret_key tx_key; cryptonote::blobdata tx_key_data; if(!epee::string_tools::parse_hexstr_to_binbuff(local_args[1], tx_key_data)) { fail_msg_writer() << tr("failed to parse tx key"); return true; } - crypto::secret_key tx_key = *reinterpret_cast<const crypto::secret_key*>(tx_key_data.data()); + tx_key = *reinterpret_cast<const crypto::secret_key*>(tx_key_data.data()); cryptonote::account_public_address address; bool has_payment_id; @@ -3000,14 +3020,14 @@ bool simple_wallet::check_tx_key(const std::vector<std::string> &args_) COMMAND_RPC_GET_TRANSACTIONS::response res; req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); if (!net_utils::invoke_http_json_remote_command2(m_daemon_address + "/gettransactions", req, res, m_http_client) || - (res.txs.empty() && res.txs_as_hex.empty())) + (res.txs.size() != 1 && res.txs_as_hex.size() != 1)) { fail_msg_writer() << tr("failed to get transaction from daemon"); return true; } cryptonote::blobdata tx_data; bool ok; - if (!res.txs.empty()) + if (res.txs.size() == 1) ok = string_tools::parse_hexstr_to_binbuff(res.txs.front().as_hex, tx_data); else ok = string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data); @@ -3046,13 +3066,49 @@ bool simple_wallet::check_tx_key(const std::vector<std::string> &args_) crypto::public_key pubkey; derive_public_key(derivation, n, address.m_spend_public_key, pubkey); if (pubkey == tx_out_to_key.key) - received += tx.vout[n].amount; + { + uint64_t amount; + if (tx.version == 1) + { + amount = tx.vout[n].amount; + } + else + { + try + { + rct::key Ctmp; + //rct::key amount_key = rct::hash_to_scalar(rct::scalarmultKey(rct::pk2rct(address.m_view_public_key), rct::sk2rct(tx_key))); + crypto::key_derivation derivation; + bool r = crypto::generate_key_derivation(address.m_view_public_key, tx_key, derivation); + if (!r) + { + LOG_ERROR("Failed to generate key derivation to decode rct output " << n); + amount = 0; + } + else + { + crypto::secret_key scalar1; + crypto::derivation_to_scalar(derivation, n, scalar1); + rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[n]; + rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1)); + rct::key C = tx.rct_signatures.outPk[n].mask; + rct::addKeys2(Ctmp, ecdh_info.mask, ecdh_info.amount, rct::H); + if (rct::equalKeys(C, Ctmp)) + amount = rct::h2d(ecdh_info.amount); + else + amount = 0; + } + } + catch (...) { amount = 0; } + } + received += amount; + } } } - catch(...) + catch(const std::exception &e) { - LOG_ERROR("unknown error"); - fail_msg_writer() << tr("unknown error"); + LOG_ERROR("error: " << e.what()); + fail_msg_writer() << tr("error: ") << e.what(); return true; } @@ -3064,6 +3120,24 @@ bool simple_wallet::check_tx_key(const std::vector<std::string> &args_) { fail_msg_writer() << get_account_address_as_str(m_wallet->testnet(), address) << " " << tr("received nothing in txid") << " " << txid; } + if (res.txs.front().in_pool) + { + success_msg_writer() << tr("WARNING: this transaction is not yet included in the blockchain!"); + } + else + { + std::string err; + uint64_t bc_height = get_daemon_blockchain_height(err); + if (err.empty()) + { + uint64_t confirmations = bc_height - (res.txs.front().block_height + 1); + success_msg_writer() << boost::format(tr("This transaction has %u confirmations")) % confirmations; + } + else + { + success_msg_writer() << tr("WARNING: failed to determine number of confirmations!"); + } + } return true; } @@ -3228,9 +3302,8 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_) m_wallet->get_unconfirmed_payments_out(upayments); for (std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>>::const_iterator i = upayments.begin(); i != upayments.end(); ++i) { const tools::wallet2::unconfirmed_transfer_details &pd = i->second; - uint64_t amount = 0; - cryptonote::get_inputs_money_amount(pd.m_tx, amount); - uint64_t fee = amount - get_outs_money_amount(pd.m_tx); + uint64_t amount = pd.m_amount_in; + uint64_t fee = amount - pd.m_amount_out; std::string payment_id = string_tools::pod_to_hex(i->second.m_payment_id); if (payment_id.substr(16).find_first_not_of('0') == std::string::npos) payment_id = payment_id.substr(0,16); diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 52a4e150f..1259b95b0 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -120,7 +120,7 @@ namespace cryptonote bool show_incoming_transfers(const std::vector<std::string> &args); bool show_payments(const std::vector<std::string> &args); bool show_blockchain_height(const std::vector<std::string> &args); - bool transfer_main(bool new_algorithm, const std::vector<std::string> &args); + bool transfer_main(int transfer_type, const std::vector<std::string> &args); bool transfer(const std::vector<std::string> &args); bool transfer_new(const std::vector<std::string> &args); bool sweep_all(const std::vector<std::string> &args); @@ -171,8 +171,8 @@ namespace cryptonote //----------------- i_wallet2_callback --------------------- virtual void on_new_block(uint64_t height, const cryptonote::block& block); - virtual void on_money_received(uint64_t height, const cryptonote::transaction& tx, size_t out_index); - virtual void on_money_spent(uint64_t height, const cryptonote::transaction& in_tx, size_t out_index, const cryptonote::transaction& spend_tx); + virtual void on_money_received(uint64_t height, const cryptonote::transaction& tx, uint64_t amount); + virtual void on_money_spent(uint64_t height, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx); virtual void on_skip_transaction(uint64_t height, const cryptonote::transaction& tx); //---------------------------------------------------------- diff --git a/src/wallet/api/transaction_history.cpp b/src/wallet/api/transaction_history.cpp index db42e2141..95aafb04f 100644 --- a/src/wallet/api/transaction_history.cpp +++ b/src/wallet/api/transaction_history.cpp @@ -165,9 +165,8 @@ void TransactionHistoryImpl::refresh() for (std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>>::const_iterator i = upayments.begin(); i != upayments.end(); ++i) { const tools::wallet2::unconfirmed_transfer_details &pd = i->second; const crypto::hash &hash = i->first; - uint64_t amount = 0; - cryptonote::get_inputs_money_amount(pd.m_tx, amount); - uint64_t fee = amount - get_outs_money_amount(pd.m_tx); + uint64_t amount = pd.m_amount_in; + uint64_t fee = amount - pd.m_amount_out; std::string payment_id = string_tools::pod_to_hex(i->second.m_payment_id); if (payment_id.substr(16).find_first_not_of('0') == std::string::npos) payment_id = payment_id.substr(0,16); diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index b3ee4112b..f8704fde3 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -79,11 +79,10 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback LOG_PRINT_L3(__FUNCTION__ << ": new block. height: " << height); } - virtual void on_money_received(uint64_t height, const cryptonote::transaction& tx, size_t out_index) + virtual void on_money_received(uint64_t height, const cryptonote::transaction& tx, uint64_t amount) { std::string tx_hash = epee::string_tools::pod_to_hex(get_transaction_hash(tx)); - uint64_t amount = tx.vout[out_index].amount; LOG_PRINT_L3(__FUNCTION__ << ": money received. height: " << height << ", tx: " << tx_hash @@ -94,12 +93,11 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback } } - virtual void on_money_spent(uint64_t height, const cryptonote::transaction& in_tx, size_t out_index, + virtual void on_money_spent(uint64_t height, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx) { // TODO; std::string tx_hash = epee::string_tools::pod_to_hex(get_transaction_hash(spend_tx)); - uint64_t amount = in_tx.vout[out_index].amount; LOG_PRINT_L3(__FUNCTION__ << ": money spent. height: " << height << ", tx: " << tx_hash << ", amount: " << print_money(amount)); @@ -491,12 +489,6 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const } de.amount = amount; - if (de.amount <= 0) { - m_status = Status_Error; - m_errorString = "Invalid amount"; - break; - } - dsts.push_back(de); //std::vector<tools::wallet2::pending_tx> ptx_vector; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 8502353ed..99866dbeb 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -29,6 +29,7 @@ // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers #include <random> +#include <tuple> #include <boost/archive/binary_oarchive.hpp> #include <boost/archive/binary_iarchive.hpp> @@ -55,6 +56,7 @@ using namespace epee; #include "rapidjson/stringbuffer.h" #include "common/json_util.h" #include "common/base58.h" +#include "ringct/rctSigs.h" extern "C" { @@ -96,14 +98,18 @@ void do_prepare_file_names(const std::string& file_path, std::string& keys_file, } } -uint64_t calculate_fee(const cryptonote::blobdata &blob, uint64_t fee_multiplier) +uint64_t calculate_fee(size_t bytes, uint64_t fee_multiplier) { THROW_WALLET_EXCEPTION_IF(fee_multiplier <= 0 || fee_multiplier > 3, tools::error::invalid_fee_multiplier); - uint64_t bytes = blob.size(); uint64_t kB = (bytes + 1023) / 1024; return kB * FEE_PER_KB * fee_multiplier; } +uint64_t calculate_fee(const cryptonote::blobdata &blob, uint64_t fee_multiplier) +{ + return calculate_fee(blob.size(), fee_multiplier); +} + } //namespace namespace tools @@ -168,7 +174,21 @@ bool wallet2::is_deprecated() const return is_old_file_format; } //---------------------------------------------------------------------------------------------------- -void wallet2::check_acc_out(const account_keys &acc, const tx_out &o, const crypto::public_key &tx_pub_key, size_t i, uint64_t &money_transfered, bool &error) const +void wallet2::set_spent(transfer_details &td, uint64_t height) +{ + LOG_PRINT_L2("Setting SPENT at " << height << ": ki " << td.m_key_image << ", amount " << print_money(td.m_amount)); + td.m_spent = true; + td.m_spent_height = height; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::set_unspent(transfer_details &td) +{ + LOG_PRINT_L2("Setting UNSPENT: ki " << td.m_key_image << ", amount " << print_money(td.m_amount)); + td.m_spent = false; + td.m_spent_height = 0; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::check_acc_out(const account_keys &acc, const tx_out &o, const crypto::public_key &tx_pub_key, size_t i, bool &received, uint64_t &money_transfered, bool &error) const { if (o.target.type() != typeid(txout_to_key)) { @@ -176,9 +196,10 @@ void wallet2::check_acc_out(const account_keys &acc, const tx_out &o, const cryp LOG_ERROR("wrong type id in transaction out"); return; } - if(is_out_to_acc(acc, boost::get<txout_to_key>(o.target), tx_pub_key, i)) + received = is_out_to_acc(acc, boost::get<txout_to_key>(o.target), tx_pub_key, i); + if(received) { - money_transfered = o.amount; + money_transfered = o.amount; // may be 0 for ringct outputs } else { @@ -187,8 +208,57 @@ void wallet2::check_acc_out(const account_keys &acc, const tx_out &o, const cryp error = false; } //---------------------------------------------------------------------------------------------------- -void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_t height, uint64_t ts, bool miner_tx, bool pool) +static uint64_t decodeRct(const rct::rctSig & rv, const crypto::public_key pub, const crypto::secret_key &sec, unsigned int i, rct::key & mask) { + crypto::key_derivation derivation; + bool r = crypto::generate_key_derivation(pub, sec, derivation); + if (!r) + { + LOG_ERROR("Failed to generate key derivation to decode rct output " << i); + return 0; + } + crypto::secret_key scalar1; + crypto::derivation_to_scalar(derivation, i, scalar1); + try + { + switch (rv.type) + { + case rct::RCTTypeSimple: + return rct::decodeRctSimple(rv, rct::sk2rct(scalar1), i, mask); + case rct::RCTTypeFull: + return rct::decodeRct(rv, rct::sk2rct(scalar1), i, mask); + default: + LOG_ERROR("Unsupported rct type: " << rv.type); + return 0; + } + } + catch (const std::exception &e) + { + LOG_ERROR("Failed to decode input " << i); + return 0; + } +} +//---------------------------------------------------------------------------------------------------- +void wallet2::process_new_transaction(const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint64_t ts, bool miner_tx, bool pool) +{ + class lazy_txid_getter + { + const cryptonote::transaction &tx; + crypto::hash lazy_txid; + bool computed; + public: + lazy_txid_getter(const transaction &tx): tx(tx), computed(false) {} + const crypto::hash &operator()() + { + if (!computed) + { + lazy_txid = cryptonote::get_transaction_hash(tx); + computed = true; + } + return lazy_txid; + } + } txid(tx); + if (!miner_tx) process_unconfirmed(tx, height); std::vector<size_t> outs; @@ -199,7 +269,7 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ if(!parse_tx_extra(tx.extra, tx_extra_fields)) { // Extra may only be partially parsed, it's OK if tx_extra_fields contains public key - LOG_PRINT_L0("Transaction extra has unsupported format: " << get_transaction_hash(tx)); + LOG_PRINT_L0("Transaction extra has unsupported format: " << txid()); } // Don't try to extract tx public key if tx has no ouputs @@ -208,14 +278,19 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ tx_extra_pub_key pub_key_field; if(!find_tx_extra_field_by_type(tx_extra_fields, pub_key_field)) { - LOG_PRINT_L0("Public key wasn't found in the transaction extra. Skipping transaction " << get_transaction_hash(tx)); + LOG_PRINT_L0("Public key wasn't found in the transaction extra. Skipping transaction " << txid()); if(0 != m_callback) m_callback->on_skip_transaction(height, tx); return; } + int num_vouts_received = 0; tx_pub_key = pub_key_field.pub_key; bool r = true; + std::deque<cryptonote::keypair> in_ephemeral(tx.vout.size()); + std::deque<crypto::key_image> ki(tx.vout.size()); + std::deque<uint64_t> amount(tx.vout.size()); + std::deque<rct::key> mask(tx.vout.size()); int threads = tools::get_max_concurrency(); if (miner_tx && m_refresh_type == RefreshNoCoinbase) { @@ -224,8 +299,8 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ else if (miner_tx && m_refresh_type == RefreshOptimizeCoinbase) { uint64_t money_transfered = 0; - bool error = false; - check_acc_out(m_account.get_keys(), tx.vout[0], tx_pub_key, 0, money_transfered, error); + bool error = false, received = false; + check_acc_out(m_account.get_keys(), tx.vout[0], tx_pub_key, 0, received, money_transfered, error); if (error) { r = false; @@ -233,10 +308,21 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ else { // this assumes that the miner tx pays a single address - if (money_transfered > 0) + if (received) { + cryptonote::generate_key_image_helper(m_account.get_keys(), tx_pub_key, 0, in_ephemeral[0], ki[0]); + THROW_WALLET_EXCEPTION_IF(in_ephemeral[0].pub != boost::get<cryptonote::txout_to_key>(tx.vout[0].target).key, + error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); + outs.push_back(0); + if (money_transfered == 0) + { + const cryptonote::account_keys& keys = m_account.get_keys(); + money_transfered = tools::decodeRct(tx.rct_signatures, pub_key_field.pub_key, keys.m_view_secret_key, 0, mask[0]); + } + amount[0] = money_transfered; tx_money_got_in_outs = money_transfered; + ++num_vouts_received; // process the other outs from that tx boost::asio::io_service ioservice; @@ -250,11 +336,12 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ const account_keys &keys = m_account.get_keys(); std::vector<uint64_t> money_transfered(tx.vout.size()); std::deque<bool> error(tx.vout.size()); + std::deque<bool> received(tx.vout.size()); // the first one was already checked for (size_t i = 1; i < tx.vout.size(); ++i) { ioservice.dispatch(boost::bind(&wallet2::check_acc_out, this, std::cref(keys), std::cref(tx.vout[i]), std::cref(tx_pub_key), i, - std::ref(money_transfered[i]), std::ref(error[i]))); + std::ref(received[i]), std::ref(money_transfered[i]), std::ref(error[i]))); } KILL_IOSERVICE(); for (size_t i = 1; i < tx.vout.size(); ++i) @@ -264,10 +351,21 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ r = false; break; } - if (money_transfered[i]) + if (received[i]) { + cryptonote::generate_key_image_helper(m_account.get_keys(), tx_pub_key, i, in_ephemeral[i], ki[i]); + THROW_WALLET_EXCEPTION_IF(in_ephemeral[i].pub != boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key, + error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); + outs.push_back(i); + if (money_transfered[i] == 0) + { + const cryptonote::account_keys& keys = m_account.get_keys(); + money_transfered[i] = tools::decodeRct(tx.rct_signatures, pub_key_field.pub_key, keys.m_view_secret_key, i, mask[i]); + } tx_money_got_in_outs += money_transfered[i]; + amount[i] = money_transfered[i]; + ++num_vouts_received; } } } @@ -286,10 +384,11 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ const account_keys &keys = m_account.get_keys(); std::vector<uint64_t> money_transfered(tx.vout.size()); std::deque<bool> error(tx.vout.size()); + std::deque<bool> received(tx.vout.size()); for (size_t i = 0; i < tx.vout.size(); ++i) { ioservice.dispatch(boost::bind(&wallet2::check_acc_out, this, std::cref(keys), std::cref(tx.vout[i]), std::cref(tx_pub_key), i, - std::ref(money_transfered[i]), std::ref(error[i]))); + std::ref(received[i]), std::ref(money_transfered[i]), std::ref(error[i]))); } KILL_IOSERVICE(); tx_money_got_in_outs = 0; @@ -300,37 +399,68 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ r = false; break; } - if (money_transfered[i]) + if (received[i]) { + cryptonote::generate_key_image_helper(m_account.get_keys(), tx_pub_key, i, in_ephemeral[i], ki[i]); + THROW_WALLET_EXCEPTION_IF(in_ephemeral[i].pub != boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key, + error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); + outs.push_back(i); + if (money_transfered[i] == 0) + { + const cryptonote::account_keys& keys = m_account.get_keys(); + money_transfered[i] = tools::decodeRct(tx.rct_signatures, pub_key_field.pub_key, keys.m_view_secret_key, i, mask[i]); + } tx_money_got_in_outs += money_transfered[i]; + amount[i] = money_transfered[i]; + ++num_vouts_received; } } } else { - r = lookup_acc_outs(m_account.get_keys(), tx, tx_pub_key, outs, tx_money_got_in_outs); + for (size_t i = 0; i < tx.vout.size(); ++i) + { + uint64_t money_transfered = 0; + bool error = false, received = false; + check_acc_out(m_account.get_keys(), tx.vout[i], tx_pub_key, i, received, money_transfered, error); + if (error) + { + r = false; + break; + } + else + { + if (received) + { + cryptonote::generate_key_image_helper(m_account.get_keys(), tx_pub_key, i, in_ephemeral[i], ki[i]); + THROW_WALLET_EXCEPTION_IF(in_ephemeral[i].pub != boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key, + error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); + + outs.push_back(i); + if (money_transfered == 0) + { + const cryptonote::account_keys& keys = m_account.get_keys(); + money_transfered = tools::decodeRct(tx.rct_signatures, pub_key_field.pub_key, keys.m_view_secret_key, i, mask[i]); + } + amount[i] = money_transfered; + tx_money_got_in_outs += money_transfered; + ++num_vouts_received; + } + } + } } THROW_WALLET_EXCEPTION_IF(!r, error::acc_outs_lookup_error, tx, tx_pub_key, m_account.get_keys()); - if(!outs.empty() && tx_money_got_in_outs) + if(!outs.empty() && num_vouts_received > 0) { //good news - got money! take care about it //usually we have only one transfer for user in transaction - cryptonote::COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::request req = AUTO_VAL_INIT(req); - cryptonote::COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::response res = AUTO_VAL_INIT(res); if (!pool) { - req.txid = get_transaction_hash(tx); - m_daemon_rpc_mutex.lock(); - bool r = net_utils::invoke_http_bin_remote_command2(m_daemon_address + "/get_o_indexes.bin", req, res, m_http_client, WALLET_RCP_CONNECTION_TIMEOUT); - m_daemon_rpc_mutex.unlock(); - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_o_indexes.bin"); - THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_o_indexes.bin"); - THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::get_out_indices_error, res.status); - THROW_WALLET_EXCEPTION_IF(res.o_indexes.size() != tx.vout.size(), error::wallet_internal_error, - "transactions outputs size=" + std::to_string(tx.vout.size()) + - " not match with COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES response size=" + std::to_string(res.o_indexes.size())); + THROW_WALLET_EXCEPTION_IF(tx.vout.size() != o_indices.size(), error::wallet_internal_error, + "transactions outputs size=" + std::to_string(tx.vout.size()) + + " not match with daemon response size=" + std::to_string(o_indices.size())); } BOOST_FOREACH(size_t o, outs) @@ -338,13 +468,7 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ THROW_WALLET_EXCEPTION_IF(tx.vout.size() <= o, error::wallet_internal_error, "wrong out in transaction: internal index=" + std::to_string(o) + ", total_outs=" + std::to_string(tx.vout.size())); - crypto::key_image ki; - cryptonote::keypair in_ephemeral; - cryptonote::generate_key_image_helper(m_account.get_keys(), tx_pub_key, o, in_ephemeral, ki); - THROW_WALLET_EXCEPTION_IF(in_ephemeral.pub != boost::get<cryptonote::txout_to_key>(tx.vout[o].target).key, - error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); - - auto kit = m_key_images.find(ki); + auto kit = m_key_images.find(ki[o]); THROW_WALLET_EXCEPTION_IF(kit != m_key_images.end() && kit->second >= m_transfers.size(), error::wallet_internal_error, std::string("Unexpected transfer index from key image: ") + "got " + (kit == m_key_images.end() ? "<none>" : boost::lexical_cast<std::string>(kit->second)) @@ -357,14 +481,32 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ transfer_details& td = m_transfers.back(); td.m_block_height = height; td.m_internal_output_index = o; - td.m_global_output_index = res.o_indexes[o]; - td.m_tx = tx; - td.m_key_image = ki; - td.m_spent = false; + td.m_global_output_index = o_indices[o]; + td.m_tx = (const cryptonote::transaction_prefix&)tx; + td.m_txid = txid(); + td.m_key_image = ki[o]; + td.m_amount = tx.vout[o].amount; + if (td.m_amount == 0) + { + td.m_mask = mask[o]; + td.m_amount = amount[o]; + td.m_rct = true; + } + else if (miner_tx && tx.version == 2) + { + td.m_mask = rct::identity(); + td.m_rct = true; + } + else + { + td.m_mask = rct::identity(); + td.m_rct = false; + } + set_unspent(td); m_key_images[td.m_key_image] = m_transfers.size()-1; - LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << get_transaction_hash(tx)); + LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << txid()); if (0 != m_callback) - m_callback->on_money_received(height, td.m_tx, td.m_internal_output_index); + m_callback->on_money_received(height, tx, td.m_amount); } } else if (m_transfers[kit->second].m_spent || m_transfers[kit->second].amount() >= tx.vout[o].amount) @@ -387,14 +529,32 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ transfer_details &td = m_transfers[kit->second]; td.m_block_height = height; td.m_internal_output_index = o; - td.m_global_output_index = res.o_indexes[o]; - td.m_tx = tx; - THROW_WALLET_EXCEPTION_IF(td.m_key_image != ki, error::wallet_internal_error, "Inconsistent key images"); + td.m_global_output_index = o_indices[o]; + td.m_tx = (const cryptonote::transaction_prefix&)tx; + td.m_txid = txid(); + td.m_amount = tx.vout[o].amount; + if (td.m_amount == 0) + { + td.m_mask = mask[o]; + td.m_amount = amount[o]; + td.m_rct = true; + } + else if (miner_tx && tx.version == 2) + { + td.m_mask = rct::identity(); + td.m_rct = true; + } + else + { + td.m_mask = rct::identity(); + td.m_rct = false; + } + THROW_WALLET_EXCEPTION_IF(td.m_key_image != ki[o], error::wallet_internal_error, "Inconsistent key images"); THROW_WALLET_EXCEPTION_IF(td.m_spent, error::wallet_internal_error, "Inconsistent spent status"); - LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << get_transaction_hash(tx)); + LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << txid()); if (0 != m_callback) - m_callback->on_money_received(height, td.m_tx, td.m_internal_output_index); + m_callback->on_money_received(height, tx, td.m_amount); } } } @@ -410,12 +570,20 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ auto it = m_key_images.find(boost::get<cryptonote::txin_to_key>(in).k_image); if(it != m_key_images.end()) { - LOG_PRINT_L0("Spent money: " << print_money(boost::get<cryptonote::txin_to_key>(in).amount) << ", with tx: " << get_transaction_hash(tx)); - tx_money_spent_in_ins += boost::get<cryptonote::txin_to_key>(in).amount; transfer_details& td = m_transfers[it->second]; - td.m_spent = true; + uint64_t amount = boost::get<cryptonote::txin_to_key>(in).amount; + if (amount > 0) + { + THROW_WALLET_EXCEPTION_IF(amount != td.amount(), error::wallet_internal_error, + std::string("Inconsistent amount in tx input: got ") + print_money(amount) + + std::string(", expected ") + print_money(td.amount())); + } + amount = td.amount(); + LOG_PRINT_L0("Spent money: " << print_money(amount) << ", with tx: " << txid()); + tx_money_spent_in_ins += amount; + set_spent(td, height); if (0 != m_callback) - m_callback->on_money_spent(height, td.m_tx, td.m_internal_output_index, tx); + m_callback->on_money_spent(height, tx, amount, tx); } } @@ -467,7 +635,7 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ } payment_details payment; - payment.m_tx_hash = cryptonote::get_transaction_hash(tx); + payment.m_tx_hash = txid(); payment.m_amount = received; payment.m_block_height = height; payment.m_unlock_time = tx.unlock_time; @@ -482,6 +650,9 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ //---------------------------------------------------------------------------------------------------- void wallet2::process_unconfirmed(const cryptonote::transaction& tx, uint64_t height) { + if (m_unconfirmed_txs.empty()) + return; + crypto::hash txid = get_transaction_hash(tx); auto unconf_it = m_unconfirmed_txs.find(txid); if(unconf_it != m_unconfirmed_txs.end()) { @@ -501,25 +672,38 @@ void wallet2::process_unconfirmed(const cryptonote::transaction& tx, uint64_t he void wallet2::process_outgoing(const cryptonote::transaction &tx, uint64_t height, uint64_t ts, uint64_t spent, uint64_t received) { crypto::hash txid = get_transaction_hash(tx); - confirmed_transfer_details &ctd = m_confirmed_txs[txid]; - // operator[] creates if not found + std::pair<std::unordered_map<crypto::hash, confirmed_transfer_details>::iterator, bool> entry = m_confirmed_txs.insert(std::make_pair(txid, confirmed_transfer_details())); // fill with the info we know, some info might already be there - ctd.m_amount_in = spent; - ctd.m_amount_out = get_outs_money_amount(tx); - ctd.m_change = received; - ctd.m_block_height = height; - ctd.m_timestamp = ts; + if (entry.second) + { + // this case will happen if the tx is from our outputs, but was sent by another + // wallet (eg, we're a cold wallet and the hot wallet sent it). For RCT transactions, + // we only see 0 input amounts, so have to deduce amount out from other parameters. + entry.first->second.m_amount_in = spent; + if (tx.version == 1) + entry.first->second.m_amount_out = get_outs_money_amount(tx); + else + entry.first->second.m_amount_out = spent - tx.rct_signatures.txnFee; + entry.first->second.m_change = received; + } + entry.first->second.m_block_height = height; + entry.first->second.m_timestamp = ts; } //---------------------------------------------------------------------------------------------------- -void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const crypto::hash& bl_id, uint64_t height) +void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const crypto::hash& bl_id, uint64_t height, const cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices &o_indices) { + size_t txidx = 0; + THROW_WALLET_EXCEPTION_IF(bche.txs.size() + 1 != o_indices.indices.size(), error::wallet_internal_error, + "block transactions=" + std::to_string(bche.txs.size()) + + " not match with daemon response size=" + std::to_string(o_indices.indices.size())); + //handle transactions from new block //optimization: seeking only for blocks that are not older then the wallet creation time plus 1 day. 1 day is for possible user incorrect time setup if(b.timestamp + 60*60*24 > m_account.get_createtime() && height >= m_refresh_from_block_height) { TIME_MEASURE_START(miner_tx_handle_time); - process_new_transaction(b.miner_tx, height, b.timestamp, true, false); + process_new_transaction(b.miner_tx, o_indices.indices[txidx++].indices, height, b.timestamp, true, false); TIME_MEASURE_FINISH(miner_tx_handle_time); TIME_MEASURE_START(txs_handle_time); @@ -528,7 +712,7 @@ void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cry cryptonote::transaction tx; bool r = parse_and_validate_tx_from_blob(txblob, tx); THROW_WALLET_EXCEPTION_IF(!r, error::tx_parse_error, txblob); - process_new_transaction(tx, height, b.timestamp, false, false); + process_new_transaction(tx, o_indices.indices[txidx++].indices, height, b.timestamp, false, false); } TIME_MEASURE_FINISH(txs_handle_time); LOG_PRINT_L2("Processed block: " << bl_id << ", height " << height << ", " << miner_tx_handle_time + txs_handle_time << "(" << miner_tx_handle_time << "/" << txs_handle_time <<")ms"); @@ -578,7 +762,7 @@ void wallet2::parse_block_round(const cryptonote::blobdata &blob, cryptonote::bl bl_id = get_block_hash(bl); } //---------------------------------------------------------------------------------------------------- -void wallet2::pull_blocks(uint64_t start_height, uint64_t &blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::list<cryptonote::block_complete_entry> &blocks) +void wallet2::pull_blocks(uint64_t start_height, uint64_t &blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::list<cryptonote::block_complete_entry> &blocks, std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices) { cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::request req = AUTO_VAL_INIT(req); cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::response res = AUTO_VAL_INIT(res); @@ -591,9 +775,13 @@ void wallet2::pull_blocks(uint64_t start_height, uint64_t &blocks_start_height, THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "getblocks.bin"); THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "getblocks.bin"); THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::get_blocks_error, res.status); + THROW_WALLET_EXCEPTION_IF(res.blocks.size() != res.output_indices.size(), error::wallet_internal_error, + "mismatched blocks (" + boost::lexical_cast<std::string>(res.blocks.size()) + ") and output_indices (" + + boost::lexical_cast<std::string>(res.output_indices.size()) + ") sizes from daemon"); blocks_start_height = res.start_height; blocks = res.blocks; + o_indices = res.output_indices; } //---------------------------------------------------------------------------------------------------- void wallet2::pull_hashes(uint64_t start_height, uint64_t &blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::list<crypto::hash> &hashes) @@ -614,10 +802,13 @@ void wallet2::pull_hashes(uint64_t start_height, uint64_t &blocks_start_height, hashes = res.m_block_ids; } //---------------------------------------------------------------------------------------------------- -void wallet2::process_blocks(uint64_t start_height, const std::list<cryptonote::block_complete_entry> &blocks, uint64_t& blocks_added) +void wallet2::process_blocks(uint64_t start_height, const std::list<cryptonote::block_complete_entry> &blocks, const std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices, uint64_t& blocks_added) { size_t current_index = start_height; blocks_added = 0; + size_t tx_o_indices_idx = 0; + + THROW_WALLET_EXCEPTION_IF(blocks.size() != o_indices.size(), error::wallet_internal_error, "size mismatch"); int threads = tools::get_max_concurrency(); if (threads > 1) @@ -660,7 +851,7 @@ void wallet2::process_blocks(uint64_t start_height, const std::list<cryptonote:: if(current_index >= m_blockchain.size()) { - process_new_blockchain_entry(bl, *blocki, bl_id, current_index); + process_new_blockchain_entry(bl, *blocki, bl_id, current_index, o_indices[b+i]); ++blocks_added; } else if(bl_id != m_blockchain[current_index]) @@ -672,7 +863,7 @@ void wallet2::process_blocks(uint64_t start_height, const std::list<cryptonote:: string_tools::pod_to_hex(m_blockchain[current_index])); detach_blockchain(current_index); - process_new_blockchain_entry(bl, *blocki, bl_id, current_index); + process_new_blockchain_entry(bl, *blocki, bl_id, current_index, o_indices[b+i]); } else { @@ -694,7 +885,7 @@ void wallet2::process_blocks(uint64_t start_height, const std::list<cryptonote:: crypto::hash bl_id = get_block_hash(bl); if(current_index >= m_blockchain.size()) { - process_new_blockchain_entry(bl, bl_entry, bl_id, current_index); + process_new_blockchain_entry(bl, bl_entry, bl_id, current_index, o_indices[tx_o_indices_idx]); ++blocks_added; } else if(bl_id != m_blockchain[current_index]) @@ -706,7 +897,7 @@ void wallet2::process_blocks(uint64_t start_height, const std::list<cryptonote:: string_tools::pod_to_hex(m_blockchain[current_index])); detach_blockchain(current_index); - process_new_blockchain_entry(bl, bl_entry, bl_id, current_index); + process_new_blockchain_entry(bl, bl_entry, bl_id, current_index, o_indices[tx_o_indices_idx]); } else { @@ -714,6 +905,7 @@ void wallet2::process_blocks(uint64_t start_height, const std::list<cryptonote:: } ++current_index; + ++tx_o_indices_idx; } } } @@ -730,7 +922,7 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched) refresh(start_height, blocks_fetched, received_money); } //---------------------------------------------------------------------------------------------------- -void wallet2::pull_next_blocks(uint64_t start_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, const std::list<cryptonote::block_complete_entry> &prev_blocks, std::list<cryptonote::block_complete_entry> &blocks, bool &error) +void wallet2::pull_next_blocks(uint64_t start_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, const std::list<cryptonote::block_complete_entry> &prev_blocks, std::list<cryptonote::block_complete_entry> &blocks, std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices, bool &error) { error = false; @@ -748,7 +940,7 @@ void wallet2::pull_next_blocks(uint64_t start_height, uint64_t &blocks_start_hei } // pull the new blocks - pull_blocks(start_height, blocks_start_height, short_chain_history, blocks); + pull_blocks(start_height, blocks_start_height, short_chain_history, blocks, o_indices); } catch(...) { @@ -772,7 +964,7 @@ void wallet2::update_pool_state() std::unordered_map<crypto::hash, wallet2::unconfirmed_transfer_details>::iterator it = m_unconfirmed_txs.begin(); while (it != m_unconfirmed_txs.end()) { - const std::string txid = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(it->second.m_tx)); + const std::string txid = epee::string_tools::pod_to_hex(it->first); bool found = false; for (auto it2: res.transactions) { @@ -799,6 +991,24 @@ void wallet2::update_pool_state() { LOG_PRINT_L1("Pending txid " << txid << " not in pool, marking as failed"); pit->second.m_state = wallet2::unconfirmed_transfer_details::failed; + + // the inputs aren't spent anymore, since the tx failed + for (size_t vini = 0; vini < pit->second.m_tx.vin.size(); ++vini) + { + if (pit->second.m_tx.vin[vini].type() == typeid(txin_to_key)) + { + txin_to_key &tx_in_to_key = boost::get<txin_to_key>(pit->second.m_tx.vin[vini]); + for (auto &td: m_transfers) + { + if (td.m_key_image == tx_in_to_key.k_image) + { + LOG_PRINT_L1("Resetting spent status for output " << vini << ": " << td.m_key_image); + set_unspent(td); + break; + } + } + } + } } } } @@ -869,7 +1079,7 @@ void wallet2::update_pool_state() { if (tx_hash == txid) { - process_new_transaction(tx, 0, time(NULL), false, true); + process_new_transaction(tx, std::vector<uint64_t>(), 0, time(NULL), false, true); } else { @@ -980,11 +1190,12 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& re blocks_fetched = 0; uint64_t added_blocks = 0; size_t try_count = 0; - crypto::hash last_tx_hash_id = m_transfers.size() ? get_transaction_hash(m_transfers.back().m_tx) : null_hash; + crypto::hash last_tx_hash_id = m_transfers.size() ? m_transfers.back().m_txid : null_hash; std::list<crypto::hash> short_chain_history; boost::thread pull_thread; uint64_t blocks_start_height; std::list<cryptonote::block_complete_entry> blocks; + std::vector<COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> o_indices; // pull the first set of blocks get_short_chain_history(short_chain_history); @@ -1001,7 +1212,7 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& re // and then fall through to regular refresh processing } - pull_blocks(start_height, blocks_start_height, short_chain_history, blocks); + pull_blocks(start_height, blocks_start_height, short_chain_history, blocks, o_indices); // always reset start_height to 0 to force short_chain_ history to be used on // subsequent pulls in this refresh. start_height = 0; @@ -1013,10 +1224,11 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& re // pull the next set of blocks while we're processing the current one uint64_t next_blocks_start_height; std::list<cryptonote::block_complete_entry> next_blocks; + std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> next_o_indices; bool error = false; - pull_thread = boost::thread([&]{pull_next_blocks(start_height, next_blocks_start_height, short_chain_history, blocks, next_blocks, error);}); + pull_thread = boost::thread([&]{pull_next_blocks(start_height, next_blocks_start_height, short_chain_history, blocks, next_blocks, next_o_indices, error);}); - process_blocks(blocks_start_height, blocks, added_blocks); + process_blocks(blocks_start_height, blocks, o_indices, added_blocks); blocks_fetched += added_blocks; pull_thread.join(); if(!added_blocks) @@ -1025,6 +1237,7 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& re // switch to the new blocks from the daemon blocks_start_height = next_blocks_start_height; blocks = next_blocks; + o_indices = next_o_indices; // handle error from async fetching thread if (error) @@ -1049,7 +1262,7 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& re } } } - if(last_tx_hash_id != (m_transfers.size() ? get_transaction_hash(m_transfers.back().m_tx) : null_hash)) + if(last_tx_hash_id != (m_transfers.size() ? m_transfers.back().m_txid : null_hash)) received_money = true; try @@ -1083,6 +1296,16 @@ void wallet2::detach_blockchain(uint64_t height) LOG_PRINT_L0("Detaching blockchain on height " << height); size_t transfers_detached = 0; + for (size_t i = 0; i < m_transfers.size(); ++i) + { + wallet2::transfer_details &td = m_transfers[i]; + if (td.m_spent && td.m_spent_height >= height) + { + LOG_PRINT_L1("Resetting spent status for output " << i << ": " << td.m_key_image); + set_unspent(td); + } + } + auto it = std::find_if(m_transfers.begin(), m_transfers.end(), [&](const transfer_details& td){return td.m_block_height >= height;}); size_t i_start = it - m_transfers.begin(); @@ -1272,26 +1495,25 @@ bool wallet2::load_keys(const std::string& keys_file_name, const std::string& pa const char *field_key_data = json["key_data"].GetString(); account_data = std::string(field_key_data, field_key_data + json["key_data"].GetStringLength()); - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, seed_language, std::string, String, false); + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, seed_language, std::string, String, false, std::string()); if (field_seed_language_found) { set_seed_language(field_seed_language); } - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, watch_only, int, Int, false); - m_watch_only = field_watch_only_found && field_watch_only; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, always_confirm_transfers, int, Int, false); + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, watch_only, int, Int, false, false); + m_watch_only = field_watch_only; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, always_confirm_transfers, int, Int, false, false); m_always_confirm_transfers = field_always_confirm_transfers_found && field_always_confirm_transfers; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, store_tx_keys, int, Int, false); - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, store_tx_info, int, Int, false); - m_store_tx_info = (field_store_tx_keys_found && (field_store_tx_keys != 0)) - || (field_store_tx_info_found && (field_store_tx_info != 0)); - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, default_mixin, unsigned int, Uint, false); - m_default_mixin = field_default_mixin_found ? field_default_mixin : 0; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, default_fee_multiplier, unsigned int, Uint, false); - m_default_fee_multiplier = field_default_fee_multiplier_found ? field_default_fee_multiplier : 0; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, auto_refresh, int, Int, false); - m_auto_refresh = !field_auto_refresh_found || (field_auto_refresh != 0); - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, refresh_type, int, Int, false); + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, store_tx_keys, int, Int, false, true); + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, store_tx_info, int, Int, false, true); + m_store_tx_info = ((field_store_tx_keys != 0) || (field_store_tx_info != 0)); + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, default_mixin, unsigned int, Uint, false, 0); + m_default_mixin = field_default_mixin; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, default_fee_multiplier, unsigned int, Uint, false, 0); + m_default_fee_multiplier = field_default_fee_multiplier; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, auto_refresh, int, Int, false, true); + m_auto_refresh = field_auto_refresh; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, refresh_type, int, Int, false, RefreshType::RefreshDefault); m_refresh_type = RefreshType::RefreshDefault; if (field_refresh_type_found) { @@ -1300,7 +1522,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const std::string& pa else LOG_PRINT_L0("Unknown refresh-type value (" << field_refresh_type << "), using default"); } - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, refresh_height, uint64_t, Uint64, false); + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, refresh_height, uint64_t, Uint64, false, 0); if (field_refresh_height_found) m_refresh_from_block_height = field_refresh_height; } @@ -1896,12 +2118,15 @@ void wallet2::rescan_spent() if (td.m_spent) { LOG_PRINT_L0("Marking output " << i << "(" << td.m_key_image << ") as unspent, it was marked as spent"); + set_unspent(td); + td.m_spent_height = 0; } else { LOG_PRINT_L0("Marking output " << i << "(" << td.m_key_image << ") as spent, it was marked as unspent"); + set_spent(td, td.m_spent_height); + // unknown height, if this gets reorged, it might still be missed } - td.m_spent = daemon_resp.spent_status[i] != COMMAND_RPC_IS_KEY_IMAGE_SPENT::UNSPENT; } } } @@ -1962,6 +2187,7 @@ namespace T pop_index(std::vector<T>& vec, size_t idx) { CHECK_AND_ASSERT_MES(!vec.empty(), T(), "Vector must be non-empty"); + CHECK_AND_ASSERT_MES(idx < vec.size(), T(), "idx out of bounds"); T res = vec[idx]; if (idx + 1 != vec.size()) @@ -1981,6 +2207,84 @@ namespace size_t idx = crypto::rand<size_t>() % vec.size(); return pop_index (vec, idx); } + + template<typename T> + T pop_back(std::vector<T>& vec) + { + CHECK_AND_ASSERT_MES(!vec.empty(), T(), "Vector must be non-empty"); + + T res = vec.back(); + vec.pop_back(); + return res; + } +} +//---------------------------------------------------------------------------------------------------- +// This returns a handwavy estimation of how much two outputs are related +// If they're from the same tx, then they're fully related. From close block +// heights, they're kinda related. The actual values don't matter, just +// their ordering, but it could become more murky if we add scores later. +float wallet2::get_output_relatedness(const transfer_details &td0, const transfer_details &td1) const +{ + int dh; + + // expensive test, and same tx will fall onto the same block height below + if (td0.m_txid == td1.m_txid) + return 1.0f; + + // same block height -> possibly tx burst, or same tx (since above is disabled) + dh = td0.m_block_height > td1.m_block_height ? td0.m_block_height - td1.m_block_height : td1.m_block_height - td0.m_block_height; + if (dh == 0) + return 0.9f; + + // adjacent blocks -> possibly tx burst + if (dh == 1) + return 0.8f; + + // could extract the payment id, and compare them, but this is a bit expensive too + + // similar block heights + if (dh < 10) + return 0.2f; + + // don't think these are particularly related + return 0.0f; +} +//---------------------------------------------------------------------------------------------------- +size_t wallet2::pop_best_value_from(const transfer_container &transfers, std::vector<size_t> &unused_indices, const std::list<transfer_container::iterator>& selected_transfers) const +{ + std::vector<size_t> candidates; + float best_relatedness = 1.0f; + for (size_t n = 0; n < unused_indices.size(); ++n) + { + const transfer_details &candidate = transfers[unused_indices[n]]; + float relatedness = 0.0f; + for (const auto &i: selected_transfers) + { + float r = get_output_relatedness(candidate, *i); + if (r > relatedness) + { + relatedness = r; + if (relatedness == 1.0f) + break; + } + } + + if (relatedness < best_relatedness) + { + best_relatedness = relatedness; + candidates.clear(); + } + + if (relatedness == best_relatedness) + candidates.push_back(n); + } + size_t idx = crypto::rand<size_t>() % candidates.size(); + return pop_index (unused_indices, candidates[idx]); +} +//---------------------------------------------------------------------------------------------------- +size_t wallet2::pop_best_value(std::vector<size_t> &unused_indices, const std::list<transfer_container::iterator>& selected_transfers) const +{ + return pop_best_value_from(m_transfers, unused_indices, selected_transfers); } //---------------------------------------------------------------------------------------------------- // Select random input sources for transaction. @@ -1992,7 +2296,7 @@ uint64_t wallet2::select_transfers(uint64_t needed_money, std::vector<size_t> un uint64_t found_money = 0; while (found_money < needed_money && !unused_transfers_indices.empty()) { - size_t idx = pop_random_value(unused_transfers_indices); + size_t idx = pop_best_value(unused_transfers_indices, selected_transfers); transfer_container::iterator it = m_transfers.begin() + idx; selected_transfers.push_back(it); @@ -2002,12 +2306,16 @@ uint64_t wallet2::select_transfers(uint64_t needed_money, std::vector<size_t> un return found_money; } //---------------------------------------------------------------------------------------------------- -void wallet2::add_unconfirmed_tx(const cryptonote::transaction& tx, const std::vector<cryptonote::tx_destination_entry> &dests, const crypto::hash &payment_id, uint64_t change_amount) +void wallet2::add_unconfirmed_tx(const cryptonote::transaction& tx, uint64_t amount_in, const std::vector<cryptonote::tx_destination_entry> &dests, const crypto::hash &payment_id, uint64_t change_amount) { unconfirmed_transfer_details& utd = m_unconfirmed_txs[cryptonote::get_transaction_hash(tx)]; + utd.m_amount_in = amount_in; + utd.m_amount_out = 0; + for (const auto &d: dests) + utd.m_amount_out += d.amount; utd.m_change = change_amount; utd.m_sent_time = time(NULL); - utd.m_tx = tx; + utd.m_tx = (const cryptonote::transaction_prefix&)tx; utd.m_dests = dests; utd.m_payment_id = payment_id; utd.m_state = wallet2::unconfirmed_transfer_details::pending; @@ -2190,19 +2498,26 @@ void wallet2::commit_tx(pending_tx& ptx) txid = get_transaction_hash(ptx.tx); crypto::hash payment_id = cryptonote::null_hash; std::vector<cryptonote::tx_destination_entry> dests; + uint64_t amount_in = 0; if (store_tx_info()) { payment_id = get_payment_id(ptx); dests = ptx.dests; + BOOST_FOREACH(transfer_container::iterator it, ptx.selected_transfers) + amount_in += it->amount(); } - add_unconfirmed_tx(ptx.tx, dests, payment_id, ptx.change_dts.amount); + add_unconfirmed_tx(ptx.tx, amount_in, dests, payment_id, ptx.change_dts.amount); if (store_tx_info()) + { m_tx_keys.insert(std::make_pair(txid, ptx.tx_key)); + } LOG_PRINT_L2("transaction " << txid << " generated ok and sent to daemon, key_images: [" << ptx.key_images << "]"); BOOST_FOREACH(transfer_container::iterator it, ptx.selected_transfers) - it->m_spent = true; + { + set_spent(*it, 0); + } //fee includes dust if dust policy specified it. LOG_PRINT_L0("Transaction successfully sent. <" << txid << ">" << ENDL @@ -2280,7 +2595,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto // mark transfers to be used as "spent" BOOST_FOREACH(transfer_container::iterator it, ptx.selected_transfers) - it->m_spent = true; + { + set_spent(*it, 0); + } } // if we made it this far, we've selected our transactions. committing them will mark them spent, @@ -2290,7 +2607,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto { // mark transfers to be used as not spent BOOST_FOREACH(transfer_container::iterator it2, ptx.selected_transfers) - it2->m_spent = false; + { + set_unspent(*it2); + } } @@ -2307,8 +2626,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto { // mark transfers to be used as not spent BOOST_FOREACH(transfer_container::iterator it2, ptx.selected_transfers) - it2->m_spent = false; - + { + set_unspent(*it2); + } } if (attempt_count >= MAX_SPLIT_ATTEMPTS) @@ -2325,8 +2645,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto { // mark transfers to be used as not spent BOOST_FOREACH(transfer_container::iterator it2, ptx.selected_transfers) - it2->m_spent = false; - + { + set_unspent(*it2); + } } throw; @@ -2334,40 +2655,12 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto } } -template<typename T> -void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::list<transfer_container::iterator> selected_transfers, size_t fake_outputs_count, - uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx) +template<typename entry> +void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<transfer_container::iterator> &selected_transfers, size_t fake_outputs_count) { - using namespace cryptonote; - // throw if attempting a transaction with no destinations - THROW_WALLET_EXCEPTION_IF(dsts.empty(), error::zero_destination); - - uint64_t upper_transaction_size_limit = get_upper_tranaction_size_limit(); - uint64_t needed_money = fee; - LOG_PRINT_L2("transfer: starting with fee " << print_money (needed_money)); - - // calculate total amount being sent to all destinations - // throw if total amount overflows uint64_t - BOOST_FOREACH(auto& dt, dsts) - { - THROW_WALLET_EXCEPTION_IF(0 == dt.amount, error::zero_destination); - needed_money += dt.amount; - LOG_PRINT_L2("transfer: adding " << print_money(dt.amount) << ", for a total of " << print_money (needed_money)); - THROW_WALLET_EXCEPTION_IF(needed_money < dt.amount, error::tx_sum_overflow, dsts, fee, m_testnet); - } - - uint64_t found_money = 0; - BOOST_FOREACH(auto it, selected_transfers) - { - found_money += it->amount(); - } - - LOG_PRINT_L2("wanted " << print_money(needed_money) << ", found " << print_money(found_money) << ", fee " << print_money(fee)); - THROW_WALLET_EXCEPTION_IF(found_money < needed_money, error::not_enough_money, found_money, needed_money - fee, fee); - - typedef std::pair<uint64_t, crypto::public_key> entry; - std::vector<std::vector<entry>> outs; - if (fake_outputs_count) + LOG_PRINT_L2("fake_outputs_count: " << fake_outputs_count); + outs.clear(); + if (fake_outputs_count > 0) { // get histogram for the amounts we need epee::json_rpc::request<cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request> req_t = AUTO_VAL_INIT(req_t); @@ -2377,7 +2670,10 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent req_t.id = epee::serialization::storage_entry(0); req_t.method = "get_output_histogram"; for(auto it: selected_transfers) - req_t.params.amounts.push_back(it->amount()); + req_t.params.amounts.push_back(it->is_rct() ? 0 : it->amount()); + std::sort(req_t.params.amounts.begin(), req_t.params.amounts.end()); + auto end = std::unique(req_t.params.amounts.begin(), req_t.params.amounts.end()); + req_t.params.amounts.resize(std::distance(req_t.params.amounts.begin(), end)); req_t.params.unlocked = true; bool r = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/json_rpc", req_t, resp_t, m_http_client); m_daemon_rpc_mutex.unlock(); @@ -2387,12 +2683,15 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent // we ask for more, to have spares if some outputs are still locked size_t requested_outputs_count = (size_t)((fake_outputs_count + 1) * 1.5 + 1); + LOG_PRINT_L2("requested_outputs_count: " << requested_outputs_count); // generate output indices to request COMMAND_RPC_GET_OUTPUTS::request req = AUTO_VAL_INIT(req); COMMAND_RPC_GET_OUTPUTS::response daemon_resp = AUTO_VAL_INIT(daemon_resp); + for(transfer_container::iterator it: selected_transfers) { + const uint64_t amount = it->is_rct() ? 0 : it->amount(); std::unordered_set<uint64_t> seen_indices; size_t start = req.outputs.size(); @@ -2401,28 +2700,30 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent uint64_t num_outs = 0; for (auto he: resp_t.result.histogram) { - if (he.amount == it->amount()) + if (he.amount == amount) { num_outs = he.instances; break; } } + LOG_PRINT_L1("" << num_outs << " outputs of size " << print_money(amount)); + if (num_outs <= requested_outputs_count) { for (uint64_t i = 0; i < num_outs; i++) - req.outputs.push_back({it->amount(), i}); + req.outputs.push_back({amount, i}); // duplicate to make up shortfall: this will be caught after the RPC call, // so we can also output the amounts for which we can't reach the required // mixin after checking the actual unlockedness for (uint64_t i = num_outs; i < requested_outputs_count; ++i) - req.outputs.push_back({it->amount(), num_outs - 1}); + req.outputs.push_back({amount, num_outs - 1}); } else { // start with real one uint64_t num_found = 1; seen_indices.emplace(it->m_global_output_index); - req.outputs.push_back({it->amount(), it->m_global_output_index}); + req.outputs.push_back({amount, it->m_global_output_index}); // while we still need more mixins while (num_found < requested_outputs_count) @@ -2447,7 +2748,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent continue; seen_indices.emplace(i); - req.outputs.push_back({it->amount(), i}); + req.outputs.push_back({amount, i}); ++num_found; } } @@ -2457,6 +2758,9 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent [](const COMMAND_RPC_GET_OUTPUTS::out &a, const COMMAND_RPC_GET_OUTPUTS::out &b) { return a.index < b.index; }); } + for (auto i: req.outputs) + LOG_PRINT_L1("asking for output " << i.index << " for " << print_money(i.amount)); + // get the keys for those m_daemon_rpc_mutex.lock(); r = epee::net_utils::invoke_http_bin_remote_command2(m_daemon_address + "/get_outs.bin", req, daemon_resp, m_http_client, 200000); @@ -2475,9 +2779,10 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent { outs.push_back(std::vector<entry>()); outs.back().reserve(fake_outputs_count + 1); + const rct::key mask = it->is_rct() ? rct::commit(it->amount(), it->m_mask) : rct::zeroCommit(it->amount()); // pick real out first (it will be sorted when done) - outs.back().push_back({it->m_global_output_index, boost::get<txout_to_key>(it->m_tx.vout[it->m_internal_output_index].target).key}); + outs.back().push_back(std::make_tuple(it->m_global_output_index, boost::get<txout_to_key>(it->m_tx.vout[it->m_internal_output_index].target).key, mask)); // then pick others in random order till we reach the required number // since we use an equiprobable pick here, we don't upset the triangular distribution @@ -2496,7 +2801,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent continue; if (o > 0 && daemon_resp.outs[i].key == daemon_resp.outs[i - 1].key) // don't add duplicates continue; - outs.back().push_back({req.outputs[i].index, daemon_resp.outs[i].key}); + outs.back().push_back(std::make_tuple(req.outputs[i].index, daemon_resp.outs[i].key, daemon_resp.outs[i].mask)); } if (outs.back().size() < fake_outputs_count + 1) { @@ -2505,14 +2810,14 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent else { // sort the subsection, so any spares are reset in order - std::sort(outs.back().begin(), outs.back().end(), [](const entry &a, const entry &b) { return a.first < b.first; }); + std::sort(outs.back().begin(), outs.back().end(), [](const entry &a, const entry &b) { return std::get<0>(a) < std::get<0>(b); }); // sanity check for (size_t n = 1; n < outs.back().size(); ++n) { - THROW_WALLET_EXCEPTION_IF(outs.back()[n].first == outs.back()[n-1].first, error::wallet_internal_error, + THROW_WALLET_EXCEPTION_IF(std::get<0>(outs.back()[n]) == std::get<0>(outs.back()[n-1]), error::wallet_internal_error, "Duplicate indices though we did not ask for any"); - THROW_WALLET_EXCEPTION_IF(outs.back()[n].second == outs.back()[n-1].second, error::wallet_internal_error, + THROW_WALLET_EXCEPTION_IF(std::get<1>(outs.back()[n]) == std::get<1>(outs.back()[n-1]), error::wallet_internal_error, "Duplicate keys after we have weeded them out"); } } @@ -2525,10 +2830,47 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent for (transfer_container::iterator it: selected_transfers) { std::vector<entry> v; - v.push_back({it->m_global_output_index, boost::get<txout_to_key>(it->m_tx.vout[it->m_internal_output_index].target).key}); + const rct::key mask = it->is_rct() ? rct::commit(it->amount(), it->m_mask) : rct::zeroCommit(it->amount()); + v.push_back(std::make_tuple(it->m_global_output_index, boost::get<txout_to_key>(it->m_tx.vout[it->m_internal_output_index].target).key, mask)); outs.push_back(v); } } +} + +template<typename T> +void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::list<transfer_container::iterator> selected_transfers, size_t fake_outputs_count, + uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx) +{ + using namespace cryptonote; + // throw if attempting a transaction with no destinations + THROW_WALLET_EXCEPTION_IF(dsts.empty(), error::zero_destination); + + uint64_t upper_transaction_size_limit = get_upper_tranaction_size_limit(); + uint64_t needed_money = fee; + LOG_PRINT_L2("transfer: starting with fee " << print_money (needed_money)); + + // calculate total amount being sent to all destinations + // throw if total amount overflows uint64_t + BOOST_FOREACH(auto& dt, dsts) + { + THROW_WALLET_EXCEPTION_IF(0 == dt.amount, error::zero_destination); + needed_money += dt.amount; + LOG_PRINT_L2("transfer: adding " << print_money(dt.amount) << ", for a total of " << print_money (needed_money)); + THROW_WALLET_EXCEPTION_IF(needed_money < dt.amount, error::tx_sum_overflow, dsts, fee, m_testnet); + } + + uint64_t found_money = 0; + BOOST_FOREACH(auto it, selected_transfers) + { + found_money += it->amount(); + } + + LOG_PRINT_L2("wanted " << print_money(needed_money) << ", found " << print_money(found_money) << ", fee " << print_money(fee)); + THROW_WALLET_EXCEPTION_IF(found_money < needed_money, error::not_enough_money, found_money, needed_money - fee, fee); + + typedef std::tuple<uint64_t, crypto::public_key, rct::key> entry; + std::vector<std::vector<entry>> outs; + get_outs(outs, selected_transfers, fake_outputs_count); // may throw //prepare inputs typedef cryptonote::tx_source_entry::output_entry tx_output_entry; @@ -2540,13 +2882,16 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent cryptonote::tx_source_entry& src = sources.back(); transfer_details& td = *it; src.amount = td.amount(); + src.rct = td.is_rct(); //paste keys (fake and real) for (size_t n = 0; n < fake_outputs_count + 1; ++n) { tx_output_entry oe; - oe.first = outs[out_index][n].first; - oe.second = outs[out_index][n].second; + oe.first = std::get<0>(outs[out_index][n]); + oe.second.dest = rct::pk2rct(std::get<1>(outs[out_index][n])); + oe.second.mask = std::get<2>(outs[out_index][n]); + src.outputs.push_back(oe); ++i; } @@ -2561,7 +2906,8 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent tx_output_entry real_oe; real_oe.first = td.m_global_output_index; - real_oe.second = boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key; + real_oe.second.dest = rct::pk2rct(boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key); + real_oe.second.mask = rct::commit(td.amount(), td.m_mask); *it_to_replace = real_oe; src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_tx); src.real_output = it_to_replace - src.outputs.begin(); @@ -2621,6 +2967,225 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent ptx.dests = dsts; } +void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::list<transfer_container::iterator> selected_transfers, size_t fake_outputs_count, + uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx) +{ + using namespace cryptonote; + // throw if attempting a transaction with no destinations + THROW_WALLET_EXCEPTION_IF(dsts.empty(), error::zero_destination); + + uint64_t upper_transaction_size_limit = get_upper_tranaction_size_limit(); + uint64_t needed_money = fee; + LOG_PRINT_L2("transfer: starting with fee " << print_money (needed_money)); + + // calculate total amount being sent to all destinations + // throw if total amount overflows uint64_t + BOOST_FOREACH(auto& dt, dsts) + { + THROW_WALLET_EXCEPTION_IF(0 == dt.amount, error::zero_destination); + needed_money += dt.amount; + LOG_PRINT_L2("transfer: adding " << print_money(dt.amount) << ", for a total of " << print_money (needed_money)); + THROW_WALLET_EXCEPTION_IF(needed_money < dt.amount, error::tx_sum_overflow, dsts, fee, m_testnet); + } + + uint64_t found_money = 0; + BOOST_FOREACH(auto it, selected_transfers) + { + found_money += it->amount(); + } + + LOG_PRINT_L2("wanted " << print_money(needed_money) << ", found " << print_money(found_money) << ", fee " << print_money(fee)); + THROW_WALLET_EXCEPTION_IF(found_money < needed_money, error::not_enough_money, found_money, needed_money - fee, fee); + + typedef std::tuple<uint64_t, crypto::public_key, rct::key> entry; + std::vector<std::vector<entry>> outs; + get_outs(outs, selected_transfers, fake_outputs_count); // may throw + + //prepare inputs + size_t i = 0, out_index = 0; + std::vector<cryptonote::tx_source_entry> sources; + BOOST_FOREACH(transfer_container::iterator it, selected_transfers) + { + sources.resize(sources.size()+1); + cryptonote::tx_source_entry& src = sources.back(); + transfer_details& td = *it; + src.amount = td.amount(); + src.rct = td.is_rct(); + //paste mixin transaction + + typedef cryptonote::tx_source_entry::output_entry tx_output_entry; + for (size_t n = 0; n < fake_outputs_count + 1; ++n) + { + tx_output_entry oe; + oe.first = std::get<0>(outs[out_index][n]); + oe.second.dest = rct::pk2rct(std::get<1>(outs[out_index][n])); + oe.second.mask = std::get<2>(outs[out_index][n]); + src.outputs.push_back(oe); + } + ++i; + + //paste real transaction to the random index + auto it_to_replace = std::find_if(src.outputs.begin(), src.outputs.end(), [&](const tx_output_entry& a) + { + return a.first == td.m_global_output_index; + }); + THROW_WALLET_EXCEPTION_IF(it_to_replace == src.outputs.end(), error::wallet_internal_error, + "real output not found"); + + tx_output_entry real_oe; + real_oe.first = td.m_global_output_index; + real_oe.second.dest = rct::pk2rct(boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key); + real_oe.second.mask = rct::commit(td.amount(), td.m_mask); + *it_to_replace = real_oe; + src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_tx); + src.real_output = it_to_replace - src.outputs.begin(); + src.real_output_in_tx_index = td.m_internal_output_index; + src.mask = td.m_mask; + detail::print_source_entry(src); + ++out_index; + } + + cryptonote::tx_destination_entry change_dts = AUTO_VAL_INIT(change_dts); + if (needed_money < found_money) + { + change_dts.addr = m_account.get_keys().m_account_address; + change_dts.amount = found_money - needed_money; + dsts.push_back(change_dts); + } + + crypto::secret_key tx_key; + bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), sources, dsts, extra, tx, unlock_time, tx_key, true); + THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, dsts, unlock_time, m_testnet); + THROW_WALLET_EXCEPTION_IF(upper_transaction_size_limit <= get_object_blobsize(tx), error::tx_too_big, tx, upper_transaction_size_limit); + + std::string key_images; + bool all_are_txin_to_key = std::all_of(tx.vin.begin(), tx.vin.end(), [&](const txin_v& s_e) -> bool + { + CHECKED_GET_SPECIFIC_VARIANT(s_e, const txin_to_key, in, false); + key_images += boost::to_string(in.k_image) + " "; + return true; + }); + THROW_WALLET_EXCEPTION_IF(!all_are_txin_to_key, error::unexpected_txin_type, tx); + + ptx.key_images = key_images; + ptx.fee = fee; + ptx.dust = 0; + ptx.dust_added_to_fee = false; + ptx.tx = tx; + ptx.change_dts = change_dts; + ptx.selected_transfers = selected_transfers; + ptx.tx_key = tx_key; + ptx.dests = dsts; +} + +static size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs) +{ + size_t size = 0; + + // tx prefix + + // first few bytes + size += 1 + 6; + + // vin + size += n_inputs * (1+6+(mixin+1)*2+32); + + // vout + size += n_outputs * (6+32); + + // extra + size += 40; + + // rct signatures + + // simple + size += 1; + + // message + size += 32; + + // rangeSigs + size += (2*64*32+32+64*32) * n_outputs; + + // MGs - only the last slot of II is saved, the rest can be reconstructed + size += n_inputs * (32 * (mixin+1) * n_inputs + 32 + 32 * (/*n_inputs+*/1)); + + // mixRing - not serialized, can be reconstructed + /* size += 2 * 32 * (mixin+1) * n_inputs; */ + + // pseudoOuts + size += 32 * n_outputs; + // ecdhInfo + size += 3 * 32 * n_outputs; + // outPk - only commitment is saved + size += 1 * 32 * n_outputs; + // txnFee + size += 4; + + LOG_PRINT_L2("estimated rct tx size for " << n_inputs << " at mixin " << mixin << " and " << n_outputs << ": " << size << " (" << ((32 * n_inputs/*+1*/) + 2 * 32 * (mixin+1) * n_inputs + 32 * n_outputs) << " saved)"); + return size; +} + +std::vector<size_t> wallet2::pick_prefered_rct_inputs(uint64_t needed_money) const +{ + std::vector<size_t> picks; + float current_output_relatdness = 1.0f; + + LOG_PRINT_L2("pick_prefered_rct_inputs: needed_money " << print_money(needed_money)); + + // try to find a rct input of enough size + for (size_t i = 0; i < m_transfers.size(); ++i) + { + const transfer_details& td = m_transfers[i]; + if (!td.m_spent && td.is_rct() && td.amount() >= needed_money && is_transfer_unlocked(td)) + { + LOG_PRINT_L2("We can use " << i << " alone: " << print_money(td.amount())); + picks.push_back(i); + return picks; + } + } + + // then try to find two outputs + // this could be made better by picking one of the outputs to be a small one, since those + // are less useful since often below the needed money, so if one can be used in a pair, + // it gets rid of it for the future + for (size_t i = 0; i < m_transfers.size(); ++i) + { + const transfer_details& td = m_transfers[i]; + if (!td.m_spent && td.is_rct() && is_transfer_unlocked(td)) + { + LOG_PRINT_L2("Considering input " << i << ", " << print_money(td.amount())); + for (size_t j = i + 1; j < m_transfers.size(); ++j) + { + const transfer_details& td2 = m_transfers[j]; + if (!td2.m_spent && td2.is_rct() && td.amount() + td2.amount() >= needed_money && is_transfer_unlocked(td2)) + { + // update our picks if those outputs are less related than any we + // already found. If the same, don't update, and oldest suitable outputs + // will be used in preference. + float relatedness = get_output_relatedness(td, td2); + LOG_PRINT_L2(" with input " << j << ", " << print_money(td2.amount()) << ", relatedness " << relatedness); + if (relatedness < current_output_relatdness) + { + // reset the current picks with those, and return them directly + // if they're unrelated. If they are related, we'll end up returning + // them if we find nothing better + picks.clear(); + picks.push_back(i); + picks.push_back(j); + LOG_PRINT_L0("we could use " << i << " and " << j); + if (relatedness == 0.0f) + return picks; + current_output_relatdness = relatedness; + } + } + } + } + } + + return picks; +} + // Another implementation of transaction creation that is hopefully better // While there is anything left to pay, it goes through random outputs and tries // to fill the next destination/amount. If it fully fills it, it will use the @@ -2662,12 +3227,13 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp bool adding_fee; // true if new outputs go towards fee, rather than destinations uint64_t needed_fee, available_for_fee = 0; uint64_t upper_transaction_size_limit = get_upper_tranaction_size_limit(); + const bool use_rct = use_fork_rules(4, 0); + + fee_multiplier = sanitize_fee_multiplier(fee_multiplier); // throw if attempting a transaction with no destinations THROW_WALLET_EXCEPTION_IF(dsts.empty(), error::zero_destination); - fee_multiplier = sanitize_fee_multiplier (fee_multiplier); - // calculate total amount being sent to all destinations // throw if total amount overflows uint64_t needed_money = 0; @@ -2686,9 +3252,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp for (size_t i = 0; i < m_transfers.size(); ++i) { const transfer_details& td = m_transfers[i]; - if (!td.m_spent && is_transfer_unlocked(td)) + if (!td.m_spent && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td)) { - if (is_valid_decomposed_amount(td.amount())) + if ((td.is_rct()) || is_valid_decomposed_amount(td.amount())) unused_transfers_indices.push_back(i); else unused_dust_indices.push_back(i); @@ -2704,6 +3270,28 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp adding_fee = false; needed_fee = 0; + // for rct, since we don't see the amounts, we will try to make all transactions + // look the same, with 1 or 2 inputs, and 2 outputs. One input is preferable, as + // this prevents linking to another by provenance analysis, but two is ok if we + // try to pick outputs not from the same block. We will get two outputs, one for + // the destination, and one for change. + std::vector<size_t> prefered_inputs; + uint64_t rct_outs_needed = 2 * (fake_outs_count + 1); + rct_outs_needed += 100; // some fudge factor since we don't know how many are locked + if (use_rct && get_num_rct_outputs() >= rct_outs_needed) + { + // this is used to build a tx that's 1 or 2 inputs, and 2 outputs, which + // will get us a known fee. + uint64_t estimated_fee = calculate_fee(estimate_rct_tx_size(2, fake_outs_count + 1, 2), fee_multiplier); + prefered_inputs = pick_prefered_rct_inputs(needed_money + estimated_fee); + if (!prefered_inputs.empty()) + { + string s; + for (auto i: prefered_inputs) s += print_money(m_transfers[i].amount()) + " "; + LOG_PRINT_L1("Found prefered rct inputs for rct tx: " << s); + } + } + // while we have something to send while ((!dsts.empty() && dsts[0].amount > 0) || adding_fee) { TX &tx = txes.back(); @@ -2716,7 +3304,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp // get a random unspent output and use it to pay part (or all) of the current destination (and maybe next one, etc) // This could be more clever, but maybe at the cost of making probabilistic inferences easier - size_t idx = !unused_transfers_indices.empty() ? pop_random_value(unused_transfers_indices) : pop_random_value(unused_dust_indices); + size_t idx = !prefered_inputs.empty() ? pop_back(prefered_inputs) : !unused_transfers_indices.empty() ? pop_best_value(unused_transfers_indices, tx.selected_transfers) : pop_best_value(unused_dust_indices, tx.selected_transfers); const transfer_details &td = m_transfers[idx]; LOG_PRINT_L2("Picking output " << idx << ", amount " << print_money(td.amount())); @@ -2765,7 +3353,12 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp } else { - try_tx = dsts.empty() || (tx.selected_transfers.size() * (fake_outs_count+1) * APPROXIMATE_INPUT_BYTES >= TX_SIZE_TARGET(upper_transaction_size_limit)); + size_t estimated_rct_tx_size; + if (use_rct) + estimated_rct_tx_size = estimate_rct_tx_size(tx.selected_transfers.size(), fake_outs_count, tx.dsts.size() + 1); + else + estimated_rct_tx_size = tx.selected_transfers.size() * (fake_outs_count+1) * APPROXIMATE_INPUT_BYTES; + try_tx = dsts.empty() || (estimated_rct_tx_size >= TX_SIZE_TARGET(upper_transaction_size_limit)); } if (try_tx) { @@ -2776,8 +3369,12 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp LOG_PRINT_L2("Trying to create a tx now, with " << tx.dsts.size() << " destinations and " << tx.selected_transfers.size() << " outputs"); - transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra, - detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); + if (use_rct) + transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra, + test_tx, test_ptx); + else + transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra, + detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); auto txBlob = t_serializable_object_to_blob(test_ptx.tx); needed_fee = calculate_fee(txBlob, fee_multiplier); available_for_fee = test_ptx.fee + test_ptx.change_dts.amount + (!test_ptx.dust_added_to_fee ? test_ptx.dust : 0); @@ -2814,11 +3411,15 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp else { LOG_PRINT_L2("We made a tx, adjusting fee and saving it"); - transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra, - detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); + if (use_rct) + transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra, + test_tx, test_ptx); + else + transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra, + detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); txBlob = t_serializable_object_to_blob(test_ptx.tx); - LOG_PRINT_L2("Made a final " << ((txBlob.size() + 1023)/1024) << " kB tx, with " << print_money(test_ptx.fee) << - " fee and " << print_money(test_ptx.change_dts.amount) << " change"); + LOG_PRINT_L2("Made a final " << ((txBlob.size() + 1023)/1024) << " kB tx, with " << print_money(test_ptx.fee) << + " fee and " << print_money(test_ptx.change_dts.amount) << " change"); tx.tx = test_tx; tx.ptx = test_ptx; @@ -2862,7 +3463,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp return ptx_vector; } -std::vector<wallet2::pending_tx> wallet2::create_transactions_all(const cryptonote::account_public_address &address, const size_t fake_outs_count, const uint64_t unlock_time, uint64_t fee_multiplier, const std::vector<uint8_t> extra, bool trusted_daemon) +std::vector<wallet2::pending_tx> wallet2::create_transactions_all(const cryptonote::account_public_address &address, const size_t fake_outs_count, const uint64_t unlock_time, const uint64_t fee_multiplier, const std::vector<uint8_t> extra, bool trusted_daemon) { std::vector<size_t> unused_transfers_indices; std::vector<size_t> unused_dust_indices; @@ -2877,16 +3478,15 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(const cryptono std::vector<TX> txes; uint64_t needed_fee, available_for_fee = 0; uint64_t upper_transaction_size_limit = get_upper_tranaction_size_limit(); - - fee_multiplier = sanitize_fee_multiplier(fee_multiplier); + const bool use_rct = use_fork_rules(4, 0); // gather all our dust and non dust outputs for (size_t i = 0; i < m_transfers.size(); ++i) { const transfer_details& td = m_transfers[i]; - if (!td.m_spent && is_transfer_unlocked(td)) + if (!td.m_spent && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td)) { - if (is_valid_decomposed_amount(td.amount())) + if (td.is_rct() || is_valid_decomposed_amount(td.amount())) unused_transfers_indices.push_back(i); else unused_dust_indices.push_back(i); @@ -2908,7 +3508,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(const cryptono // get a random unspent output and use it to pay next chunk. We try to alternate // dust and non dust to ensure we never get with only dust, from which we might // get a tx that can't pay for itself - size_t idx = unused_transfers_indices.empty() ? pop_random_value(unused_dust_indices) : unused_dust_indices.empty() ? pop_random_value(unused_transfers_indices) : ((tx.selected_transfers.size() & 1) || accumulated_outputs > FEE_PER_KB * fee_multiplier * (upper_transaction_size_limit + 1023) / 1024) ? pop_random_value(unused_dust_indices) : pop_random_value(unused_transfers_indices); + size_t idx = unused_transfers_indices.empty() ? pop_best_value(unused_dust_indices, tx.selected_transfers) : unused_dust_indices.empty() ? pop_best_value(unused_transfers_indices, tx.selected_transfers) : ((tx.selected_transfers.size() & 1) || accumulated_outputs > FEE_PER_KB * fee_multiplier * (upper_transaction_size_limit + 1023) / 1024) ? pop_best_value(unused_dust_indices, tx.selected_transfers) : pop_best_value(unused_transfers_indices, tx.selected_transfers); const transfer_details &td = m_transfers[idx]; LOG_PRINT_L2("Picking output " << idx << ", amount " << print_money(td.amount())); @@ -2921,7 +3521,12 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(const cryptono // here, check if we need to sent tx and start a new one LOG_PRINT_L2("Considering whether to create a tx now, " << tx.selected_transfers.size() << " inputs, tx limit " << upper_transaction_size_limit); - bool try_tx = (unused_dust_indices.empty() && unused_transfers_indices.empty()) || (tx.selected_transfers.size() * (fake_outs_count+1) * APPROXIMATE_INPUT_BYTES >= TX_SIZE_TARGET(upper_transaction_size_limit)); + size_t estimated_rct_tx_size; + if (use_rct) + estimated_rct_tx_size = estimate_rct_tx_size(tx.selected_transfers.size(), fake_outs_count, tx.dsts.size() + 1); + else + estimated_rct_tx_size = tx.selected_transfers.size() * (fake_outs_count+1) * APPROXIMATE_INPUT_BYTES; + bool try_tx = (unused_dust_indices.empty() && unused_transfers_indices.empty()) || ( estimated_rct_tx_size >= TX_SIZE_TARGET(upper_transaction_size_limit)); if (try_tx) { cryptonote::transaction test_tx; @@ -2933,8 +3538,12 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(const cryptono LOG_PRINT_L2("Trying to create a tx now, with " << tx.dsts.size() << " destinations and " << tx.selected_transfers.size() << " outputs"); - transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra, - detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); + if (use_rct) + transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra, + test_tx, test_ptx); + else + transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra, + detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); auto txBlob = t_serializable_object_to_blob(test_ptx.tx); needed_fee = calculate_fee(txBlob, fee_multiplier); available_for_fee = test_ptx.fee + test_ptx.dests[0].amount + test_ptx.change_dts.amount; @@ -2946,8 +3555,12 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(const cryptono do { LOG_PRINT_L2("We made a tx, adjusting fee and saving it"); tx.dsts[0].amount = available_for_fee - needed_fee; - transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra, - detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); + if (use_rct) + transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra, + test_tx, test_ptx); + else + transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra, + detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); txBlob = t_serializable_object_to_blob(test_ptx.tx); needed_fee = calculate_fee(txBlob, fee_multiplier); LOG_PRINT_L2("Made an attempt at a final " << ((txBlob.size() + 1023)/1024) << " kB tx, with " << print_money(test_ptx.fee) << @@ -3057,6 +3670,7 @@ void wallet2::transfer_from(const std::vector<size_t> &outs, size_t num_outputs, cryptonote::tx_source_entry& src = sources.back(); transfer_details& td = *it; src.amount = td.amount(); + src.rct = td.is_rct(); //paste real transaction to the random index auto it_to_insert = std::find_if(src.outputs.begin(), src.outputs.end(), [&](const tx_output_entry& a) @@ -3065,7 +3679,8 @@ void wallet2::transfer_from(const std::vector<size_t> &outs, size_t num_outputs, }); tx_output_entry real_oe; real_oe.first = td.m_global_output_index; - real_oe.second = boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key; + real_oe.second.dest = rct::pk2rct(boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key); + real_oe.second.mask = rct::commit(td.amount(), td.m_mask); auto interted_it = src.outputs.insert(it_to_insert, real_oe); src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_tx); src.real_output = interted_it - src.outputs.begin(); @@ -3113,36 +3728,45 @@ void wallet2::transfer_from(const std::vector<size_t> &outs, size_t num_outputs, } //---------------------------------------------------------------------------------------------------- -bool wallet2::use_fork_rules(uint8_t version) +void wallet2::get_hard_fork_info(uint8_t version, uint64_t &earliest_height) { - cryptonote::COMMAND_RPC_GET_HEIGHT::request req = AUTO_VAL_INIT(req); - cryptonote::COMMAND_RPC_GET_HEIGHT::response res = AUTO_VAL_INIT(res); epee::json_rpc::request<cryptonote::COMMAND_RPC_HARD_FORK_INFO::request> req_t = AUTO_VAL_INIT(req_t); epee::json_rpc::response<cryptonote::COMMAND_RPC_HARD_FORK_INFO::response, std::string> resp_t = AUTO_VAL_INIT(resp_t); m_daemon_rpc_mutex.lock(); - bool r = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/getheight", req, res, m_http_client); - m_daemon_rpc_mutex.unlock(); - CHECK_AND_ASSERT_MES(r, false, "Failed to connect to daemon"); - CHECK_AND_ASSERT_MES(res.status != CORE_RPC_STATUS_BUSY, false, "Failed to connect to daemon"); - CHECK_AND_ASSERT_MES(res.status == CORE_RPC_STATUS_OK, false, "Failed to get current blockchain height"); - - m_daemon_rpc_mutex.lock(); req_t.jsonrpc = "2.0"; req_t.id = epee::serialization::storage_entry(0); req_t.method = "hard_fork_info"; req_t.params.version = version; - r = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/json_rpc", req_t, resp_t, m_http_client); + bool r = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/json_rpc", req_t, resp_t, m_http_client); + m_daemon_rpc_mutex.unlock(); + CHECK_AND_ASSERT_THROW_MES(r, "Failed to connect to daemon"); + CHECK_AND_ASSERT_THROW_MES(resp_t.result.status != CORE_RPC_STATUS_BUSY, "Failed to connect to daemon"); + CHECK_AND_ASSERT_THROW_MES(resp_t.result.status == CORE_RPC_STATUS_OK, "Failed to get hard fork status"); + + earliest_height = resp_t.result.earliest_height; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::use_fork_rules(uint8_t version, uint64_t early_blocks) +{ + cryptonote::COMMAND_RPC_GET_HEIGHT::request req = AUTO_VAL_INIT(req); + cryptonote::COMMAND_RPC_GET_HEIGHT::response res = AUTO_VAL_INIT(res); + + m_daemon_rpc_mutex.lock(); + bool r = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/getheight", req, res, m_http_client); m_daemon_rpc_mutex.unlock(); CHECK_AND_ASSERT_MES(r, false, "Failed to connect to daemon"); - CHECK_AND_ASSERT_MES(resp_t.result.status != CORE_RPC_STATUS_BUSY, false, "Failed to connect to daemon"); - CHECK_AND_ASSERT_MES(resp_t.result.status == CORE_RPC_STATUS_OK, false, "Failed to get hard fork status"); + CHECK_AND_ASSERT_MES(res.status != CORE_RPC_STATUS_BUSY, false, "Failed to connect to daemon"); + CHECK_AND_ASSERT_MES(res.status == CORE_RPC_STATUS_OK, false, "Failed to get current blockchain height"); + + uint64_t earliest_height; + get_hard_fork_info(version, earliest_height); // can throw - bool close_enough = res.height >= resp_t.result.earliest_height - 10; // start using the rules a bit beforehand + bool close_enough = res.height >= earliest_height - early_blocks; // start using the rules that many blocks beforehand if (close_enough) - LOG_PRINT_L2("Using HF1 rules"); + LOG_PRINT_L2("Using v" << (unsigned)version << " rules"); else - LOG_PRINT_L2("Not using HF1 rules"); + LOG_PRINT_L2("Not using v" << (unsigned)version << " rules"); return close_enough; } //---------------------------------------------------------------------------------------------------- @@ -3150,7 +3774,7 @@ uint64_t wallet2::get_upper_tranaction_size_limit() { if (m_upper_transaction_size_limit > 0) return m_upper_transaction_size_limit; - uint64_t full_reward_zone = use_fork_rules(2) ? CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 : CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V1; + uint64_t full_reward_zone = use_fork_rules(2, 10) ? CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 : CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V1; return ((full_reward_zone * 125) / 100) - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE; } //---------------------------------------------------------------------------------------------------- @@ -3213,6 +3837,8 @@ std::vector<size_t> wallet2::select_available_outputs_from_histogram(uint64_t co } return select_available_outputs([mixable, atleast](const transfer_details &td) { + if (td.is_rct()) + return false; const uint64_t amount = td.amount(); if (atleast) { if (mixable.find(amount) != mixable.end()) @@ -3226,6 +3852,28 @@ std::vector<size_t> wallet2::select_available_outputs_from_histogram(uint64_t co }); } //---------------------------------------------------------------------------------------------------- +uint64_t wallet2::get_num_rct_outputs() +{ + epee::json_rpc::request<cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request> req_t = AUTO_VAL_INIT(req_t); + epee::json_rpc::response<cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response, std::string> resp_t = AUTO_VAL_INIT(resp_t); + m_daemon_rpc_mutex.lock(); + req_t.jsonrpc = "2.0"; + req_t.id = epee::serialization::storage_entry(0); + req_t.method = "get_output_histogram"; + req_t.params.amounts.push_back(0); + req_t.params.min_count = 0; + req_t.params.max_count = 0; + bool r = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/json_rpc", req_t, resp_t, m_http_client); + m_daemon_rpc_mutex.unlock(); + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_num_rct_outputs"); + THROW_WALLET_EXCEPTION_IF(resp_t.result.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_output_histogram"); + THROW_WALLET_EXCEPTION_IF(resp_t.result.status != CORE_RPC_STATUS_OK, error::get_histogram_error, resp_t.result.status); + THROW_WALLET_EXCEPTION_IF(resp_t.result.histogram.size() != 1, error::get_histogram_error, "Expected exactly one response"); + THROW_WALLET_EXCEPTION_IF(resp_t.result.histogram[0].amount != 0, error::get_histogram_error, "Expected 0 amount"); + + return resp_t.result.histogram[0].instances; +} +//---------------------------------------------------------------------------------------------------- std::vector<size_t> wallet2::select_available_unmixable_outputs(bool trusted_daemon) { // request all outputs with less than 3 instances @@ -3241,7 +3889,7 @@ std::vector<size_t> wallet2::select_available_mixable_outputs(bool trusted_daemo std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions(bool trusted_daemon) { // From hard fork 1, we don't consider small amounts to be dust anymore - const bool hf1_rules = use_fork_rules(2); // first hard fork has version 2 + const bool hf1_rules = use_fork_rules(2, 10); // first hard fork has version 2 tx_dust_policy dust_policy(hf1_rules ? 0 : ::config::DEFAULT_DUST_THRESHOLD); // may throw @@ -3290,7 +3938,9 @@ std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions(bo // mark transfers to be used as "spent" BOOST_FOREACH(transfer_container::iterator it, ptx.selected_transfers) - it->m_spent = true; + { + set_spent(*it, 0); + } } // if we made it this far, we've selected our transactions. committing them will mark them spent, @@ -3300,8 +3950,9 @@ std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions(bo { // mark transfers to be used as not spent BOOST_FOREACH(transfer_container::iterator it2, ptx.selected_transfers) - it2->m_spent = false; - + { + set_unspent(*it2); + } } // if we made it this far, we're OK to actually send the transactions @@ -3317,8 +3968,9 @@ std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions(bo { // mark transfers to be used as not spent BOOST_FOREACH(transfer_container::iterator it2, ptx.selected_transfers) - it2->m_spent = false; - + { + set_unspent(*it2); + } } if (attempt_count >= MAX_SPLIT_ATTEMPTS) @@ -3335,8 +3987,9 @@ std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions(bo { // mark transfers to be used as not spent BOOST_FOREACH(transfer_container::iterator it2, ptx.selected_transfers) - it2->m_spent = false; - + { + set_unspent(*it2); + } } throw; @@ -3521,7 +4174,7 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag for (size_t n = 0; n < daemon_resp.spent_status.size(); ++n) { transfer_details &td = m_transfers[n]; - uint64_t amount = td.m_tx.vout[td.m_internal_output_index].amount; + uint64_t amount = td.amount(); td.m_spent = daemon_resp.spent_status[n] != COMMAND_RPC_IS_KEY_IMAGE_SPENT::UNSPENT; if (td.m_spent) spent += amount; diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index d0c514a6d..0e87614b4 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -31,6 +31,7 @@ #pragma once #include <memory> +#include <boost/archive/binary_iarchive.hpp> #include <boost/serialization/list.hpp> #include <boost/serialization/vector.hpp> #include <atomic> @@ -46,6 +47,8 @@ #include "common/unordered_containers_boost_serialization.h" #include "crypto/chacha8.h" #include "crypto/hash.h" +#include "ringct/rctTypes.h" +#include "ringct/rctOps.h" #include "wallet_errors.h" @@ -58,8 +61,8 @@ namespace tools { public: virtual void on_new_block(uint64_t height, const cryptonote::block& block) {} - virtual void on_money_received(uint64_t height, const cryptonote::transaction& tx, size_t out_index) {} - virtual void on_money_spent(uint64_t height, const cryptonote::transaction& in_tx, size_t out_index, const cryptonote::transaction& spend_tx) {} + virtual void on_money_received(uint64_t height, const cryptonote::transaction& tx, uint64_t amount) {} + virtual void on_money_spent(uint64_t height, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx) {} virtual void on_skip_transaction(uint64_t height, const cryptonote::transaction& tx) {} virtual ~i_wallet2_callback() {} }; @@ -96,13 +99,19 @@ namespace tools struct transfer_details { uint64_t m_block_height; - cryptonote::transaction m_tx; + cryptonote::transaction_prefix m_tx; + crypto::hash m_txid; size_t m_internal_output_index; uint64_t m_global_output_index; bool m_spent; + uint64_t m_spent_height; crypto::key_image m_key_image; //TODO: key_image stored twice :( + rct::key m_mask; + uint64_t m_amount; + bool m_rct; - uint64_t amount() const { return m_tx.vout[m_internal_output_index].amount; } + bool is_rct() const { return m_rct; } + uint64_t amount() const { return m_amount; } }; struct payment_details @@ -116,7 +125,9 @@ namespace tools struct unconfirmed_transfer_details { - cryptonote::transaction m_tx; + cryptonote::transaction_prefix m_tx; + uint64_t m_amount_in; + uint64_t m_amount_out; uint64_t m_change; time_t m_sent_time; std::vector<cryptonote::tx_destination_entry> m_dests; @@ -137,7 +148,7 @@ namespace tools confirmed_transfer_details(): m_amount_in(0), m_amount_out(0), m_change((uint64_t)-1), m_block_height(0), m_payment_id(cryptonote::null_hash) {} confirmed_transfer_details(const unconfirmed_transfer_details &utd, uint64_t height): - m_amount_out(get_outs_money_amount(utd.m_tx)), m_change(utd.m_change), m_block_height(height), m_dests(utd.m_dests), m_payment_id(utd.m_payment_id), m_timestamp(utd.m_timestamp) { get_inputs_money_amount(utd.m_tx, m_amount_in); } + m_amount_in(utd.m_amount_in), m_amount_out(utd.m_amount_out), m_change(utd.m_change), m_block_height(height), m_dests(utd.m_dests), m_payment_id(utd.m_payment_id), m_timestamp(utd.m_timestamp) {} }; typedef std::vector<transfer_details> transfer_container; @@ -289,6 +300,8 @@ namespace tools template<typename T> void transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::list<transfer_container::iterator> selected_transfers, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx); + void transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::list<transfer_container::iterator> selected_transfers, size_t fake_outputs_count, + uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx); void commit_tx(pending_tx& ptx_vector); void commit_tx(std::vector<pending_tx>& ptx_vector); @@ -308,6 +321,7 @@ namespace tools uint64_t get_blockchain_current_height() const { return m_local_bc_height; } void rescan_spent(); void rescan_blockchain(bool refresh = true); + bool is_transfer_unlocked(const transfer_details& td) const; template <class t_archive> inline void serialize(t_archive &a, const unsigned int ver) { @@ -339,6 +353,8 @@ namespace tools if(ver < 13) return; a & m_unconfirmed_payments; + if(ver < 14) + return; } /*! @@ -376,8 +392,10 @@ namespace tools bool get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key) const; + uint64_t get_num_rct_outputs(); - bool use_fork_rules(uint8_t version); + void get_hard_fork_info(uint8_t version, uint64_t &earliest_height); + bool use_fork_rules(uint8_t version, uint64_t early_blocks = 0); std::string get_wallet_file() const; std::string get_keys_file() const; @@ -388,6 +406,9 @@ namespace tools std::vector<size_t> select_available_unmixable_outputs(bool trusted_daemon); std::vector<size_t> select_available_mixable_outputs(bool trusted_daemon); + size_t pop_best_value_from(const transfer_container &transfers, std::vector<size_t> &unused_dust_indices, const std::list<transfer_container::iterator>& selected_transfers) const; + size_t pop_best_value(std::vector<size_t> &unused_dust_indices, const std::list<transfer_container::iterator>& selected_transfers) const; + void set_tx_note(const crypto::hash &txid, const std::string ¬e); std::string get_tx_note(const crypto::hash &txid) const; @@ -413,32 +434,37 @@ namespace tools * \param password Password of wallet file */ bool load_keys(const std::string& keys_file_name, const std::string& password); - void process_new_transaction(const cryptonote::transaction& tx, uint64_t height, uint64_t ts, bool miner_tx, bool pool); - void process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const crypto::hash& bl_id, uint64_t height); + void process_new_transaction(const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint64_t ts, bool miner_tx, bool pool); + void process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const crypto::hash& bl_id, uint64_t height, const cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices &o_indices); void detach_blockchain(uint64_t height); void get_short_chain_history(std::list<crypto::hash>& ids) const; bool is_tx_spendtime_unlocked(uint64_t unlock_time, uint64_t block_height) const; - bool is_transfer_unlocked(const transfer_details& td) const; bool clear(); - void pull_blocks(uint64_t start_height, uint64_t& blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::list<cryptonote::block_complete_entry> &blocks); + void pull_blocks(uint64_t start_height, uint64_t& blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::list<cryptonote::block_complete_entry> &blocks, std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices); void pull_hashes(uint64_t start_height, uint64_t& blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::list<crypto::hash> &hashes); void fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history); - void pull_next_blocks(uint64_t start_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, const std::list<cryptonote::block_complete_entry> &prev_blocks, std::list<cryptonote::block_complete_entry> &blocks, bool &error); - void process_blocks(uint64_t start_height, const std::list<cryptonote::block_complete_entry> &blocks, uint64_t& blocks_added); + void pull_next_blocks(uint64_t start_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, const std::list<cryptonote::block_complete_entry> &prev_blocks, std::list<cryptonote::block_complete_entry> &blocks, std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices, bool &error); + void process_blocks(uint64_t start_height, const std::list<cryptonote::block_complete_entry> &blocks, const std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices, uint64_t& blocks_added); uint64_t select_transfers(uint64_t needed_money, std::vector<size_t> unused_transfers_indices, std::list<transfer_container::iterator>& selected_transfers, bool trusted_daemon); bool prepare_file_names(const std::string& file_path); void process_unconfirmed(const cryptonote::transaction& tx, uint64_t height); void process_outgoing(const cryptonote::transaction& tx, uint64_t height, uint64_t ts, uint64_t spent, uint64_t received); - void add_unconfirmed_tx(const cryptonote::transaction& tx, const std::vector<cryptonote::tx_destination_entry> &dests, const crypto::hash &payment_id, uint64_t change_amount); + void add_unconfirmed_tx(const cryptonote::transaction& tx, uint64_t amount_in, const std::vector<cryptonote::tx_destination_entry> &dests, const crypto::hash &payment_id, uint64_t change_amount); void generate_genesis(cryptonote::block& b); void check_genesis(const crypto::hash& genesis_hash) const; //throws bool generate_chacha8_key_from_secret_keys(crypto::chacha8_key &key) const; crypto::hash get_payment_id(const pending_tx &ptx) const; - void check_acc_out(const cryptonote::account_keys &acc, const cryptonote::tx_out &o, const crypto::public_key &tx_pub_key, size_t i, uint64_t &money_transfered, bool &error) const; + void check_acc_out(const cryptonote::account_keys &acc, const cryptonote::tx_out &o, const crypto::public_key &tx_pub_key, size_t i, bool &received, uint64_t &money_transfered, bool &error) const; void parse_block_round(const cryptonote::blobdata &blob, cryptonote::block &bl, crypto::hash &bl_id, bool &error) const; uint64_t get_upper_tranaction_size_limit(); std::vector<uint64_t> get_unspent_amounts_vector(); uint64_t sanitize_fee_multiplier(uint64_t fee_multiplier) const; + float get_output_relatedness(const transfer_details &td0, const transfer_details &td1) const; + std::vector<size_t> pick_prefered_rct_inputs(uint64_t needed_money) const; + void set_spent(transfer_details &td, uint64_t height); + void set_unspent(transfer_details &td); + template<typename entry> + void get_outs(std::vector<std::vector<entry>> &outs, const std::list<transfer_container::iterator> &selected_transfers, size_t fake_outputs_count); cryptonote::account_base m_account; std::string m_daemon_address; @@ -478,9 +504,10 @@ namespace tools uint64_t m_refresh_from_block_height; }; } -BOOST_CLASS_VERSION(tools::wallet2, 13) +BOOST_CLASS_VERSION(tools::wallet2, 14) +BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 4) BOOST_CLASS_VERSION(tools::wallet2::payment_details, 1) -BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 3) +BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 5) BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 2) namespace boost @@ -488,14 +515,72 @@ namespace boost namespace serialization { template <class Archive> + inline void initialize_transfer_details(Archive &a, tools::wallet2::transfer_details &x, const boost::serialization::version_type ver) + { + } + template<> + inline void initialize_transfer_details(boost::archive::binary_iarchive &a, tools::wallet2::transfer_details &x, const boost::serialization::version_type ver) + { + if (ver < 1) + { + x.m_mask = rct::identity(); + x.m_amount = x.m_tx.vout[x.m_internal_output_index].amount; + } + if (ver < 2) + { + x.m_spent_height = 0; + } + if (ver < 4) + { + x.m_rct = x.m_tx.vout[x.m_internal_output_index].amount == 0; + } + } + + template <class Archive> inline void serialize(Archive &a, tools::wallet2::transfer_details &x, const boost::serialization::version_type ver) { a & x.m_block_height; a & x.m_global_output_index; a & x.m_internal_output_index; - a & x.m_tx; + if (ver < 3) + { + cryptonote::transaction tx; + a & tx; + x.m_tx = (const cryptonote::transaction_prefix&)tx; + x.m_txid = cryptonote::get_transaction_hash(tx); + } + else + { + a & x.m_tx; + } a & x.m_spent; a & x.m_key_image; + if (ver < 1) + { + // ensure mask and amount are set + initialize_transfer_details(a, x, ver); + return; + } + a & x.m_mask; + a & x.m_amount; + if (ver < 2) + { + initialize_transfer_details(a, x, ver); + return; + } + a & x.m_spent_height; + if (ver < 3) + { + initialize_transfer_details(a, x, ver); + return; + } + a & x.m_txid; + if (ver < 4) + { + initialize_transfer_details(a, x, ver); + return; + } + a & x.m_rct; } template <class Archive> @@ -503,7 +588,16 @@ namespace boost { a & x.m_change; a & x.m_sent_time; - a & x.m_tx; + if (ver < 5) + { + cryptonote::transaction tx; + a & tx; + x.m_tx = (const cryptonote::transaction_prefix&)tx; + } + else + { + a & x.m_tx; + } if (ver < 1) return; a & x.m_dests; @@ -514,6 +608,10 @@ namespace boost if (ver < 3) return; a & x.m_timestamp; + if (ver < 4) + return; + a & x.m_amount_in; + a & x.m_amount_out; } template <class Archive> @@ -688,6 +786,7 @@ namespace tools cryptonote::tx_source_entry& src = sources.back(); transfer_details& td = *it; src.amount = td.amount(); + src.rct = false; //paste mixin transaction if(daemon_resp.outs.size()) { @@ -698,7 +797,8 @@ namespace tools continue; tx_output_entry oe; oe.first = daemon_oe.global_amount_index; - oe.second = daemon_oe.out_key; + oe.second.dest = rct::pk2rct(daemon_oe.out_key); + oe.second.mask = rct::identity(); src.outputs.push_back(oe); if(src.outputs.size() >= fake_outputs_count) break; @@ -713,7 +813,8 @@ namespace tools //size_t real_index = src.outputs.size() ? (rand() % src.outputs.size() ):0; tx_output_entry real_oe; real_oe.first = td.m_global_output_index; - real_oe.second = boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key; + real_oe.second.dest = rct::pk2rct(boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key); + real_oe.second.mask = rct::identity(); auto interted_it = src.outputs.insert(it_to_insert, real_oe); src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_tx); src.real_output = interted_it - src.outputs.begin(); diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index debd7056a..4c190b005 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -235,11 +235,11 @@ namespace tools try { uint64_t mixin = req.mixin; - if (mixin < 2 && m_wallet.use_fork_rules(2)) { + if (mixin < 2 && m_wallet.use_fork_rules(2, 10)) { LOG_PRINT_L1("Requested mixin " << req.mixin << " too low for hard fork 2, using 2"); mixin = 2; } - std::vector<wallet2::pending_tx> ptx_vector = m_wallet.create_transactions(dsts, mixin, req.unlock_time, req.fee_multiplier, extra, req.trusted_daemon); + std::vector<wallet2::pending_tx> ptx_vector = m_wallet.create_transactions_2(dsts, mixin, req.unlock_time, req.fee_multiplier, extra, req.trusted_daemon); // reject proposed transactions if there are more than one. see on_transfer_split below. if (ptx_vector.size() != 1) @@ -254,7 +254,9 @@ namespace tools // populate response with tx hash res.tx_hash = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx_vector.back().tx)); if (req.get_tx_key) + { res.tx_key = epee::string_tools::pod_to_hex(ptx_vector.back().tx_key); + } return true; } catch (const tools::error::daemon_busy& e) @@ -300,15 +302,12 @@ namespace tools try { uint64_t mixin = req.mixin; - if (mixin < 2 && m_wallet.use_fork_rules(2)) { + if (mixin < 2 && m_wallet.use_fork_rules(2, 10)) { LOG_PRINT_L1("Requested mixin " << req.mixin << " too low for hard fork 2, using 2"); mixin = 2; } std::vector<wallet2::pending_tx> ptx_vector; - if (req.new_algorithm) - ptx_vector = m_wallet.create_transactions_2(dsts, mixin, req.unlock_time, req.fee_multiplier, extra, req.trusted_daemon); - else - ptx_vector = m_wallet.create_transactions(dsts, mixin, req.unlock_time, req.fee_multiplier, extra, req.trusted_daemon); + ptx_vector = m_wallet.create_transactions_2(dsts, mixin, req.unlock_time, req.fee_multiplier, extra, req.trusted_daemon); m_wallet.commit_tx(ptx_vector); @@ -317,7 +316,9 @@ namespace tools { res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx))); if (req.get_tx_keys) + { res.tx_key_list.push_back(epee::string_tools::pod_to_hex(ptx.tx_key)); + } } return true; @@ -363,7 +364,9 @@ namespace tools { res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx))); if (req.get_tx_keys) + { res.tx_key_list.push_back(epee::string_tools::pod_to_hex(ptx.tx_key)); + } } return true; @@ -422,7 +425,9 @@ namespace tools { res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx))); if (req.get_tx_keys) + { res.tx_key_list.push_back(epee::string_tools::pod_to_hex(ptx.tx_key)); + } } return true; @@ -693,7 +698,7 @@ namespace tools rpc_transfers.amount = td.amount(); rpc_transfers.spent = td.m_spent; rpc_transfers.global_index = td.m_global_output_index; - rpc_transfers.tx_hash = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(td.m_tx)); + rpc_transfers.tx_hash = epee::string_tools::pod_to_hex(td.m_txid); rpc_transfers.tx_size = txBlob.size(); res.transfers.push_back(rpc_transfers); } @@ -961,10 +966,8 @@ namespace tools entry.payment_id = entry.payment_id.substr(0,16); entry.height = 0; entry.timestamp = pd.m_timestamp; - uint64_t amount = 0; - cryptonote::get_inputs_money_amount(pd.m_tx, amount); - entry.fee = amount - get_outs_money_amount(pd.m_tx); - entry.amount = amount - pd.m_change - entry.fee; + entry.fee = pd.m_amount_in - pd.m_amount_out; + entry.amount = pd.m_amount_in - pd.m_change - entry.fee; entry.note = m_wallet.get_tx_note(i->first); } } diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index d7f01d9ee..27b897dab 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -132,10 +132,12 @@ namespace wallet_rpc { std::string tx_hash; std::string tx_key; + std::list<std::string> amount_keys; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash) KV_SERIALIZE(tx_key) + KV_SERIALIZE(amount_keys) END_KV_SERIALIZE_MAP() }; }; @@ -149,7 +151,6 @@ namespace wallet_rpc uint64_t mixin; uint64_t unlock_time; std::string payment_id; - bool new_algorithm; bool get_tx_keys; bool trusted_daemon; @@ -159,12 +160,20 @@ namespace wallet_rpc KV_SERIALIZE(mixin) KV_SERIALIZE(unlock_time) KV_SERIALIZE(payment_id) - KV_SERIALIZE(new_algorithm) KV_SERIALIZE(get_tx_keys) KV_SERIALIZE(trusted_daemon) END_KV_SERIALIZE_MAP() }; + struct key_list + { + std::list<std::string> keys; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(keys) + END_KV_SERIALIZE_MAP() + }; + struct response { std::list<std::string> tx_hash_list; @@ -190,6 +199,15 @@ namespace wallet_rpc END_KV_SERIALIZE_MAP() }; + struct key_list + { + std::list<std::string> keys; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(keys) + END_KV_SERIALIZE_MAP() + }; + struct response { std::list<std::string> tx_hash_list; @@ -225,6 +243,15 @@ namespace wallet_rpc END_KV_SERIALIZE_MAP() }; + struct key_list + { + std::list<std::string> keys; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(keys) + END_KV_SERIALIZE_MAP() + }; + struct response { std::list<std::string> tx_hash_list; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d1be97afc..daaa078db 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -63,7 +63,9 @@ add_subdirectory(unit_tests) add_subdirectory(difficulty) add_subdirectory(hash) add_subdirectory(net_load_tests) -add_subdirectory(libwallet_api_tests) + +# Disabled until issue #895 is resolved +#add_subdirectory(libwallet_api_tests) # add_subdirectory(daemon_tests) @@ -86,5 +88,11 @@ add_test( NAME hash-target COMMAND hash-target-tests) -add_custom_target(tests DEPENDS coretests difficulty hash performance_tests core_proxy unit_tests) +# Skip the core_tests if we are running in Travis-CI because they will take too long +if (DEFINED ENV{TRAVIS}) + add_custom_target(tests DEPENDS difficulty hash performance_tests core_proxy unit_tests) +else () + add_custom_target(tests DEPENDS coretests difficulty hash performance_tests core_proxy unit_tests) +endif () + set_property(TARGET gtest gtest_main hash-target-tests tests PROPERTY FOLDER "tests") diff --git a/tests/core_proxy/core_proxy.h b/tests/core_proxy/core_proxy.h index 0315fc8c8..0f6d6571e 100644 --- a/tests/core_proxy/core_proxy.h +++ b/tests/core_proxy/core_proxy.h @@ -34,7 +34,6 @@ #include "cryptonote_core/cryptonote_basic_impl.h" #include "cryptonote_core/verification_context.h" -#include "cryptonote_core/blockchain_storage.h" #include <unordered_map> namespace tests @@ -82,7 +81,7 @@ namespace tests bool on_idle(){return true;} bool find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, cryptonote::NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp){return true;} bool handle_get_objects(cryptonote::NOTIFY_REQUEST_GET_OBJECTS::request& arg, cryptonote::NOTIFY_RESPONSE_GET_OBJECTS::request& rsp, cryptonote::cryptonote_connection_context& context){return true;} - cryptonote::blockchain_storage &get_blockchain_storage() { throw std::runtime_error("Called invalid member function: please never call get_blockchain_storage on the TESTING class proxy_core."); } + cryptonote::Blockchain &get_blockchain_storage() { throw std::runtime_error("Called invalid member function: please never call get_blockchain_storage on the TESTING class proxy_core."); } bool get_test_drop_download() {return true;} bool get_test_drop_download_height() {return true;} bool prepare_handle_incoming_blocks(const std::list<cryptonote::block_complete_entry> &blocks) { return true; } diff --git a/tests/core_tests/CMakeLists.txt b/tests/core_tests/CMakeLists.txt index c34039c17..6f07fbd25 100644 --- a/tests/core_tests/CMakeLists.txt +++ b/tests/core_tests/CMakeLists.txt @@ -39,7 +39,8 @@ set(core_tests_sources ring_signature_1.cpp transaction_tests.cpp tx_validation.cpp - v2_tests.cpp) + v2_tests.cpp + rct.cpp) set(core_tests_headers block_reward.h @@ -54,7 +55,8 @@ set(core_tests_headers ring_signature_1.h transaction_tests.h tx_validation.h - v2_tests.h) + v2_tests.h + rct.h) add_executable(coretests ${core_tests_sources} diff --git a/tests/core_tests/block_validation.cpp b/tests/core_tests/block_validation.cpp index bce0980bf..df8972556 100644 --- a/tests/core_tests/block_validation.cpp +++ b/tests/core_tests/block_validation.cpp @@ -335,8 +335,9 @@ bool gen_block_miner_tx_has_2_in::generate(std::vector<test_event_entry>& events tx_source_entry se; se.amount = blk_0.miner_tx.vout[0].amount; - se.outputs.push_back(std::make_pair(0, boost::get<txout_to_key>(blk_0.miner_tx.vout[0].target).key)); + se.push_output(0, boost::get<txout_to_key>(blk_0.miner_tx.vout[0].target).key, se.amount); se.real_output = 0; + se.rct = false; se.real_out_tx_key = get_tx_pub_key_from_extra(blk_0.miner_tx); se.real_output_in_tx_index = 0; std::vector<tx_source_entry> sources; @@ -377,8 +378,9 @@ bool gen_block_miner_tx_with_txin_to_key::generate(std::vector<test_event_entry> tx_source_entry se; se.amount = blk_1.miner_tx.vout[0].amount; - se.outputs.push_back(std::make_pair(0, boost::get<txout_to_key>(blk_1.miner_tx.vout[0].target).key)); + se.push_output(0, boost::get<txout_to_key>(blk_1.miner_tx.vout[0].target).key, se.amount); se.real_output = 0; + se.rct = false; se.real_out_tx_key = get_tx_pub_key_from_extra(blk_1.miner_tx); se.real_output_in_tx_index = 0; std::vector<tx_source_entry> sources; diff --git a/tests/core_tests/chaingen.cpp b/tests/core_tests/chaingen.cpp index efd0bf1ea..4cb70e745 100644 --- a/tests/core_tests/chaingen.cpp +++ b/tests/core_tests/chaingen.cpp @@ -94,11 +94,11 @@ uint64_t test_generator::get_already_generated_coins(const cryptonote::block& bl return get_already_generated_coins(blk_hash); } -void test_generator::add_block(const cryptonote::block& blk, size_t tsx_size, std::vector<size_t>& block_sizes, uint64_t already_generated_coins) +void test_generator::add_block(const cryptonote::block& blk, size_t tsx_size, std::vector<size_t>& block_sizes, uint64_t already_generated_coins, uint8_t hf_version) { const size_t block_size = tsx_size + get_object_blobsize(blk.miner_tx); uint64_t block_reward; - get_block_reward(misc_utils::median(block_sizes), block_size, already_generated_coins, block_reward, 1); + get_block_reward(misc_utils::median(block_sizes), block_size, already_generated_coins, block_reward, hf_version); m_blocks_info[get_block_hash(blk)] = block_info(blk.prev_id, already_generated_coins + block_reward, block_size); } @@ -215,7 +215,7 @@ bool test_generator::construct_block_manually(block& blk, const block& prev_bloc const crypto::hash& prev_id/* = crypto::hash()*/, const difficulty_type& diffic/* = 1*/, const transaction& miner_tx/* = transaction()*/, const std::vector<crypto::hash>& tx_hashes/* = std::vector<crypto::hash>()*/, - size_t txs_sizes/* = 0*/, size_t max_outs/* = 0*/) + size_t txs_sizes/* = 0*/, size_t max_outs/* = 0*/, uint8_t hf_version/* = 1*/) { blk.major_version = actual_params & bf_major_ver ? major_ver : CURRENT_BLOCK_MAJOR_VERSION; blk.minor_version = actual_params & bf_minor_ver ? minor_ver : CURRENT_BLOCK_MINOR_VERSION; @@ -223,6 +223,7 @@ bool test_generator::construct_block_manually(block& blk, const block& prev_bloc blk.prev_id = actual_params & bf_prev_id ? prev_id : get_block_hash(prev_block); blk.tx_hashes = actual_params & bf_tx_hashes ? tx_hashes : std::vector<crypto::hash>(); max_outs = actual_params & bf_max_outs ? max_outs : 9999; + hf_version = actual_params & bf_hf_version ? hf_version : 1; size_t height = get_block_height(prev_block) + 1; uint64_t already_generated_coins = get_already_generated_coins(prev_block); @@ -236,7 +237,7 @@ bool test_generator::construct_block_manually(block& blk, const block& prev_bloc { size_t current_block_size = txs_sizes + get_object_blobsize(blk.miner_tx); // TODO: This will work, until size of constructed block is less then CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE - if (!construct_miner_tx(height, misc_utils::median(block_sizes), already_generated_coins, current_block_size, 0, miner_acc.get_keys().m_account_address, blk.miner_tx, blobdata(), max_outs)) + if (!construct_miner_tx(height, misc_utils::median(block_sizes), already_generated_coins, current_block_size, 0, miner_acc.get_keys().m_account_address, blk.miner_tx, blobdata(), max_outs, hf_version)) return false; } @@ -245,7 +246,7 @@ bool test_generator::construct_block_manually(block& blk, const block& prev_bloc difficulty_type a_diffic = actual_params & bf_diffic ? diffic : get_test_difficulty(); fill_nonce(blk, a_diffic, height); - add_block(blk, txs_sizes, block_sizes, already_generated_coins); + add_block(blk, txs_sizes, block_sizes, already_generated_coins, hf_version); return true; } @@ -412,7 +413,7 @@ bool fill_output_entries(std::vector<output_index>& out_indices, size_t sender_o if (append) { const txout_to_key& otk = boost::get<txout_to_key>(oi.out); - output_entries.push_back(tx_source_entry::output_entry(oi.idx, otk.key)); + output_entries.push_back(tx_source_entry::output_entry(oi.idx, rct::ctkey({rct::pk2rct(otk.key), rct::identity()}))); } } @@ -457,6 +458,7 @@ bool fill_tx_sources(std::vector<tx_source_entry>& sources, const std::vector<te continue; ts.real_output = realOutput; + ts.rct = false; sources.push_back(ts); @@ -544,7 +546,7 @@ bool construct_miner_tx_manually(size_t height, uint64_t already_generated_coins out.target = txout_to_key(out_eph_public_key); tx.vout.push_back(out); - tx.version = CURRENT_TRANSACTION_VERSION; + tx.version = 1; tx.unlock_time = height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW; return true; diff --git a/tests/core_tests/chaingen.h b/tests/core_tests/chaingen.h index 0e5dbb0e4..047d2c81c 100644 --- a/tests/core_tests/chaingen.h +++ b/tests/core_tests/chaingen.h @@ -221,7 +221,8 @@ public: bf_miner_tx = 1 << 4, bf_tx_hashes = 1 << 5, bf_diffic = 1 << 6, - bf_max_outs = 1 << 7 + bf_max_outs = 1 << 7, + bf_hf_version= 1 << 8 }; void get_block_chain(std::vector<block_info>& blockchain, const crypto::hash& head, size_t n) const; @@ -229,7 +230,8 @@ public: uint64_t get_already_generated_coins(const crypto::hash& blk_id) const; uint64_t get_already_generated_coins(const cryptonote::block& blk) const; - void add_block(const cryptonote::block& blk, size_t tsx_size, std::vector<size_t>& block_sizes, uint64_t already_generated_coins); + void add_block(const cryptonote::block& blk, size_t tsx_size, std::vector<size_t>& block_sizes, uint64_t already_generated_coins, + uint8_t hf_version = 1); bool construct_block(cryptonote::block& blk, uint64_t height, const crypto::hash& prev_id, const cryptonote::account_base& miner_acc, uint64_t timestamp, uint64_t already_generated_coins, std::vector<size_t>& block_sizes, const std::list<cryptonote::transaction>& tx_list); @@ -241,7 +243,8 @@ public: const cryptonote::account_base& miner_acc, int actual_params = bf_none, uint8_t major_ver = 0, uint8_t minor_ver = 0, uint64_t timestamp = 0, const crypto::hash& prev_id = crypto::hash(), const cryptonote::difficulty_type& diffic = 1, const cryptonote::transaction& miner_tx = cryptonote::transaction(), - const std::vector<crypto::hash>& tx_hashes = std::vector<crypto::hash>(), size_t txs_sizes = 0, size_t max_outs = 999); + const std::vector<crypto::hash>& tx_hashes = std::vector<crypto::hash>(), size_t txs_sizes = 0, size_t max_outs = 999, + uint8_t hf_version = 1); bool construct_block_manually_tx(cryptonote::block& blk, const cryptonote::block& prev_block, const cryptonote::account_base& miner_acc, const std::vector<crypto::hash>& tx_hashes, size_t txs_size); @@ -472,11 +475,11 @@ inline bool replay_events_through_core(cryptonote::core& cr, const std::vector<t //-------------------------------------------------------------------------- template<typename t_test_class> struct get_test_options { - const std::pair<uint8_t, uint64_t> hard_forks[1]; + const std::pair<uint8_t, uint64_t> hard_forks[2]; const cryptonote::test_options test_options = { hard_forks }; - get_test_options():hard_forks{std::make_pair((uint8_t)1, (uint64_t)0)}{} + get_test_options():hard_forks{std::make_pair((uint8_t)1, (uint64_t)0), std::make_pair((uint8_t)0, (uint64_t)0)}{} }; //-------------------------------------------------------------------------- diff --git a/tests/core_tests/chaingen_main.cpp b/tests/core_tests/chaingen_main.cpp index e20f7a152..09cdb9227 100644 --- a/tests/core_tests/chaingen_main.cpp +++ b/tests/core_tests/chaingen_main.cpp @@ -173,6 +173,35 @@ int main(int argc, char* argv[]) // GENERATE_AND_PLAY(gen_v2_tx_unmixable_two); GENERATE_AND_PLAY(gen_v2_tx_dust); + GENERATE_AND_PLAY(gen_rct_tx_valid_from_pre_rct); + GENERATE_AND_PLAY(gen_rct_tx_valid_from_rct); + GENERATE_AND_PLAY(gen_rct_tx_valid_from_mixed); + GENERATE_AND_PLAY(gen_rct_tx_pre_rct_bad_real_dest); + GENERATE_AND_PLAY(gen_rct_tx_pre_rct_bad_real_mask); + GENERATE_AND_PLAY(gen_rct_tx_pre_rct_bad_fake_dest); + GENERATE_AND_PLAY(gen_rct_tx_pre_rct_bad_fake_mask); + GENERATE_AND_PLAY(gen_rct_tx_rct_bad_real_dest); + GENERATE_AND_PLAY(gen_rct_tx_rct_bad_real_mask); + GENERATE_AND_PLAY(gen_rct_tx_rct_bad_fake_dest); + GENERATE_AND_PLAY(gen_rct_tx_rct_bad_fake_mask); + GENERATE_AND_PLAY(gen_rct_tx_rct_spend_with_zero_commit); + GENERATE_AND_PLAY(gen_rct_tx_pre_rct_zero_vin_amount); + GENERATE_AND_PLAY(gen_rct_tx_rct_non_zero_vin_amount); + GENERATE_AND_PLAY(gen_rct_tx_non_zero_vout_amount); + GENERATE_AND_PLAY(gen_rct_tx_pre_rct_duplicate_key_image); + GENERATE_AND_PLAY(gen_rct_tx_rct_duplicate_key_image); + GENERATE_AND_PLAY(gen_rct_tx_pre_rct_wrong_key_image); + GENERATE_AND_PLAY(gen_rct_tx_rct_wrong_key_image); + GENERATE_AND_PLAY(gen_rct_tx_pre_rct_wrong_fee); + GENERATE_AND_PLAY(gen_rct_tx_rct_wrong_fee); + GENERATE_AND_PLAY(gen_rct_tx_pre_rct_remove_vin); + GENERATE_AND_PLAY(gen_rct_tx_rct_remove_vin); + GENERATE_AND_PLAY(gen_rct_tx_pre_rct_add_vout); + GENERATE_AND_PLAY(gen_rct_tx_rct_add_vout); + GENERATE_AND_PLAY(gen_rct_tx_pre_rct_increase_vin_and_fee); + GENERATE_AND_PLAY(gen_rct_tx_pre_rct_altered_extra); + GENERATE_AND_PLAY(gen_rct_tx_rct_altered_extra); + std::cout << (failed_tests.empty() ? concolor::green : concolor::magenta); std::cout << "\nREPORT:\n"; std::cout << " Test run: " << tests_count << '\n'; diff --git a/tests/core_tests/chaingen_tests_list.h b/tests/core_tests/chaingen_tests_list.h index 4da87a973..1b9ebd756 100644 --- a/tests/core_tests/chaingen_tests_list.h +++ b/tests/core_tests/chaingen_tests_list.h @@ -40,6 +40,7 @@ #include "ring_signature_1.h" #include "tx_validation.h" #include "v2_tests.h" +#include "rct.h" /************************************************************************/ /* */ /************************************************************************/ diff --git a/tests/core_tests/double_spend.inl b/tests/core_tests/double_spend.inl index 55e5f4ec9..f97d48851 100644 --- a/tests/core_tests/double_spend.inl +++ b/tests/core_tests/double_spend.inl @@ -128,8 +128,9 @@ bool gen_double_spend_in_tx<txs_keeped_by_block>::generate(std::vector<test_even std::vector<cryptonote::tx_source_entry> sources; cryptonote::tx_source_entry se; se.amount = tx_0.vout[0].amount; - se.outputs.push_back(std::make_pair(0, boost::get<cryptonote::txout_to_key>(tx_0.vout[0].target).key)); + se.push_output(0, boost::get<cryptonote::txout_to_key>(tx_0.vout[0].target).key, se.amount); se.real_output = 0; + se.rct = false; se.real_out_tx_key = get_tx_pub_key_from_extra(tx_0); se.real_output_in_tx_index = 0; sources.push_back(se); diff --git a/tests/core_tests/integer_overflow.cpp b/tests/core_tests/integer_overflow.cpp index 55a55b7d8..936f29675 100644 --- a/tests/core_tests/integer_overflow.cpp +++ b/tests/core_tests/integer_overflow.cpp @@ -61,8 +61,9 @@ namespace { cryptonote::tx_source_entry se; se.amount = tx.vout[out_idx].amount; - se.outputs.push_back(std::make_pair(0, boost::get<cryptonote::txout_to_key>(tx.vout[out_idx].target).key)); + se.push_output(0, boost::get<cryptonote::txout_to_key>(tx.vout[out_idx].target).key, se.amount); se.real_output = 0; + se.rct = false; se.real_out_tx_key = get_tx_pub_key_from_extra(tx); se.real_output_in_tx_index = out_idx; diff --git a/tests/core_tests/rct.cpp b/tests/core_tests/rct.cpp new file mode 100644 index 000000000..c29854888 --- /dev/null +++ b/tests/core_tests/rct.cpp @@ -0,0 +1,499 @@ +// Copyright (c) 2014-2016, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#include "ringct/rctSigs.h" +#include "chaingen.h" +#include "chaingen_tests_list.h" + +using namespace epee; +using namespace crypto; +using namespace cryptonote; + +//---------------------------------------------------------------------------------------------------------------------- +// Tests + +bool gen_rct_tx_validation_base::generate_with(std::vector<test_event_entry>& events, + const int *out_idx, int mixin, uint64_t amount_paid, bool valid, + const std::function<void(std::vector<tx_source_entry> &sources, std::vector<tx_destination_entry> &destinations)> &pre_tx, + const std::function<void(transaction &tx)> &post_tx) const +{ + uint64_t ts_start = 1338224400; + + GENERATE_ACCOUNT(miner_account); + MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start); + + // create 4 miner accounts, and have them mine the next 4 blocks + cryptonote::account_base miner_accounts[4]; + const cryptonote::block *prev_block = &blk_0; + cryptonote::block blocks[4]; + for (size_t n = 0; n < 4; ++n) { + miner_accounts[n].generate(); + CHECK_AND_ASSERT_MES(generator.construct_block_manually(blocks[n], *prev_block, miner_accounts[n], + test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_hf_version, + 2, 2, prev_block->timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long + crypto::hash(), 0, transaction(), std::vector<crypto::hash>(), 0, 0, 2), + false, "Failed to generate block"); + events.push_back(blocks[n]); + prev_block = blocks + n; + LOG_PRINT_L0("Initial miner tx " << n << ": " << obj_to_json_str(blocks[n].miner_tx)); + } + + // rewind + cryptonote::block blk_r, blk_last; + { + blk_last = blocks[3]; + for (size_t i = 0; i < CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW; ++i) + { + cryptonote::block blk; + CHECK_AND_ASSERT_MES(generator.construct_block_manually(blk, blk_last, miner_account, + test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_hf_version, + 2, 2, blk_last.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long + crypto::hash(), 0, transaction(), std::vector<crypto::hash>(), 0, 0, 2), + false, "Failed to generate block"); + events.push_back(blk); + blk_last = blk; + } + blk_r = blk_last; + } + + // create 4 txes from these miners in another block, to generate some rct outputs + transaction rct_txes[4]; + rct::key rct_tx_masks[16]; + cryptonote::block blk_txes[4]; + for (size_t n = 0; n < 4; ++n) + { + std::vector<crypto::hash> starting_rct_tx_hashes; + std::vector<tx_source_entry> sources; + + sources.resize(1); + tx_source_entry& src = sources.back(); + + const size_t index_in_tx = 5; + src.amount = 30000000000000; + for (int m = 0; m < 4; ++m) { + src.push_output(m, boost::get<txout_to_key>(blocks[m].miner_tx.vout[index_in_tx].target).key, src.amount); + } + src.real_out_tx_key = cryptonote::get_tx_pub_key_from_extra(blocks[n].miner_tx); + src.real_output = n; + src.real_output_in_tx_index = index_in_tx; + src.mask = rct::identity(); + src.rct = false; + + //fill outputs entry + tx_destination_entry td; + td.addr = miner_accounts[n].get_keys().m_account_address; + td.amount = 7390000000000; + std::vector<tx_destination_entry> destinations; + destinations.push_back(td); + destinations.push_back(td); + destinations.push_back(td); + destinations.push_back(td); // 30 -> 7.39 * 4 + + crypto::secret_key tx_key; + bool r = construct_tx_and_get_tx_key(miner_accounts[n].get_keys(), sources, destinations, std::vector<uint8_t>(), rct_txes[n], 0, tx_key, true); + CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction"); + events.push_back(rct_txes[n]); + starting_rct_tx_hashes.push_back(get_transaction_hash(rct_txes[n])); + + for (size_t o = 0; o < 4; ++o) + { + crypto::key_derivation derivation; + bool r = crypto::generate_key_derivation(destinations[o].addr.m_view_public_key, tx_key, derivation); + CHECK_AND_ASSERT_MES(r, false, "Failed to generate key derivation"); + crypto::secret_key amount_key; + crypto::derivation_to_scalar(derivation, o, amount_key); + if (rct_txes[n].rct_signatures.type == rct::RCTTypeSimple) + rct::decodeRctSimple(rct_txes[n].rct_signatures, rct::sk2rct(amount_key), o, rct_tx_masks[o+n*4]); + else + rct::decodeRct(rct_txes[n].rct_signatures, rct::sk2rct(amount_key), o, rct_tx_masks[o+n*4]); + } + + CHECK_AND_ASSERT_MES(generator.construct_block_manually(blk_txes[n], blk_last, miner_account, + test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_tx_hashes | test_generator::bf_hf_version | test_generator::bf_max_outs, + 4, 4, blk_last.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long + crypto::hash(), 0, transaction(), starting_rct_tx_hashes, 0, 6, 4), + false, "Failed to generate block"); + events.push_back(blk_txes[n]); + blk_last = blk_txes[n]; + } + + // rewind + { + for (size_t i = 0; i < CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW; ++i) + { + cryptonote::block blk; + CHECK_AND_ASSERT_MES(generator.construct_block_manually(blk, blk_last, miner_account, + test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_hf_version | test_generator::bf_max_outs, + 4, 4, blk_last.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long + crypto::hash(), 0, transaction(), std::vector<crypto::hash>(), 0, 6, 4), + false, "Failed to generate block"); + events.push_back(blk); + blk_last = blk; + } + blk_r = blk_last; + } + + // create a tx from the requested ouputs + std::vector<tx_source_entry> sources; + size_t global_rct_idx = 6; // skip first coinbase (6 outputs) + size_t rct_idx = 0; + size_t pre_rct_idx = 0; + for (size_t out_idx_idx = 0; out_idx[out_idx_idx] >= 0; ++out_idx_idx) { + sources.resize(sources.size()+1); + tx_source_entry& src = sources.back(); + + src.real_output = 0; + if (out_idx[out_idx_idx]) { + // rct + src.amount = 7390000000000; + src.real_out_tx_key = get_tx_pub_key_from_extra(rct_txes[rct_idx/4]); + src.real_output_in_tx_index = rct_idx&3; + src.mask = rct_tx_masks[rct_idx]; + src.rct = true; + for (int m = 0; m <= mixin; ++m) { + rct::ctkey ctkey; + ctkey.dest = rct::pk2rct(boost::get<txout_to_key>(rct_txes[rct_idx/4].vout[rct_idx&3].target).key); + ctkey.mask = rct_txes[rct_idx/4].rct_signatures.outPk[rct_idx&3].mask; + src.outputs.push_back(std::make_pair(global_rct_idx, ctkey)); + ++rct_idx; + ++global_rct_idx; + if (global_rct_idx % 10 == 0) + global_rct_idx += 6; // skip the coinbase + } + } + else + { + // pre rct + src.amount = 5000000000000; + src.real_out_tx_key = cryptonote::get_tx_pub_key_from_extra(blocks[pre_rct_idx].miner_tx); + src.real_output_in_tx_index = 4; + src.mask = rct::identity(); + src.rct = false; + for (int m = 0; m <= mixin; ++m) { + src.push_output(m, boost::get<txout_to_key>(blocks[pre_rct_idx].miner_tx.vout[4].target).key, src.amount); + ++pre_rct_idx; + } + } + } + + //fill outputs entry + tx_destination_entry td; + td.addr = miner_account.get_keys().m_account_address; + td.amount = amount_paid; + std::vector<tx_destination_entry> destinations; + destinations.push_back(td); + + if (pre_tx) + pre_tx(sources, destinations); + + transaction tx; + crypto::secret_key tx_key; + bool r = construct_tx_and_get_tx_key(miner_accounts[0].get_keys(), sources, destinations, std::vector<uint8_t>(), tx, 0, tx_key, true); + CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction"); + + if (post_tx) + post_tx(tx); + + if (!valid) + DO_CALLBACK(events, "mark_invalid_tx"); + events.push_back(tx); + LOG_PRINT_L0("Test tx: " << obj_to_json_str(tx)); + + return true; +} + +bool gen_rct_tx_valid_from_pre_rct::generate(std::vector<test_event_entry>& events) const +{ + const int mixin = 2; + const int out_idx[] = {0, -1}; + const uint64_t amount_paid = 10000; + return generate_with(events, out_idx, mixin, amount_paid, true, NULL, NULL); +} + +bool gen_rct_tx_valid_from_rct::generate(std::vector<test_event_entry>& events) const +{ + const int mixin = 2; + const int out_idx[] = {1, -1}; + const uint64_t amount_paid = 10000; + return generate_with(events, out_idx, mixin, amount_paid, true, NULL, NULL); +} + +bool gen_rct_tx_valid_from_mixed::generate(std::vector<test_event_entry>& events) const +{ + const int mixin = 2; + const int out_idx[] = {1, 0, -1}; + const uint64_t amount_paid = 10000; + return generate_with(events, out_idx, mixin, amount_paid, true, NULL, NULL); +} + +bool gen_rct_tx_pre_rct_bad_real_dest::generate(std::vector<test_event_entry>& events) const +{ + const int mixin = 2; + const int out_idx[] = {0, -1}; + const uint64_t amount_paid = 10000; + bool tx_creation_succeeded = false; + // in the case, the tx will fail to create, due to mismatched sk/pk + bool ret = generate_with(events, out_idx, mixin, amount_paid, false, + [](std::vector<tx_source_entry> &sources, std::vector<tx_destination_entry> &destinations) {rct::key sk; rct::skpkGen(sk, sources[0].outputs[0].second.dest);}, + [&tx_creation_succeeded](const transaction &tx){tx_creation_succeeded=true;}); + return !ret && !tx_creation_succeeded; +} + +bool gen_rct_tx_pre_rct_bad_real_mask::generate(std::vector<test_event_entry>& events) const +{ + const int mixin = 2; + const int out_idx[] = {0, -1}; + const uint64_t amount_paid = 10000; + return generate_with(events, out_idx, mixin, amount_paid, false, + [](std::vector<tx_source_entry> &sources, std::vector<tx_destination_entry> &destinations) {sources[0].outputs[0].second.mask = rct::zeroCommit(99999);}, + NULL); +} + +bool gen_rct_tx_pre_rct_bad_fake_dest::generate(std::vector<test_event_entry>& events) const +{ + const int mixin = 2; + const int out_idx[] = {0, -1}; + const uint64_t amount_paid = 10000; + return generate_with(events, out_idx, mixin, amount_paid, false, + [](std::vector<tx_source_entry> &sources, std::vector<tx_destination_entry> &destinations) {rct::key sk; rct::skpkGen(sk, sources[0].outputs[1].second.dest);}, + NULL); +} + +bool gen_rct_tx_pre_rct_bad_fake_mask::generate(std::vector<test_event_entry>& events) const +{ + const int mixin = 2; + const int out_idx[] = {0, -1}; + const uint64_t amount_paid = 10000; + return generate_with(events, out_idx, mixin, amount_paid, false, + [](std::vector<tx_source_entry> &sources, std::vector<tx_destination_entry> &destinations) {sources[0].outputs[1].second.mask = rct::zeroCommit(99999);}, + NULL); +} + +bool gen_rct_tx_rct_bad_real_dest::generate(std::vector<test_event_entry>& events) const +{ + const int mixin = 2; + const int out_idx[] = {1, -1}; + const uint64_t amount_paid = 10000; + bool tx_creation_succeeded = false; + // in the case, the tx will fail to create, due to mismatched sk/pk + bool ret = generate_with(events, out_idx, mixin, amount_paid, false, + [](std::vector<tx_source_entry> &sources, std::vector<tx_destination_entry> &destinations) {rct::key sk; rct::skpkGen(sk, sources[0].outputs[0].second.dest);}, + [&tx_creation_succeeded](const transaction &tx){tx_creation_succeeded=true;}); + return !ret && !tx_creation_succeeded; +} + +bool gen_rct_tx_rct_bad_real_mask::generate(std::vector<test_event_entry>& events) const +{ + const int mixin = 2; + const int out_idx[] = {1, -1}; + const uint64_t amount_paid = 10000; + return generate_with(events, out_idx, mixin, amount_paid, false, + [](std::vector<tx_source_entry> &sources, std::vector<tx_destination_entry> &destinations) {sources[0].outputs[0].second.mask = rct::zeroCommit(99999);}, + NULL); +} + +bool gen_rct_tx_rct_bad_fake_dest::generate(std::vector<test_event_entry>& events) const +{ + const int mixin = 2; + const int out_idx[] = {1, -1}; + const uint64_t amount_paid = 10000; + return generate_with(events, out_idx, mixin, amount_paid, false, + [](std::vector<tx_source_entry> &sources, std::vector<tx_destination_entry> &destinations) {rct::key sk; rct::skpkGen(sk, sources[0].outputs[1].second.dest);}, + NULL); +} + +bool gen_rct_tx_rct_bad_fake_mask::generate(std::vector<test_event_entry>& events) const +{ + const int mixin = 2; + const int out_idx[] = {1, -1}; + const uint64_t amount_paid = 10000; + return generate_with(events, out_idx, mixin, amount_paid, false, + [](std::vector<tx_source_entry> &sources, std::vector<tx_destination_entry> &destinations) {sources[0].outputs[1].second.mask = rct::zeroCommit(99999);}, + NULL); +} + +bool gen_rct_tx_rct_spend_with_zero_commit::generate(std::vector<test_event_entry>& events) const +{ + const int mixin = 2; + const int out_idx[] = {1, -1}; + const uint64_t amount_paid = 10000; + return generate_with(events, out_idx, mixin, amount_paid, false, + [](std::vector<tx_source_entry> &sources, std::vector<tx_destination_entry> &destinations) {sources[0].outputs[0].second.mask = rct::zeroCommit(sources[0].amount); sources[0].mask = rct::identity();}, + [](transaction &tx){boost::get<txin_to_key>(tx.vin[0]).amount = 0;}); +} + +bool gen_rct_tx_pre_rct_zero_vin_amount::generate(std::vector<test_event_entry>& events) const +{ + const int mixin = 2; + const int out_idx[] = {0, -1}; + const uint64_t amount_paid = 10000; + return generate_with(events, out_idx, mixin, amount_paid, false, + NULL, [](transaction &tx) {boost::get<txin_to_key>(tx.vin[0]).amount = 0;}); +} + +bool gen_rct_tx_rct_non_zero_vin_amount::generate(std::vector<test_event_entry>& events) const +{ + const int mixin = 2; + const int out_idx[] = {1, -1}; + const uint64_t amount_paid = 10000; + return generate_with(events, out_idx, mixin, amount_paid, false, + NULL, [](transaction &tx) {boost::get<txin_to_key>(tx.vin[0]).amount = 5000000000000;}); // one that we know exists +} + +bool gen_rct_tx_non_zero_vout_amount::generate(std::vector<test_event_entry>& events) const +{ + const int mixin = 2; + const int out_idx[] = {1, -1}; + const uint64_t amount_paid = 10000; + return generate_with(events, out_idx, mixin, amount_paid, false, + NULL, [](transaction &tx) {tx.vout[0].amount = 5000000000000;}); // one that we know exists +} + +bool gen_rct_tx_pre_rct_duplicate_key_image::generate(std::vector<test_event_entry>& events) const +{ + const int mixin = 2; + const int out_idx[] = {0, -1}; + const uint64_t amount_paid = 10000; + return generate_with(events, out_idx, mixin, amount_paid, false, + NULL, [&events](transaction &tx) {boost::get<txin_to_key>(tx.vin[0]).k_image = boost::get<txin_to_key>(boost::get<transaction>(events[67]).vin[0]).k_image;}); +} + +bool gen_rct_tx_rct_duplicate_key_image::generate(std::vector<test_event_entry>& events) const +{ + const int mixin = 2; + const int out_idx[] = {1, -1}; + const uint64_t amount_paid = 10000; + return generate_with(events, out_idx, mixin, amount_paid, false, + NULL, [&events](transaction &tx) {boost::get<txin_to_key>(tx.vin[0]).k_image = boost::get<txin_to_key>(boost::get<transaction>(events[67]).vin[0]).k_image;}); +} + +bool gen_rct_tx_pre_rct_wrong_key_image::generate(std::vector<test_event_entry>& events) const +{ + const int mixin = 2; + const int out_idx[] = {0, -1}; + const uint64_t amount_paid = 10000; + // some random key image from the monero blockchain, so we get something that is a valid key image + static const uint8_t k_image[33] = "\x49\x3b\x56\x16\x54\x76\xa8\x75\xb7\xf4\xa8\x51\xf5\x55\xd3\x44\xe7\x3e\xea\x73\xee\xc1\x06\x7c\x7d\xb6\x57\x28\x46\x85\xe1\x07"; + return generate_with(events, out_idx, mixin, amount_paid, false, + NULL, [](transaction &tx) {memcpy(&boost::get<txin_to_key>(tx.vin[0]).k_image, k_image, 32);}); +} + +bool gen_rct_tx_rct_wrong_key_image::generate(std::vector<test_event_entry>& events) const +{ + const int mixin = 2; + const int out_idx[] = {1, -1}; + const uint64_t amount_paid = 10000; + // some random key image from the monero blockchain, so we get something that is a valid key image + static const uint8_t k_image[33] = "\x49\x3b\x56\x16\x54\x76\xa8\x75\xb7\xf4\xa8\x51\xf5\x55\xd3\x44\xe7\x3e\xea\x73\xee\xc1\x06\x7c\x7d\xb6\x57\x28\x46\x85\xe1\x07"; + return generate_with(events, out_idx, mixin, amount_paid, false, + NULL, [](transaction &tx) {memcpy(&boost::get<txin_to_key>(tx.vin[0]).k_image, k_image, 32);}); +} + +bool gen_rct_tx_pre_rct_wrong_fee::generate(std::vector<test_event_entry>& events) const +{ + const int mixin = 2; + const int out_idx[] = {0, -1}; + const uint64_t amount_paid = 10000; + return generate_with(events, out_idx, mixin, amount_paid, false, + NULL, [](transaction &tx) {tx.rct_signatures.txnFee++;}); +} + +bool gen_rct_tx_rct_wrong_fee::generate(std::vector<test_event_entry>& events) const +{ + const int mixin = 2; + const int out_idx[] = {1, -1}; + const uint64_t amount_paid = 10000; + return generate_with(events, out_idx, mixin, amount_paid, false, + NULL, [](transaction &tx) {tx.rct_signatures.txnFee++;}); +} + +bool gen_rct_tx_pre_rct_increase_vin_and_fee::generate(std::vector<test_event_entry>& events) const +{ + const int mixin = 2; + const int out_idx[] = {0, -1}; + const uint64_t amount_paid = 10000; + return generate_with(events, out_idx, mixin, amount_paid, false, + NULL, [](transaction &tx) {boost::get<txin_to_key>(tx.vin[0]).amount++;tx.rct_signatures.txnFee++;}); +} + +bool gen_rct_tx_pre_rct_remove_vin::generate(std::vector<test_event_entry>& events) const +{ + const int mixin = 2; + const int out_idx[] = {0, -1}; + const uint64_t amount_paid = 10000; + return generate_with(events, out_idx, mixin, amount_paid, false, + NULL, [](transaction &tx) {tx.vin.pop_back();}); +} + +bool gen_rct_tx_rct_remove_vin::generate(std::vector<test_event_entry>& events) const +{ + const int mixin = 2; + const int out_idx[] = {1, -1}; + const uint64_t amount_paid = 10000; + return generate_with(events, out_idx, mixin, amount_paid, false, + NULL, [](transaction &tx) {tx.vin.pop_back();}); +} + +bool gen_rct_tx_pre_rct_add_vout::generate(std::vector<test_event_entry>& events) const +{ + const int mixin = 2; + const int out_idx[] = {0, -1}; + const uint64_t amount_paid = 10000; + return generate_with(events, out_idx, mixin, amount_paid, false, + NULL, [](transaction &tx) {tx.vout.push_back(tx.vout.back());}); +} + +bool gen_rct_tx_rct_add_vout::generate(std::vector<test_event_entry>& events) const +{ + const int mixin = 2; + const int out_idx[] = {1, -1}; + const uint64_t amount_paid = 10000; + return generate_with(events, out_idx, mixin, amount_paid, false, + NULL, [](transaction &tx) {tx.vout.push_back(tx.vout.back());}); +} + +bool gen_rct_tx_pre_rct_altered_extra::generate(std::vector<test_event_entry>& events) const +{ + const int mixin = 2; + const int out_idx[] = {0, -1}; + const uint64_t amount_paid = 10000; + return generate_with(events, out_idx, mixin, amount_paid, false, + NULL, [](transaction &tx) {std::string extra_nonce; crypto::hash pid = cryptonote::null_hash; set_payment_id_to_tx_extra_nonce(extra_nonce, pid); add_extra_nonce_to_tx_extra(tx.extra, extra_nonce);}); +} + +bool gen_rct_tx_rct_altered_extra::generate(std::vector<test_event_entry>& events) const +{ + const int mixin = 2; + const int out_idx[] = {1, -1}; + const uint64_t amount_paid = 10000; + return generate_with(events, out_idx, mixin, amount_paid, false, + NULL, [](transaction &tx) {std::string extra_nonce; crypto::hash pid = cryptonote::null_hash; set_payment_id_to_tx_extra_nonce(extra_nonce, pid); add_extra_nonce_to_tx_extra(tx.extra, extra_nonce);}); +} + diff --git a/tests/core_tests/rct.h b/tests/core_tests/rct.h new file mode 100644 index 000000000..f16e665f9 --- /dev/null +++ b/tests/core_tests/rct.h @@ -0,0 +1,264 @@ +// Copyright (c) 2014-2016, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once +#include "chaingen.h" + +struct gen_rct_tx_validation_base : public test_chain_unit_base +{ + gen_rct_tx_validation_base() + : m_invalid_tx_index(0) + , m_invalid_block_index(0) + { + REGISTER_CALLBACK_METHOD(gen_rct_tx_validation_base, mark_invalid_tx); + REGISTER_CALLBACK_METHOD(gen_rct_tx_validation_base, mark_invalid_block); + } + + bool check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t event_idx, const cryptonote::transaction& /*tx*/) + { + if (m_invalid_tx_index == event_idx) + return tvc.m_verifivation_failed; + else + return !tvc.m_verifivation_failed && tx_added; + } + + bool check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::block& /*block*/) + { + if (m_invalid_block_index == event_idx) + return bvc.m_verifivation_failed; + else + return !bvc.m_verifivation_failed; + } + + bool mark_invalid_block(cryptonote::core& /*c*/, size_t ev_index, const std::vector<test_event_entry>& /*events*/) + { + m_invalid_block_index = ev_index + 1; + return true; + } + + bool mark_invalid_tx(cryptonote::core& /*c*/, size_t ev_index, const std::vector<test_event_entry>& /*events*/) + { + m_invalid_tx_index = ev_index + 1; + return true; + } + + bool generate_with(std::vector<test_event_entry>& events, const int *out_idx, int mixin, + uint64_t amount_paid, bool valid, + const std::function<void(std::vector<cryptonote::tx_source_entry> &sources, std::vector<cryptonote::tx_destination_entry> &destinations)> &pre_tx, + const std::function<void(cryptonote::transaction &tx)> &post_tx) const; + +private: + size_t m_invalid_tx_index; + size_t m_invalid_block_index; +}; + +template<> +struct get_test_options<gen_rct_tx_validation_base> { + const std::pair<uint8_t, uint64_t> hard_forks[4] = {std::make_pair(1, 0), std::make_pair(2, 1), std::make_pair(4, 65), std::make_pair(0, 0)}; + const cryptonote::test_options test_options = { + hard_forks + }; +}; + +// valid +struct gen_rct_tx_valid_from_pre_rct : public gen_rct_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_rct_tx_valid_from_pre_rct>: public get_test_options<gen_rct_tx_validation_base> {}; + +struct gen_rct_tx_valid_from_rct : public gen_rct_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_rct_tx_valid_from_rct>: public get_test_options<gen_rct_tx_validation_base> {}; + +struct gen_rct_tx_valid_from_mixed : public gen_rct_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_rct_tx_valid_from_mixed>: public get_test_options<gen_rct_tx_validation_base> {}; + +// altered commitment/dest +struct gen_rct_tx_pre_rct_bad_real_dest : public gen_rct_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_rct_tx_pre_rct_bad_real_dest>: public get_test_options<gen_rct_tx_validation_base> {}; + +struct gen_rct_tx_pre_rct_bad_real_mask : public gen_rct_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_rct_tx_pre_rct_bad_real_mask>: public get_test_options<gen_rct_tx_validation_base> {}; + +struct gen_rct_tx_pre_rct_bad_fake_dest : public gen_rct_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_rct_tx_pre_rct_bad_fake_dest>: public get_test_options<gen_rct_tx_validation_base> {}; + +struct gen_rct_tx_pre_rct_bad_fake_mask : public gen_rct_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_rct_tx_pre_rct_bad_fake_mask>: public get_test_options<gen_rct_tx_validation_base> {}; + +struct gen_rct_tx_rct_bad_real_dest : public gen_rct_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_rct_tx_rct_bad_real_dest>: public get_test_options<gen_rct_tx_validation_base> {}; + +struct gen_rct_tx_rct_bad_real_mask : public gen_rct_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_rct_tx_rct_bad_real_mask>: public get_test_options<gen_rct_tx_validation_base> {}; + +struct gen_rct_tx_rct_bad_fake_dest : public gen_rct_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_rct_tx_rct_bad_fake_dest>: public get_test_options<gen_rct_tx_validation_base> {}; + +struct gen_rct_tx_rct_bad_fake_mask : public gen_rct_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_rct_tx_rct_bad_fake_mask>: public get_test_options<gen_rct_tx_validation_base> {}; + +struct gen_rct_tx_rct_spend_with_zero_commit : public gen_rct_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_rct_tx_rct_spend_with_zero_commit>: public get_test_options<gen_rct_tx_validation_base> {}; + +// altered amounts +struct gen_rct_tx_pre_rct_zero_vin_amount : public gen_rct_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_rct_tx_pre_rct_zero_vin_amount>: public get_test_options<gen_rct_tx_validation_base> {}; + +struct gen_rct_tx_rct_non_zero_vin_amount : public gen_rct_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_rct_tx_rct_non_zero_vin_amount>: public get_test_options<gen_rct_tx_validation_base> {}; + +struct gen_rct_tx_non_zero_vout_amount : public gen_rct_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_rct_tx_non_zero_vout_amount>: public get_test_options<gen_rct_tx_validation_base> {}; + +// key image +struct gen_rct_tx_pre_rct_duplicate_key_image : public gen_rct_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_rct_tx_pre_rct_duplicate_key_image>: public get_test_options<gen_rct_tx_validation_base> {}; + +struct gen_rct_tx_rct_duplicate_key_image : public gen_rct_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_rct_tx_rct_duplicate_key_image>: public get_test_options<gen_rct_tx_validation_base> {}; + +struct gen_rct_tx_pre_rct_wrong_key_image : public gen_rct_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_rct_tx_pre_rct_wrong_key_image>: public get_test_options<gen_rct_tx_validation_base> {}; + +struct gen_rct_tx_rct_wrong_key_image : public gen_rct_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_rct_tx_rct_wrong_key_image>: public get_test_options<gen_rct_tx_validation_base> {}; + +// fee +struct gen_rct_tx_pre_rct_wrong_fee : public gen_rct_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_rct_tx_pre_rct_wrong_fee>: public get_test_options<gen_rct_tx_validation_base> {}; + +struct gen_rct_tx_rct_wrong_fee : public gen_rct_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_rct_tx_rct_wrong_fee>: public get_test_options<gen_rct_tx_validation_base> {}; + +struct gen_rct_tx_pre_rct_increase_vin_and_fee : public gen_rct_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_rct_tx_pre_rct_increase_vin_and_fee>: public get_test_options<gen_rct_tx_validation_base> {}; + +// modify vin/vout +struct gen_rct_tx_pre_rct_remove_vin : public gen_rct_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_rct_tx_pre_rct_remove_vin>: public get_test_options<gen_rct_tx_validation_base> {}; + +struct gen_rct_tx_rct_remove_vin : public gen_rct_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_rct_tx_rct_remove_vin>: public get_test_options<gen_rct_tx_validation_base> {}; + +struct gen_rct_tx_pre_rct_add_vout : public gen_rct_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_rct_tx_pre_rct_add_vout>: public get_test_options<gen_rct_tx_validation_base> {}; + +struct gen_rct_tx_rct_add_vout : public gen_rct_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_rct_tx_rct_add_vout>: public get_test_options<gen_rct_tx_validation_base> {}; + +// extra +struct gen_rct_tx_pre_rct_altered_extra : public gen_rct_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_rct_tx_pre_rct_altered_extra>: public get_test_options<gen_rct_tx_validation_base> {}; + +struct gen_rct_tx_rct_altered_extra : public gen_rct_tx_validation_base +{ + bool generate(std::vector<test_event_entry>& events) const; +}; +template<> struct get_test_options<gen_rct_tx_rct_altered_extra>: public get_test_options<gen_rct_tx_validation_base> {}; + diff --git a/tests/core_tests/transaction_tests.cpp b/tests/core_tests/transaction_tests.cpp index 5c866b618..cb585b975 100644 --- a/tests/core_tests/transaction_tests.cpp +++ b/tests/core_tests/transaction_tests.cpp @@ -82,32 +82,22 @@ bool test_transaction_generation_and_ring_signature() src.amount = 70368744177663; { tx_output_entry oe; - oe.first = 0; - oe.second = boost::get<txout_to_key>(tx_mine_1.vout[0].target).key; - src.outputs.push_back(oe); - oe.first = 1; - oe.second = boost::get<txout_to_key>(tx_mine_2.vout[0].target).key; - src.outputs.push_back(oe); + src.push_output(0, boost::get<txout_to_key>(tx_mine_1.vout[0].target).key, src.amount); - oe.first = 2; - oe.second = boost::get<txout_to_key>(tx_mine_3.vout[0].target).key; - src.outputs.push_back(oe); + src.push_output(1, boost::get<txout_to_key>(tx_mine_2.vout[0].target).key, src.amount); - oe.first = 3; - oe.second = boost::get<txout_to_key>(tx_mine_4.vout[0].target).key; - src.outputs.push_back(oe); + src.push_output(2, boost::get<txout_to_key>(tx_mine_3.vout[0].target).key, src.amount); - oe.first = 4; - oe.second = boost::get<txout_to_key>(tx_mine_5.vout[0].target).key; - src.outputs.push_back(oe); + src.push_output(3, boost::get<txout_to_key>(tx_mine_4.vout[0].target).key, src.amount); - oe.first = 5; - oe.second = boost::get<txout_to_key>(tx_mine_6.vout[0].target).key; - src.outputs.push_back(oe); + src.push_output(4, boost::get<txout_to_key>(tx_mine_5.vout[0].target).key, src.amount); + + src.push_output(5, boost::get<txout_to_key>(tx_mine_6.vout[0].target).key, src.amount); src.real_out_tx_key = cryptonote::get_tx_pub_key_from_extra(tx_mine_2); src.real_output = 1; + src.rct = false; src.real_output_in_tx_index = 0; } //fill outputs entry diff --git a/tests/core_tests/tx_validation.cpp b/tests/core_tests/tx_validation.cpp index f72c906e5..cf018c8e2 100644 --- a/tests/core_tests/tx_validation.cpp +++ b/tests/core_tests/tx_validation.cpp @@ -39,7 +39,7 @@ namespace { struct tx_builder { - void step1_init(size_t version = CURRENT_TRANSACTION_VERSION, uint64_t unlock_time = 0) + void step1_init(size_t version = 1, uint64_t unlock_time = 0) { m_tx.vin.clear(); m_tx.vout.clear(); @@ -108,9 +108,13 @@ namespace BOOST_FOREACH(const tx_source_entry& src_entr, sources) { std::vector<const crypto::public_key*> keys_ptrs; + std::vector<crypto::public_key> keys(src_entr.outputs.size()); + size_t j = 0; BOOST_FOREACH(const tx_source_entry::output_entry& o, src_entr.outputs) { - keys_ptrs.push_back(&o.second); + keys[j] = rct::rct2pk(o.second.dest); + keys_ptrs.push_back(&keys[j]); + ++j; } m_tx.signatures.push_back(std::vector<crypto::signature>()); @@ -136,7 +140,7 @@ namespace fill_tx_sources_and_destinations(events, blk_head, from, to, amount, TESTS_DEFAULT_FEE, 0, sources, destinations); tx_builder builder; - builder.step1_init(CURRENT_TRANSACTION_VERSION, unlock_time); + builder.step1_init(1, unlock_time); builder.step2_fill_inputs(from.get_keys(), sources); builder.step3_fill_outputs(destinations); builder.step4_calc_hash(); @@ -177,7 +181,7 @@ bool gen_tx_big_version::generate(std::vector<test_event_entry>& events) const fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); tx_builder builder; - builder.step1_init(CURRENT_TRANSACTION_VERSION + 1, 0); + builder.step1_init(1 + 1, 0); builder.step2_fill_inputs(miner_account.get_keys(), sources); builder.step3_fill_outputs(destinations); builder.step4_calc_hash(); diff --git a/tests/core_tests/v2_tests.cpp b/tests/core_tests/v2_tests.cpp index fe6b8b279..93ddd8a12 100644 --- a/tests/core_tests/v2_tests.cpp +++ b/tests/core_tests/v2_tests.cpp @@ -79,7 +79,6 @@ bool gen_v2_tx_validation_base::generate_with(std::vector<test_event_entry>& eve } // create a tx with the Nth outputs of miner's block reward - typedef tx_source_entry::output_entry tx_output_entry; std::vector<tx_source_entry> sources; for (size_t out_idx_idx = 0; out_idx[out_idx_idx] >= 0; ++out_idx_idx) { sources.resize(sources.size()+1); @@ -88,16 +87,16 @@ bool gen_v2_tx_validation_base::generate_with(std::vector<test_event_entry>& eve src.amount = blocks[0].miner_tx.vout[out_idx[out_idx_idx]].amount; std::cout << "using " << print_money(src.amount) << " output at index " << out_idx[out_idx_idx] << std::endl; for (int m = 0; m <= mixin; ++m) { - tx_output_entry oe; + int idx; if (is_valid_decomposed_amount(src.amount)) - oe.first = m+1; // one out of that size per miner tx, including genesis + idx = m+1; // one out of that size per miner tx, including genesis else - oe.first = 0; // dusty, no other output of that size - oe.second = boost::get<txout_to_key>(blocks[m].miner_tx.vout[out_idx[out_idx_idx]].target).key; - src.outputs.push_back(oe); + idx = 0; // dusty, no other output of that size + src.push_output(idx, boost::get<txout_to_key>(blocks[m].miner_tx.vout[out_idx[out_idx_idx]].target).key, src.amount); } src.real_out_tx_key = cryptonote::get_tx_pub_key_from_extra(blocks[0].miner_tx); src.real_output = 0; + src.rct = false; src.real_output_in_tx_index = out_idx[out_idx_idx]; } diff --git a/tests/core_tests/v2_tests.h b/tests/core_tests/v2_tests.h index 10049ec95..fbc2b5295 100644 --- a/tests/core_tests/v2_tests.h +++ b/tests/core_tests/v2_tests.h @@ -79,7 +79,7 @@ private: template<> struct get_test_options<gen_v2_tx_validation_base> { - const std::pair<uint8_t, uint64_t> hard_forks[2] = {std::make_pair(1, 0), std::make_pair(2, 1)}; + const std::pair<uint8_t, uint64_t> hard_forks[3] = {std::make_pair(1, 0), std::make_pair(2, 1), std::make_pair(0, 0)}; const cryptonote::test_options test_options = { hard_forks }; diff --git a/tests/functional_tests/transactions_flow_test.cpp b/tests/functional_tests/transactions_flow_test.cpp index 6bf910101..585328348 100644 --- a/tests/functional_tests/transactions_flow_test.cpp +++ b/tests/functional_tests/transactions_flow_test.cpp @@ -281,7 +281,7 @@ bool transactions_flow_test(std::string& working_folder, w2.get_transfers(tc); BOOST_FOREACH(tools::wallet2::transfer_details& td, tc) { - auto it = txs.find(get_transaction_hash(td.m_tx)); + auto it = txs.find(td.m_txid); CHECK_AND_ASSERT_MES(it != txs.end(), false, "transaction not found in local cache"); it->second.m_received_count += 1; } diff --git a/tests/functional_tests/transactions_generation_from_blockchain.cpp b/tests/functional_tests/transactions_generation_from_blockchain.cpp index c076776c4..63ff0343b 100644 --- a/tests/functional_tests/transactions_generation_from_blockchain.cpp +++ b/tests/functional_tests/transactions_generation_from_blockchain.cpp @@ -31,7 +31,6 @@ #include "include_base_utils.h" using namespace epee; #include "wallet/wallet2.h" -#include "cryptonote_core/blockchain_storage.h" using namespace cryptonote; @@ -114,6 +113,7 @@ bool make_tx(blockchain_storage& bch) src.real_out_tx_key = td.m_tx.tx_pub_key; src.real_output = interted_it - src.outputs.begin(); src.real_output_in_tx_index = td.m_internal_output_index; + src.rct = false; ++i; } diff --git a/tests/performance_tests/multi_tx_test_base.h b/tests/performance_tests/multi_tx_test_base.h index c28e8cad4..d8898b60d 100644 --- a/tests/performance_tests/multi_tx_test_base.h +++ b/tests/performance_tests/multi_tx_test_base.h @@ -59,7 +59,7 @@ public: return false; txout_to_key tx_out = boost::get<txout_to_key>(m_miner_txs[i].vout[0].target); - output_entries.push_back(std::make_pair(i, tx_out.key)); + output_entries.push_back(std::make_pair(i, rct::ctkey({rct::pk2rct(tx_out.key), rct::identity()}))); m_public_keys[i] = tx_out.key; m_public_key_ptrs[i] = &m_public_keys[i]; } @@ -72,6 +72,7 @@ public: source_entry.real_output_in_tx_index = 0; source_entry.outputs.swap(output_entries); source_entry.real_output = real_source_idx; + source_entry.rct = false; m_sources.push_back(source_entry); diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt index 1b272cf5a..3d42809e3 100644 --- a/tests/unit_tests/CMakeLists.txt +++ b/tests/unit_tests/CMakeLists.txt @@ -51,7 +51,9 @@ set(unit_tests_sources test_protocol_pack.cpp hardfork.cpp unbound.cpp - varint.cpp) + varint.cpp + ringct.cpp + output_selection.cpp) set(unit_tests_headers unit_tests_utils.h) @@ -61,6 +63,7 @@ add_executable(unit_tests ${unit_tests_headers}) target_link_libraries(unit_tests LINK_PRIVATE + ringct cryptonote_core blockchain_db rpc diff --git a/tests/unit_tests/hardfork.cpp b/tests/unit_tests/hardfork.cpp index eab6b9cf8..a279b6c68 100644 --- a/tests/unit_tests/hardfork.cpp +++ b/tests/unit_tests/hardfork.cpp @@ -96,7 +96,7 @@ public: virtual void remove_block() { blocks.pop_back(); } virtual uint64_t add_transaction_data(const crypto::hash& blk_hash, const transaction& tx, const crypto::hash& tx_hash) {return 0;} virtual void remove_transaction_data(const crypto::hash& tx_hash, const transaction& tx) {} - virtual uint64_t add_output(const crypto::hash& tx_hash, const tx_out& tx_output, const uint64_t& local_index, const uint64_t unlock_time) {return 0;} + virtual uint64_t add_output(const crypto::hash& tx_hash, const tx_out& tx_output, const uint64_t& local_index, const uint64_t unlock_time, const rct::key *commitment) {return 0;} virtual void add_tx_amount_output_indices(const uint64_t tx_index, const std::vector<uint64_t>& amount_output_indices) {} virtual void add_spent_key(const crypto::key_image& k_image) {} virtual void remove_spent_key(const crypto::key_image& k_image) {} diff --git a/tests/unit_tests/output_selection.cpp b/tests/unit_tests/output_selection.cpp new file mode 100644 index 000000000..4344d1ffc --- /dev/null +++ b/tests/unit_tests/output_selection.cpp @@ -0,0 +1,103 @@ +// Copyright (c) 2014-2016, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// FIXME: move this into a full wallet2 unit test suite, if possible + +#include "gtest/gtest.h" + +#include "wallet/wallet2.h" +#include <string> + +static tools::wallet2::transfer_container make_transfers_container(size_t N) +{ + tools::wallet2::transfer_container transfers; + for (size_t n = 0; n < N; ++n) + { + transfers.push_back(AUTO_VAL_INIT(tools::wallet2::transfer_details())); + tools::wallet2::transfer_details &td = transfers.back(); + td.m_block_height = 1000; + td.m_spent = false; + td.m_txid = cryptonote::null_hash; + td.m_txid.data[0] = n & 0xff; + td.m_txid.data[1] = (n >> 8) & 0xff; + td.m_txid.data[2] = (n >> 16) & 0xff; + td.m_txid.data[3] = (n >> 24) & 0xff; + } + return transfers; +} + +#define SELECT(idx) \ + do { \ + auto i = std::find(unused_indices.begin(), unused_indices.end(), idx); \ + ASSERT_TRUE(i != unused_indices.end()); \ + unused_indices.erase(i); \ + selected.push_back(transfers.begin() + idx); \ + } while(0) + +#define PICK(expected) \ + do { \ + size_t idx = w.pop_best_value_from(transfers, unused_indices, selected); \ + ASSERT_EQ(expected, idx); \ + selected.push_back(transfers.begin() + idx); \ + } while(0) + +TEST(select_outputs, one_out_of_N) +{ + tools::wallet2 w; + + // check that if there are N-1 outputs of the same height, one of them + // already selected, the next one selected is the one that's from a + // different height + tools::wallet2::transfer_container transfers = make_transfers_container(10); + transfers[6].m_block_height = 700; + std::vector<size_t> unused_indices({0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); + std::list<tools::wallet2::transfer_container::iterator> selected; + SELECT(2); + PICK(6); +} + +TEST(select_outputs, order) +{ + tools::wallet2 w; + + // check that most unrelated heights are picked in order + tools::wallet2::transfer_container transfers = make_transfers_container(5); + transfers[0].m_block_height = 700; + transfers[1].m_block_height = 700; + transfers[2].m_block_height = 704; + transfers[3].m_block_height = 716; + transfers[4].m_block_height = 701; + std::vector<size_t> unused_indices({0, 1, 2, 3, 4}); + std::list<tools::wallet2::transfer_container::iterator> selected; + SELECT(0); + PICK(3); // first the one that's far away + PICK(2); // then the one that's close + PICK(4); // then the one that's adjacent + PICK(1); // then the one that's on the same height +} + diff --git a/tests/unit_tests/ringct.cpp b/tests/unit_tests/ringct.cpp new file mode 100644 index 000000000..224e32e61 --- /dev/null +++ b/tests/unit_tests/ringct.cpp @@ -0,0 +1,1070 @@ +// Copyright (c) 2014-2016, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#include "gtest/gtest.h" + +#include <cstdint> +#include <algorithm> + +#include "ringct/rctTypes.h" +#include "ringct/rctSigs.h" +#include "ringct/rctOps.h" + +using namespace crypto; +using namespace rct; + +TEST(ringct, SNL) +{ + key x, P1; + skpkGen(x, P1); + + key P2 = pkGen(); + key P3 = pkGen(); + + key L1, s1, s2; + GenSchnorrNonLinkable(L1, s1, s2, x, P1, P2, 0); + + // a valid one + // an invalid one + ASSERT_TRUE(VerSchnorrNonLinkable(P1, P2, L1, s1, s2)); + ASSERT_FALSE(VerSchnorrNonLinkable(P1, P3, L1, s1, s2)); +} + +TEST(ringct, ASNL) +{ + int j = 0; + + //Tests for ASNL + //#ASNL true one, false one, C != sum Ci, and one out of the range.. + int N = 64; + key64 xv; + key64 P1v; + key64 P2v; + bits indi; + + for (j = 0 ; j < N ; j++) { + indi[j] = (int)randXmrAmount(2); + + xv[j] = skGen(); + if ( (int)indi[j] == 0 ) { + P1v[j] = scalarmultBase(xv[j]); + P2v[j] = pkGen(); + + } else { + + P2v[j] = scalarmultBase(xv[j]); + P1v[j] = pkGen(); + + } + } + + //#true one + asnlSig L1s2s = GenASNL(xv, P1v, P2v, indi); + ASSERT_TRUE(VerASNL(P1v, P2v, L1s2s)); + + //#false one + indi[3] = (indi[3] + 1) % 2; + L1s2s = GenASNL(xv, P1v, P2v, indi); + ASSERT_FALSE(VerASNL(P1v, P2v, L1s2s)); + + //#true one again + indi[3] = (indi[3] + 1) % 2; + L1s2s = GenASNL(xv, P1v, P2v, indi); + ASSERT_TRUE(VerASNL(P1v, P2v, L1s2s)); + + //#false one + L1s2s = GenASNL(xv, P2v, P1v, indi); + ASSERT_FALSE(VerASNL(P1v, P2v, L1s2s)); +} + +TEST(ringct, MG_sigs) +{ + int j = 0; + int N = 0; + + //Tests for MG Sigs + //#MG sig: true one + N = 3;// #cols + int R = 3;// #rows + keyV xtmp = skvGen(R); + keyM xm = keyMInit(R, N);// = [[None]*N] #just used to generate test public keys + keyV sk = skvGen(R); + keyM P = keyMInit(R, N);// = keyM[[None]*N] #stores the public keys; + int ind = 2; + int i = 0; + for (j = 0 ; j < R ; j++) { + for (i = 0 ; i < N ; i++) + { + xm[i][j] = skGen(); + P[i][j] = scalarmultBase(xm[i][j]); + } + } + for (j = 0 ; j < R ; j++) { + sk[j] = xm[ind][j]; + } + key message = identity(); + mgSig IIccss = MLSAG_Gen(message, P, sk, ind, R); + ASSERT_TRUE(MLSAG_Ver(message, P, IIccss, R)); + + //#MG sig: false one + N = 3;// #cols + R = 3;// #rows + xtmp = skvGen(R); + keyM xx(N, xtmp);// = [[None]*N] #just used to generate test public keys + sk = skvGen(R); + //P (N, xtmp);// = keyM[[None]*N] #stores the public keys; + + ind = 2; + for (j = 0 ; j < R ; j++) { + for (i = 0 ; i < N ; i++) + { + xx[i][j] = skGen(); + P[i][j] = scalarmultBase(xx[i][j]); + } + sk[j] = xx[ind][j]; + } + sk[2] = skGen();//asume we don't know one of the private keys.. + IIccss = MLSAG_Gen(message, P, sk, ind, R); + ASSERT_FALSE(MLSAG_Ver(message, P, IIccss, R)); +} + +TEST(ringct, range_proofs) +{ + //Ring CT Stuff + //ct range proofs + ctkeyV sc, pc; + ctkey sctmp, pctmp; + //add fake input 5000 + tie(sctmp, pctmp) = ctskpkGen(6000); + sc.push_back(sctmp); + pc.push_back(pctmp); + + + tie(sctmp, pctmp) = ctskpkGen(7000); + sc.push_back(sctmp); + pc.push_back(pctmp); + vector<xmr_amount >amounts; + rct::keyV amount_keys; + key mask; + + //add output 500 + amounts.push_back(500); + amount_keys.push_back(rct::hash_to_scalar(rct::zero())); + keyV destinations; + key Sk, Pk; + skpkGen(Sk, Pk); + destinations.push_back(Pk); + + + //add output for 12500 + amounts.push_back(12500); + amount_keys.push_back(rct::hash_to_scalar(rct::zero())); + skpkGen(Sk, Pk); + destinations.push_back(Pk); + + //compute rct data with mixin 500 + rctSig s = genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, 3); + + //verify rct data + ASSERT_TRUE(verRct(s)); + + //decode received amount + ASSERT_TRUE(decodeRct(s, amount_keys[1], 1, mask)); + + // Ring CT with failing MG sig part should not verify! + // Since sum of inputs != outputs + + amounts[1] = 12501; + skpkGen(Sk, Pk); + destinations[1] = Pk; + + + //compute rct data with mixin 500 + s = genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, 3); + + //verify rct data + ASSERT_FALSE(verRct(s)); + + //decode received amount + ASSERT_TRUE(decodeRct(s, amount_keys[1], 1, mask)); +} + +TEST(ringct, range_proofs_with_fee) +{ + //Ring CT Stuff + //ct range proofs + ctkeyV sc, pc; + ctkey sctmp, pctmp; + //add fake input 5000 + tie(sctmp, pctmp) = ctskpkGen(6001); + sc.push_back(sctmp); + pc.push_back(pctmp); + + + tie(sctmp, pctmp) = ctskpkGen(7000); + sc.push_back(sctmp); + pc.push_back(pctmp); + vector<xmr_amount >amounts; + keyV amount_keys; + key mask; + + //add output 500 + amounts.push_back(500); + amount_keys.push_back(rct::hash_to_scalar(rct::zero())); + keyV destinations; + key Sk, Pk; + skpkGen(Sk, Pk); + destinations.push_back(Pk); + + //add txn fee for 1 + //has no corresponding destination.. + amounts.push_back(1); + amount_keys.push_back(hash_to_scalar(zero())); + + //add output for 12500 + amounts.push_back(12500); + amount_keys.push_back(hash_to_scalar(zero())); + skpkGen(Sk, Pk); + destinations.push_back(Pk); + + //compute rct data with mixin 500 + rctSig s = genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, 3); + + //verify rct data + ASSERT_TRUE(verRct(s)); + + //decode received amount + ASSERT_TRUE(decodeRct(s, amount_keys[1], 1, mask)); + + // Ring CT with failing MG sig part should not verify! + // Since sum of inputs != outputs + + amounts[1] = 12501; + skpkGen(Sk, Pk); + destinations[1] = Pk; + + + //compute rct data with mixin 500 + s = genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, 3); + + //verify rct data + ASSERT_FALSE(verRct(s)); + + //decode received amount + ASSERT_TRUE(decodeRct(s, amount_keys[1], 1, mask)); +} + +TEST(ringct, simple) +{ + ctkeyV sc, pc; + ctkey sctmp, pctmp; + //this vector corresponds to output amounts + vector<xmr_amount>outamounts; + //this vector corresponds to input amounts + vector<xmr_amount>inamounts; + //this keyV corresponds to destination pubkeys + keyV destinations; + keyV amount_keys; + key mask; + + //add fake input 3000 + //the sc is secret data + //pc is public data + tie(sctmp, pctmp) = ctskpkGen(3000); + sc.push_back(sctmp); + pc.push_back(pctmp); + inamounts.push_back(3000); + + //add fake input 3000 + //the sc is secret data + //pc is public data + tie(sctmp, pctmp) = ctskpkGen(3000); + sc.push_back(sctmp); + pc.push_back(pctmp); + inamounts.push_back(3000); + + //add output 5000 + outamounts.push_back(5000); + amount_keys.push_back(rct::hash_to_scalar(rct::zero())); + //add the corresponding destination pubkey + key Sk, Pk; + skpkGen(Sk, Pk); + destinations.push_back(Pk); + + //add output 999 + outamounts.push_back(999); + amount_keys.push_back(rct::hash_to_scalar(rct::zero())); + //add the corresponding destination pubkey + skpkGen(Sk, Pk); + destinations.push_back(Pk); + + key message = skGen(); //real message later (hash of txn..) + + //compute sig with mixin 2 + xmr_amount txnfee = 1; + + rctSig s = genRctSimple(message, sc, pc, destinations,inamounts, outamounts, amount_keys, txnfee, 2); + + //verify ring ct signature + ASSERT_TRUE(verRctSimple(s)); + + //decode received amount corresponding to output pubkey index 1 + ASSERT_TRUE(decodeRctSimple(s, amount_keys[1], 1, mask)); +} + +static rct::rctSig make_sample_rct_sig(int n_inputs, const uint64_t input_amounts[], int n_outputs, const uint64_t output_amounts[], bool last_is_fee) +{ + ctkeyV sc, pc; + ctkey sctmp, pctmp; + vector<xmr_amount >amounts; + keyV destinations; + keyV amount_keys; + key Sk, Pk; + + for (int n = 0; n < n_inputs; ++n) { + tie(sctmp, pctmp) = ctskpkGen(input_amounts[n]); + sc.push_back(sctmp); + pc.push_back(pctmp); + } + + for (int n = 0; n < n_outputs; ++n) { + amounts.push_back(output_amounts[n]); + amount_keys.push_back(rct::hash_to_scalar(rct::zero())); + skpkGen(Sk, Pk); + if (n < n_outputs - 1 || !last_is_fee) + destinations.push_back(Pk); + } + + return genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, 3);; +} + +static rct::rctSig make_sample_simple_rct_sig(int n_inputs, const uint64_t input_amounts[], int n_outputs, const uint64_t output_amounts[], uint64_t fee) +{ + ctkeyV sc, pc; + ctkey sctmp, pctmp; + vector<xmr_amount> inamounts, outamounts; + keyV destinations; + keyV amount_keys; + key Sk, Pk; + + for (int n = 0; n < n_inputs; ++n) { + inamounts.push_back(input_amounts[n]); + tie(sctmp, pctmp) = ctskpkGen(input_amounts[n]); + sc.push_back(sctmp); + pc.push_back(pctmp); + } + + for (int n = 0; n < n_outputs; ++n) { + outamounts.push_back(output_amounts[n]); + amount_keys.push_back(hash_to_scalar(zero())); + skpkGen(Sk, Pk); + destinations.push_back(Pk); + } + + return genRctSimple(rct::zero(), sc, pc, destinations, inamounts, outamounts, amount_keys, fee, 3);; +} + +static bool range_proof_test(bool expected_valid, + int n_inputs, const uint64_t input_amounts[], int n_outputs, const uint64_t output_amounts[], bool last_is_fee, bool simple) +{ + //compute rct data + bool valid; + try { + rctSig s; + // simple takes fee as a parameter, non-simple takes it as an extra element to output amounts + if (simple) { + s = make_sample_simple_rct_sig(n_inputs, input_amounts, last_is_fee ? n_outputs - 1 : n_outputs, output_amounts, last_is_fee ? output_amounts[n_outputs - 1] : 0); + valid = verRctSimple(s); + } + else { + s = make_sample_rct_sig(n_inputs, input_amounts, n_outputs, output_amounts, last_is_fee); + valid = verRct(s); + } + } + catch (const std::exception &e) { + valid = false; + } + + if (valid == expected_valid) { + return testing::AssertionSuccess(); + } + else { + return testing::AssertionFailure(); + } +} + +#define NELTS(array) (sizeof(array)/sizeof(array[0])) + +TEST(ringct, range_proofs_reject_empty_outs) +{ + const uint64_t inputs[] = {5000}; + const uint64_t outputs[] = {}; + EXPECT_TRUE(range_proof_test(false, NELTS(inputs), inputs, NELTS(outputs), outputs, false, false)); +} + +TEST(ringct, range_proofs_reject_empty_outs_simple) +{ + const uint64_t inputs[] = {5000}; + const uint64_t outputs[] = {}; + EXPECT_TRUE(range_proof_test(false, NELTS(inputs), inputs, NELTS(outputs), outputs, false, true)); +} + +TEST(ringct, range_proofs_reject_empty_ins) +{ + const uint64_t inputs[] = {}; + const uint64_t outputs[] = {5000}; + EXPECT_TRUE(range_proof_test(false, NELTS(inputs), inputs, NELTS(outputs), outputs, false, false)); +} + +TEST(ringct, range_proofs_reject_empty_ins_simple) +{ + const uint64_t inputs[] = {}; + const uint64_t outputs[] = {5000}; + EXPECT_TRUE(range_proof_test(false, NELTS(inputs), inputs, NELTS(outputs), outputs, false, true)); +} + +TEST(ringct, range_proofs_reject_all_empty) +{ + const uint64_t inputs[] = {}; + const uint64_t outputs[] = {}; + EXPECT_TRUE(range_proof_test(false, NELTS(inputs), inputs, NELTS(outputs), outputs, false, false)); +} + +TEST(ringct, range_proofs_reject_all_empty_simple) +{ + const uint64_t inputs[] = {}; + const uint64_t outputs[] = {}; + EXPECT_TRUE(range_proof_test(false, NELTS(inputs), inputs, NELTS(outputs), outputs, false, true)); +} + +TEST(ringct, range_proofs_accept_zero_empty) +{ + const uint64_t inputs[] = {0}; + const uint64_t outputs[] = {}; + EXPECT_TRUE(range_proof_test(true, NELTS(inputs), inputs, NELTS(outputs), outputs, false, false)); +} + +TEST(ringct, range_proofs_accept_zero_empty_simple) +{ + const uint64_t inputs[] = {0}; + const uint64_t outputs[] = {}; + EXPECT_TRUE(range_proof_test(true, NELTS(inputs), inputs, NELTS(outputs), outputs, false, true)); +} + +TEST(ringct, range_proofs_reject_empty_zero) +{ + const uint64_t inputs[] = {}; + const uint64_t outputs[] = {0}; + EXPECT_TRUE(range_proof_test(false, NELTS(inputs), inputs, NELTS(outputs), outputs, false, false)); +} + +TEST(ringct, range_proofs_reject_empty_zero_simple) +{ + const uint64_t inputs[] = {}; + const uint64_t outputs[] = {0}; + EXPECT_TRUE(range_proof_test(false, NELTS(inputs), inputs, NELTS(outputs), outputs, false, true)); +} + +TEST(ringct, range_proofs_accept_zero_zero) +{ + const uint64_t inputs[] = {0}; + const uint64_t outputs[] = {0}; + EXPECT_TRUE(range_proof_test(true, NELTS(inputs), inputs, NELTS(outputs), outputs, false, false)); +} + +TEST(ringct, range_proofs_accept_zero_zero_simple) +{ + const uint64_t inputs[] = {0}; + const uint64_t outputs[] = {0}; + EXPECT_TRUE(range_proof_test(true, NELTS(inputs), inputs, NELTS(outputs), outputs, false, true)); +} + +TEST(ringct, range_proofs_accept_zero_out_first) +{ + const uint64_t inputs[] = {5000}; + const uint64_t outputs[] = {0, 5000}; + EXPECT_TRUE(range_proof_test(true, NELTS(inputs), inputs, NELTS(outputs), outputs, false, false)); +} + +TEST(ringct, range_proofs_accept_zero_out_first_simple) +{ + const uint64_t inputs[] = {5000}; + const uint64_t outputs[] = {0, 5000}; + EXPECT_TRUE(range_proof_test(true, NELTS(inputs), inputs, NELTS(outputs), outputs, false, true)); +} + +TEST(ringct, range_proofs_accept_zero_out_last) +{ + const uint64_t inputs[] = {5000}; + const uint64_t outputs[] = {5000, 0}; + EXPECT_TRUE(range_proof_test(true, NELTS(inputs), inputs, NELTS(outputs), outputs, false, false)); +} + +TEST(ringct, range_proofs_accept_zero_out_last_simple) +{ + const uint64_t inputs[] = {5000}; + const uint64_t outputs[] = {5000, 0}; + EXPECT_TRUE(range_proof_test(true, NELTS(inputs), inputs, NELTS(outputs), outputs, false, true)); +} + +TEST(ringct, range_proofs_accept_zero_out_middle) +{ + const uint64_t inputs[] = {5000}; + const uint64_t outputs[] = {2500, 0, 2500}; + EXPECT_TRUE(range_proof_test(true, NELTS(inputs), inputs, NELTS(outputs), outputs, false, false)); +} + +TEST(ringct, range_proofs_accept_zero_out_middle_simple) +{ + const uint64_t inputs[] = {5000}; + const uint64_t outputs[] = {2500, 0, 2500}; + EXPECT_TRUE(range_proof_test(true, NELTS(inputs), inputs, NELTS(outputs), outputs, false, true)); +} + +TEST(ringct, range_proofs_accept_zero_in_first) +{ + const uint64_t inputs[] = {0, 5000}; + const uint64_t outputs[] = {5000}; + EXPECT_TRUE(range_proof_test(true, NELTS(inputs), inputs, NELTS(outputs), outputs, false, false)); +} + +TEST(ringct, range_proofs_accept_zero_in_first_simple) +{ + const uint64_t inputs[] = {0, 5000}; + const uint64_t outputs[] = {5000}; + EXPECT_TRUE(range_proof_test(true, NELTS(inputs), inputs, NELTS(outputs), outputs, false, true)); +} + +TEST(ringct, range_proofs_accept_zero_in_last) +{ + const uint64_t inputs[] = {5000, 0}; + const uint64_t outputs[] = {5000}; + EXPECT_TRUE(range_proof_test(true, NELTS(inputs), inputs, NELTS(outputs), outputs, false, false)); +} + +TEST(ringct, range_proofs_accept_zero_in_last_simple) +{ + const uint64_t inputs[] = {5000, 0}; + const uint64_t outputs[] = {5000}; + EXPECT_TRUE(range_proof_test(true, NELTS(inputs), inputs, NELTS(outputs), outputs, false, true)); +} + +TEST(ringct, range_proofs_accept_zero_in_middle) +{ + const uint64_t inputs[] = {2500, 0, 2500}; + const uint64_t outputs[] = {5000}; + EXPECT_TRUE(range_proof_test(true, NELTS(inputs), inputs, NELTS(outputs), outputs, false, false)); +} + +TEST(ringct, range_proofs_accept_zero_in_middle_simple) +{ + const uint64_t inputs[] = {2500, 0, 2500}; + const uint64_t outputs[] = {5000}; + EXPECT_TRUE(range_proof_test(true, NELTS(inputs), inputs, NELTS(outputs), outputs, false, true)); +} + +TEST(ringct, range_proofs_reject_single_lower) +{ + const uint64_t inputs[] = {5000}; + const uint64_t outputs[] = {1}; + EXPECT_TRUE(range_proof_test(false, NELTS(inputs), inputs, NELTS(outputs), outputs, false, false)); +} + +TEST(ringct, range_proofs_reject_single_lower_simple) +{ + const uint64_t inputs[] = {5000}; + const uint64_t outputs[] = {1}; + EXPECT_TRUE(range_proof_test(false, NELTS(inputs), inputs, NELTS(outputs), outputs, false, true)); +} + +TEST(ringct, range_proofs_reject_single_higher) +{ + const uint64_t inputs[] = {5000}; + const uint64_t outputs[] = {5001}; + EXPECT_TRUE(range_proof_test(false, NELTS(inputs), inputs, NELTS(outputs), outputs, false, false)); +} + +TEST(ringct, range_proofs_reject_single_higher_simple) +{ + const uint64_t inputs[] = {5000}; + const uint64_t outputs[] = {5001}; + EXPECT_TRUE(range_proof_test(false, NELTS(inputs), inputs, NELTS(outputs), outputs, false, true)); +} + +TEST(ringct, range_proofs_reject_single_out_negative) +{ + const uint64_t inputs[] = {5000}; + const uint64_t outputs[] = {(uint64_t)-1000ll}; + EXPECT_TRUE(range_proof_test(false, NELTS(inputs), inputs, NELTS(outputs), outputs, false, false)); +} + +TEST(ringct, range_proofs_reject_single_out_negative_simple) +{ + const uint64_t inputs[] = {5000}; + const uint64_t outputs[] = {(uint64_t)-1000ll}; + EXPECT_TRUE(range_proof_test(false, NELTS(inputs), inputs, NELTS(outputs), outputs, false, true)); +} + +TEST(ringct, range_proofs_reject_out_negative_first) +{ + const uint64_t inputs[] = {5000}; + const uint64_t outputs[] = {(uint64_t)-1000ll, 6000}; + EXPECT_TRUE(range_proof_test(false, NELTS(inputs), inputs, NELTS(outputs), outputs, false, false)); +} + +TEST(ringct, range_proofs_reject_out_negative_first_simple) +{ + const uint64_t inputs[] = {5000}; + const uint64_t outputs[] = {(uint64_t)-1000ll, 6000}; + EXPECT_TRUE(range_proof_test(false, NELTS(inputs), inputs, NELTS(outputs), outputs, false, true)); +} + +TEST(ringct, range_proofs_reject_out_negative_last) +{ + const uint64_t inputs[] = {5000}; + const uint64_t outputs[] = {6000, (uint64_t)-1000ll}; + EXPECT_TRUE(range_proof_test(false, NELTS(inputs), inputs, NELTS(outputs), outputs, false, false)); +} + +TEST(ringct, range_proofs_reject_out_negative_last_simple) +{ + const uint64_t inputs[] = {5000}; + const uint64_t outputs[] = {6000, (uint64_t)-1000ll}; + EXPECT_TRUE(range_proof_test(false, NELTS(inputs), inputs, NELTS(outputs), outputs, false, true)); +} + +TEST(ringct, range_proofs_reject_out_negative_middle) +{ + const uint64_t inputs[] = {5000}; + const uint64_t outputs[] = {3000, (uint64_t)-1000ll, 3000}; + EXPECT_TRUE(range_proof_test(false, NELTS(inputs), inputs, NELTS(outputs), outputs, false, false)); +} + +TEST(ringct, range_proofs_reject_out_negative_middle_simple) +{ + const uint64_t inputs[] = {5000}; + const uint64_t outputs[] = {3000, (uint64_t)-1000ll, 3000}; + EXPECT_TRUE(range_proof_test(false, NELTS(inputs), inputs, NELTS(outputs), outputs, false, true)); +} + +TEST(ringct, range_proofs_reject_single_in_negative) +{ + const uint64_t inputs[] = {(uint64_t)-1000ll}; + const uint64_t outputs[] = {5000}; + EXPECT_TRUE(range_proof_test(false, NELTS(inputs), inputs, NELTS(outputs), outputs, false, false)); +} + +TEST(ringct, range_proofs_reject_single_in_negative_simple) +{ + const uint64_t inputs[] = {(uint64_t)-1000ll}; + const uint64_t outputs[] = {5000}; + EXPECT_TRUE(range_proof_test(false, NELTS(inputs), inputs, NELTS(outputs), outputs, false, true)); +} + +TEST(ringct, range_proofs_reject_in_negative_first) +{ + const uint64_t inputs[] = {(uint64_t)-1000ll, 6000}; + const uint64_t outputs[] = {5000}; + EXPECT_TRUE(range_proof_test(false, NELTS(inputs), inputs, NELTS(outputs), outputs, false, false)); +} + +TEST(ringct, range_proofs_reject_in_negative_first_simple) +{ + const uint64_t inputs[] = {(uint64_t)-1000ll, 6000}; + const uint64_t outputs[] = {5000}; + EXPECT_TRUE(range_proof_test(false, NELTS(inputs), inputs, NELTS(outputs), outputs, false, true)); +} + +TEST(ringct, range_proofs_reject_in_negative_last) +{ + const uint64_t inputs[] = {6000, (uint64_t)-1000ll}; + const uint64_t outputs[] = {5000}; + EXPECT_TRUE(range_proof_test(false, NELTS(inputs), inputs, NELTS(outputs), outputs, false, false)); +} + +TEST(ringct, range_proofs_reject_in_negative_last_simple) +{ + const uint64_t inputs[] = {6000, (uint64_t)-1000ll}; + const uint64_t outputs[] = {5000}; + EXPECT_TRUE(range_proof_test(false, NELTS(inputs), inputs, NELTS(outputs), outputs, false, true)); +} + +TEST(ringct, range_proofs_reject_in_negative_middle) +{ + const uint64_t inputs[] = {3000, (uint64_t)-1000ll, 3000}; + const uint64_t outputs[] = {5000}; + EXPECT_TRUE(range_proof_test(false, NELTS(inputs), inputs, NELTS(outputs), outputs, false, false)); +} + +TEST(ringct, range_proofs_reject_in_negative_middle_simple) +{ + const uint64_t inputs[] = {3000, (uint64_t)-1000ll, 3000}; + const uint64_t outputs[] = {5000}; + EXPECT_TRUE(range_proof_test(false, NELTS(inputs), inputs, NELTS(outputs), outputs, false, true)); +} + +TEST(ringct, range_proofs_reject_higher_list) +{ + const uint64_t inputs[] = {5000}; + const uint64_t outputs[] = {1000, 1000, 1000, 1000, 1000, 1000}; + EXPECT_TRUE(range_proof_test(false, NELTS(inputs), inputs, NELTS(outputs), outputs, false, false)); +} + +TEST(ringct, range_proofs_reject_higher_list_simple) +{ + const uint64_t inputs[] = {5000}; + const uint64_t outputs[] = {1000, 1000, 1000, 1000, 1000, 1000}; + EXPECT_TRUE(range_proof_test(false, NELTS(inputs), inputs, NELTS(outputs), outputs, false, true)); +} + +TEST(ringct, range_proofs_accept_1_to_1) +{ + const uint64_t inputs[] = {5000}; + const uint64_t outputs[] = {5000}; + EXPECT_TRUE(range_proof_test(true, NELTS(inputs), inputs, NELTS(outputs), outputs, false, false)); +} + +TEST(ringct, range_proofs_accept_1_to_1_simple) +{ + const uint64_t inputs[] = {5000}; + const uint64_t outputs[] = {5000}; + EXPECT_TRUE(range_proof_test(true, NELTS(inputs), inputs, NELTS(outputs), outputs, false, true)); +} + +TEST(ringct, range_proofs_accept_1_to_N) +{ + const uint64_t inputs[] = {5000}; + const uint64_t outputs[] = {1000, 1000, 1000, 1000, 1000}; + EXPECT_TRUE(range_proof_test(true, NELTS(inputs), inputs, NELTS(outputs), outputs, false, false)); +} + +TEST(ringct, range_proofs_accept_1_to_N_simple) +{ + const uint64_t inputs[] = {5000}; + const uint64_t outputs[] = {1000, 1000, 1000, 1000, 1000}; + EXPECT_TRUE(range_proof_test(true, NELTS(inputs), inputs, NELTS(outputs), outputs, false,true)); +} + +TEST(ringct, range_proofs_accept_N_to_1) +{ + const uint64_t inputs[] = {1000, 1000, 1000, 1000, 1000}; + const uint64_t outputs[] = {5000}; + EXPECT_TRUE(range_proof_test(true, NELTS(inputs), inputs, NELTS(outputs), outputs, false, false)); +} + +TEST(ringct, range_proofs_accept_N_to_1_simple) +{ + const uint64_t inputs[] = {1000, 1000, 1000, 1000, 1000}; + const uint64_t outputs[] = {5000}; + EXPECT_TRUE(range_proof_test(true, NELTS(inputs), inputs, NELTS(outputs), outputs, false, true)); +} + +TEST(ringct, range_proofs_accept_N_to_N) +{ + const uint64_t inputs[] = {1000, 1000, 1000, 1000, 1000}; + const uint64_t outputs[] = {1000, 1000, 1000, 1000, 1000}; + EXPECT_TRUE(range_proof_test(true, NELTS(inputs), inputs, NELTS(outputs), outputs, false, false)); +} + +TEST(ringct, range_proofs_accept_N_to_N_simple) +{ + const uint64_t inputs[] = {1000, 1000, 1000, 1000, 1000}; + const uint64_t outputs[] = {1000, 1000, 1000, 1000, 1000}; + EXPECT_TRUE(range_proof_test(true, NELTS(inputs), inputs, NELTS(outputs), outputs, false, true)); +} + +TEST(ringct, range_proofs_accept_very_long) +{ + const size_t N=12; + uint64_t inputs[N]; + uint64_t outputs[N]; + for (size_t n = 0; n < N; ++n) { + inputs[n] = n; + outputs[n] = n; + } + std::random_shuffle(inputs, inputs + N); + std::random_shuffle(outputs, outputs + N); + EXPECT_TRUE(range_proof_test(true, NELTS(inputs), inputs, NELTS(outputs), outputs, false, false)); +} + +TEST(ringct, range_proofs_accept_very_long_simple) +{ + const size_t N=12; + uint64_t inputs[N]; + uint64_t outputs[N]; + for (size_t n = 0; n < N; ++n) { + inputs[n] = n; + outputs[n] = n; + } + std::random_shuffle(inputs, inputs + N); + std::random_shuffle(outputs, outputs + N); + EXPECT_TRUE(range_proof_test(true, NELTS(inputs), inputs, NELTS(outputs), outputs, false, true)); +} + +TEST(ringct, HPow2) +{ + key G = scalarmultBase(d2h(1)); + + key H = hashToPointSimple(G); + for (int j = 0 ; j < ATOMS ; j++) { + ASSERT_TRUE(equalKeys(H, H2[j])); + addKeys(H, H, H); + } +} + +static const xmr_amount test_amounts[]={0, 1, 2, 3, 4, 5, 10000, 10000000000000000000ull, 10203040506070809000ull, 123456789123456789}; + +TEST(ringct, ecdh_roundtrip) +{ + key k; + ecdhTuple t0, t1; + + for (auto amount: test_amounts) { + skGen(k); + + t0.mask = skGen(); + t0.amount = d2h(amount); + + t1 = t0; + ecdhEncode(t1, k); + ecdhDecode(t1, k); + ASSERT_TRUE(t0.mask == t1.mask); + ASSERT_TRUE(equalKeys(t0.mask, t1.mask)); + ASSERT_TRUE(t0.amount == t1.amount); + ASSERT_TRUE(equalKeys(t0.amount, t1.amount)); + } +} + +TEST(ringct, d2h) +{ + key k, P1; + skpkGen(k, P1); + for (auto amount: test_amounts) { + d2h(k, amount); + ASSERT_TRUE(amount == h2d(k)); + } +} + +TEST(ringct, d2b) +{ + for (auto amount: test_amounts) { + bits b; + d2b(b, amount); + ASSERT_TRUE(amount == b2d(b)); + } +} + +TEST(ringct, prooveRange_is_non_deterministic) +{ + key C[2], mask[2]; + for (int n = 0; n < 2; ++n) + proveRange(C[n], mask[n], 80); + ASSERT_TRUE(memcmp(C[0].bytes, C[1].bytes, sizeof(C[0].bytes))); + ASSERT_TRUE(memcmp(mask[0].bytes, mask[1].bytes, sizeof(mask[0].bytes))); +} + +TEST(ringct, fee_0_valid) +{ + const uint64_t inputs[] = {1000, 1000}; + const uint64_t outputs[] = {2000, 0}; + EXPECT_TRUE(range_proof_test(true, NELTS(inputs), inputs, NELTS(outputs), outputs, true, false)); +} + +TEST(ringct, fee_0_valid_simple) +{ + const uint64_t inputs[] = {1000, 1000}; + const uint64_t outputs[] = {2000, 0}; + EXPECT_TRUE(range_proof_test(true, NELTS(inputs), inputs, NELTS(outputs), outputs, true, true)); +} + +TEST(ringct, fee_non_0_valid) +{ + const uint64_t inputs[] = {1000, 1000}; + const uint64_t outputs[] = {1900, 100}; + EXPECT_TRUE(range_proof_test(true, NELTS(inputs), inputs, NELTS(outputs), outputs, true, false)); +} + +TEST(ringct, fee_non_0_valid_simple) +{ + const uint64_t inputs[] = {1000, 1000}; + const uint64_t outputs[] = {1900, 100}; + EXPECT_TRUE(range_proof_test(true, NELTS(inputs), inputs, NELTS(outputs), outputs, true, true)); +} + +TEST(ringct, fee_non_0_invalid_higher) +{ + const uint64_t inputs[] = {1000, 1000}; + const uint64_t outputs[] = {1990, 100}; + EXPECT_TRUE(range_proof_test(false, NELTS(inputs), inputs, NELTS(outputs), outputs, true, false)); +} + +TEST(ringct, fee_non_0_invalid_higher_simple) +{ + const uint64_t inputs[] = {1000, 1000}; + const uint64_t outputs[] = {1990, 100}; + EXPECT_TRUE(range_proof_test(false, NELTS(inputs), inputs, NELTS(outputs), outputs, true, true)); +} + +TEST(ringct, fee_non_0_invalid_lower) +{ + const uint64_t inputs[] = {1000, 1000}; + const uint64_t outputs[] = {1000, 100}; + EXPECT_TRUE(range_proof_test(false, NELTS(inputs), inputs, NELTS(outputs), outputs, true, false)); +} + +TEST(ringct, fee_non_0_invalid_lower_simple) +{ + const uint64_t inputs[] = {1000, 1000}; + const uint64_t outputs[] = {1000, 100}; + EXPECT_TRUE(range_proof_test(false, NELTS(inputs), inputs, NELTS(outputs), outputs, true, true)); +} + +TEST(ringct, fee_burn_valid_one_out) +{ + const uint64_t inputs[] = {1000, 1000}; + const uint64_t outputs[] = {0, 2000}; + EXPECT_TRUE(range_proof_test(true, NELTS(inputs), inputs, NELTS(outputs), outputs, true, false)); +} + +TEST(ringct, fee_burn_valid_one_out_simple) +{ + const uint64_t inputs[] = {1000, 1000}; + const uint64_t outputs[] = {0, 2000}; + EXPECT_TRUE(range_proof_test(true, NELTS(inputs), inputs, NELTS(outputs), outputs, true, true)); +} + +TEST(ringct, fee_burn_valid_zero_out) +{ + const uint64_t inputs[] = {1000, 1000}; + const uint64_t outputs[] = {2000}; + EXPECT_TRUE(range_proof_test(true, NELTS(inputs), inputs, NELTS(outputs), outputs, true, false)); +} + +TEST(ringct, fee_burn_valid_zero_out_simple) +{ + const uint64_t inputs[] = {1000, 1000}; + const uint64_t outputs[] = {2000}; + EXPECT_TRUE(range_proof_test(true, NELTS(inputs), inputs, NELTS(outputs), outputs, true, true)); +} + +#define TEST_rctSig_elements(name, op) \ +TEST(ringct, rctSig_##name) \ +{ \ + const uint64_t inputs[] = {1000, 1000}; \ + const uint64_t outputs[] = {1000, 1000}; \ + rct::rctSig sig = make_sample_rct_sig(NELTS(inputs), inputs, NELTS(outputs), outputs, true); \ + ASSERT_TRUE(rct::verRct(sig)); \ + op; \ + ASSERT_FALSE(rct::verRct(sig)); \ +} + +TEST_rctSig_elements(rangeSigs_empty, sig.p.rangeSigs.resize(0)); +TEST_rctSig_elements(rangeSigs_too_many, sig.p.rangeSigs.push_back(sig.p.rangeSigs.back())); +TEST_rctSig_elements(rangeSigs_too_few, sig.p.rangeSigs.pop_back()); +TEST_rctSig_elements(mgSig_MG_empty, sig.p.MGs.resize(0)); +TEST_rctSig_elements(mgSig_ss_empty, sig.p.MGs[0].ss.resize(0)); +TEST_rctSig_elements(mgSig_ss_too_many, sig.p.MGs[0].ss.push_back(sig.p.MGs[0].ss.back())); +TEST_rctSig_elements(mgSig_ss_too_few, sig.p.MGs[0].ss.pop_back()); +TEST_rctSig_elements(mgSig_ss0_empty, sig.p.MGs[0].ss[0].resize(0)); +TEST_rctSig_elements(mgSig_ss0_too_many, sig.p.MGs[0].ss[0].push_back(sig.p.MGs[0].ss[0].back())); +TEST_rctSig_elements(mgSig_ss0_too_few, sig.p.MGs[0].ss[0].pop_back()); +TEST_rctSig_elements(mgSig_II_empty, sig.p.MGs[0].II.resize(0)); +TEST_rctSig_elements(mgSig_II_too_many, sig.p.MGs[0].II.push_back(sig.p.MGs[0].II.back())); +TEST_rctSig_elements(mgSig_II_too_few, sig.p.MGs[0].II.pop_back()); +TEST_rctSig_elements(mixRing_empty, sig.mixRing.resize(0)); +TEST_rctSig_elements(mixRing_too_many, sig.mixRing.push_back(sig.mixRing.back())); +TEST_rctSig_elements(mixRing_too_few, sig.mixRing.pop_back()); +TEST_rctSig_elements(mixRing0_empty, sig.mixRing[0].resize(0)); +TEST_rctSig_elements(mixRing0_too_many, sig.mixRing[0].push_back(sig.mixRing[0].back())); +TEST_rctSig_elements(mixRing0_too_few, sig.mixRing[0].pop_back()); +TEST_rctSig_elements(ecdhInfo_empty, sig.ecdhInfo.resize(0)); +TEST_rctSig_elements(ecdhInfo_too_many, sig.ecdhInfo.push_back(sig.ecdhInfo.back())); +TEST_rctSig_elements(ecdhInfo_too_few, sig.ecdhInfo.pop_back()); +TEST_rctSig_elements(outPk_empty, sig.outPk.resize(0)); +TEST_rctSig_elements(outPk_too_many, sig.outPk.push_back(sig.outPk.back())); +TEST_rctSig_elements(outPk_too_few, sig.outPk.pop_back()); + +#define TEST_rctSig_elements_simple(name, op) \ +TEST(ringct, rctSig_##name##_simple) \ +{ \ + const uint64_t inputs[] = {1000, 1000}; \ + const uint64_t outputs[] = {1000}; \ + rct::rctSig sig = make_sample_simple_rct_sig(NELTS(inputs), inputs, NELTS(outputs), outputs, 1000); \ + ASSERT_TRUE(rct::verRctSimple(sig)); \ + op; \ + ASSERT_FALSE(rct::verRctSimple(sig)); \ +} + +TEST_rctSig_elements_simple(rangeSigs_empty, sig.p.rangeSigs.resize(0)); +TEST_rctSig_elements_simple(rangeSigs_too_many, sig.p.rangeSigs.push_back(sig.p.rangeSigs.back())); +TEST_rctSig_elements_simple(rangeSigs_too_few, sig.p.rangeSigs.pop_back()); +TEST_rctSig_elements_simple(mgSig_empty, sig.p.MGs.resize(0)); +TEST_rctSig_elements_simple(mgSig_too_many, sig.p.MGs.push_back(sig.p.MGs.back())); +TEST_rctSig_elements_simple(mgSig_too_few, sig.p.MGs.pop_back()); +TEST_rctSig_elements_simple(mgSig0_ss_empty, sig.p.MGs[0].ss.resize(0)); +TEST_rctSig_elements_simple(mgSig0_ss_too_many, sig.p.MGs[0].ss.push_back(sig.p.MGs[0].ss.back())); +TEST_rctSig_elements_simple(mgSig0_ss_too_few, sig.p.MGs[0].ss.pop_back()); +TEST_rctSig_elements_simple(mgSig_ss0_empty, sig.p.MGs[0].ss[0].resize(0)); +TEST_rctSig_elements_simple(mgSig_ss0_too_many, sig.p.MGs[0].ss[0].push_back(sig.p.MGs[0].ss[0].back())); +TEST_rctSig_elements_simple(mgSig_ss0_too_few, sig.p.MGs[0].ss[0].pop_back()); +TEST_rctSig_elements_simple(mgSig0_II_empty, sig.p.MGs[0].II.resize(0)); +TEST_rctSig_elements_simple(mgSig0_II_too_many, sig.p.MGs[0].II.push_back(sig.p.MGs[0].II.back())); +TEST_rctSig_elements_simple(mgSig0_II_too_few, sig.p.MGs[0].II.pop_back()); +TEST_rctSig_elements_simple(mixRing_empty, sig.mixRing.resize(0)); +TEST_rctSig_elements_simple(mixRing_too_many, sig.mixRing.push_back(sig.mixRing.back())); +TEST_rctSig_elements_simple(mixRing_too_few, sig.mixRing.pop_back()); +TEST_rctSig_elements_simple(mixRing0_empty, sig.mixRing[0].resize(0)); +TEST_rctSig_elements_simple(mixRing0_too_many, sig.mixRing[0].push_back(sig.mixRing[0].back())); +TEST_rctSig_elements_simple(mixRing0_too_few, sig.mixRing[0].pop_back()); +TEST_rctSig_elements_simple(pseudoOuts_empty, sig.pseudoOuts.resize(0)); +TEST_rctSig_elements_simple(pseudoOuts_too_many, sig.pseudoOuts.push_back(sig.pseudoOuts.back())); +TEST_rctSig_elements_simple(pseudoOuts_too_few, sig.pseudoOuts.pop_back()); +TEST_rctSig_elements_simple(ecdhInfo_empty, sig.ecdhInfo.resize(0)); +TEST_rctSig_elements_simple(ecdhInfo_too_many, sig.ecdhInfo.push_back(sig.ecdhInfo.back())); +TEST_rctSig_elements_simple(ecdhInfo_too_few, sig.ecdhInfo.pop_back()); +TEST_rctSig_elements_simple(outPk_empty, sig.outPk.resize(0)); +TEST_rctSig_elements_simple(outPk_too_many, sig.outPk.push_back(sig.outPk.back())); +TEST_rctSig_elements_simple(outPk_too_few, sig.outPk.pop_back()); + +TEST(ringct, reject_gen_simple_ver_non_simple) +{ + const uint64_t inputs[] = {1000, 1000}; + const uint64_t outputs[] = {1000}; + rct::rctSig sig = make_sample_simple_rct_sig(NELTS(inputs), inputs, NELTS(outputs), outputs, 1000); + ASSERT_FALSE(rct::verRct(sig)); +} + +TEST(ringct, reject_gen_non_simple_ver_simple) +{ + const uint64_t inputs[] = {1000, 1000}; + const uint64_t outputs[] = {1000, 1000}; + rct::rctSig sig = make_sample_rct_sig(NELTS(inputs), inputs, NELTS(outputs), outputs, true); + ASSERT_FALSE(rct::verRctSimple(sig)); +} diff --git a/tests/unit_tests/serialization.cpp b/tests/unit_tests/serialization.cpp index b110a41ab..40209f6ed 100644 --- a/tests/unit_tests/serialization.cpp +++ b/tests/unit_tests/serialization.cpp @@ -36,6 +36,7 @@ #include <boost/foreach.hpp> #include "cryptonote_core/cryptonote_basic.h" #include "cryptonote_core/cryptonote_basic_impl.h" +#include "ringct/rctSigs.h" #include "serialization/serialization.h" #include "serialization/binary_archive.h" #include "serialization/json_archive.h" @@ -442,3 +443,200 @@ TEST(Serialization, serializes_transacion_signatures_correctly) blob.resize(blob.size() + sizeof(crypto::signature) / 2); ASSERT_FALSE(serialization::parse_binary(blob, tx1)); } + +TEST(Serialization, serializes_ringct_types) +{ + string blob; + rct::key key0, key1; + rct::keyV keyv0, keyv1; + rct::keyM keym0, keym1; + rct::ctkey ctkey0, ctkey1; + rct::ctkeyV ctkeyv0, ctkeyv1; + rct::ctkeyM ctkeym0, ctkeym1; + rct::ecdhTuple ecdh0, ecdh1; + rct::asnlSig asnl0, asnl1; + rct::mgSig mg0, mg1; + rct::rangeSig rg0, rg1; + rct::rctSig s0, s1; + cryptonote::transaction tx0, tx1; + + key0 = rct::skGen(); + ASSERT_TRUE(serialization::dump_binary(key0, blob)); + ASSERT_TRUE(serialization::parse_binary(blob, key1)); + ASSERT_TRUE(key0 == key1); + + keyv0 = rct::skvGen(30); + for (size_t n = 0; n < keyv0.size(); ++n) + keyv0[n] = rct::skGen(); + ASSERT_TRUE(serialization::dump_binary(keyv0, blob)); + ASSERT_TRUE(serialization::parse_binary(blob, keyv1)); + ASSERT_TRUE(keyv0.size() == keyv1.size()); + for (size_t n = 0; n < keyv0.size(); ++n) + { + ASSERT_TRUE(keyv0[n] == keyv1[n]); + } + + keym0 = rct::keyMInit(9, 12); + for (size_t n = 0; n < keym0.size(); ++n) + for (size_t i = 0; i < keym0[n].size(); ++i) + keym0[n][i] = rct::skGen(); + ASSERT_TRUE(serialization::dump_binary(keym0, blob)); + ASSERT_TRUE(serialization::parse_binary(blob, keym1)); + ASSERT_TRUE(keym0.size() == keym1.size()); + for (size_t n = 0; n < keym0.size(); ++n) + { + ASSERT_TRUE(keym0[n].size() == keym1[n].size()); + for (size_t i = 0; i < keym0[n].size(); ++i) + { + ASSERT_TRUE(keym0[n][i] == keym1[n][i]); + } + } + + rct::skpkGen(ctkey0.dest, ctkey0.mask); + ASSERT_TRUE(serialization::dump_binary(ctkey0, blob)); + ASSERT_TRUE(serialization::parse_binary(blob, ctkey1)); + ASSERT_TRUE(!memcmp(&ctkey0, &ctkey1, sizeof(ctkey0))); + + ctkeyv0 = std::vector<rct::ctkey>(14); + for (size_t n = 0; n < ctkeyv0.size(); ++n) + rct::skpkGen(ctkeyv0[n].dest, ctkeyv0[n].mask); + ASSERT_TRUE(serialization::dump_binary(ctkeyv0, blob)); + ASSERT_TRUE(serialization::parse_binary(blob, ctkeyv1)); + ASSERT_TRUE(ctkeyv0.size() == ctkeyv1.size()); + for (size_t n = 0; n < ctkeyv0.size(); ++n) + { + ASSERT_TRUE(!memcmp(&ctkeyv0[n], &ctkeyv1[n], sizeof(ctkeyv0[n]))); + } + + ctkeym0 = std::vector<rct::ctkeyV>(9); + for (size_t n = 0; n < ctkeym0.size(); ++n) + { + ctkeym0[n] = std::vector<rct::ctkey>(11); + for (size_t i = 0; i < ctkeym0[n].size(); ++i) + rct::skpkGen(ctkeym0[n][i].dest, ctkeym0[n][i].mask); + } + ASSERT_TRUE(serialization::dump_binary(ctkeym0, blob)); + ASSERT_TRUE(serialization::parse_binary(blob, ctkeym1)); + ASSERT_TRUE(ctkeym0.size() == ctkeym1.size()); + for (size_t n = 0; n < ctkeym0.size(); ++n) + { + ASSERT_TRUE(ctkeym0[n].size() == ctkeym1[n].size()); + for (size_t i = 0; i < ctkeym0.size(); ++i) + { + ASSERT_TRUE(!memcmp(&ctkeym0[n][i], &ctkeym1[n][i], sizeof(ctkeym0[n][i]))); + } + } + + ecdh0.mask = rct::skGen(); + ecdh0.amount = rct::skGen(); + ecdh0.senderPk = rct::skGen(); + ASSERT_TRUE(serialization::dump_binary(ecdh0, blob)); + ASSERT_TRUE(serialization::parse_binary(blob, ecdh1)); + ASSERT_TRUE(!memcmp(&ecdh0.mask, &ecdh1.mask, sizeof(ecdh0.mask))); + ASSERT_TRUE(!memcmp(&ecdh0.amount, &ecdh1.amount, sizeof(ecdh0.amount))); + // senderPk is not serialized + + for (size_t n = 0; n < 64; ++n) + { + asnl0.L1[n] = rct::skGen(); + asnl0.s2[n] = rct::skGen(); + } + asnl0.s = rct::skGen(); + ASSERT_TRUE(serialization::dump_binary(asnl0, blob)); + ASSERT_TRUE(serialization::parse_binary(blob, asnl1)); + ASSERT_TRUE(!memcmp(&asnl0, &asnl1, sizeof(asnl0))); + + // create a full rct signature to use its innards + rct::ctkeyV sc, pc; + rct::ctkey sctmp, pctmp; + tie(sctmp, pctmp) = rct::ctskpkGen(6000); + sc.push_back(sctmp); + pc.push_back(pctmp); + tie(sctmp, pctmp) = rct::ctskpkGen(7000); + sc.push_back(sctmp); + pc.push_back(pctmp); + vector<uint64_t> amounts; + rct::keyV amount_keys; + //add output 500 + amounts.push_back(500); + rct::keyV destinations; + rct::key Sk, Pk; + rct::skpkGen(Sk, Pk); + destinations.push_back(Pk); + //add output for 12500 + amounts.push_back(12500); + amount_keys.push_back(rct::hash_to_scalar(rct::zero())); + rct::skpkGen(Sk, Pk); + destinations.push_back(Pk); + //compute rct data with mixin 500 + s0 = rct::genRct(rct::zero(), sc, pc, destinations, amounts, amount_keys, 3); + + mg0 = s0.p.MGs[0]; + ASSERT_TRUE(serialization::dump_binary(mg0, blob)); + ASSERT_TRUE(serialization::parse_binary(blob, mg1)); + ASSERT_TRUE(mg0.ss.size() == mg1.ss.size()); + for (size_t n = 0; n < mg0.ss.size(); ++n) + { + ASSERT_TRUE(mg0.ss[n] == mg1.ss[n]); + } + ASSERT_TRUE(mg0.cc == mg1.cc); + + // mixRing and II are not serialized, they are meant to be reconstructed + ASSERT_TRUE(mg1.II.empty()); + + rg0 = s0.p.rangeSigs.front(); + ASSERT_TRUE(serialization::dump_binary(rg0, blob)); + ASSERT_TRUE(serialization::parse_binary(blob, rg1)); + ASSERT_TRUE(!memcmp(&rg0, &rg1, sizeof(rg0))); + + ASSERT_TRUE(serialization::dump_binary(s0, blob)); + ASSERT_TRUE(serialization::parse_binary(blob, s1)); + ASSERT_TRUE(s0.type == s1.type); + ASSERT_TRUE(s0.p.rangeSigs.size() == s1.p.rangeSigs.size()); + for (size_t n = 0; n < s0.p.rangeSigs.size(); ++n) + { + ASSERT_TRUE(!memcmp(&s0.p.rangeSigs[n], &s1.p.rangeSigs[n], sizeof(s0.p.rangeSigs[n]))); + } + ASSERT_TRUE(s0.p.MGs.size() == s1.p.MGs.size()); + ASSERT_TRUE(s0.p.MGs[0].ss.size() == s1.p.MGs[0].ss.size()); + for (size_t n = 0; n < s0.p.MGs[0].ss.size(); ++n) + { + ASSERT_TRUE(s0.p.MGs[0].ss[n] == s1.p.MGs[0].ss[n]); + } + ASSERT_TRUE(s0.p.MGs[0].cc == s1.p.MGs[0].cc); + // mixRing and II are not serialized, they are meant to be reconstructed + ASSERT_TRUE(s1.p.MGs[0].II.empty()); + + // mixRing and II are not serialized, they are meant to be reconstructed + ASSERT_TRUE(s1.mixRing.size() == 0); + + ASSERT_TRUE(s0.ecdhInfo.size() == s1.ecdhInfo.size()); + for (size_t n = 0; n < s0.ecdhInfo.size(); ++n) + { + ASSERT_TRUE(!memcmp(&s0.ecdhInfo[n], &s1.ecdhInfo[n], sizeof(s0.ecdhInfo[n]))); + } + ASSERT_TRUE(s0.outPk.size() == s1.outPk.size()); + for (size_t n = 0; n < s0.outPk.size(); ++n) + { + // serialization only does the mask + ASSERT_TRUE(!memcmp(&s0.outPk[n].mask, &s1.outPk[n].mask, sizeof(s0.outPk[n].mask))); + } + + tx0.set_null(); + tx0.version = 2; + cryptonote::txin_to_key txin_to_key1; + txin_to_key1.key_offsets.resize(2); + cryptonote::txin_to_key txin_to_key2; + txin_to_key2.key_offsets.resize(2); + tx0.vin.push_back(txin_to_key1); + tx0.vin.push_back(txin_to_key2); + tx0.vout.push_back(cryptonote::tx_out()); + tx0.rct_signatures = s0; + ASSERT_EQ(tx0.rct_signatures.p.rangeSigs.size(), 2); + ASSERT_TRUE(serialization::dump_binary(tx0, blob)); + ASSERT_TRUE(serialization::parse_binary(blob, tx1)); + ASSERT_EQ(tx1.rct_signatures.p.rangeSigs.size(), 2); + std::string blob2; + ASSERT_TRUE(serialization::dump_binary(tx1, blob2)); + ASSERT_TRUE(blob == blob2); +} diff --git a/utils/conf/bitmonerod.conf b/utils/conf/bitmonerod.conf new file mode 100644 index 000000000..64a02741d --- /dev/null +++ b/utils/conf/bitmonerod.conf @@ -0,0 +1,7 @@ +# Configuration for bitmonerod +# Syntax: any command line option may be specified as 'clioptionname=value'. +# See 'bitmonerod --help' for all available options. + +data-dir=/var/lib/bitmonero +log-file=/var/log/bitmonero/bitmonero.log +log-level=0 diff --git a/utils/systemd/bitmonerod.service b/utils/systemd/bitmonerod.service new file mode 100644 index 000000000..da012c4e3 --- /dev/null +++ b/utils/systemd/bitmonerod.service @@ -0,0 +1,22 @@ +[Unit] +Description=Monero cryptocurrency node +After=network.target + +[Service] +User=bitmonero +Group=bitmonero +WorkingDirectory=~ + +Type=forking +ExecStart=/usr/bin/bitmonerod --config-file /etc/bitmonerod.conf --detach + +# This is necessary because bitmonerod does not yet support +# writing a PID file, which means systemd tries to guess the PID +# by default, but it guesses wrong (sometimes, depending on +# random timing of events), because the daemon forks twice. +# The ultimate fix is for the daemon to write a PID file, and +# a workaround is to disable the guessing feature in systemd. +GuessMainPID=no + +[Install] +WantedBy=multi-user.target |