diff options
224 files changed, 14438 insertions, 3252 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d53e049e..1f74f59e3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -186,7 +186,7 @@ endif() # elseif(CMAKE_SYSTEM_NAME MATCHES ".*BSDI.*") # set(BSDI TRUE) -include_directories(external/easylogging++ src contrib/epee/include external "${CMAKE_BINARY_DIR}/version") +include_directories(external/easylogging++ src contrib/epee/include external) if(APPLE) include_directories(SYSTEM /usr/include/malloc) @@ -234,6 +234,7 @@ if(STATIC) else() set(CMAKE_FIND_LIBRARY_SUFFIXES .a ${CMAKE_FIND_LIBRARY_SUFFIXES}) endif() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DZMQ_STATIC") endif() # Set default blockchain storage location: @@ -319,6 +320,12 @@ else() message(STATUS "Stack trace on exception disabled") endif() +if (UNIX AND NOT APPLE) + # Note that at the time of this writing the -Wstrict-prototypes flag added below will make this fail + set(THREADS_PREFER_PTHREAD_FLAG ON) + find_package(Threads) +endif() + # Handle OpenSSL, used for sha256sum on binary updates if (APPLE AND NOT IOS) if (NOT OpenSSL_DIR) @@ -332,16 +339,10 @@ endif() find_package(OpenSSL REQUIRED) if(STATIC AND NOT IOS) if(UNIX) - set(OPENSSL_LIBRARIES "${OPENSSL_LIBRARIES};${CMAKE_DL_LIBS}") + set(OPENSSL_LIBRARIES "${OPENSSL_LIBRARIES};${CMAKE_DL_LIBS};${CMAKE_THREAD_LIBS_INIT}") endif() endif() -if (UNIX AND NOT APPLE) - # Note that at the time of this writing the -Wstrict-prototypes flag added below will make this fail - set(THREADS_PREFER_PTHREAD_FLAG ON) - find_package(Threads) -endif() - add_definitions(-DAUTO_INITIALIZE_EASYLOGGINGPP) add_subdirectory(external) @@ -407,7 +408,7 @@ else() set(WARNINGS "${WARNINGS} -Wno-error=inline-asm") endif() else() - set(WARNINGS "${WARNINGS} -Wlogical-op -Wno-error=maybe-uninitialized") + set(WARNINGS "${WARNINGS} -Wlogical-op -Wno-error=maybe-uninitialized -Wno-error=cpp") endif() if(MINGW) set(WARNINGS "${WARNINGS} -Wno-error=unused-value -Wno-error=unused-but-set-variable") @@ -689,17 +690,19 @@ endif() include(version.cmake) -function (treat_warnings_as_errors dirs) - foreach(dir ${ARGV}) - set_property(DIRECTORY ${dir} - APPEND PROPERTY COMPILE_FLAGS "-Werror") - endforeach() -endfunction() +find_path(ZMQ_INCLUDE_PATH zmq.hpp) +find_library(ZMQ_LIB zmq) +find_library(SODIUM_LIBRARY sodium) -add_subdirectory(contrib) -add_subdirectory(src) - -treat_warnings_as_errors(contrib src) +if(NOT ZMQ_INCLUDE_PATH) + message(FATAL_ERROR "Could not find required header zmq.hpp") +endif() +if(NOT ZMQ_LIB) + message(FATAL_ERROR "Could not find required libzmq") +endif() +if(SODIUM_LIBRARY) + set(ZMQ_LIB "${ZMQ_LIB};${SODIUM_LIBRARY}") +endif() option(BUILD_TESTS "Build tests." OFF) @@ -707,7 +710,13 @@ if(BUILD_TESTS) add_subdirectory(tests) endif() +# warnings are cleared only for GCC on Linux +if (NOT (MINGW OR APPLE OR FREEBSD OR OPENBSD OR DRAGONFLY)) +add_compile_options("${WARNINGS_AS_ERRORS_FLAG}") # applies only to targets that follow +endif() +add_subdirectory(contrib) +add_subdirectory(src) if(BUILD_DOCUMENTATION) set(DOC_GRAPHS "YES" CACHE STRING "Create dependency graphs (needs graphviz)") diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 78d78f7bf..86eb45191 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,3 +38,123 @@ are conflicts (even trivially resolvable ones). PGP signing commits is strongly encouraged. That should explain why the previous paragraph is here. + +# [Code of Conduct (22/C4.1)](http://rfc.zeromq.org/spec:22) + +## License + +Copyright (c) 2009-2015 Pieter Hintjens. +Copyright (c) 2017 The Monero Project. + +This Specification is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. + +This Specification is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with this program; if not, see <http://www.gnu.org/licenses>. + +## Language + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. + +The "Monero Maintainer Team" is defined in this document as the following users: +- fluffypony +- moneromooo +- hyc + +## Goals + +C4 is meant to provide a reusable optimal collaboration model for open source software projects. It has these specific goals: + +- To maximize the scale and diversity of the community around a project, by reducing the friction for new Contributors and creating a scaled participation model with strong positive feedbacks; +- To relieve dependencies on key individuals by separating different skill sets so that there is a larger pool of competence in any required domain; +- To allow the project to develop faster and more accurately, by increasing the diversity of the decision making process; +- To support the natural life cycle of project versions from experimental through to stable, by allowing safe experimentation, rapid failure, and isolation of stable code; +- To reduce the internal complexity of project repositories, thus making it easier for Contributors to participate and reducing the scope for error; +- To enforce collective ownership of the project, which increases economic incentive to Contributors and reduces the risk of hijack by hostile entities. + +## Design + +### Preliminaries + +- The project SHALL use the git distributed revision control system. +- The project SHALL be hosted on github.com or equivalent, herein called the "Platform". +- The project SHALL use the Platform issue tracker. + - Non-GitHub example: + - "Platform" could be a vanilla git repo and Trac hosted on the same machine/network. + - The Platform issue tracker would be Trac. +- The project SHOULD have clearly documented guidelines for code style. +- A "Contributor" is a person who wishes to provide a patch, being a set of commits that solve some clearly identified problem. +- A "Maintainer" is a person who merges patches to the project. Maintainers are not developers; their job is to enforce process. +- Contributors SHALL NOT have commit access to the repository unless they are also Maintainers. +- Maintainers SHALL have commit access to the repository. +- Everyone, without distinction or discrimination, SHALL have an equal right to become a Contributor under the terms of this contract. + +### Licensing and Ownership + +- The project SHALL use a share-alike license, such as BSD-3, the GPLv3 or a variant thereof (LGPL, AGPL), or the MPLv2. +- All contributions to the project source code ("patches") SHALL use the same license as the project. +- All patches are owned by their authors. There SHALL NOT be any copyright assignment process. +- The copyrights in the project SHALL be owned collectively by all its Contributors. +- Each Contributor SHALL be responsible for identifying themselves in the project Contributor list. + +### Patch Requirements + +- Maintainers MUST have a Platform account and SHOULD use their real names or a well-known alias. +- Contributors SHOULD have a Platform account and MAY use their real names or a well-known alias. +- A patch SHOULD be a minimal and accurate answer to exactly one identified and agreed problem. +- A patch MUST adhere to the code style guidelines of the project if these are defined. +- A patch MUST adhere to the "Evolution of Public Contracts" guidelines defined below. +- A patch SHALL NOT include non-trivial code from other projects unless the Contributor is the original author of that code. +- A patch MUST compile cleanly and pass project self-tests on at least the principle target platform. +- A patch commit message SHOULD consist of a single short (less than 50 character) line summarizing the change, optionally followed by a blank line and then a more thorough description. +- A "Correct Patch" is one that satisfies the above requirements. + +### Development Process + +- Change on the project SHALL be governed by the pattern of accurately identifying problems and applying minimal, accurate solutions to these problems. +- To request changes, a user SHOULD log an issue on the project Platform issue tracker. +- The user or Contributor SHOULD write the issue by describing the problem they face or observe. +- The user or Contributor SHOULD seek consensus on the accuracy of their observation, and the value of solving the problem. +- Users SHALL NOT log feature requests, ideas, or suggestions unrelated to Monero code or Monero's dependency code or Monero's potential/future dependency code or research which successfully implements Monero. +- Users SHALL NOT log any solutions to problems (verifiable or hypothetical) of which are not explicitly documented and/or not provable and/or cannot be reasonably proven. +- Thus, the release history of the project SHALL be a list of meaningful issues logged and solved. +- To work on an issue, a Contributor SHALL fork the project repository and then work on their forked repository. +- To submit a patch, a Contributor SHALL create a Platform pull request back to the project. +- A Contributor SHALL NOT commit changes directly to the project. +- To discuss a patch, people MAY comment on the Platform pull request, on the commit, or elsewhere. +- To accept or reject a patch, a Maintainer SHALL use the Platform interface. +- Maintainers SHOULD NOT merge their own patches except in exceptional cases, such as non-responsiveness from other Maintainers for an extended period (more than 30 days) or unless urgent as defined by the Monero Maintainers Team. +- Maintainers SHALL NOT make value judgments on correct patches unless the Maintainer (as may happen in rare circumstances) is a core code developer. +- Maintainers MUST NOT merge pull requests in less than 168 hours (1 week) unless deemed urgent by at least 2 people from the Monero Maintainer Team. +- The Contributor MAY tag an issue as "Ready" after making a pull request for the issue. +- The user who created an issue SHOULD close the issue after checking the patch is successful. +- Maintainers SHOULD ask for improvements to incorrect patches and SHOULD reject incorrect patches if the Contributor does not respond constructively. +- Any Contributor who has value judgments on a correct patch SHOULD express these via their own patches. +- Maintainers MAY commit changes to non-source documentation directly to the project. + +### Creating Stable Releases + +- The project SHALL have one branch ("master") that always holds the latest in-progress version and SHOULD always build. +- The project SHALL NOT use topic branches for any reason. Personal forks MAY use topic branches. +- To make a stable release someone SHALL fork the repository by copying it and thus become maintainer of this repository. +- Forking a project for stabilization MAY be done unilaterally and without agreement of project maintainers. +- A patch to a stabilization project declared "stable" SHALL be accompanied by a reproducible test case. + +### Evolution of Public Contracts + +- All Public Contracts (APIs or protocols) SHALL be documented. +- All Public Contracts SHOULD have space for extensibility and experimentation. +- A patch that modifies a stable Public Contract SHOULD not break existing applications unless there is overriding consensus on the value of doing this. +- A patch that introduces new features to a Public Contract SHOULD do so using new names. +- Old names SHOULD be deprecated in a systematic fashion by marking new names as "experimental" until they are stable, then marking the old names as "deprecated". +- When sufficient time has passed, old deprecated names SHOULD be marked "legacy" and eventually removed. +- Old names SHALL NOT be reused by new features. +- When old names are removed, their implementations MUST provoke an exception (assertion) if used by applications. + +### Project Administration + +- The project founders SHALL act as Administrators to manage the set of project Maintainers. +- The Administrators SHALL ensure their own succession over time by promoting the most effective Maintainers. +- A new Contributor who makes a correct patch SHALL be invited to become a Maintainer. +- Administrators MAY remove Maintainers who are inactive for an extended period of time, or who repeatedly fail to apply this process accurately. +- Administrators SHOULD block or ban "bad actors" who cause stress and pain to others in the project. This should be done after public discussion, with a chance for all parties to speak. A bad actor is someone who repeatedly ignores the rules and culture of the project, who is needlessly argumentative or hostile, or who is offensive, and who is unable to self-correct their behavior when asked to do so by others. @@ -11,6 +11,11 @@ Portions Copyright (c) 2012-2013, The Cryptonote developers - GitHub: [https://github.com/monero-project/monero](https://github.com/monero-project/monero) - IRC: [#monero-dev on Freenode](http://webchat.freenode.net/?randomnick=1&channels=%23monero-dev&prompt=1&uio=d4) +## Vulnerability Response + +- Our [Vulnerability Response Process](https://github.com/monero-project/meta/blob/master/VULNERABILITY_RESPONSE_PROCESS.md) encourages responsible disclosure +- We are also available via [HackerOne](https://hackerone.com/monero) + ## Build | Operating System | Processor | Status | @@ -89,18 +94,22 @@ If you want to help out, see [CONTRIBUTING](CONTRIBUTING.md) for a set of guidel See [Vulnerability Response Process](VULNERABILITY_RESPONSE_PROCESS.md). -## Monero software updates and consensus protocol changes (hard fork schedule) +## Monero software updates and Network Consensus Protocol Upgrade (hard fork schedule) -Monero uses a fixed-schedule hard fork mechanism to implement new features. This means that users of Monero (end users and service providers) need to run current versions and update their software on a regular schedule. Here is the current schedule, versions, and compatibility. +Monero uses a fixed-schedule network consensus protocol upgrade (hard fork) mechanism to implement new features. This means that users of Monero (end users and service providers) need to run current versions and upgrade their software on a regular schedule. Network consensus protocol upgrades occur during the months of March and September. Required software for these consensus protocol upgrades is available prior to the date of the consensus protocol upgrade. Please check the git repository prior to this date for the proper Monero software version. Below is the historical schedule and the projected schedule for the next upgrade. Dates are provided in the format YYYY-MM-DD. -| Fork Date | Consensus version | Minimum Monero Version | Recommended Monero Version | Details | -| ----------------- | ----------------- | ---------------------- | -------------------------- | ------------------ | -| 2016-09-21 | v3 | v0.9.4 | v0.10.0 | Splits coinbase into denominations | -| 2017-01-05 | v4 | v0.10.1 | v0.10.2.1 | Allow normal and RingCT transactions | -| 2017-04-15 | v5 | v0.10.3.0 | v0.10.3.1 | Adjusted minimum blocksize and fee algorithm | -| 2017-09-21 | v6 | Not determined as of 2017-03-27 | Not determined as of 2017-03-27 | Allow only RingCT transactions | +| Consensus Upgrade Block Height | Date | Consensus version | Minimum Monero Version | Recommended Monero Version | Details | +| ------------------------------ | -----------| ----------------- | ---------------------- | -------------------------- | ---------------------------------------------------------------------------------- | +| 1009827 | 2016-03-22 | v2 | v0.9.4 | v0.9.4 | Allow only >= ringsize 3, blocktime = 120 seconds, fee-free blocksize 60 kb | +| 1141317 | 2016-09-21 | v3 | v0.9.4 | v0.10.0 | Splits coinbase into denominations | +| 1220516 | 2017-01-05 | v4 | v0.10.1 | v0.10.2.1 | Allow normal and RingCT transactions | +| 1288616 | 2017-04-15 | v5 | v0.10.3.0 | v0.10.3.1 | Adjusted minimum blocksize and fee algorithm | +| 1400000 | 2017-09-16 | v6 | v0.11.0.0 | v0.11.0.0 | Allow only RingCT transactions, allow only >= ringsize 5 | +| XXXXXXX | 2018-03-XX | XX | XXXXXXXXX | XXXXXXXXX | XXXXXX + +X's indicate that these details have not been determined as of commit date, 2017-09-20. ## Installing Monero from a Package @@ -153,10 +162,12 @@ library archives (`.a`). | pkg-config | any | NO | `pkg-config` | `base-devel` | NO | | | Boost | 1.58 | NO | `libboost-all-dev` | `boost` | NO | C++ libraries | | OpenSSL | basically any | NO | `libssl-dev` | `openssl` | NO | sha256 sum | +| libzmq | 3.0.0 | NO | `libzmq3-dev` | `zeromq` | NO | ZeroMQ library | | libunbound | 1.4.16 | YES | `libunbound-dev` | `unbound` | NO | DNS resolver | | libminiupnpc | 2.0 | YES | `libminiupnpc-dev` | `miniupnpc` | YES | NAT punching | | libunwind | any | NO | `libunwind8-dev` | `libunwind` | YES | Stack traces | | liblzma | any | NO | `liblzma-dev` | `xz` | YES | For libunwind | +| libreadline | 6.3.0 | NO | `libreadline6-dev` | `readline` | YES | Input editing | | ldns | 1.6.17 | NO | `libldns-dev` | `ldns` | YES | SSL toolkit | | expat | 1.1 | NO | `libexpat1-dev` | `expat` | YES | XML parsing | | GTest | 1.5 | YES | `libgtest-dev`^ | `gtest` | YES | Test suite | @@ -184,6 +195,9 @@ invokes cmake commands as needed. this to be worthwhile, the machine should have one core and about 2GB of RAM available per thread. + *Note*: If cmake can not find zmq.hpp file on OS X, installing `zmq.hpp` from + https://github.com/zeromq/cppzmq to `/usr/local/include` should fix that error. + * The resulting executables can be found in `build/release/bin` * Add `PATH="$PATH:$HOME/monero/build/release/bin"` to `.profile` @@ -208,13 +222,13 @@ invokes cmake commands as needed. HAVE_DOT=YES doxygen Doxyfile -#### On the Raspberry Pi 2 +#### On the Raspberry Pi -Tested on a Raspberry Pi 2 with a clean install of minimal Debian Jessie from https://www.raspberrypi.org/downloads/raspbian/ +Tested on a Raspberry Pi Zero with a clean install of minimal Raspbian Stretch (2017-09-07 or later) from https://www.raspberrypi.org/downloads/raspbian/. If you are using Raspian Jessie, [please see note in the following section](#note-for-raspbian-jessie-users). * `apt-get update && apt-get upgrade` to install all of the latest software -* Install the dependencies for Monero except libunwind and libboost-all-dev +* Install the dependencies for Monero from the 'Debian' column in the table above. * Increase the system swap size: ``` @@ -223,6 +237,41 @@ Tested on a Raspberry Pi 2 with a clean install of minimal Debian Jessie from ht CONF_SWAPSIZE=1024 sudo /etc/init.d/dphys-swapfile start ``` +* Clone monero and checkout most recent release version: +``` + git clone https://github.com/monero-project/monero.git + cd monero + git checkout tags/v0.11.0.0 +``` +* Build: +``` + make release +``` +* Wait 4-6 hours + +* The resulting executables can be found in `build/release/bin` + +* Add `PATH="$PATH:$HOME/monero/build/release/bin"` to `.profile` + +* Run Monero with `monerod --detach` + +* You may wish to reduce the size of the swap file after the build has finished, and delete the boost directory from your home directory + +#### *Note for Raspbian Jessie Users:* + +If you are using the older Raspbian Jessie image, compiling Monero is a bit more complicated. The version of Boost available in the Debian Jessie repositories is too old to use with Monero, and thus you must compile a newer version yourself. The following explains the extra steps, and has been tested on a Raspberry Pi 2 with a clean install of minimal Raspbian Jessie. + +* As before, `apt-get update && apt-get upgrade` to install all of the latest software, and increase the system swap size + +``` + sudo /etc/init.d/dphys-swapfile stop + sudo nano /etc/dphys-swapfile + CONF_SWAPSIZE=1024 + sudo /etc/init.d/dphys-swapfile start +``` + +* Then, install the dependencies for Monero except `libunwind` and `libboost-all-dev` + * Install the latest version of boost (this may first require invoking `apt-get remove --purge libboost*` to remove a previous version if you're not using a clean install): ``` cd @@ -238,20 +287,7 @@ Tested on a Raspberry Pi 2 with a clean install of minimal Debian Jessie from ht ``` * Wait ~4 hours -* Change to the root of the source code directory and build: -``` - cd monero - make release -``` -* Wait ~4 hours - -* The resulting executables can be found in `build/release/bin` - -* Add `PATH="$PATH:$HOME/monero/build/release/bin"` to `.profile` - -* Run Monero with `monerod --detach` - -* You may wish to reduce the size of the swap file after the build has finished, and delete the boost directory from your home directory +* From here, follow the [general Raspberry Pi instructions](#on-the-raspberry-pi) from the "Clone monero and checkout most recent release version" step. #### On Windows: @@ -280,11 +316,11 @@ application. To build for 64-bit Windows: - pacman -S mingw-w64-x86_64-toolchain make mingw-w64-x86_64-cmake mingw-w64-x86_64-boost + pacman -S mingw-w64-x86_64-toolchain make mingw-w64-x86_64-cmake mingw-w64-x86_64-boost mingw-w64-x86_64-openssl mingw-w64-x86_64-zeromq mingw-w64-x86_64-libsodium To build for 32-bit Windows: - pacman -S mingw-w64-i686-toolchain make mingw-w64-i686-cmake mingw-w64-i686-boost + pacman -S mingw-w64-i686-toolchain make mingw-w64-i686-cmake mingw-w64-i686-boost mingw-w64-i686-openssl mingw-w64-i686-zeromq mingw-w64-i686-libsodium * 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 @@ -336,11 +372,11 @@ To build: `env CC=egcc CXX=eg++ CPP=ecpp DEVELOPER_LOCAL_TOOLS=1 BOOST_ROOT=/pat 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-armv8``` builds binaries on Linux portable across POSIX systems on armv8 processors -* ```make release-static-armv7``` builds binaries on Linux portable across POSIX systems on armv7 processors -* ```make release-static-armv6``` builds binaries on Linux portable across POSIX systems on armv6 processors +* ```make release-static-linux-x86_64``` builds binaries on Linux on x86_64 portable across POSIX systems on x86_64 processors +* ```make release-static-linux-i686``` builds binaries on Linux on x86_64 or i686 portable across POSIX systems on i686 processors +* ```make release-static-linux-armv8``` builds binaries on Linux portable across POSIX systems on armv8 processors +* ```make release-static-linux-armv7``` builds binaries on Linux portable across POSIX systems on armv7 processors +* ```make release-static-linux-armv6``` builds binaries on Linux portable across POSIX systems on armv6 processors * ```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 @@ -394,14 +430,6 @@ TAILS ships with a very restrictive set of firewall rules. Therefore, you need t `./monero-wallet-cli` -## Using readline - -While monerod and monero-wallet-cli do not use readline directly, most of the functionality can be obtained by running them via rlwrap. This allows command recall, edit capabilities, etc. It does not give autocompletion without an extra completion file, however. To use rlwrap, simply prepend `rlwrap` to the command line, eg: - -`rlwrap bin/monero-wallet-cli --wallet-file /path/to/wallet` - -Note: rlwrap will save things like your seed and private keys, if you supply them on prompt. You may want to not use rlwrap when you use simplewallet to restore from seed, etc. - # Debugging This section contains general instructions for debugging failed installs or problems encountered with Monero. First ensure you are running the latest version built from the github repo. @@ -426,9 +454,11 @@ Type `thread apply all bt` within gdb in order to obtain the stack trace Enter `ulimit -c unlimited` on the command line to enable unlimited filesizes for core dumps +Enter `echo core | sudo tee /proc/sys/kernel/core_pattern` to stop cores from being hijacked by other tools + Run the build. -When it terminates with an output along the lines of "Segmentation fault (core dumped)", there should be a core dump file in the same directory as monerod. +When it terminates with an output along the lines of "Segmentation fault (core dumped)", there should be a core dump file in the same directory as monerod. It may be named just `core`, or `core.xxxx` with numbers appended. You can now analyse this core dump with `gdb` as follows: diff --git a/VULNERABILITY_RESPONSE_PROCESS.md b/VULNERABILITY_RESPONSE_PROCESS.md deleted file mode 100644 index eea3a06e7..000000000 --- a/VULNERABILITY_RESPONSE_PROCESS.md +++ /dev/null @@ -1,143 +0,0 @@ -# Monero Vulnerability Response Process - -## Preamble - -Researchers/Hackers: while you research/hack, we ask that you please refrain from committing the following: -- Denial of Service / Active exploiting against the network -- Social Engineering of Monero staff or contractors -- Any physical or electronic attempts against Monero community property and/or data centers - -## I. Point of Contacts for Security Issues - -``` -ric@getmonero.org -BDA6 BD70 42B7 21C4 67A9 759D 7455 C5E3 C0CD CEB9 - -luigi1111@getmonero.org -8777 AB8F 778E E894 87A2 F8E7 F4AC A018 3641 E010 - -moneromooo.monero@gmail.com -48B0 8161 FBDA DFE3 93AD FC3E 686F 0745 4D6C EFC3 -``` - -## II. Security Response Team - -- fluffypony -- luigi1111 -- moneromooo - -## III. Incident Response - -1. Researcher submits report via one or both of two methods: - - a. Email - - b. [HackerOne](https://hackerone.com/monero) - -2. Response Team designates a Response Manager who is in charge of the particular report based on availability and/or knowledge-set - -3. In no more than 3 working days, Response Team should gratefully respond to researcher using only encrypted, secure channels - -4. Response Manager makes inquiries to satisfy any needed information to confirm if submission is indeed a vulnerability - - a. If submission proves to be vulnerable, proceed to next step - - b. If not vulnerable: - - i. Response Manager responds with reasons why submission is not a vulnerability - - ii. Response Manager moves discussion to a new or existing ticket on GitHub if necessary - -5. If over email, Response Manager opens a HackerOne issue for new submission - -6. Establish severity of vulnerability: - - a. HIGH: impacts network as a whole, has potential to break entire network, results in the loss of monero, or is on a scale of great catastrophe - - b. MEDIUM: impacts individual nodes, wallets, or must be carefully exploited - - c. LOW: is not easily exploitable - -7. Respond according to the severity of the vulnerability: - - a. HIGH severities must be notified on website and reddit /r/Monero within 3 working days of classification - - i. The notification should list appropriate steps for users to take, if any - - ii. The notification must not include any details that could suggest an exploitation path - - iii. The latter takes precedence over the former - - b. MEDIUM and HIGH severities will require a Point Release - - c. LOW severities will be addressed in the next Regular Release - -8. Response Team applies appropriate patch(es) - - a. Response Manager designates a PRIVATE git "hotfix branch" to work in - - b. Patches are reviewed with the researcher - - c. Any messages associated with PUBLIC commits during the time of review should not make reference to the security nature of the PRIVATE branch or its commits - - d. Vulnerability announcement is drafted - - i. Include the severity of the vulnerability - - ii. Include all vulnerable systems/apps/code - - iii. Include solutions (if any) if patch cannot be applied - - e. Release date is discussed - -9. At release date, Response Team coordinates with developers to finalize update: - - a. Response Manager propagates the "hotfix branch" to trunk - - b. Response Manager includes vulnerability announcement draft in release notes - - c. Proceed with the Point or Regular Release - -## IV. Post-release Disclosure Process - -1. Response Team has 90 days to fulfill all points within section III - -2. If the Incident Response process in section III is successfully completed: - - a. Response Manager contacts researcher and asks if researcher wishes for credit - - b. Finalize vulnerability announcement draft and include the following: - - i. Project name and URL - - ii. Versions known to be affected - - iii. Versions known to be not affected (for example, the vulnerable code was introduced in a recent version, and older versions are therefore unaffected) - - iv. Versions not checked - - v. Type of vulnerability and its impact - - vi. If already obtained or applicable, a CVE-ID - - vii. The planned, coordinated release date - - viii. Mitigating factors (for example, the vulnerability is only exposed in uncommon, non-default configurations) - - ix. Workarounds (configuration changes users can make to reduce their exposure to the vulnerability) - - x. If applicable, credits to the original reporter - - c. Release finalized vulnerability announcement on website and reddit /r/Monero - - d. For HIGH severities, release finalized vulnerability announcement on well-known mailing lists: - - i. oss-security@lists.openwall.com - - ii. bugtraq@securityfocus.com - - e. If applicable, developers request a CVE-ID - - i. The commit that applied the fix is made reference too in a future commit and includes a CVE-ID - -3. If the Incident Response process in section III is *not* successfully completed: - - a. Response Team and developers organize an IRC meeting to discuss why/what points in section III were not resolved and how the team can resolve them in the future - - b. Any developer meetings immediately following the incident should include points made in section V - - c. If disputes arise about whether or when to disclose information about a vulnerability, the Response Team will publicly discuss the issue via IRC and attempt to reach consensus - - d. If consensus on a timely disclosure is not met (no later than 90 days), the researcher (after 90 days) has every right to expose the vulnerability to the public - -## V. Incident Analysis - -1. Isolate codebase - - a. Response Team and developers should coordinate to work on the following: - - i. Problematic implementation of classes/libraries/functions, etc. - - ii. Focus on apps/distro packaging, etc. - - iii. Operator/config error, etc. - -2. Auditing - - a. Response Team and developers should coordinate to work on the following: - - i. Auditing of problem area(s) as discussed in point 1 - - ii. Generate internal reports and store for future reference - - iii. If results are not sensitive, share with the public via IRC or GitHub - -3. Response Team has 45 days following completion of section III to ensure completion of section V - -## VI. Resolutions - -Any further questions or resolutions regarding the incident(s) between the researcher and response + development team after public disclosure can be addressed via the following: - -- [GitHub](https://github.com/monero-project/monero/issues/) -- [HackerOne](https://hackerone.com/monero) -- [Reddit /r/Monero](https://reddit.com/r/Monero/) -- IRC -- Email - -## VII. Continuous Improvement - -1. Response Team and developers should hold annual meetings to review the previous year's incidents - -2. Response Team or designated person(s) should give a brief presentation, including: - - a. Areas of Monero affected by the incidents - - b. Any network downtime or monetary cost (if any) of the incidents - - c. Ways in which the incidents could have been avoided (if any) - - d. How effective this process was in dealing with the incidents - -3. After the presentation, Response Team and developers should discuss: - - a. Potential changes to development processes to reduce future incidents - - b. Potential changes to this process to improve future responses diff --git a/cmake/FindMiniupnpc.cmake b/cmake/FindMiniupnpc.cmake index 77675db48..ad2004afc 100644 --- a/cmake/FindMiniupnpc.cmake +++ b/cmake/FindMiniupnpc.cmake @@ -46,13 +46,13 @@ IF(MINIUPNPC_FOUND) file(STRINGS "${MINIUPNP_INCLUDE_DIR}/miniupnpc.h" MINIUPNPC_API_VERSION_STR REGEX "^#define[\t ]+MINIUPNPC_API_VERSION[\t ]+[0-9]+") if(MINIUPNPC_API_VERSION_STR MATCHES "^#define[\t ]+MINIUPNPC_API_VERSION[\t ]+([0-9]+)") set(MINIUPNPC_API_VERSION "${CMAKE_MATCH_1}") + if (${MINIUPNPC_API_VERSION} GREATER "10" OR ${MINIUPNPC_API_VERSION} EQUAL "10") + message(STATUS "Found miniupnpc API version " ${MINIUPNPC_API_VERSION}) + set(MINIUPNP_FOUND true) + set(MINIUPNPC_VERSION_1_7_OR_HIGHER true) + endif() endif() - if (${MINIUPNPC_API_VERSION} GREATER "10" OR ${MINIUPNPC_API_VERSION} EQUAL "10") - message(STATUS "Found miniupnpc API version " ${MINIUPNPC_API_VERSION}) - set(MINIUPNP_FOUND true) - set(MINIUPNPC_VERSION_1_7_OR_HIGHER true) - endif() ENDIF() mark_as_advanced(MINIUPNP_INCLUDE_DIR MINIUPNP_LIBRARY MINIUPNP_STATIC_LIBRARY) diff --git a/contrib/epee/include/copyable_atomic.h b/contrib/epee/include/copyable_atomic.h index 410b4b4ff..00a5f484b 100644 --- a/contrib/epee/include/copyable_atomic.h +++ b/contrib/epee/include/copyable_atomic.h @@ -35,6 +35,8 @@ namespace epee public: copyable_atomic() {}; + copyable_atomic(uint32_t value) + { store(value); } copyable_atomic(const copyable_atomic& a):std::atomic<uint32_t>(a.load()) {} copyable_atomic& operator= (const copyable_atomic& a) diff --git a/contrib/epee/include/misc_log_ex.h b/contrib/epee/include/misc_log_ex.h index ec4bcbe2d..982aaea06 100644 --- a/contrib/epee/include/misc_log_ex.h +++ b/contrib/epee/include/misc_log_ex.h @@ -52,6 +52,7 @@ #include "easylogging++.h" #define MONERO_DEFAULT_LOG_CATEGORY "default" +#define MAX_LOG_FILE_SIZE 104850000 // 100 MB - 7600 bytes #define MCFATAL(cat,x) CLOG(FATAL,cat) << x #define MCERROR(cat,x) CLOG(ERROR,cat) << x @@ -123,8 +124,9 @@ #endif std::string mlog_get_default_log_path(const char *default_filename); -void mlog_configure(const std::string &filename_base, bool console); +void mlog_configure(const std::string &filename_base, bool console, const std::size_t max_log_file_size = MAX_LOG_FILE_SIZE); void mlog_set_categories(const char *categories); +std::string mlog_get_categories(); void mlog_set_log_level(int level); void mlog_set_log(const char *log); diff --git a/contrib/epee/include/net/abstract_tcp_server2.h b/contrib/epee/include/net/abstract_tcp_server2.h index ca58d5467..03f143fe4 100644 --- a/contrib/epee/include/net/abstract_tcp_server2.h +++ b/contrib/epee/include/net/abstract_tcp_server2.h @@ -281,8 +281,6 @@ namespace net_utils bool is_thread_worker(); - bool cleanup_connections(); - /// The io_service used to perform asynchronous operations. std::unique_ptr<boost::asio::io_service> m_io_service_local_instance; boost::asio::io_service& io_service_; @@ -309,7 +307,7 @@ namespace net_utils connection_ptr new_connection_; boost::mutex connections_mutex; - std::deque<std::pair<boost::system_time, connection_ptr>> connections_; + std::set<connection_ptr> connections_; }; // class <>boosted_tcp_server diff --git a/contrib/epee/include/net/abstract_tcp_server2.inl b/contrib/epee/include/net/abstract_tcp_server2.inl index 61276e761..94ef7c3b3 100644 --- a/contrib/epee/include/net/abstract_tcp_server2.inl +++ b/contrib/epee/include/net/abstract_tcp_server2.inl @@ -54,8 +54,6 @@ #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "net" -#define CONNECTION_CLEANUP_TIME 30 // seconds - PRAGMA_WARNING_PUSH namespace epee { @@ -139,14 +137,14 @@ PRAGMA_WARNING_DISABLE_VS(4355) CHECK_AND_NO_ASSERT_MES(!ec, false, "Failed to get local endpoint: " << ec.message() << ':' << ec.value()); context = boost::value_initialized<t_connection_context>(); - long ip_ = boost::asio::detail::socket_ops::host_to_network_long(remote_ep.address().to_v4().to_ulong()); + const unsigned long ip_{boost::asio::detail::socket_ops::host_to_network_long(remote_ep.address().to_v4().to_ulong())}; // create a random uuid boost::uuids::uuid random_uuid; // that stuff turns out to be included, even though it's from src... Taking advantage random_uuid = crypto::rand<boost::uuids::uuid>(); - context.set_details(random_uuid, new epee::net_utils::ipv4_network_address(ip_, remote_ep.port()), is_income); + context.set_details(random_uuid, epee::net_utils::ipv4_network_address(ip_, remote_ep.port()), is_income); _dbg3("[sock " << socket_.native_handle() << "] new connection from " << print_connection_context_short(context) << " to " << local_ep.address().to_string() << ':' << local_ep.port() << ", total sockets objects " << m_ref_sock_count); @@ -808,7 +806,6 @@ POP_WARNINGS m_threads_count = threads_count; m_main_thread_id = boost::this_thread::get_id(); MLOG_SET_THREAD_NAME("[SRV_MAIN]"); - add_idle_handler(boost::bind(&boosted_tcp_server::cleanup_connections, this), 5000); while(!m_stop_signal_sent) { @@ -898,7 +895,7 @@ POP_WARNINGS connections_mutex.lock(); for (auto &c: connections_) { - c.second->cancel(); + c->cancel(); } connections_.clear(); connections_mutex.unlock(); @@ -907,19 +904,6 @@ POP_WARNINGS } //--------------------------------------------------------------------------------- template<class t_protocol_handler> - bool boosted_tcp_server<t_protocol_handler>::cleanup_connections() - { - connections_mutex.lock(); - boost::system_time cutoff = boost::get_system_time() - boost::posix_time::seconds(CONNECTION_CLEANUP_TIME); - while (!connections_.empty() && connections_.front().first < cutoff) - { - connections_.pop_front(); - } - connections_mutex.unlock(); - return true; - } - //--------------------------------------------------------------------------------- - template<class t_protocol_handler> bool boosted_tcp_server<t_protocol_handler>::is_stop_signal_sent() { return m_stop_signal_sent; @@ -958,9 +942,10 @@ POP_WARNINGS connection_ptr new_connection_l(new connection<t_protocol_handler>(io_service_, m_config, m_sock_count, m_sock_number, m_pfilter, m_connection_type) ); connections_mutex.lock(); - connections_.push_back(std::make_pair(boost::get_system_time(), new_connection_l)); + connections_.insert(new_connection_l); MDEBUG("connections_ size now " << connections_.size()); connections_mutex.unlock(); + epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){ CRITICAL_REGION_LOCAL(connections_mutex); connections_.erase(new_connection_l); }); boost::asio::ip::tcp::socket& sock_ = new_connection_l->socket(); ////////////////////////////////////////////////////////////////////////// @@ -1038,6 +1023,10 @@ POP_WARNINGS _dbg3("Connected success to " << adr << ':' << port); + // start adds the connection to the config object's list, so we don't need to have it locally anymore + connections_mutex.lock(); + connections_.erase(new_connection_l); + connections_mutex.unlock(); bool r = new_connection_l->start(false, 1 < m_threads_count); if (r) { @@ -1062,9 +1051,10 @@ POP_WARNINGS TRY_ENTRY(); connection_ptr new_connection_l(new connection<t_protocol_handler>(io_service_, m_config, m_sock_count, m_sock_number, m_pfilter, m_connection_type) ); connections_mutex.lock(); - connections_.push_back(std::make_pair(boost::get_system_time(), new_connection_l)); + connections_.insert(new_connection_l); MDEBUG("connections_ size now " << connections_.size()); connections_mutex.unlock(); + epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){ CRITICAL_REGION_LOCAL(connections_mutex); connections_.erase(new_connection_l); }); boost::asio::ip::tcp::socket& sock_ = new_connection_l->socket(); ////////////////////////////////////////////////////////////////////////// @@ -1113,6 +1103,11 @@ POP_WARNINGS { _dbg3("[sock " << new_connection_l->socket().native_handle() << "] Connected success to " << adr << ':' << port << " from " << lep.address().to_string() << ':' << lep.port()); + + // start adds the connection to the config object's list, so we don't need to have it locally anymore + connections_mutex.lock(); + connections_.erase(new_connection_l); + connections_mutex.unlock(); bool r = new_connection_l->start(false, 1 < m_threads_count); if (r) { diff --git a/contrib/epee/include/net/http_base.h b/contrib/epee/include/net/http_base.h index e5aa06cb4..144acad9d 100644 --- a/contrib/epee/include/net/http_base.h +++ b/contrib/epee/include/net/http_base.h @@ -155,7 +155,8 @@ namespace net_utils http_request_info():m_http_method(http_method_unknown), m_http_ver_hi(0), m_http_ver_lo(0), - m_have_to_block(false) + m_have_to_block(false), + m_full_request_buf_size(0) {} http_method m_http_method; diff --git a/contrib/epee/include/net/levin_protocol_handler_async.h b/contrib/epee/include/net/levin_protocol_handler_async.h index 60a667690..779f4e78f 100644 --- a/contrib/epee/include/net/levin_protocol_handler_async.h +++ b/contrib/epee/include/net/levin_protocol_handler_async.h @@ -740,9 +740,15 @@ void async_protocol_handler_config<t_connection_context>::del_out_connections(si shuffle(out_connections.begin(), out_connections.end(), std::default_random_engine(seed)); while (count > 0 && out_connections.size() > 0) { - close(*out_connections.begin()); - del_connection(m_connects.at(*out_connections.begin())); + boost::uuids::uuid connection_id = *out_connections.begin(); + async_protocol_handler<t_connection_context> *connection = find_connection(connection_id); + // we temporarily ref the connection so it doesn't drop from the m_connects table + // when we close it + connection->start_outer_call(); + close(connection_id); + del_connection(m_connects.at(connection_id)); out_connections.erase(out_connections.begin()); + connection->finish_outer_call(); --count; } diff --git a/contrib/epee/include/net/net_utils_base.h b/contrib/epee/include/net/net_utils_base.h index ef3a1d146..0e31ee86f 100644 --- a/contrib/epee/include/net/net_utils_base.h +++ b/contrib/epee/include/net/net_utils_base.h @@ -29,11 +29,12 @@ #ifndef _NET_UTILS_BASE_H_ #define _NET_UTILS_BASE_H_ -#include <typeinfo> #include <boost/asio/io_service.hpp> #include <boost/uuid/uuid.hpp> +#include <memory> +#include <typeinfo> +#include <type_traits> #include "serialization/keyvalue_serialization.h" -#include "net/local_ip.h" #include "string_tools.h" #include "misc_log_ex.h" @@ -49,75 +50,119 @@ namespace epee { namespace net_utils { - struct network_address_base + class ipv4_network_address { - public: - bool operator==(const network_address_base &other) const { return m_full_id == other.m_full_id; } - bool operator!=(const network_address_base &other) const { return !operator==(other); } - bool operator<(const network_address_base &other) const { return m_full_id < other.m_full_id; } - bool is_same_host(const network_address_base &other) const { return m_host_id == other.m_host_id; } - virtual std::string str() const = 0; - virtual std::string host_str() const = 0; - virtual bool is_loopback() const = 0; - virtual bool is_local() const = 0; - virtual uint8_t get_type_id() const = 0; - protected: - // A very simple non cryptographic hash function by Fowler, Noll, Vo - uint64_t fnv1a(const uint8_t *data, size_t len) const { - uint64_t h = 0xcbf29ce484222325; - while (len--) - h = (h ^ *data++) * 0x100000001b3; - return h; - } - uint64_t m_host_id; - uint64_t m_full_id; - }; - struct ipv4_network_address: public network_address_base - { - void init_ids() - { - m_host_id = fnv1a((const uint8_t*)&m_ip, sizeof(m_ip)); - m_full_id = fnv1a((const uint8_t*)&m_ip, sizeof(m_ip) + sizeof(m_port)); - } - public: - ipv4_network_address(uint32_t ip, uint16_t port): network_address_base(), m_ip(ip), m_port(port) { init_ids(); } - uint32_t ip() const { return m_ip; } - uint16_t port() const { return m_port; } - virtual std::string str() const { return epee::string_tools::get_ip_string_from_int32(m_ip) + ":" + std::to_string(m_port); } - virtual std::string host_str() const { return epee::string_tools::get_ip_string_from_int32(m_ip); } - virtual bool is_loopback() const { return epee::net_utils::is_ip_loopback(m_ip); } - virtual bool is_local() const { return epee::net_utils::is_ip_local(m_ip); } - virtual uint8_t get_type_id() const { return ID; } - public: // serialization - static const uint8_t ID = 1; -#pragma pack(push) -#pragma pack(1) uint32_t m_ip; uint16_t m_port; -#pragma pack(pop) + + public: + constexpr ipv4_network_address(uint32_t ip, uint16_t port) noexcept + : m_ip(ip), m_port(port) {} + + bool equal(const ipv4_network_address& other) const noexcept; + bool less(const ipv4_network_address& other) const noexcept; + constexpr bool is_same_host(const ipv4_network_address& other) const noexcept + { return ip() == other.ip(); } + + constexpr uint32_t ip() const noexcept { return m_ip; } + constexpr uint16_t port() const noexcept { return m_port; } + std::string str() const; + std::string host_str() const; + bool is_loopback() const; + bool is_local() const; + static constexpr uint8_t get_type_id() noexcept { return ID; } + + static const uint8_t ID = 1; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(m_ip) KV_SERIALIZE(m_port) - if (!is_store) - const_cast<ipv4_network_address&>(this_ref).init_ids(); END_KV_SERIALIZE_MAP() }; - class network_address: public boost::shared_ptr<network_address_base> + + inline bool operator==(const ipv4_network_address& lhs, const ipv4_network_address& rhs) noexcept + { return lhs.equal(rhs); } + inline bool operator!=(const ipv4_network_address& lhs, const ipv4_network_address& rhs) noexcept + { return !lhs.equal(rhs); } + inline bool operator<(const ipv4_network_address& lhs, const ipv4_network_address& rhs) noexcept + { return lhs.less(rhs); } + inline bool operator<=(const ipv4_network_address& lhs, const ipv4_network_address& rhs) noexcept + { return !rhs.less(lhs); } + inline bool operator>(const ipv4_network_address& lhs, const ipv4_network_address& rhs) noexcept + { return rhs.less(lhs); } + inline bool operator>=(const ipv4_network_address& lhs, const ipv4_network_address& rhs) noexcept + { return !lhs.less(rhs); } + + class network_address { + struct interface + { + virtual ~interface() {}; + + virtual bool equal(const interface&) const = 0; + virtual bool less(const interface&) const = 0; + virtual bool is_same_host(const interface&) const = 0; + + virtual std::string str() const = 0; + virtual std::string host_str() const = 0; + virtual bool is_loopback() const = 0; + virtual bool is_local() const = 0; + virtual uint8_t get_type_id() const = 0; + }; + + template<typename T> + struct implementation final : interface + { + T value; + + implementation(const T& src) : value(src) {} + ~implementation() = default; + + // Type-checks for cast are done in cpp + static const T& cast(const interface& src) noexcept + { return static_cast<const implementation<T>&>(src).value; } + + virtual bool equal(const interface& other) const override + { return value.equal(cast(other)); } + + virtual bool less(const interface& other) const override + { return value.less(cast(other)); } + + virtual bool is_same_host(const interface& other) const override + { return value.is_same_host(cast(other)); } + + virtual std::string str() const override { return value.str(); } + virtual std::string host_str() const override { return value.host_str(); } + virtual bool is_loopback() const override { return value.is_loopback(); } + virtual bool is_local() const override { return value.is_local(); } + virtual uint8_t get_type_id() const override { return value.get_type_id(); } + }; + + std::shared_ptr<interface> self; + + template<typename Type> + Type& as_mutable() const + { + // types `implmentation<Type>` and `implementation<const Type>` are unique + using Type_ = typename std::remove_const<Type>::type; + network_address::interface* const self_ = self.get(); // avoid clang warning in typeid + if (!self_ || typeid(implementation<Type_>) != typeid(*self_)) + throw std::bad_cast{}; + return static_cast<implementation<Type_>*>(self_)->value; + } public: - network_address() {} - network_address(ipv4_network_address *address): boost::shared_ptr<network_address_base>(address) {} - bool operator==(const network_address &other) const { return (*this)->operator==(*other); } - bool operator!=(const network_address &other) const { return (*this)->operator!=(*other); } - bool operator<(const network_address &other) const { return (*this)->operator<(*other); } - bool is_same_host(const network_address &other) const { return (*this)->is_same_host(*other); } - std::string str() const { return (*this) ? (*this)->str() : "<none>"; } - std::string host_str() const { return (*this) ? (*this)->host_str() : "<none>"; } - bool is_loopback() const { return (*this)->is_loopback(); } - bool is_local() const { return (*this)->is_local(); } - uint8_t get_type_id() const { return (*this)->get_type_id(); } - template<typename Type> Type &as() { if (get_type_id() != Type::ID) throw std::runtime_error("Bad type"); return *(Type*)get(); } - template<typename Type> const Type &as() const { if (get_type_id() != Type::ID) throw std::runtime_error("Bad type"); return *(const Type*)get(); } + network_address() : self(nullptr) {} + template<typename T> + network_address(const T& src) + : self(std::make_shared<implementation<T>>(src)) {} + bool equal(const network_address &other) const; + bool less(const network_address &other) const; + bool is_same_host(const network_address &other) const; + std::string str() const { return self ? self->str() : "<none>"; } + std::string host_str() const { return self ? self->host_str() : "<none>"; } + bool is_loopback() const { return self ? self->is_loopback() : false; } + bool is_local() const { return self ? self->is_local() : false; } + uint8_t get_type_id() const { return self ? self->get_type_id() : 0; } + template<typename Type> const Type &as() const { return as_mutable<const Type>(); } BEGIN_KV_SERIALIZE_MAP() uint8_t type = is_store ? this_ref.get_type_id() : 0; @@ -126,13 +171,27 @@ namespace net_utils { case ipv4_network_address::ID: if (!is_store) - const_cast<network_address&>(this_ref).reset(new ipv4_network_address(0, 0)); - KV_SERIALIZE(template as<ipv4_network_address>()); + const_cast<network_address&>(this_ref) = ipv4_network_address{0, 0}; + KV_SERIALIZE(template as_mutable<ipv4_network_address>()); break; default: MERROR("Unsupported network address type: " << type); return false; } END_KV_SERIALIZE_MAP() }; + + inline bool operator==(const network_address& lhs, const network_address& rhs) + { return lhs.equal(rhs); } + inline bool operator!=(const network_address& lhs, const network_address& rhs) + { return !lhs.equal(rhs); } + inline bool operator<(const network_address& lhs, const network_address& rhs) + { return lhs.less(rhs); } + inline bool operator<=(const network_address& lhs, const network_address& rhs) + { return !rhs.less(lhs); } + inline bool operator>(const network_address& lhs, const network_address& rhs) + { return rhs.less(lhs); } + inline bool operator>=(const network_address& lhs, const network_address& rhs) + { return !lhs.less(rhs); } + inline bool create_network_address(network_address &address, const std::string &string, uint16_t default_port = 0) { uint32_t ip; @@ -141,7 +200,7 @@ namespace net_utils { if (default_port && !port) port = default_port; - address.reset(new ipv4_network_address(ip, port)); + address = ipv4_network_address{ip, port}; return true; } return false; @@ -179,7 +238,7 @@ namespace net_utils {} connection_context_base(): m_connection_id(), - m_remote_address(new ipv4_network_address(0,0)), + m_remote_address(ipv4_network_address{0,0}), m_is_income(false), m_started(time(NULL)), m_last_recv(0), @@ -232,7 +291,7 @@ namespace net_utils std::string print_connection_context(const connection_context_base& ctx) { std::stringstream ss; - ss << ctx.m_remote_address->str() << " " << epee::string_tools::get_str_from_guid_a(ctx.m_connection_id) << (ctx.m_is_income ? " INC":" OUT"); + ss << ctx.m_remote_address.str() << " " << epee::string_tools::get_str_from_guid_a(ctx.m_connection_id) << (ctx.m_is_income ? " INC":" OUT"); return ss.str(); } @@ -240,7 +299,7 @@ namespace net_utils std::string print_connection_context_short(const connection_context_base& ctx) { std::stringstream ss; - ss << ctx.m_remote_address->str() << (ctx.m_is_income ? " INC":" OUT"); + ss << ctx.m_remote_address.str() << (ctx.m_is_income ? " INC":" OUT"); return ss.str(); } diff --git a/contrib/epee/include/serialization/keyvalue_serialization_overloads.h b/contrib/epee/include/serialization/keyvalue_serialization_overloads.h index 1a58cab99..a94ecacc5 100644 --- a/contrib/epee/include/serialization/keyvalue_serialization_overloads.h +++ b/contrib/epee/include/serialization/keyvalue_serialization_overloads.h @@ -117,9 +117,9 @@ namespace epee typename stl_container::value_type exchange_val; typename t_storage::harray hval_array = stg.get_first_value(pname, exchange_val, hparent_section); if(!hval_array) return false; - container.push_back(std::move(exchange_val)); + container.insert(container.end(), std::move(exchange_val)); while(stg.get_next_value(hval_array, exchange_val)) - container.push_back(std::move(exchange_val)); + container.insert(container.end(), std::move(exchange_val)); return true; }//-------------------------------------------------------------------------------------------------------------------- template<class stl_container, class t_storage> @@ -152,7 +152,7 @@ namespace epee "size in blob " << loaded_size << " not have not zero modulo for sizeof(value_type) = " << sizeof(typename stl_container::value_type)); size_t count = (loaded_size/sizeof(typename stl_container::value_type)); for(size_t i = 0; i < count; i++) - container.push_back(*(pelem++)); + container.insert(container.end(), *(pelem++)); } return res; } @@ -186,12 +186,12 @@ namespace epee typename t_storage::harray hsec_array = stg.get_first_section(pname, hchild_section, hparent_section); if(!hsec_array || !hchild_section) return false; res = val._load(stg, hchild_section); - container.push_back(val); + container.insert(container.end(), val); while(stg.get_next_section(hsec_array, hchild_section)) { typename stl_container::value_type val_l = typename stl_container::value_type(); res |= val_l._load(stg, hchild_section); - container.push_back(std::move(val_l)); + container.insert(container.end(), std::move(val_l)); } return res; } @@ -227,6 +227,18 @@ namespace epee } //------------------------------------------------------------------------------------------------------------------- template<class t_type, class t_storage> + static bool kv_serialize(const std::deque<t_type>& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) + { + return serialize_stl_container_t_val(d, stg, hparent_section, pname); + } + //------------------------------------------------------------------------------------------------------------------- + template<class t_type, class t_storage> + static bool kv_unserialize(std::deque<t_type>& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) + { + return unserialize_stl_container_t_val(d, stg, hparent_section, pname); + } + //------------------------------------------------------------------------------------------------------------------- + template<class t_type, class t_storage> static bool kv_serialize(const std::list<t_type>& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) { return serialize_stl_container_t_val(d, stg, hparent_section, pname); @@ -238,6 +250,18 @@ namespace epee return unserialize_stl_container_t_val(d, stg, hparent_section, pname); } //------------------------------------------------------------------------------------------------------------------- + template<class t_type, class t_storage> + static bool kv_serialize(const std::set<t_type>& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) + { + return serialize_stl_container_t_val(d, stg, hparent_section, pname); + } + //------------------------------------------------------------------------------------------------------------------- + template<class t_type, class t_storage> + static bool kv_unserialize(std::set<t_type>& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) + { + return unserialize_stl_container_t_val(d, stg, hparent_section, pname); + } + //------------------------------------------------------------------------------------------------------------------- }; template<> struct kv_serialization_overloads_impl_is_base_serializable_types<false> @@ -268,6 +292,18 @@ namespace epee } //------------------------------------------------------------------------------------------------------------------- template<class t_type, class t_storage> + static bool kv_serialize(const std::deque<t_type>& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) + { + return serialize_stl_container_t_obj(d, stg, hparent_section, pname); + } + //------------------------------------------------------------------------------------------------------------------- + template<class t_type, class t_storage> + static bool kv_unserialize(std::deque<t_type>& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) + { + return unserialize_stl_container_t_obj(d, stg, hparent_section, pname); + } + //------------------------------------------------------------------------------------------------------------------- + template<class t_type, class t_storage> static bool kv_serialize(const std::list<t_type>& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) { return serialize_stl_container_t_obj(d, stg, hparent_section, pname); @@ -278,6 +314,18 @@ namespace epee { return unserialize_stl_container_t_obj(d, stg, hparent_section, pname); } + //------------------------------------------------------------------------------------------------------------------- + template<class t_type, class t_storage> + static bool kv_serialize(const std::set<t_type>& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) + { + return serialize_stl_container_t_obj(d, stg, hparent_section, pname); + } + //------------------------------------------------------------------------------------------------------------------- + template<class t_type, class t_storage> + static bool kv_unserialize(std::set<t_type>& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) + { + return unserialize_stl_container_t_obj(d, stg, hparent_section, pname); + } }; template<class t_storage> struct base_serializable_types: public boost::mpl::vector<uint64_t, uint32_t, uint16_t, uint8_t, int64_t, int32_t, int16_t, int8_t, double, bool, std::string, typename t_storage::meta_entry>::type @@ -353,6 +401,18 @@ namespace epee } //------------------------------------------------------------------------------------------------------------------- template<class t_type, class t_storage> + bool kv_serialize(const std::deque<t_type>& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) + { + return kv_serialization_overloads_impl_is_base_serializable_types<boost::mpl::contains<base_serializable_types<t_storage>, typename std::remove_const<t_type>::type>::value>::kv_serialize(d, stg, hparent_section, pname); + } + //------------------------------------------------------------------------------------------------------------------- + template<class t_type, class t_storage> + bool kv_unserialize(std::deque<t_type>& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) + { + return kv_serialization_overloads_impl_is_base_serializable_types<boost::mpl::contains<base_serializable_types<t_storage>, typename std::remove_const<t_type>::type>::value>::kv_unserialize(d, stg, hparent_section, pname); + } + //------------------------------------------------------------------------------------------------------------------- + template<class t_type, class t_storage> bool kv_serialize(const std::list<t_type>& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) { return kv_serialization_overloads_impl_is_base_serializable_types<boost::mpl::contains<base_serializable_types<t_storage>, typename std::remove_const<t_type>::type>::value>::kv_serialize(d, stg, hparent_section, pname); @@ -363,5 +423,17 @@ namespace epee { return kv_serialization_overloads_impl_is_base_serializable_types<boost::mpl::contains<base_serializable_types<t_storage>, typename std::remove_const<t_type>::type>::value>::kv_unserialize(d, stg, hparent_section, pname); } + //------------------------------------------------------------------------------------------------------------------- + template<class t_type, class t_storage> + bool kv_serialize(const std::set<t_type>& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) + { + return kv_serialization_overloads_impl_is_base_serializable_types<boost::mpl::contains<base_serializable_types<t_storage>, typename std::remove_const<t_type>::type>::value>::kv_serialize(d, stg, hparent_section, pname); + } + //------------------------------------------------------------------------------------------------------------------- + template<class t_type, class t_storage> + bool kv_unserialize(std::set<t_type>& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) + { + return kv_serialization_overloads_impl_is_base_serializable_types<boost::mpl::contains<base_serializable_types<t_storage>, typename std::remove_const<t_type>::type>::value>::kv_unserialize(d, stg, hparent_section, pname); + } } } diff --git a/contrib/epee/src/CMakeLists.txt b/contrib/epee/src/CMakeLists.txt index c61a6e684..294515f83 100644 --- a/contrib/epee/src/CMakeLists.txt +++ b/contrib/epee/src/CMakeLists.txt @@ -27,9 +27,9 @@ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. if (USE_READLINE AND GNU_READLINE_FOUND) - add_library(epee STATIC hex.cpp http_auth.cpp mlog.cpp string_tools.cpp readline_buffer.cpp) + add_library(epee STATIC hex.cpp http_auth.cpp mlog.cpp net_utils_base.cpp string_tools.cpp readline_buffer.cpp) else() - add_library(epee STATIC hex.cpp http_auth.cpp mlog.cpp string_tools.cpp) + add_library(epee STATIC hex.cpp http_auth.cpp mlog.cpp net_utils_base.cpp string_tools.cpp) endif() # Build and install libepee if we're building for GUI diff --git a/contrib/epee/src/mlog.cpp b/contrib/epee/src/mlog.cpp index a3f38e677..cfb2b7b15 100644 --- a/contrib/epee/src/mlog.cpp +++ b/contrib/epee/src/mlog.cpp @@ -111,7 +111,7 @@ static const char *get_default_categories(int level) return categories; } -void mlog_configure(const std::string &filename_base, bool console) +void mlog_configure(const std::string &filename_base, bool console, const std::size_t max_log_file_size) { el::Configurations c; c.setGlobally(el::ConfigurationType::Filename, filename_base); @@ -121,7 +121,7 @@ void mlog_configure(const std::string &filename_base, bool console) log_format = MLOG_BASE_FORMAT; c.setGlobally(el::ConfigurationType::Format, log_format); c.setGlobally(el::ConfigurationType::ToStandardOutput, console ? "true" : "false"); - c.setGlobally(el::ConfigurationType::MaxLogFileSize, "104850000"); // 100 MB - 7600 bytes + c.setGlobally(el::ConfigurationType::MaxLogFileSize, std::to_string(max_log_file_size)); el::Loggers::setDefaultConfigurations(c, true); el::Loggers::addFlag(el::LoggingFlag::HierarchicalLogging); @@ -144,16 +144,52 @@ void mlog_configure(const std::string &filename_base, bool console) void mlog_set_categories(const char *categories) { - el::Loggers::setCategories(categories); - MLOG_LOG("New log categories: " << categories); + std::string new_categories; + if (*categories) + { + if (*categories == '+') + { + ++categories; + new_categories = mlog_get_categories(); + if (*categories) + { + if (!new_categories.empty()) + new_categories += ","; + new_categories += categories; + } + } + else if (*categories == '-') + { + ++categories; + new_categories = mlog_get_categories(); + std::vector<std::string> single_categories; + boost::split(single_categories, categories, boost::is_any_of(","), boost::token_compress_on); + for (const std::string &s: single_categories) + { + size_t pos = new_categories.find(s); + if (pos != std::string::npos) + new_categories = new_categories.erase(pos, s.size()); + } + } + else + { + new_categories = categories; + } + } + el::Loggers::setCategories(new_categories.c_str(), true); + MLOG_LOG("New log categories: " << el::Loggers::getCategories()); +} + +std::string mlog_get_categories() +{ + return el::Loggers::getCategories(); } // maps epee style log level to new logging system void mlog_set_log_level(int level) { const char *categories = get_default_categories(level); - el::Loggers::setCategories(categories); - MLOG_LOG("New log categories: " << categories); + mlog_set_categories(categories); } void mlog_set_log(const char *log) diff --git a/contrib/epee/src/net_utils_base.cpp b/contrib/epee/src/net_utils_base.cpp new file mode 100644 index 000000000..22afcf819 --- /dev/null +++ b/contrib/epee/src/net_utils_base.cpp @@ -0,0 +1,60 @@ + +#include "net/net_utils_base.h" + +#include <cstring> +#include <typeindex> +#include "net/local_ip.h" + +namespace epee { namespace net_utils +{ + const uint8_t ipv4_network_address::ID; + + bool ipv4_network_address::equal(const ipv4_network_address& other) const noexcept + { return is_same_host(other) && port() == other.port(); } + + bool ipv4_network_address::less(const ipv4_network_address& other) const noexcept + { return is_same_host(other) ? port() < other.port() : ip() < other.ip(); } + + std::string ipv4_network_address::str() const + { return string_tools::get_ip_string_from_int32(ip()) + ":" + std::to_string(port()); } + + std::string ipv4_network_address::host_str() const { return string_tools::get_ip_string_from_int32(ip()); } + bool ipv4_network_address::is_loopback() const { return net_utils::is_ip_loopback(ip()); } + bool ipv4_network_address::is_local() const { return net_utils::is_ip_local(ip()); } + + + bool network_address::equal(const network_address& other) const + { + // clang typeid workaround + network_address::interface const* const self_ = self.get(); + network_address::interface const* const other_self = other.self.get(); + if (self_ == other_self) return true; + if (!self_ || !other_self) return false; + if (typeid(*self_) != typeid(*other_self)) return false; + return self_->equal(*other_self); + } + + bool network_address::less(const network_address& other) const + { + // clang typeid workaround + network_address::interface const* const self_ = self.get(); + network_address::interface const* const other_self = other.self.get(); + if (self_ == other_self) return false; + if (!self_ || !other_self) return self == nullptr; + if (typeid(*self_) != typeid(*other_self)) + return self_->get_type_id() < other_self->get_type_id(); + return self_->less(*other_self); + } + + bool network_address::is_same_host(const network_address& other) const + { + // clang typeid workaround + network_address::interface const* const self_ = self.get(); + network_address::interface const* const other_self = other.self.get(); + if (self_ == other_self) return true; + if (!self_ || !other_self) return false; + if (typeid(*self_) != typeid(*other_self)) return false; + return self_->is_same_host(*other_self); + } +}} + diff --git a/external/db_drivers/liblmdb/mdb.c b/external/db_drivers/liblmdb/mdb.c index 377512ebe..b3de9702f 100644 --- a/external/db_drivers/liblmdb/mdb.c +++ b/external/db_drivers/liblmdb/mdb.c @@ -6288,6 +6288,10 @@ release: if (rc) return rc; } +#ifdef MDB_VL32 + if (mc->mc_ovpg == mp) + mc->mc_ovpg = NULL; +#endif mc->mc_db->md_overflow_pages -= ovpages; return 0; } diff --git a/external/db_drivers/liblmdb/mdb_load.1 b/external/db_drivers/liblmdb/mdb_load.1 index 712ed0540..ede3702d9 100644 --- a/external/db_drivers/liblmdb/mdb_load.1 +++ b/external/db_drivers/liblmdb/mdb_load.1 @@ -37,6 +37,13 @@ option below. .BR \-V Write the library version number to the standard output, and exit. .TP +.BR \-a +Append all records in the order they appear in the input. The input is assumed to already be +in correctly sorted order and no sorting or checking for redundant values will be performed. +This option must be used to reload data that was produced by running +.B mdb_dump +on a database that uses custom compare functions. +.TP .BR \-f \ file Read from the specified file instead of from the standard input. .TP diff --git a/external/db_drivers/liblmdb/mdb_load.c b/external/db_drivers/liblmdb/mdb_load.c index d1fda4bf5..797c2f979 100644 --- a/external/db_drivers/liblmdb/mdb_load.c +++ b/external/db_drivers/liblmdb/mdb_load.c @@ -37,6 +37,7 @@ static int Eof; static MDB_envinfo info; static MDB_val kbuf, dbuf; +static MDB_val k0buf; #ifdef _WIN32 #define Z "I" @@ -285,10 +286,15 @@ badend: static void usage(void) { - fprintf(stderr, "usage: %s [-V] [-f input] [-n] [-s name] [-N] [-T] dbpath\n", prog); + fprintf(stderr, "usage: %s [-V] [-a] [-f input] [-n] [-s name] [-N] [-T] dbpath\n", prog); exit(EXIT_FAILURE); } +static int greater(const MDB_val *a, const MDB_val *b) +{ + return 1; +} + int main(int argc, char *argv[]) { int i, rc; @@ -298,7 +304,8 @@ int main(int argc, char *argv[]) MDB_dbi dbi; char *envname; int envflags = 0, putflags = 0; - int dohdr = 0; + int dohdr = 0, append = 0; + MDB_val prevk; prog = argv[0]; @@ -306,19 +313,23 @@ int main(int argc, char *argv[]) usage(); } - /* -f: load file instead of stdin + /* -a: append records in input order + * -f: load file instead of stdin * -n: use NOSUBDIR flag on env_open * -s: load into named subDB * -N: use NOOVERWRITE on puts * -T: read plaintext * -V: print version and exit */ - while ((i = getopt(argc, argv, "f:ns:NTV")) != EOF) { + while ((i = getopt(argc, argv, "af:ns:NTV")) != EOF) { switch(i) { case 'V': printf("%s\n", MDB_VERSION_STRING); exit(0); break; + case 'a': + append = 1; + break; case 'f': if (freopen(optarg, "r", stdin) == NULL) { fprintf(stderr, "%s: %s: reopen: %s\n", @@ -377,12 +388,17 @@ int main(int argc, char *argv[]) } kbuf.mv_size = mdb_env_get_maxkeysize(env) * 2 + 2; - kbuf.mv_data = malloc(kbuf.mv_size); + kbuf.mv_data = malloc(kbuf.mv_size * 2); + k0buf.mv_size = kbuf.mv_size; + k0buf.mv_data = (char *)kbuf.mv_data + kbuf.mv_size; + prevk.mv_size = 0; + prevk.mv_data = k0buf.mv_data; while(!Eof) { MDB_val key, data; int batch = 0; flags = 0; + int appflag; if (!dohdr) { dohdr = 1; @@ -400,6 +416,11 @@ int main(int argc, char *argv[]) fprintf(stderr, "mdb_open failed, error %d %s\n", rc, mdb_strerror(rc)); goto txn_abort; } + if (append) { + mdb_set_compare(txn, dbi, greater); + if (flags & MDB_DUPSORT) + mdb_set_dupsort(txn, dbi, greater); + } rc = mdb_cursor_open(txn, dbi, &mc); if (rc) { @@ -418,7 +439,20 @@ int main(int argc, char *argv[]) goto txn_abort; } - rc = mdb_cursor_put(mc, &key, &data, putflags); + if (append) { + appflag = MDB_APPEND; + if (flags & MDB_DUPSORT) { + if (prevk.mv_size == key.mv_size && !memcmp(prevk.mv_data, key.mv_data, key.mv_size)) + appflag = MDB_APPENDDUP; + else { + memcpy(prevk.mv_data, key.mv_data, key.mv_size); + prevk.mv_size = key.mv_size; + } + } + } else { + appflag = 0; + } + rc = mdb_cursor_put(mc, &key, &data, putflags|appflag); if (rc == MDB_KEYEXIST && putflags) continue; if (rc) { diff --git a/external/easylogging++/easylogging++.cc b/external/easylogging++/easylogging++.cc index 721b2af15..6bc6b2619 100644 --- a/external/easylogging++/easylogging++.cc +++ b/external/easylogging++/easylogging++.cc @@ -1961,8 +1961,13 @@ void VRegistry::setCategories(const char* categories, bool clear) { m_categories.push_back(std::make_pair(ss.str(), level)); }; - if (clear) + if (clear) { m_categories.clear(); + m_categoriesString.clear(); + } + if (!m_categoriesString.empty()) + m_categoriesString += ","; + m_categoriesString += categories; if (!categories) return; @@ -2001,6 +2006,11 @@ void VRegistry::setCategories(const char* categories, bool clear) { } } +std::string VRegistry::getCategories() { + base::threading::ScopedLock scopedLock(lock()); + return m_categoriesString; +} + // Log levels are sorted in a weird way... static int priority(Level level) { if (level == Level::Fatal) return 0; @@ -3073,6 +3083,10 @@ void Loggers::setCategories(const char* categories, bool clear) { ELPP->vRegistry()->setCategories(categories, clear); } +std::string Loggers::getCategories() { + return ELPP->vRegistry()->getCategories(); +} + void Loggers::clearCategories(void) { ELPP->vRegistry()->clearCategories(); } diff --git a/external/easylogging++/easylogging++.h b/external/easylogging++/easylogging++.h index 8f592899e..c55cce755 100644 --- a/external/easylogging++/easylogging++.h +++ b/external/easylogging++/easylogging++.h @@ -2488,6 +2488,8 @@ class VRegistry : base::NoCopy, public base::threading::ThreadSafe { void setCategories(const char* categories, bool clear = true); + std::string getCategories(); + void setModules(const char* modules); bool allowed(Level level, const char* category); @@ -2518,6 +2520,7 @@ class VRegistry : base::NoCopy, public base::threading::ThreadSafe { base::type::EnumType* m_pFlags; std::map<std::string, base::type::VerboseLevel> m_modules; std::deque<std::pair<std::string, Level>> m_categories; + std::string m_categoriesString; std::string m_filenameCommonPrefix; }; } // namespace base @@ -3953,6 +3956,8 @@ class Loggers : base::StaticClass { static void setVModules(const char* modules); /// @brief Sets categories as specified (on the fly) static void setCategories(const char* categories, bool clear = true); + /// @brief Gets current categories + static std::string getCategories(); /// @brief Clears vmodules static void clearVModules(void); /// @brief Clears categories diff --git a/external/miniupnpc/.gitignore b/external/miniupnpc/.gitignore index b1209ccd8..6bc38af20 100644 --- a/external/miniupnpc/.gitignore +++ b/external/miniupnpc/.gitignore @@ -4,6 +4,9 @@ build/ *.a *.so *.dll +*.dll.def +*.exe +*.lib *.dylib Makefile.bak miniupnpcstrings.h @@ -30,3 +33,4 @@ testigddescparse validateigddescparse dist/ miniupnpc.egg-info/ +init diff --git a/external/miniupnpc/Changelog.txt b/external/miniupnpc/Changelog.txt index 078bebce3..a3ef51809 100644 --- a/external/miniupnpc/Changelog.txt +++ b/external/miniupnpc/Changelog.txt @@ -1,6 +1,16 @@ -$Id: Changelog.txt,v 1.223 2016/04/19 21:06:20 nanard Exp $ +$Id: Changelog.txt,v 1.226 2016/12/16 08:57:19 nanard Exp $ miniUPnP client Changelog. +2017/05/05: + Fix CVE-2017-8798 Thanks to tin/Team OSTStrom + +2016/11/11: + check strlen before memcmp in XML parsing portlistingparse.c + fix build under SOLARIS and CYGWIN + +2016/10/11: + Add python 3 compatibility to IGD test + VERSION 2.0 : released 2016/04/19 2016/01/24: diff --git a/external/miniupnpc/LICENSE b/external/miniupnpc/LICENSE index cb5a06044..081673370 100644 --- a/external/miniupnpc/LICENSE +++ b/external/miniupnpc/LICENSE @@ -1,5 +1,5 @@ MiniUPnPc -Copyright (c) 2005-2015, Thomas BERNARD +Copyright (c) 2005-2016, Thomas BERNARD All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/external/miniupnpc/MANIFEST.in b/external/miniupnpc/MANIFEST.in index 54b86f95e..543ffd6ac 100644 --- a/external/miniupnpc/MANIFEST.in +++ b/external/miniupnpc/MANIFEST.in @@ -1,4 +1,6 @@ include README +include VERSION +include LICENSE include miniupnpcmodule.c include setup.py include *.h diff --git a/external/miniupnpc/Makefile b/external/miniupnpc/Makefile index b7826caa1..178fbf5d3 100644 --- a/external/miniupnpc/Makefile +++ b/external/miniupnpc/Makefile @@ -1,9 +1,9 @@ -# $Id: Makefile,v 1.133 2016/01/24 17:24:35 nanard Exp $ +# $Id: Makefile,v 1.134 2016/10/07 09:04:36 nanard Exp $ # MiniUPnP Project # http://miniupnp.free.fr/ # http://miniupnp.tuxfamily.org/ # https://github.com/miniupnp/miniupnp -# (c) 2005-2015 Thomas Bernard +# (c) 2005-2017 Thomas Bernard # to install use : # $ make DESTDIR=/tmp/dummylocation install # or @@ -48,7 +48,7 @@ CFLAGS += -D_XOPEN_SOURCE=600 endif endif #CFLAGS += -ansi -# -DNO_GETADDRINFO +#CFLAGS += -DNO_GETADDRINFO INSTALL = install SH = /bin/sh JAVA = java @@ -65,7 +65,9 @@ JNAERATORARGS = -mode StandaloneJar -runtime JNAerator -library miniupnpc JNAERATORBASEURL = https://repo1.maven.org/maven2/com/nativelibs4java/jnaerator/0.12 ifeq (SunOS, $(OS)) - LDFLAGS=-lsocket -lnsl -lresolv + LDLIBS=-lsocket -lnsl -lresolv + CFLAGS += -D__EXTENSIONS__ + CFLAGS += -std=c99 endif # APIVERSION is used to build SONAME @@ -85,7 +87,9 @@ LIBOBJS = miniwget.o minixml.o igd_desc_parse.o minisoap.o \ connecthostport.o portlistingparse.o receivedata.o upnpdev.o ifneq ($(OS), AmigaOS) +ifeq (,$(findstring CYGWIN,$(OS))) CFLAGS := -fPIC $(CFLAGS) +endif LIBOBJS := $(LIBOBJS) minissdpc.o endif @@ -169,18 +173,18 @@ check: validateminixml validateminiwget validateupnpreplyparse \ everything: all $(EXECUTABLES_ADDTESTS) pythonmodule: $(LIBRARY) miniupnpcmodule.c setup.py - python setup.py build + MAKE=$(MAKE) python setup.py build touch $@ installpythonmodule: pythonmodule - python setup.py install + MAKE=$(MAKE) python setup.py install pythonmodule3: $(LIBRARY) miniupnpcmodule.c setup.py - python3 setup.py build + MAKE=$(MAKE) python3 setup.py build touch $@ installpythonmodule3: pythonmodule3 - python3 setup.py install + MAKE=$(MAKE) python3 setup.py install validateminixml: minixmlvalid @echo "minixml validation test" diff --git a/external/miniupnpc/README b/external/miniupnpc/README index 91535dbc8..0d3b8054c 100644 --- a/external/miniupnpc/README +++ b/external/miniupnpc/README @@ -1,9 +1,8 @@ Project: miniupnp -Project web page: http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ +Project web page: http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ github: https://github.com/miniupnp/miniupnp -freecode: http://freecode.com/projects/miniupnp Author: Thomas Bernard -Copyright (c) 2005-2016 Thomas Bernard +Copyright (c) 2005-2017 Thomas Bernard This software is subject to the conditions detailed in the LICENSE file provided within this distribution. @@ -58,7 +57,7 @@ If you are using libminiupnpc in your application, please send me an email ! For any question, you can use the web forum : -http://miniupnp.tuxfamily.org/forum/ +https://miniupnp.tuxfamily.org/forum/ Bugs should be reported on github : https://github.com/miniupnp/miniupnp/issues diff --git a/external/miniupnpc/connecthostport.c b/external/miniupnpc/connecthostport.c index 1f2a032ee..d28aaf5e0 100644 --- a/external/miniupnpc/connecthostport.c +++ b/external/miniupnpc/connecthostport.c @@ -1,7 +1,7 @@ /* $Id: connecthostport.c,v 1.15 2015/10/09 16:26:19 nanard Exp $ */ /* Project : miniupnp * Author : Thomas Bernard - * Copyright (c) 2010-2015 Thomas Bernard + * Copyright (c) 2010-2017 Thomas Bernard * This software is subject to the conditions detailed in the * LICENCE file provided in this distribution. */ @@ -36,15 +36,13 @@ /* defining MINIUPNPC_IGNORE_EINTR enable the ignore of interruptions * during the connect() call */ #define MINIUPNPC_IGNORE_EINTR -#ifndef USE_GETHOSTBYNAME #include <sys/socket.h> #include <sys/select.h> -#endif /* #ifndef USE_GETHOSTBYNAME */ #endif /* #else _WIN32 */ /* definition of PRINT_SOCKET_ERROR */ #ifdef _WIN32 -#define PRINT_SOCKET_ERROR(x) printf("Socket error: %s, %d\n", x, WSAGetLastError()); +#define PRINT_SOCKET_ERROR(x) fprintf(stderr, "Socket error: %s, %d\n", x, WSAGetLastError()); #else #define PRINT_SOCKET_ERROR(x) perror(x) #endif @@ -100,13 +98,13 @@ int connecthostport(const char * host, unsigned short port, timeout.tv_usec = 0; if(setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(struct timeval)) < 0) { - PRINT_SOCKET_ERROR("setsockopt"); + PRINT_SOCKET_ERROR("setsockopt SO_RCVTIMEO"); } timeout.tv_sec = 3; timeout.tv_usec = 0; if(setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(struct timeval)) < 0) { - PRINT_SOCKET_ERROR("setsockopt"); + PRINT_SOCKET_ERROR("setsockopt SO_SNDTIMEO"); } #endif /* #ifdef MINIUPNPC_SET_SOCKET_TIMEOUT */ dest.sin_family = AF_INET; diff --git a/external/miniupnpc/minihttptestserver.c b/external/miniupnpc/minihttptestserver.c index 1848bde26..e4cdc203c 100644 --- a/external/miniupnpc/minihttptestserver.c +++ b/external/miniupnpc/minihttptestserver.c @@ -1,7 +1,7 @@ /* $Id: minihttptestserver.c,v 1.19 2015/11/17 09:07:17 nanard Exp $ */ /* Project : miniUPnP * Author : Thomas Bernard - * Copyright (c) 2011-2015 Thomas Bernard + * Copyright (c) 2011-2016 Thomas Bernard * This software is subject to the conditions detailed in the * LICENCE file provided in this distribution. * */ @@ -611,7 +611,7 @@ int main(int argc, char * * argv) { if(pid < 0) { perror("wait"); } else { - printf("child(%d) terminated with status %d\n", pid, status); + printf("child(%d) terminated with status %d\n", (int)pid, status); } --child_to_wait_for; } @@ -648,7 +648,7 @@ int main(int argc, char * * argv) { if(pid < 0) { perror("wait"); } else { - printf("child(%d) terminated with status %d\n", pid, status); + printf("child(%d) terminated with status %d\n", (int)pid, status); } --child_to_wait_for; } diff --git a/external/miniupnpc/minisoap.c b/external/miniupnpc/minisoap.c index 5c9a11438..76225f4b6 100644 --- a/external/miniupnpc/minisoap.c +++ b/external/miniupnpc/minisoap.c @@ -25,7 +25,7 @@ #include <stdlib.h> #ifdef _WIN32 -#define PRINT_SOCKET_ERROR(x) printf("Socket error: %s, %d\n", x, WSAGetLastError()); +#define PRINT_SOCKET_ERROR(x) fprintf(stderr, "Socket error: %s, %d\n", x, WSAGetLastError()); #else #define PRINT_SOCKET_ERROR(x) perror(x) #endif diff --git a/external/miniupnpc/minissdpc.c b/external/miniupnpc/minissdpc.c index dc4f94702..8eee2e927 100644 --- a/external/miniupnpc/minissdpc.c +++ b/external/miniupnpc/minissdpc.c @@ -1,9 +1,9 @@ -/* $Id: minissdpc.c,v 1.28 2015/09/18 13:05:39 nanard Exp $ */ +/* $Id: minissdpc.c,v 1.32 2016/10/07 09:04:36 nanard Exp $ */ /* vim: tabstop=4 shiftwidth=4 noexpandtab * Project : miniupnp * Web : http://miniupnp.free.fr/ * Author : Thomas BERNARD - * copyright (c) 2005-2015 Thomas Bernard + * copyright (c) 2005-2017 Thomas Bernard * This software is subjet to the conditions detailed in the * provided LICENCE file. */ /*#include <syslog.h>*/ @@ -62,7 +62,7 @@ struct sockaddr_un { #endif #ifdef _WIN32 -#define PRINT_SOCKET_ERROR(x) printf("Socket error: %s, %d\n", x, WSAGetLastError()); +#define PRINT_SOCKET_ERROR(x) fprintf(stderr, "Socket error: %s, %d\n", x, WSAGetLastError()); #else #define PRINT_SOCKET_ERROR(x) perror(x) #endif @@ -73,6 +73,9 @@ struct sockaddr_un { #if !defined(HAS_IP_MREQN) && !defined(_WIN32) #include <sys/ioctl.h> +#if defined(__sun) +#include <sys/sockio.h> +#endif #endif #if defined(HAS_IP_MREQN) && defined(NEED_STRUCT_IP_MREQN) @@ -169,7 +172,7 @@ connectToMiniSSDPD(const char * socketpath) { int s; struct sockaddr_un addr; -#ifdef MINIUPNPC_SET_SOCKET_TIMEOUT +#if defined(MINIUPNPC_SET_SOCKET_TIMEOUT) && !defined(__sun) struct timeval timeout; #endif /* #ifdef MINIUPNPC_SET_SOCKET_TIMEOUT */ @@ -180,23 +183,25 @@ connectToMiniSSDPD(const char * socketpath) perror("socket(unix)"); return MINISSDPC_SOCKET_ERROR; } -#ifdef MINIUPNPC_SET_SOCKET_TIMEOUT +#if defined(MINIUPNPC_SET_SOCKET_TIMEOUT) && !defined(__sun) /* setting a 3 seconds timeout */ + /* not supported for AF_UNIX sockets under Solaris */ timeout.tv_sec = 3; timeout.tv_usec = 0; if(setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(struct timeval)) < 0) { - perror("setsockopt"); + perror("setsockopt SO_RCVTIMEO unix"); } timeout.tv_sec = 3; timeout.tv_usec = 0; if(setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(struct timeval)) < 0) { - perror("setsockopt"); + perror("setsockopt SO_SNDTIMEO unix"); } #endif /* #ifdef MINIUPNPC_SET_SOCKET_TIMEOUT */ if(!socketpath) socketpath = "/var/run/minissdpd.sock"; + memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, socketpath, sizeof(addr.sun_path)); /* TODO : check if we need to handle the EINTR */ @@ -498,6 +503,7 @@ ssdpDiscoverDevices(const char * const deviceTypes[], unsigned long _ttl = (unsigned long)ttl; #endif int linklocal = 1; + int sentok; if(error) *error = MINISSDPC_UNKNOWN_ERROR; @@ -608,14 +614,22 @@ ssdpDiscoverDevices(const char * const deviceTypes[], return NULL; } + if(ipv6) { + int mcastHops = ttl; + if(setsockopt(sudp, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &mcastHops, sizeof(mcastHops)) < 0) + { + PRINT_SOCKET_ERROR("setsockopt(IPV6_MULTICAST_HOPS,...)"); + } + } else { #ifdef _WIN32 - if(setsockopt(sudp, IPPROTO_IP, IP_MULTICAST_TTL, (const char *)&_ttl, sizeof(_ttl)) < 0) + if(setsockopt(sudp, IPPROTO_IP, IP_MULTICAST_TTL, (const char *)&_ttl, sizeof(_ttl)) < 0) #else /* _WIN32 */ - if(setsockopt(sudp, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) < 0) + if(setsockopt(sudp, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) < 0) #endif /* _WIN32 */ - { - /* not a fatal error */ - PRINT_SOCKET_ERROR("setsockopt(IP_MULTICAST_TTL,...)"); + { + /* not a fatal error */ + PRINT_SOCKET_ERROR("setsockopt(IP_MULTICAST_TTL,...)"); + } } if(multicastif) @@ -628,7 +642,7 @@ ssdpDiscoverDevices(const char * const deviceTypes[], unsigned int ifindex = if_nametoindex(multicastif); /* eth0, etc. */ if(setsockopt(sudp, IPPROTO_IPV6, IPV6_MULTICAST_IF, &ifindex, sizeof(ifindex)) < 0) { - PRINT_SOCKET_ERROR("setsockopt"); + PRINT_SOCKET_ERROR("setsockopt IPV6_MULTICAST_IF"); } #else #ifdef DEBUG @@ -643,7 +657,7 @@ ssdpDiscoverDevices(const char * const deviceTypes[], ((struct sockaddr_in *)&sockudp_r)->sin_addr.s_addr = mc_if.s_addr; if(setsockopt(sudp, IPPROTO_IP, IP_MULTICAST_IF, (const char *)&mc_if, sizeof(mc_if)) < 0) { - PRINT_SOCKET_ERROR("setsockopt"); + PRINT_SOCKET_ERROR("setsockopt IP_MULTICAST_IF"); } } else { #ifdef HAS_IP_MREQN @@ -653,7 +667,7 @@ ssdpDiscoverDevices(const char * const deviceTypes[], reqn.imr_ifindex = if_nametoindex(multicastif); if(setsockopt(sudp, IPPROTO_IP, IP_MULTICAST_IF, (const char *)&reqn, sizeof(reqn)) < 0) { - PRINT_SOCKET_ERROR("setsockopt"); + PRINT_SOCKET_ERROR("setsockopt IP_MULTICAST_IF"); } #elif !defined(_WIN32) struct ifreq ifr; @@ -667,7 +681,7 @@ ssdpDiscoverDevices(const char * const deviceTypes[], mc_if.s_addr = ((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr.s_addr; if(setsockopt(sudp, IPPROTO_IP, IP_MULTICAST_IF, (const char *)&mc_if, sizeof(mc_if)) < 0) { - PRINT_SOCKET_ERROR("setsockopt"); + PRINT_SOCKET_ERROR("setsockopt IP_MULTICAST_IF"); } #else /* _WIN32 */ #ifdef DEBUG @@ -700,6 +714,7 @@ ssdpDiscoverDevices(const char * const deviceTypes[], } /* receiving SSDP response packet */ for(deviceIndex = 0; deviceTypes[deviceIndex]; deviceIndex++) { + sentok = 0; /* sending the SSDP M-SEARCH packet */ n = snprintf(bufr, sizeof(bufr), MSearchMsgFmt, @@ -743,7 +758,8 @@ ssdpDiscoverDevices(const char * const deviceTypes[], if(error) *error = MINISSDPC_SOCKET_ERROR; PRINT_SOCKET_ERROR("sendto"); - break; + } else { + sentok = 1; } #else /* #ifdef NO_GETADDRINFO */ memset(&hints, 0, sizeof(hints)); @@ -775,19 +791,20 @@ ssdpDiscoverDevices(const char * const deviceTypes[], #endif PRINT_SOCKET_ERROR("sendto"); continue; + } else { + sentok = 1; } } freeaddrinfo(servinfo); - if(n < 0) { + if(!sentok) { if(error) *error = MINISSDPC_SOCKET_ERROR; - break; } #endif /* #ifdef NO_GETADDRINFO */ /* Waiting for SSDP REPLY packet to M-SEARCH * if searchalltypes is set, enter the loop only * when the last deviceType is reached */ - if(!searchalltypes || !deviceTypes[deviceIndex + 1]) do { + if((sentok && !searchalltypes) || !deviceTypes[deviceIndex + 1]) do { n = receivedata(sudp, bufr, sizeof(bufr), delay, &scope_id); if (n < 0) { /* error */ diff --git a/external/miniupnpc/miniupnpcmodule.c b/external/miniupnpc/miniupnpcmodule.c index fd970476c..8ffbf51a1 100644 --- a/external/miniupnpc/miniupnpcmodule.c +++ b/external/miniupnpc/miniupnpcmodule.c @@ -2,7 +2,7 @@ /* Project : miniupnp * Author : Thomas BERNARD * website : http://miniupnp.tuxfamily.org/ - * copyright (c) 2007-2014 Thomas Bernard + * copyright (c) 2007-2016 Thomas Bernard * This software is subjet to the conditions detailed in the * provided LICENCE file. */ #include <Python.h> @@ -12,6 +12,10 @@ #include "upnpcommands.h" #include "upnperrors.h" +#ifdef _WIN32 +#include <winsock2.h> +#endif + /* for compatibility with Python < 2.4 */ #ifndef Py_RETURN_NONE #define Py_RETURN_NONE return Py_INCREF(Py_None), Py_None @@ -303,7 +307,7 @@ UPnP_addportmapping(UPnPObject *self, PyObject *args) const char * remoteHost; const char * leaseDuration = "0"; int r; - if (!PyArg_ParseTuple(args, "HssHss", &ePort, &proto, + if (!PyArg_ParseTuple(args, "HssHzz", &ePort, &proto, &host, &iPort, &desc, &remoteHost)) return NULL; Py_BEGIN_ALLOW_THREADS @@ -345,7 +349,7 @@ UPnP_addanyportmapping(UPnPObject *self, PyObject *args) const char * remoteHost; const char * leaseDuration = "0"; int r; - if (!PyArg_ParseTuple(args, "HssHss", &ePort, &proto, &host, &iPort, &desc, &remoteHost)) + if (!PyArg_ParseTuple(args, "HssHzz", &ePort, &proto, &host, &iPort, &desc, &remoteHost)) return NULL; Py_BEGIN_ALLOW_THREADS sprintf(extPort, "%hu", ePort); @@ -669,6 +673,10 @@ initminiupnpc(void) PyObject* m; #ifdef _WIN32 + /* initialize Winsock. */ + WSADATA wsaData; + int nResult = WSAStartup(MAKEWORD(2,2), &wsaData); + UPnPType.tp_new = PyType_GenericNew; #endif if (PyType_Ready(&UPnPType) < 0) diff --git a/external/miniupnpc/miniwget.c b/external/miniupnpc/miniwget.c index ca88a1e9d..1eda57c5e 100644 --- a/external/miniupnpc/miniwget.c +++ b/external/miniupnpc/miniwget.c @@ -2,7 +2,7 @@ /* Project : miniupnp * Website : http://miniupnp.free.fr/ * Author : Thomas Bernard - * Copyright (c) 2005-2016 Thomas Bernard + * Copyright (c) 2005-2017 Thomas Bernard * This software is subject to the conditions detailed in the * LICENCE file provided in this distribution. */ @@ -83,8 +83,10 @@ getHTTPResponse(int s, int * size, int * status_code) unsigned int content_buf_used = 0; char chunksize_buf[32]; unsigned int chunksize_buf_index; +#ifdef DEBUG char * reason_phrase = NULL; int reason_phrase_len = 0; +#endif if(status_code) *status_code = -1; header_buf = malloc(header_buf_len); @@ -109,7 +111,7 @@ getHTTPResponse(int s, int * size, int * status_code) chunksize_buf[0] = '\0'; chunksize_buf_index = 0; - while((n = receivedata(s, buf, 2048, 5000, NULL)) > 0) + while((n = receivedata(s, buf, sizeof(buf), 5000, NULL)) > 0) { if(endofheaders == 0) { @@ -181,8 +183,10 @@ getHTTPResponse(int s, int * size, int * status_code) *status_code = atoi(header_buf + sp + 1); else { +#ifdef DEBUG reason_phrase = header_buf + sp + 1; reason_phrase_len = i - sp - 1; +#endif break; } } @@ -280,11 +284,12 @@ getHTTPResponse(int s, int * size, int * status_code) goto end_of_stream; } } - bytestocopy = ((int)chunksize < (n - i))?chunksize:(unsigned int)(n - i); + /* it is guaranteed that (n >= i) */ + bytestocopy = (chunksize < (unsigned int)(n - i))?chunksize:(unsigned int)(n - i); if((content_buf_used + bytestocopy) > content_buf_len) { char * tmp; - if(content_length >= (int)(content_buf_used + bytestocopy)) { + if((content_length >= 0) && ((unsigned int)content_length >= (content_buf_used + bytestocopy))) { content_buf_len = content_length; } else { content_buf_len = content_buf_used + bytestocopy; @@ -309,14 +314,15 @@ getHTTPResponse(int s, int * size, int * status_code) { /* not chunked */ if(content_length > 0 - && (int)(content_buf_used + n) > content_length) { + && (content_buf_used + n) > (unsigned int)content_length) { /* skipping additional bytes */ n = content_length - content_buf_used; } if(content_buf_used + n > content_buf_len) { char * tmp; - if(content_length >= (int)(content_buf_used + n)) { + if(content_length >= 0 + && (unsigned int)content_length >= (content_buf_used + n)) { content_buf_len = content_length; } else { content_buf_len = content_buf_used + n; @@ -336,7 +342,7 @@ getHTTPResponse(int s, int * size, int * status_code) } } /* use the Content-Length header value if available */ - if(content_length > 0 && (int)content_buf_used >= content_length) + if(content_length > 0 && content_buf_used >= (unsigned int)content_length) { #ifdef DEBUG printf("End of HTTP content\n"); diff --git a/external/miniupnpc/msvc/.gitignore b/external/miniupnpc/msvc/.gitignore new file mode 100644 index 000000000..6d88bf128 --- /dev/null +++ b/external/miniupnpc/msvc/.gitignore @@ -0,0 +1,4 @@ +*.db +.vs +Debug/ +Release/ diff --git a/external/miniupnpc/msvc/miniupnpc.vcxproj b/external/miniupnpc/msvc/miniupnpc.vcxproj new file mode 100644 index 000000000..2725136e3 --- /dev/null +++ b/external/miniupnpc/msvc/miniupnpc.vcxproj @@ -0,0 +1,111 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup Label="ProjectConfigurations"> + <ProjectConfiguration Include="Debug|Win32"> + <Configuration>Debug</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|Win32"> + <Configuration>Release</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + </ItemGroup> + <PropertyGroup Label="Globals"> + <ProjectGuid>{D28CE435-CB33-4BAE-8A52-C6EF915956F5}</ProjectGuid> + <RootNamespace>miniupnpc</RootNamespace> + <Keyword>Win32Proj</Keyword> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> + <ConfigurationType>StaticLibrary</ConfigurationType> + <PlatformToolset>v140</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + <WholeProgramOptimization>true</WholeProgramOptimization> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> + <ConfigurationType>StaticLibrary</ConfigurationType> + <PlatformToolset>v140</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <ImportGroup Label="ExtensionSettings"> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <PropertyGroup Label="UserMacros" /> + <PropertyGroup> + <_ProjectFileVersion>14.0.25123.0</_ProjectFileVersion> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <OutDir>$(SolutionDir)$(Configuration)\</OutDir> + <IntDir>$(Configuration)\</IntDir> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <OutDir>$(SolutionDir)$(Configuration)\</OutDir> + <IntDir>$(Configuration)\</IntDir> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <ClCompile> + <Optimization>Disabled</Optimization> + <PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;MINIUPNP_STATICLIB;DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>true</MinimalRebuild> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <PrecompiledHeader /> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>EditAndContinue</DebugInformationFormat> + </ClCompile> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <ClCompile> + <Optimization>MaxSpeed</Optimization> + <IntrinsicFunctions>true</IntrinsicFunctions> + <PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;MINIUPNP_STATICLIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <FunctionLevelLinking>true</FunctionLevelLinking> + <PrecompiledHeader /> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + </ClCompile> + </ItemDefinitionGroup> + <ItemGroup> + <ClCompile Include="..\connecthostport.c" /> + <ClCompile Include="..\igd_desc_parse.c" /> + <ClCompile Include="..\minisoap.c" /> + <ClCompile Include="..\minissdpc.c" /> + <ClCompile Include="..\miniupnpc.c" /> + <ClCompile Include="..\miniwget.c" /> + <ClCompile Include="..\minixml.c" /> + <ClCompile Include="..\portlistingparse.c" /> + <ClCompile Include="..\receivedata.c" /> + <ClCompile Include="..\upnpcommands.c" /> + <ClCompile Include="..\upnpdev.c" /> + <ClCompile Include="..\upnperrors.c" /> + <ClCompile Include="..\upnpreplyparse.c" /> + </ItemGroup> + <ItemGroup> + <ClInclude Include="..\connecthostport.h" /> + <ClInclude Include="..\declspec.h" /> + <ClInclude Include="..\igd_desc_parse.h" /> + <ClInclude Include="..\minisoap.h" /> + <ClInclude Include="..\minissdpc.h" /> + <ClInclude Include="..\miniupnpc.h" /> + <ClInclude Include="..\miniupnpcstrings.h" /> + <ClInclude Include="..\miniupnpctypes.h" /> + <ClInclude Include="..\miniwget.h" /> + <ClInclude Include="..\minixml.h" /> + <ClInclude Include="..\portlistingparse.h" /> + <ClInclude Include="..\receivedata.h" /> + <ClInclude Include="..\upnpcommands.h" /> + <ClInclude Include="..\upnpdev.h" /> + <ClInclude Include="..\upnperrors.h" /> + <ClInclude Include="..\upnpreplyparse.h" /> + </ItemGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <ImportGroup Label="ExtensionTargets"> + </ImportGroup> +</Project>
\ No newline at end of file diff --git a/external/miniupnpc/msvc/miniupnpc.vcxproj.filters b/external/miniupnpc/msvc/miniupnpc.vcxproj.filters new file mode 100644 index 000000000..01a4dbeb2 --- /dev/null +++ b/external/miniupnpc/msvc/miniupnpc.vcxproj.filters @@ -0,0 +1,108 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <Filter Include="Fichiers sources"> + <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier> + <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions> + </Filter> + <Filter Include="Fichiers d%27en-tête"> + <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier> + <Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions> + </Filter> + <Filter Include="Fichiers de ressources"> + <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier> + <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav</Extensions> + </Filter> + </ItemGroup> + <ItemGroup> + <ClCompile Include="..\connecthostport.c"> + <Filter>Fichiers sources</Filter> + </ClCompile> + <ClCompile Include="..\igd_desc_parse.c"> + <Filter>Fichiers sources</Filter> + </ClCompile> + <ClCompile Include="..\minisoap.c"> + <Filter>Fichiers sources</Filter> + </ClCompile> + <ClCompile Include="..\minissdpc.c"> + <Filter>Fichiers sources</Filter> + </ClCompile> + <ClCompile Include="..\miniupnpc.c"> + <Filter>Fichiers sources</Filter> + </ClCompile> + <ClCompile Include="..\miniwget.c"> + <Filter>Fichiers sources</Filter> + </ClCompile> + <ClCompile Include="..\minixml.c"> + <Filter>Fichiers sources</Filter> + </ClCompile> + <ClCompile Include="..\portlistingparse.c"> + <Filter>Fichiers sources</Filter> + </ClCompile> + <ClCompile Include="..\receivedata.c"> + <Filter>Fichiers sources</Filter> + </ClCompile> + <ClCompile Include="..\upnpcommands.c"> + <Filter>Fichiers sources</Filter> + </ClCompile> + <ClCompile Include="..\upnpdev.c"> + <Filter>Fichiers sources</Filter> + </ClCompile> + <ClCompile Include="..\upnperrors.c"> + <Filter>Fichiers sources</Filter> + </ClCompile> + <ClCompile Include="..\upnpreplyparse.c"> + <Filter>Fichiers sources</Filter> + </ClCompile> + </ItemGroup> + <ItemGroup> + <ClInclude Include="..\connecthostport.h"> + <Filter>Fichiers d%27en-tête</Filter> + </ClInclude> + <ClInclude Include="..\declspec.h"> + <Filter>Fichiers d%27en-tête</Filter> + </ClInclude> + <ClInclude Include="..\igd_desc_parse.h"> + <Filter>Fichiers d%27en-tête</Filter> + </ClInclude> + <ClInclude Include="..\minisoap.h"> + <Filter>Fichiers d%27en-tête</Filter> + </ClInclude> + <ClInclude Include="..\minissdpc.h"> + <Filter>Fichiers d%27en-tête</Filter> + </ClInclude> + <ClInclude Include="..\miniupnpc.h"> + <Filter>Fichiers d%27en-tête</Filter> + </ClInclude> + <ClInclude Include="..\miniupnpcstrings.h"> + <Filter>Fichiers d%27en-tête</Filter> + </ClInclude> + <ClInclude Include="..\miniupnpctypes.h"> + <Filter>Fichiers d%27en-tête</Filter> + </ClInclude> + <ClInclude Include="..\miniwget.h"> + <Filter>Fichiers d%27en-tête</Filter> + </ClInclude> + <ClInclude Include="..\minixml.h"> + <Filter>Fichiers d%27en-tête</Filter> + </ClInclude> + <ClInclude Include="..\portlistingparse.h"> + <Filter>Fichiers d%27en-tête</Filter> + </ClInclude> + <ClInclude Include="..\receivedata.h"> + <Filter>Fichiers d%27en-tête</Filter> + </ClInclude> + <ClInclude Include="..\upnpcommands.h"> + <Filter>Fichiers d%27en-tête</Filter> + </ClInclude> + <ClInclude Include="..\upnpdev.h"> + <Filter>Fichiers d%27en-tête</Filter> + </ClInclude> + <ClInclude Include="..\upnperrors.h"> + <Filter>Fichiers d%27en-tête</Filter> + </ClInclude> + <ClInclude Include="..\upnpreplyparse.h"> + <Filter>Fichiers d%27en-tête</Filter> + </ClInclude> + </ItemGroup> +</Project>
\ No newline at end of file diff --git a/external/miniupnpc/msvc/miniupnpc_vs2015.sln b/external/miniupnpc/msvc/miniupnpc_vs2015.sln new file mode 100644 index 000000000..27a43f6c3 --- /dev/null +++ b/external/miniupnpc/msvc/miniupnpc_vs2015.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25123.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "miniupnpc", "miniupnpc.vcxproj", "{D28CE435-CB33-4BAE-8A52-C6EF915956F5}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "upnpc-static", "upnpc-static.vcxproj", "{469E1CF6-08A2-4B7B-A2AA-5BDB089857C1}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D28CE435-CB33-4BAE-8A52-C6EF915956F5}.Debug|Win32.ActiveCfg = Debug|Win32 + {D28CE435-CB33-4BAE-8A52-C6EF915956F5}.Debug|Win32.Build.0 = Debug|Win32 + {D28CE435-CB33-4BAE-8A52-C6EF915956F5}.Release|Win32.ActiveCfg = Release|Win32 + {D28CE435-CB33-4BAE-8A52-C6EF915956F5}.Release|Win32.Build.0 = Release|Win32 + {469E1CF6-08A2-4B7B-A2AA-5BDB089857C1}.Debug|Win32.ActiveCfg = Debug|Win32 + {469E1CF6-08A2-4B7B-A2AA-5BDB089857C1}.Debug|Win32.Build.0 = Debug|Win32 + {469E1CF6-08A2-4B7B-A2AA-5BDB089857C1}.Release|Win32.ActiveCfg = Release|Win32 + {469E1CF6-08A2-4B7B-A2AA-5BDB089857C1}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/external/miniupnpc/msvc/upnpc-static.vcxproj b/external/miniupnpc/msvc/upnpc-static.vcxproj new file mode 100644 index 000000000..44dea81e8 --- /dev/null +++ b/external/miniupnpc/msvc/upnpc-static.vcxproj @@ -0,0 +1,103 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup Label="ProjectConfigurations"> + <ProjectConfiguration Include="Debug|Win32"> + <Configuration>Debug</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|Win32"> + <Configuration>Release</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + </ItemGroup> + <PropertyGroup Label="Globals"> + <ProjectGuid>{469E1CF6-08A2-4B7B-A2AA-5BDB089857C1}</ProjectGuid> + <RootNamespace>upnpcstatic</RootNamespace> + <Keyword>Win32Proj</Keyword> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <PlatformToolset>v140</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + <WholeProgramOptimization>true</WholeProgramOptimization> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <PlatformToolset>v140</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <ImportGroup Label="ExtensionSettings"> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <PropertyGroup Label="UserMacros" /> + <PropertyGroup> + <_ProjectFileVersion>14.0.25123.0</_ProjectFileVersion> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <OutDir>$(SolutionDir)$(Configuration)\</OutDir> + <IntDir>$(Configuration)\</IntDir> + <LinkIncremental>true</LinkIncremental> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <OutDir>$(SolutionDir)$(Configuration)\</OutDir> + <IntDir>$(Configuration)\</IntDir> + <LinkIncremental>false</LinkIncremental> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <ClCompile> + <Optimization>Disabled</Optimization> + <PreprocessorDefinitions>_DEBUG;_CONSOLE;MINIUPNP_STATICLIB;DEBUG;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <MinimalRebuild>true</MinimalRebuild> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <PrecompiledHeader /> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>EditAndContinue</DebugInformationFormat> + </ClCompile> + <Link> + <AdditionalDependencies>ws2_32.lib;IPHlpApi.Lib;Debug\miniupnpc.lib;%(AdditionalDependencies)</AdditionalDependencies> + <GenerateDebugInformation>true</GenerateDebugInformation> + <SubSystem>Console</SubSystem> + <TargetMachine>MachineX86</TargetMachine> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <ClCompile> + <Optimization>MaxSpeed</Optimization> + <IntrinsicFunctions>true</IntrinsicFunctions> + <PreprocessorDefinitions>NDEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;MINIUPNP_STATICLIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <FunctionLevelLinking>true</FunctionLevelLinking> + <PrecompiledHeader /> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + </ClCompile> + <Link> + <AdditionalDependencies>ws2_32.lib;IPHlpApi.Lib;Release\miniupnpc.lib;%(AdditionalDependencies)</AdditionalDependencies> + <GenerateDebugInformation>true</GenerateDebugInformation> + <SubSystem>Console</SubSystem> + <OptimizeReferences>true</OptimizeReferences> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <TargetMachine>MachineX86</TargetMachine> + </Link> + </ItemDefinitionGroup> + <ItemGroup> + <ClCompile Include="..\upnpc.c" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="miniupnpc.vcxproj"> + <Project>{d28ce435-cb33-4bae-8a52-c6ef915956f5}</Project> + <ReferenceOutputAssembly>false</ReferenceOutputAssembly> + </ProjectReference> + </ItemGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <ImportGroup Label="ExtensionTargets"> + </ImportGroup> +</Project>
\ No newline at end of file diff --git a/external/miniupnpc/msvc/upnpc-static.vcxproj.filters b/external/miniupnpc/msvc/upnpc-static.vcxproj.filters new file mode 100644 index 000000000..2e75de02f --- /dev/null +++ b/external/miniupnpc/msvc/upnpc-static.vcxproj.filters @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <Filter Include="Fichiers sources"> + <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier> + <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions> + </Filter> + <Filter Include="Fichiers d%27en-tête"> + <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier> + <Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions> + </Filter> + <Filter Include="Fichiers de ressources"> + <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier> + <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav</Extensions> + </Filter> + </ItemGroup> + <ItemGroup> + <ClCompile Include="..\upnpc.c"> + <Filter>Fichiers sources</Filter> + </ClCompile> + </ItemGroup> +</Project>
\ No newline at end of file diff --git a/external/miniupnpc/portlistingparse.c b/external/miniupnpc/portlistingparse.c index 0e0927803..55859f271 100644 --- a/external/miniupnpc/portlistingparse.c +++ b/external/miniupnpc/portlistingparse.c @@ -1,7 +1,7 @@ /* $Id: portlistingparse.c,v 1.9 2015/07/15 12:41:13 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ - * (c) 2011-2015 Thomas Bernard + * (c) 2011-2016 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include <string.h> @@ -55,7 +55,7 @@ startelt(void * d, const char * name, int l) pdata->curelt = PortMappingEltNone; for(i = 0; elements[i].str; i++) { - if(memcmp(name, elements[i].str, l) == 0) + if(strlen(elements[i].str) == (size_t)l && memcmp(name, elements[i].str, l) == 0) { pdata->curelt = elements[i].code; break; diff --git a/external/miniupnpc/receivedata.c b/external/miniupnpc/receivedata.c index ef85a3db4..d003eb69d 100644 --- a/external/miniupnpc/receivedata.c +++ b/external/miniupnpc/receivedata.c @@ -28,7 +28,7 @@ #endif /* _WIN32 */ #ifdef _WIN32 -#define PRINT_SOCKET_ERROR(x) printf("Socket error: %s, %d\n", x, WSAGetLastError()); +#define PRINT_SOCKET_ERROR(x) fprintf(stderr, "Socket error: %s, %d\n", x, WSAGetLastError()); #else #define PRINT_SOCKET_ERROR(x) perror(x) #endif diff --git a/external/miniupnpc/setup.py b/external/miniupnpc/setup.py index aa632db82..24a676d85 100755 --- a/external/miniupnpc/setup.py +++ b/external/miniupnpc/setup.py @@ -1,19 +1,25 @@ #! /usr/bin/python # vim: tabstop=8 shiftwidth=8 expandtab # $Id: setup.py,v 1.9 2012/05/23 08:50:10 nanard Exp $ -# the MiniUPnP Project (c) 2007-2014 Thomas Bernard +# the MiniUPnP Project (c) 2007-2017 Thomas Bernard # http://miniupnp.tuxfamily.org/ or http://miniupnp.free.fr/ # # python script to build the miniupnpc module under unix # -# replace libminiupnpc.a by libminiupnpc.so for shared library usage -try: - from setuptools import setup, Extension -except ImportError: - from distutils.core import setup, Extension -from distutils import sysconfig -sysconfig.get_config_vars()["OPT"] = '' -sysconfig.get_config_vars()["CFLAGS"] = '' +# Uses MAKE environment variable (defaulting to 'make') + +from setuptools import setup, Extension +from setuptools.command import build_ext +import subprocess +import os + +EXT = ['libminiupnpc.a'] + +class make_then_build_ext(build_ext.build_ext): + def run(self): + subprocess.check_call([os.environ.get('MAKE', 'make')] + EXT) + build_ext.build_ext.run(self) + setup(name="miniupnpc", version=open('VERSION').read().strip(), author='Thomas BERNARD', @@ -21,8 +27,9 @@ setup(name="miniupnpc", license=open('LICENSE').read(), url='http://miniupnp.free.fr/', description='miniUPnP client', + cmdclass={'build_ext': make_then_build_ext}, ext_modules=[ Extension(name="miniupnpc", sources=["miniupnpcmodule.c"], - extra_objects=["libminiupnpc.a"]) + extra_objects=EXT) ]) diff --git a/external/miniupnpc/testdesc/new_LiveBox_desc.xml b/external/miniupnpc/testdesc/new_LiveBox_desc.xml index 9d5160bb8..620eb55af 100644 --- a/external/miniupnpc/testdesc/new_LiveBox_desc.xml +++ b/external/miniupnpc/testdesc/new_LiveBox_desc.xml @@ -87,4 +87,4 @@ </device>
</deviceList>
</device>
-</root> +</root>
\ No newline at end of file diff --git a/external/miniupnpc/testupnpigd.py b/external/miniupnpc/testupnpigd.py index 6d167a4ce..33019bd99 100755 --- a/external/miniupnpc/testupnpigd.py +++ b/external/miniupnpc/testupnpigd.py @@ -8,7 +8,11 @@ # import the python miniupnpc module import miniupnpc import socket -import BaseHTTPServer + +try: + from http.server import BaseHTTPRequestHandler, HTTPServer +except ImportError: + from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer # function definition def list_redirections(): @@ -17,11 +21,11 @@ def list_redirections(): p = u.getgenericportmapping(i) if p==None: break - print i, p + print(i, p) i = i + 1 #define the handler class for HTTP connections -class handler_class(BaseHTTPServer.BaseHTTPRequestHandler): +class handler_class(BaseHTTPRequestHandler): def do_GET(self): self.send_response(200) self.end_headers() @@ -37,20 +41,20 @@ u = miniupnpc.UPnP() u.discoverdelay = 200; try: - print 'Discovering... delay=%ums' % u.discoverdelay + print('Discovering... delay=%ums' % u.discoverdelay) ndevices = u.discover() - print ndevices, 'device(s) detected' + print(ndevices, 'device(s) detected') # select an igd u.selectigd() # display information about the IGD and the internet connection - print 'local ip address :', u.lanaddr + print('local ip address :', u.lanaddr) externalipaddress = u.externalipaddress() - print 'external ip address :', externalipaddress - print u.statusinfo(), u.connectiontype() + print('external ip address :', externalipaddress) + print(u.statusinfo(), u.connectiontype()) #instanciate a HTTPd object. The port is assigned by the system. - httpd = BaseHTTPServer.HTTPServer((u.lanaddr, 0), handler_class) + httpd = HTTPServer((u.lanaddr, 0), handler_class) eport = httpd.server_port # find a free port for the redirection @@ -59,26 +63,26 @@ try: eport = eport + 1 r = u.getspecificportmapping(eport, 'TCP') - print 'trying to redirect %s port %u TCP => %s port %u TCP' % (externalipaddress, eport, u.lanaddr, httpd.server_port) + print('trying to redirect %s port %u TCP => %s port %u TCP' % (externalipaddress, eport, u.lanaddr, httpd.server_port)) b = u.addportmapping(eport, 'TCP', u.lanaddr, httpd.server_port, 'UPnP IGD Tester port %u' % eport, '') if b: - print 'Success. Now waiting for some HTTP request on http://%s:%u' % (externalipaddress ,eport) + print('Success. Now waiting for some HTTP request on http://%s:%u' % (externalipaddress ,eport)) try: httpd.handle_request() httpd.server_close() - except KeyboardInterrupt, details: - print "CTRL-C exception!", details + except KeyboardInterrupt as details: + print("CTRL-C exception!", details) b = u.deleteportmapping(eport, 'TCP') if b: - print 'Successfully deleted port mapping' + print('Successfully deleted port mapping') else: - print 'Failed to remove port mapping' + print('Failed to remove port mapping') else: - print 'Failed' + print('Failed') httpd.server_close() -except Exception, e: - print 'Exception :', e +except Exception as e: + print('Exception :', e) diff --git a/external/miniupnpc/upnpc.c b/external/miniupnpc/upnpc.c index 94f131c87..e719ecec5 100644 --- a/external/miniupnpc/upnpc.c +++ b/external/miniupnpc/upnpc.c @@ -1,7 +1,7 @@ -/* $Id: upnpc.c,v 1.114 2016/01/22 15:04:23 nanard Exp $ */ +/* $Id: upnpc.c,v 1.117 2017/05/26 15:26:55 nanard Exp $ */ /* Project : miniupnp * Author : Thomas Bernard - * Copyright (c) 2005-2016 Thomas Bernard + * Copyright (c) 2005-2017 Thomas Bernard * This software is subject to the conditions detailed in the * LICENCE file provided in this distribution. */ @@ -242,7 +242,7 @@ static void NewListRedirections(struct UPNPUrls * urls, * 2 - get extenal ip address * 3 - Add port mapping * 4 - get this port mapping from the IGD */ -static void SetRedirectAndTest(struct UPNPUrls * urls, +static int SetRedirectAndTest(struct UPNPUrls * urls, struct IGDdatas * data, const char * iaddr, const char * iport, @@ -262,13 +262,13 @@ static void SetRedirectAndTest(struct UPNPUrls * urls, if(!iaddr || !iport || !eport || !proto) { fprintf(stderr, "Wrong arguments\n"); - return; + return -1; } proto = protofix(proto); if(!proto) { fprintf(stderr, "invalid protocol\n"); - return; + return -1; } r = UPNP_GetExternalIPAddress(urls->controlURL, @@ -292,9 +292,11 @@ static void SetRedirectAndTest(struct UPNPUrls * urls, r = UPNP_AddPortMapping(urls->controlURL, data->first.servicetype, eport, iport, iaddr, description, proto, 0, leaseDuration); - if(r!=UPNPCOMMAND_SUCCESS) + if(r!=UPNPCOMMAND_SUCCESS) { printf("AddPortMapping(%s, %s, %s) failed with code %d (%s)\n", eport, iport, iaddr, r, strupnperror(r)); + return -2; + } } r = UPNP_GetSpecificPortMappingEntry(urls->controlURL, @@ -302,17 +304,19 @@ static void SetRedirectAndTest(struct UPNPUrls * urls, eport, proto, NULL/*remoteHost*/, intClient, intPort, NULL/*desc*/, NULL/*enabled*/, duration); - if(r!=UPNPCOMMAND_SUCCESS) + if(r!=UPNPCOMMAND_SUCCESS) { printf("GetSpecificPortMappingEntry() failed with code %d (%s)\n", r, strupnperror(r)); - else { + return -2; + } else { printf("InternalIP:Port = %s:%s\n", intClient, intPort); printf("external %s:%s %s is redirected to internal %s:%s (duration=%s)\n", externalIPAddress, eport, proto, intClient, intPort, duration); } + return 0; } -static void +static int RemoveRedirect(struct UPNPUrls * urls, struct IGDdatas * data, const char * eport, @@ -323,19 +327,25 @@ RemoveRedirect(struct UPNPUrls * urls, if(!proto || !eport) { fprintf(stderr, "invalid arguments\n"); - return; + return -1; } proto = protofix(proto); if(!proto) { fprintf(stderr, "protocol invalid\n"); - return; + return -1; } r = UPNP_DeletePortMapping(urls->controlURL, data->first.servicetype, eport, proto, remoteHost); - printf("UPNP_DeletePortMapping() returned : %d\n", r); + if(r!=UPNPCOMMAND_SUCCESS) { + printf("UPNP_DeletePortMapping() failed with code : %d\n", r); + return -2; + }else { + printf("UPNP_DeletePortMapping() returned : %d\n", r); + } + return 0; } -static void +static int RemoveRedirectRange(struct UPNPUrls * urls, struct IGDdatas * data, const char * ePortStart, char const * ePortEnd, @@ -349,16 +359,22 @@ RemoveRedirectRange(struct UPNPUrls * urls, if(!proto || !ePortStart || !ePortEnd) { fprintf(stderr, "invalid arguments\n"); - return; + return -1; } proto = protofix(proto); if(!proto) { fprintf(stderr, "protocol invalid\n"); - return; + return -1; } r = UPNP_DeletePortMappingRange(urls->controlURL, data->first.servicetype, ePortStart, ePortEnd, proto, manage); - printf("UPNP_DeletePortMappingRange() returned : %d\n", r); + if(r!=UPNPCOMMAND_SUCCESS) { + printf("UPNP_DeletePortMappingRange() failed with code : %d\n", r); + return -2; + }else { + printf("UPNP_DeletePortMappingRange() returned : %d\n", r); + } + return 0; } /* IGD:2, functions for service WANIPv6FirewallControl:1 */ @@ -562,8 +578,8 @@ int main(int argc, char ** argv) } #endif printf("upnpc : miniupnpc library test client, version %s.\n", MINIUPNPC_VERSION_STRING); - printf(" (c) 2005-2016 Thomas Bernard.\n"); - printf("Go to http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/\n" + printf(" (c) 2005-2017 Thomas Bernard.\n"); + printf("Go to http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/\n" "for more information.\n"); /* command line processing */ for(i=1; i<argc; i++) @@ -711,29 +727,33 @@ int main(int argc, char ** argv) NewListRedirections(&urls, &data); break; case 'a': - SetRedirectAndTest(&urls, &data, + if (SetRedirectAndTest(&urls, &data, commandargv[0], commandargv[1], commandargv[2], commandargv[3], (commandargc > 4)?commandargv[4]:"0", - description, 0); + description, 0) < 0) + retcode = 2; break; case 'd': - RemoveRedirect(&urls, &data, commandargv[0], commandargv[1], - commandargc > 2 ? commandargv[2] : NULL); + if (RemoveRedirect(&urls, &data, commandargv[0], commandargv[1], + commandargc > 2 ? commandargv[2] : NULL) < 0) + retcode = 2; break; case 'n': /* aNy */ - SetRedirectAndTest(&urls, &data, + if (SetRedirectAndTest(&urls, &data, commandargv[0], commandargv[1], commandargv[2], commandargv[3], (commandargc > 4)?commandargv[4]:"0", - description, 1); + description, 1) < 0) + retcode = 2; break; case 'N': if (commandargc < 3) fprintf(stderr, "too few arguments\n"); - RemoveRedirectRange(&urls, &data, commandargv[0], commandargv[1], commandargv[2], - commandargc > 3 ? commandargv[3] : NULL); + if (RemoveRedirectRange(&urls, &data, commandargv[0], commandargv[1], commandargv[2], + commandargc > 3 ? commandargv[3] : NULL) < 0) + retcode = 2; break; case 's': GetConnectionStatus(&urls, &data); @@ -749,17 +769,19 @@ int main(int argc, char ** argv) break; } else if(is_int(commandargv[i+1])){ /* 2nd parameter is an integer : <port> <external_port> <protocol> */ - SetRedirectAndTest(&urls, &data, + if (SetRedirectAndTest(&urls, &data, lanaddr, commandargv[i], commandargv[i+1], commandargv[i+2], "0", - description, 0); + description, 0) < 0) + retcode = 2; i+=3; /* 3 parameters parsed */ } else { /* 2nd parameter not an integer : <port> <protocol> */ - SetRedirectAndTest(&urls, &data, + if (SetRedirectAndTest(&urls, &data, lanaddr, commandargv[i], commandargv[i], commandargv[i+1], "0", - description, 0); + description, 0) < 0) + retcode = 2; i+=2; /* 2 parameters parsed */ } } diff --git a/external/miniupnpc/upnpcommands.c b/external/miniupnpc/upnpcommands.c index 76cf9e391..9f704496f 100644 --- a/external/miniupnpc/upnpcommands.c +++ b/external/miniupnpc/upnpcommands.c @@ -1,7 +1,8 @@ /* $Id: upnpcommands.c,v 1.46 2015/07/15 12:19:00 nanard Exp $ */ -/* Project : miniupnp +/* vim: tabstop=4 shiftwidth=4 noexpandtab + * Project : miniupnp * Author : Thomas Bernard - * Copyright (c) 2005-2015 Thomas Bernard + * Copyright (c) 2005-2017 Thomas Bernard * This software is subject to the conditions detailed in the * LICENCE file provided in this distribution. * */ @@ -372,10 +373,11 @@ UPNP_AddPortMapping(const char * controlURL, const char * servicetype, AddPortMappingArgs[6].val = desc?desc:"libminiupnpc"; AddPortMappingArgs[7].elt = "NewLeaseDuration"; AddPortMappingArgs[7].val = leaseDuration?leaseDuration:"0"; - if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, - "AddPortMapping", AddPortMappingArgs, - &bufsize))) { - free(AddPortMappingArgs); + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "AddPortMapping", AddPortMappingArgs, + &bufsize); + free(AddPortMappingArgs); + if(!buffer) { return UPNPCOMMAND_HTTP_ERROR; } /*DisplayNameValueList(buffer, bufsize);*/ @@ -392,7 +394,6 @@ UPNP_AddPortMapping(const char * controlURL, const char * servicetype, ret = UPNPCOMMAND_SUCCESS; } ClearNameValueList(&pdata); - free(AddPortMappingArgs); return ret; } @@ -436,10 +437,11 @@ UPNP_AddAnyPortMapping(const char * controlURL, const char * servicetype, AddPortMappingArgs[6].val = desc?desc:"libminiupnpc"; AddPortMappingArgs[7].elt = "NewLeaseDuration"; AddPortMappingArgs[7].val = leaseDuration?leaseDuration:"0"; - if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, - "AddAnyPortMapping", AddPortMappingArgs, - &bufsize))) { - free(AddPortMappingArgs); + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "AddAnyPortMapping", AddPortMappingArgs, + &bufsize); + free(AddPortMappingArgs); + if(!buffer) { return UPNPCOMMAND_HTTP_ERROR; } ParseNameValue(buffer, bufsize, &pdata); @@ -461,7 +463,6 @@ UPNP_AddAnyPortMapping(const char * controlURL, const char * servicetype, } } ClearNameValueList(&pdata); - free(AddPortMappingArgs); return ret; } @@ -490,10 +491,11 @@ UPNP_DeletePortMapping(const char * controlURL, const char * servicetype, DeletePortMappingArgs[1].val = extPort; DeletePortMappingArgs[2].elt = "NewProtocol"; DeletePortMappingArgs[2].val = proto; - if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, - "DeletePortMapping", - DeletePortMappingArgs, &bufsize))) { - free(DeletePortMappingArgs); + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "DeletePortMapping", + DeletePortMappingArgs, &bufsize); + free(DeletePortMappingArgs); + if(!buffer) { return UPNPCOMMAND_HTTP_ERROR; } /*DisplayNameValueList(buffer, bufsize);*/ @@ -507,7 +509,6 @@ UPNP_DeletePortMapping(const char * controlURL, const char * servicetype, ret = UPNPCOMMAND_SUCCESS; } ClearNameValueList(&pdata); - free(DeletePortMappingArgs); return ret; } @@ -539,10 +540,11 @@ UPNP_DeletePortMappingRange(const char * controlURL, const char * servicetype, DeletePortMappingArgs[3].elt = "NewManage"; DeletePortMappingArgs[3].val = manage; - if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, - "DeletePortMappingRange", - DeletePortMappingArgs, &bufsize))) { - free(DeletePortMappingArgs); + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "DeletePortMappingRange", + DeletePortMappingArgs, &bufsize); + free(DeletePortMappingArgs); + if(!buffer) { return UPNPCOMMAND_HTTP_ERROR; } ParseNameValue(buffer, bufsize, &pdata); @@ -555,7 +557,6 @@ UPNP_DeletePortMappingRange(const char * controlURL, const char * servicetype, ret = UPNPCOMMAND_SUCCESS; } ClearNameValueList(&pdata); - free(DeletePortMappingArgs); return ret; } @@ -587,10 +588,11 @@ UPNP_GetGenericPortMappingEntry(const char * controlURL, return UPNPCOMMAND_MEM_ALLOC_ERROR; GetPortMappingArgs[0].elt = "NewPortMappingIndex"; GetPortMappingArgs[0].val = index; - if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, - "GetGenericPortMappingEntry", - GetPortMappingArgs, &bufsize))) { - free(GetPortMappingArgs); + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetGenericPortMappingEntry", + GetPortMappingArgs, &bufsize); + free(GetPortMappingArgs); + if(!buffer) { return UPNPCOMMAND_HTTP_ERROR; } ParseNameValue(buffer, bufsize, &pdata); @@ -652,7 +654,6 @@ UPNP_GetGenericPortMappingEntry(const char * controlURL, sscanf(p, "%d", &r); } ClearNameValueList(&pdata); - free(GetPortMappingArgs); return r; } @@ -728,10 +729,11 @@ UPNP_GetSpecificPortMappingEntry(const char * controlURL, GetPortMappingArgs[1].val = extPort; GetPortMappingArgs[2].elt = "NewProtocol"; GetPortMappingArgs[2].val = proto; - if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, - "GetSpecificPortMappingEntry", - GetPortMappingArgs, &bufsize))) { - free(GetPortMappingArgs); + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetSpecificPortMappingEntry", + GetPortMappingArgs, &bufsize); + free(GetPortMappingArgs); + if(!buffer) { return UPNPCOMMAND_HTTP_ERROR; } /*DisplayNameValueList(buffer, bufsize);*/ @@ -779,7 +781,6 @@ UPNP_GetSpecificPortMappingEntry(const char * controlURL, } ClearNameValueList(&pdata); - free(GetPortMappingArgs); return ret; } @@ -824,13 +825,13 @@ UPNP_GetListOfPortMappings(const char * controlURL, GetListOfPortMappingsArgs[4].elt = "NewNumberOfPorts"; GetListOfPortMappingsArgs[4].val = numberOfPorts?numberOfPorts:"1000"; - if(!(buffer = simpleUPnPcommand(-1, controlURL, servicetype, - "GetListOfPortMappings", - GetListOfPortMappingsArgs, &bufsize))) { - free(GetListOfPortMappingsArgs); + buffer = simpleUPnPcommand(-1, controlURL, servicetype, + "GetListOfPortMappings", + GetListOfPortMappingsArgs, &bufsize); + free(GetListOfPortMappingsArgs); + if(!buffer) { return UPNPCOMMAND_HTTP_ERROR; } - free(GetListOfPortMappingsArgs); /*DisplayNameValueList(buffer, bufsize);*/ ParseNameValue(buffer, bufsize, &pdata); @@ -954,6 +955,7 @@ UPNP_GetOutboundPinholeTimeout(const char * controlURL, const char * servicetype GetOutboundPinholeTimeoutArgs[4].val = intClient; buffer = simpleUPnPcommand(-1, controlURL, servicetype, "GetOutboundPinholeTimeout", GetOutboundPinholeTimeoutArgs, &bufsize); + free(GetOutboundPinholeTimeoutArgs); if(!buffer) return UPNPCOMMAND_HTTP_ERROR; ParseNameValue(buffer, bufsize, &pdata); @@ -972,7 +974,6 @@ UPNP_GetOutboundPinholeTimeout(const char * controlURL, const char * servicetype *opTimeout = my_atoui(p); } ClearNameValueList(&pdata); - free(GetOutboundPinholeTimeoutArgs); return ret; } @@ -1031,6 +1032,7 @@ UPNP_AddPinhole(const char * controlURL, const char * servicetype, AddPinholeArgs[5].val = leaseTime; buffer = simpleUPnPcommand(-1, controlURL, servicetype, "AddPinhole", AddPinholeArgs, &bufsize); + free(AddPinholeArgs); if(!buffer) return UPNPCOMMAND_HTTP_ERROR; ParseNameValue(buffer, bufsize, &pdata); @@ -1053,7 +1055,6 @@ UPNP_AddPinhole(const char * controlURL, const char * servicetype, ret = UPNPCOMMAND_SUCCESS; } ClearNameValueList(&pdata); - free(AddPinholeArgs); return ret; } @@ -1081,6 +1082,7 @@ UPNP_UpdatePinhole(const char * controlURL, const char * servicetype, UpdatePinholeArgs[1].val = leaseTime; buffer = simpleUPnPcommand(-1, controlURL, servicetype, "UpdatePinhole", UpdatePinholeArgs, &bufsize); + free(UpdatePinholeArgs); if(!buffer) return UPNPCOMMAND_HTTP_ERROR; ParseNameValue(buffer, bufsize, &pdata); @@ -1097,7 +1099,6 @@ UPNP_UpdatePinhole(const char * controlURL, const char * servicetype, ret = UPNPCOMMAND_SUCCESS; } ClearNameValueList(&pdata); - free(UpdatePinholeArgs); return ret; } @@ -1122,6 +1123,7 @@ UPNP_DeletePinhole(const char * controlURL, const char * servicetype, const char DeletePinholeArgs[0].val = uniqueID; buffer = simpleUPnPcommand(-1, controlURL, servicetype, "DeletePinhole", DeletePinholeArgs, &bufsize); + free(DeletePinholeArgs); if(!buffer) return UPNPCOMMAND_HTTP_ERROR; /*DisplayNameValueList(buffer, bufsize);*/ @@ -1138,7 +1140,6 @@ UPNP_DeletePinhole(const char * controlURL, const char * servicetype, const char ret = UPNPCOMMAND_SUCCESS; } ClearNameValueList(&pdata); - free(DeletePinholeArgs); return ret; } @@ -1163,8 +1164,11 @@ UPNP_CheckPinholeWorking(const char * controlURL, const char * servicetype, CheckPinholeWorkingArgs[0].val = uniqueID; buffer = simpleUPnPcommand(-1, controlURL, servicetype, "CheckPinholeWorking", CheckPinholeWorkingArgs, &bufsize); + free(CheckPinholeWorkingArgs); if(!buffer) + { return UPNPCOMMAND_HTTP_ERROR; + } ParseNameValue(buffer, bufsize, &pdata); free(buffer); buffer = NULL; @@ -1185,7 +1189,6 @@ UPNP_CheckPinholeWorking(const char * controlURL, const char * servicetype, } ClearNameValueList(&pdata); - free(CheckPinholeWorkingArgs); return ret; } @@ -1210,6 +1213,7 @@ UPNP_GetPinholePackets(const char * controlURL, const char * servicetype, GetPinholePacketsArgs[0].val = uniqueID; buffer = simpleUPnPcommand(-1, controlURL, servicetype, "GetPinholePackets", GetPinholePacketsArgs, &bufsize); + free(GetPinholePacketsArgs); if(!buffer) return UPNPCOMMAND_HTTP_ERROR; ParseNameValue(buffer, bufsize, &pdata); @@ -1230,7 +1234,6 @@ UPNP_GetPinholePackets(const char * controlURL, const char * servicetype, } ClearNameValueList(&pdata); - free(GetPinholePacketsArgs); return ret; } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e473c2984..57ff28bfc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -96,9 +96,13 @@ function (monero_add_library name) PRIVATE $<TARGET_PROPERTY:${name},INTERFACE_COMPILE_DEFINITIONS>) endfunction () +set_source_files_properties(${CMAKE_BINARY_DIR}/version.cpp PROPERTIES GENERATED ON) +monero_add_library(version ${CMAKE_BINARY_DIR}/version.cpp) + add_subdirectory(common) add_subdirectory(crypto) add_subdirectory(ringct) +add_subdirectory(checkpoints) add_subdirectory(cryptonote_basic) add_subdirectory(cryptonote_core) if(NOT IOS) @@ -107,6 +111,7 @@ endif() add_subdirectory(mnemonics) if(NOT IOS) add_subdirectory(rpc) + add_subdirectory(serialization) endif() add_subdirectory(wallet) if(NOT IOS) diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h index 85a494ce7..838385e8a 100644 --- a/src/blockchain_db/blockchain_db.h +++ b/src/blockchain_db/blockchain_db.h @@ -711,7 +711,7 @@ public: * * @return true if we started the batch, false if already started */ - virtual bool batch_start(uint64_t batch_num_blocks=0) = 0; + virtual bool batch_start(uint64_t batch_num_blocks=0, uint64_t batch_bytes=0) = 0; /** * @brief ends a batch transaction diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index b6978bdc4..985244f6b 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -548,7 +548,7 @@ bool BlockchainLMDB::need_resize(uint64_t threshold_size) const #endif } -void BlockchainLMDB::check_and_resize_for_batch(uint64_t batch_num_blocks) +void BlockchainLMDB::check_and_resize_for_batch(uint64_t batch_num_blocks, uint64_t batch_bytes) { LOG_PRINT_L3("BlockchainLMDB::" << __func__); LOG_PRINT_L1("[" << __func__ << "] " << "checking DB size"); @@ -557,7 +557,7 @@ void BlockchainLMDB::check_and_resize_for_batch(uint64_t batch_num_blocks) uint64_t increase_size = 0; if (batch_num_blocks > 0) { - threshold_size = get_estimated_batch_size(batch_num_blocks); + threshold_size = get_estimated_batch_size(batch_num_blocks, batch_bytes); LOG_PRINT_L1("calculated batch size: " << threshold_size); // The increased DB size could be a multiple of threshold_size, a fixed @@ -580,7 +580,7 @@ void BlockchainLMDB::check_and_resize_for_batch(uint64_t batch_num_blocks) } } -uint64_t BlockchainLMDB::get_estimated_batch_size(uint64_t batch_num_blocks) const +uint64_t BlockchainLMDB::get_estimated_batch_size(uint64_t batch_num_blocks, uint64_t batch_bytes) const { LOG_PRINT_L3("BlockchainLMDB::" << __func__); uint64_t threshold_size = 0; @@ -607,6 +607,11 @@ uint64_t BlockchainLMDB::get_estimated_batch_size(uint64_t batch_num_blocks) con uint64_t total_block_size = 0; LOG_PRINT_L1("[" << __func__ << "] " << "m_height: " << m_height << " block_start: " << block_start << " block_stop: " << block_stop); size_t avg_block_size = 0; + if (batch_bytes) + { + avg_block_size = batch_bytes / batch_num_blocks; + goto estim; + } if (m_height == 0) { LOG_PRINT_L1("No existing blocks to check for average block size"); @@ -635,6 +640,7 @@ uint64_t BlockchainLMDB::get_estimated_batch_size(uint64_t batch_num_blocks) con avg_block_size = total_block_size / num_blocks_used; LOG_PRINT_L1("average block size across recent " << num_blocks_used << " blocks: " << avg_block_size); } +estim: if (avg_block_size < min_block_size) avg_block_size = min_block_size; LOG_PRINT_L1("estimated average block size for batch: " << avg_block_size); @@ -2420,8 +2426,8 @@ bool BlockchainLMDB::for_blocks_range(const uint64_t& h1, const uint64_t& h2, st MDB_cursor_op op; if (h1) { - MDB_val_set(k, h1); - op = MDB_SET; + k = MDB_val{sizeof(h1), (void*)&h1}; + op = MDB_SET; } else { op = MDB_FIRST; @@ -2540,7 +2546,7 @@ bool BlockchainLMDB::for_all_outputs(std::function<bool(uint64_t amount, const c } // batch_num_blocks: (optional) Used to check if resize needed before batch transaction starts. -bool BlockchainLMDB::batch_start(uint64_t batch_num_blocks) +bool BlockchainLMDB::batch_start(uint64_t batch_num_blocks, uint64_t batch_bytes) { LOG_PRINT_L3("BlockchainLMDB::" << __func__); if (! m_batch_transactions) @@ -2554,7 +2560,7 @@ bool BlockchainLMDB::batch_start(uint64_t batch_num_blocks) check_open(); m_writer = boost::this_thread::get_id(); - check_and_resize_for_batch(batch_num_blocks); + check_and_resize_for_batch(batch_num_blocks, batch_bytes); m_write_batch_txn = new mdb_txn_safe(); diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h index 90274b904..98571a7f8 100644 --- a/src/blockchain_db/lmdb/db_lmdb.h +++ b/src/blockchain_db/lmdb/db_lmdb.h @@ -264,7 +264,7 @@ public: ); virtual void set_batch_transactions(bool batch_transactions); - virtual bool batch_start(uint64_t batch_num_blocks=0); + virtual bool batch_start(uint64_t batch_num_blocks=0, uint64_t batch_bytes=0); virtual void batch_commit(); virtual void batch_stop(); virtual void batch_abort(); @@ -294,8 +294,8 @@ private: void do_resize(uint64_t size_increase=0); bool need_resize(uint64_t threshold_size=0) const; - void check_and_resize_for_batch(uint64_t batch_num_blocks); - uint64_t get_estimated_batch_size(uint64_t batch_num_blocks) const; + void check_and_resize_for_batch(uint64_t batch_num_blocks, uint64_t batch_bytes); + uint64_t get_estimated_batch_size(uint64_t batch_num_blocks, uint64_t batch_bytes) const; virtual void add_block( const block& blk , const size_t& block_size diff --git a/src/blockchain_utilities/CMakeLists.txt b/src/blockchain_utilities/CMakeLists.txt index ffdaad4af..0eaf71084 100644 --- a/src/blockchain_utilities/CMakeLists.txt +++ b/src/blockchain_utilities/CMakeLists.txt @@ -77,6 +77,7 @@ target_link_libraries(blockchain_import cryptonote_core blockchain_db p2p + version epee ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY} @@ -89,8 +90,6 @@ if(ARCH_WIDTH) PUBLIC -DARCH_WIDTH=${ARCH_WIDTH}) endif() -add_dependencies(blockchain_import - version) set_property(TARGET blockchain_import PROPERTY OUTPUT_NAME "monero-blockchain-import") @@ -104,6 +103,7 @@ target_link_libraries(blockchain_export cryptonote_core blockchain_db p2p + version epee ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY} @@ -111,8 +111,6 @@ target_link_libraries(blockchain_export ${CMAKE_THREAD_LIBS_INIT} ${EXTRA_LIBRARIES}) -add_dependencies(blockchain_export - version) set_property(TARGET blockchain_export PROPERTY OUTPUT_NAME "monero-blockchain-export") diff --git a/src/blockchain_utilities/blockchain_import.cpp b/src/blockchain_utilities/blockchain_import.cpp index 635a70b10..895ea9147 100644 --- a/src/blockchain_utilities/blockchain_import.cpp +++ b/src/blockchain_utilities/blockchain_import.cpp @@ -169,6 +169,26 @@ int check_flush(cryptonote::core &core, std::list<block_complete_entry> &blocks, if (!force && blocks.size() < db_batch_size) return 0; + // wait till we can verify a full HOH without extra, for speed + uint64_t new_height = core.get_blockchain_storage().get_db().height() + blocks.size(); + if (!force && new_height % HASH_OF_HASHES_STEP) + return 0; + + std::list<crypto::hash> hashes; + for (const auto &b: blocks) + { + cryptonote::block block; + if (!parse_and_validate_block_from_blob(b.block, block)) + { + MERROR("Failed to parse block: " + << epee::string_tools::pod_to_hex(get_blob_hash(b.block))); + core.cleanup_handle_incoming_blocks(); + return 1; + } + hashes.push_back(cryptonote::get_block_hash(block)); + } + core.prevalidate_block_hashes(core.get_blockchain_storage().get_db().height(), hashes); + core.prepare_handle_incoming_blocks(blocks); for(const block_complete_entry& block_entry: blocks) @@ -230,11 +250,22 @@ int import_from_file(cryptonote::core& core, const std::string& import_file_path return false; } + uint64_t start_height = 1, seek_height; + if (opt_resume) + start_height = core.get_blockchain_storage().get_current_blockchain_height(); + + seek_height = start_height; BootstrapFile bootstrap; + streampos pos; // BootstrapFile bootstrap(import_file_path); - uint64_t total_source_blocks = bootstrap.count_blocks(import_file_path); + uint64_t total_source_blocks = bootstrap.count_blocks(import_file_path, pos, seek_height); MINFO("bootstrap file last block number: " << total_source_blocks-1 << " (zero-based height) total blocks: " << total_source_blocks); + if (total_source_blocks-1 <= start_height) + { + return false; + } + std::cout << ENDL; std::cout << "Preparing to read blocks..." << ENDL; std::cout << ENDL; @@ -259,11 +290,7 @@ int import_from_file(cryptonote::core& core, const std::string& import_file_path block b; transaction tx; int quit = 0; - uint64_t bytes_read = 0; - - uint64_t start_height = 1; - if (opt_resume) - start_height = core.get_blockchain_storage().get_current_blockchain_height(); + uint64_t bytes_read; // Note that a new blockchain will start with block number 0 (total blocks: 1) // due to genesis block being added at initialization. @@ -280,18 +307,35 @@ int import_from_file(cryptonote::core& core, const std::string& import_file_path bool use_batch = opt_batch && !opt_verify; - if (use_batch) - core.get_blockchain_storage().get_db().batch_start(db_batch_size); - MINFO("Reading blockchain from bootstrap file..."); std::cout << ENDL; std::list<block_complete_entry> blocks; - // Within the loop, we skip to start_height before we start adding. - // TODO: Not a bottleneck, but we can use what's done in count_blocks() and - // only do the chunk size reads, skipping the chunk content reads until we're - // at start_height. + // Skip to start_height before we start adding. + { + bool q2 = false; + import_file.seekg(pos); + bytes_read = bootstrap.count_bytes(import_file, start_height-seek_height, h, q2); + if (q2) + { + quit = 2; + goto quitting; + } + h = start_height; + } + + if (use_batch) + { + uint64_t bytes, h2; + bool q2; + pos = import_file.tellg(); + bytes = bootstrap.count_bytes(import_file, db_batch_size, h2, q2); + if (import_file.eof()) + import_file.clear(); + import_file.seekg(pos); + core.get_blockchain_storage().get_db().batch_start(db_batch_size, bytes); + } while (! quit) { uint32_t chunk_size; @@ -344,11 +388,6 @@ int import_from_file(cryptonote::core& core, const std::string& import_file_path bytes_read += chunk_size; MDEBUG("Total bytes read: " << bytes_read); - if (h + NUM_BLOCKS_PER_CHUNK < start_height + 1) - { - h += NUM_BLOCKS_PER_CHUNK; - continue; - } if (h > block_stop) { std::cout << refresh_string << "block " << h-1 @@ -456,11 +495,16 @@ int import_from_file(cryptonote::core& core, const std::string& import_file_path { if ((h-1) % db_batch_size == 0) { + uint64_t bytes, h2; + bool q2; std::cout << refresh_string; // zero-based height std::cout << ENDL << "[- batch commit at height " << h-1 << " -]" << ENDL; core.get_blockchain_storage().get_db().batch_stop(); - core.get_blockchain_storage().get_db().batch_start(db_batch_size); + pos = import_file.tellg(); + bytes = bootstrap.count_bytes(import_file, db_batch_size, h2, q2); + import_file.seekg(pos); + core.get_blockchain_storage().get_db().batch_start(db_batch_size, bytes); std::cout << ENDL; core.get_blockchain_storage().get_db().show_stats(); } @@ -477,6 +521,7 @@ int import_from_file(cryptonote::core& core, const std::string& import_file_path } } // while +quitting: import_file.close(); if (opt_verify) diff --git a/src/blockchain_utilities/blocksdat_file.cpp b/src/blockchain_utilities/blocksdat_file.cpp index 63224225f..32e93345e 100644 --- a/src/blockchain_utilities/blocksdat_file.cpp +++ b/src/blockchain_utilities/blocksdat_file.cpp @@ -82,7 +82,7 @@ bool BlocksdatFile::open_writer(const boost::filesystem::path& file_path, uint64 bool BlocksdatFile::initialize_file(uint64_t block_stop) { - const uint32_t nblocks = block_stop + 1; + const uint32_t nblocks = (block_stop + 1) / HASH_OF_HASHES_STEP; unsigned char nblocksc[4]; nblocksc[0] = nblocks & 0xff; @@ -101,8 +101,16 @@ bool BlocksdatFile::initialize_file(uint64_t block_stop) void BlocksdatFile::write_block(const crypto::hash& block_hash) { - const std::string data(block_hash.data, sizeof(block_hash)); - *m_raw_data_file << data; + m_hashes.push_back(block_hash); + while (m_hashes.size() >= HASH_OF_HASHES_STEP) + { + crypto::hash hash; + crypto::cn_fast_hash(m_hashes.data(), HASH_OF_HASHES_STEP * sizeof(crypto::hash), hash); + memmove(m_hashes.data(), m_hashes.data() + HASH_OF_HASHES_STEP * sizeof(crypto::hash), (m_hashes.size() - HASH_OF_HASHES_STEP) * sizeof(crypto::hash)); + m_hashes.resize(m_hashes.size() - HASH_OF_HASHES_STEP); + const std::string data(hash.data, sizeof(hash)); + *m_raw_data_file << data; + } } bool BlocksdatFile::close() diff --git a/src/blockchain_utilities/blocksdat_file.h b/src/blockchain_utilities/blocksdat_file.h index 0a5913058..d43811772 100644 --- a/src/blockchain_utilities/blocksdat_file.h +++ b/src/blockchain_utilities/blocksdat_file.h @@ -76,4 +76,5 @@ protected: private: uint64_t m_cur_height; // tracks current height during export + std::vector<crypto::hash> m_hashes; }; diff --git a/src/blockchain_utilities/bootstrap_file.cpp b/src/blockchain_utilities/bootstrap_file.cpp index 2b1a5d6c7..a004d3547 100644 --- a/src/blockchain_utilities/bootstrap_file.cpp +++ b/src/blockchain_utilities/bootstrap_file.cpp @@ -375,39 +375,15 @@ uint64_t BootstrapFile::seek_to_first_chunk(std::ifstream& import_file) return full_header_size; } -uint64_t BootstrapFile::count_blocks(const std::string& import_file_path) +uint64_t BootstrapFile::count_bytes(std::ifstream& import_file, uint64_t blocks, uint64_t& h, bool& quit) { - boost::filesystem::path raw_file_path(import_file_path); - boost::system::error_code ec; - if (!boost::filesystem::exists(raw_file_path, ec)) - { - MFATAL("bootstrap file not found: " << raw_file_path); - throw std::runtime_error("Aborting"); - } - std::ifstream import_file; - import_file.open(import_file_path, std::ios_base::binary | std::ifstream::in); - - uint64_t h = 0; - if (import_file.fail()) - { - MFATAL("import_file.open() fail"); - throw std::runtime_error("Aborting"); - } - - uint64_t full_header_size; // 4 byte magic + length of header structures - full_header_size = seek_to_first_chunk(import_file); - - MINFO("Scanning blockchain from bootstrap file..."); - block b; - bool quit = false; uint64_t bytes_read = 0; - int progress_interval = 10; - + uint32_t chunk_size; + char buf1[sizeof(chunk_size)]; std::string str1; - char buf1[2048]; - while (! quit) + h = 0; + while (1) { - uint32_t chunk_size; import_file.read(buf1, sizeof(chunk_size)); if (!import_file) { std::cout << refresh_string; @@ -415,15 +391,7 @@ uint64_t BootstrapFile::count_blocks(const std::string& import_file_path) quit = true; break; } - h += NUM_BLOCKS_PER_CHUNK; - if ((h-1) % progress_interval == 0) - { - std::cout << "\r" << "block height: " << h-1 << - " " << - std::flush; - } bytes_read += sizeof(chunk_size); - str1.assign(buf1, sizeof(chunk_size)); if (! ::serialization::parse_binary(str1, chunk_size)) throw std::runtime_error("Error in deserialization of chunk_size"); @@ -456,6 +424,64 @@ uint64_t BootstrapFile::count_blocks(const std::string& import_file_path) throw std::runtime_error("Aborting"); } bytes_read += chunk_size; + h += NUM_BLOCKS_PER_CHUNK; + if (h >= blocks) + break; + } + return bytes_read; +} + +uint64_t BootstrapFile::count_blocks(const std::string& import_file_path) +{ + streampos dummy_pos; + uint64_t dummy_height = 0; + return count_blocks(import_file_path, dummy_pos, dummy_height); +} + +// If seek_height is non-zero on entry, return a stream position <= this height when finished. +// And return the actual height corresponding to this position. Allows the caller to locate its +// starting position without having to reread the entire file again. +uint64_t BootstrapFile::count_blocks(const std::string& import_file_path, streampos &start_pos, uint64_t& seek_height) +{ + boost::filesystem::path raw_file_path(import_file_path); + boost::system::error_code ec; + if (!boost::filesystem::exists(raw_file_path, ec)) + { + MFATAL("bootstrap file not found: " << raw_file_path); + throw std::runtime_error("Aborting"); + } + std::ifstream import_file; + import_file.open(import_file_path, std::ios_base::binary | std::ifstream::in); + + uint64_t start_height = seek_height; + uint64_t h = 0; + if (import_file.fail()) + { + MFATAL("import_file.open() fail"); + throw std::runtime_error("Aborting"); + } + + uint64_t full_header_size; // 4 byte magic + length of header structures + full_header_size = seek_to_first_chunk(import_file); + + MINFO("Scanning blockchain from bootstrap file..."); + bool quit = false; + uint64_t bytes_read = 0, blocks; + int progress_interval = 10; + + while (! quit) + { + if (start_height && h + progress_interval >= start_height - 1) + { + start_height = 0; + start_pos = import_file.tellg(); + seek_height = h; + } + bytes_read += count_bytes(import_file, progress_interval, blocks, quit); + h += blocks; + std::cout << "\r" << "block height: " << h-1 << + " " << + std::flush; // std::cout << refresh_string; MDEBUG("Number bytes scanned: " << bytes_read); diff --git a/src/blockchain_utilities/bootstrap_file.h b/src/blockchain_utilities/bootstrap_file.h index 1a646b54b..c3969a357 100644 --- a/src/blockchain_utilities/bootstrap_file.h +++ b/src/blockchain_utilities/bootstrap_file.h @@ -56,6 +56,8 @@ class BootstrapFile { public: + uint64_t count_bytes(std::ifstream& import_file, uint64_t blocks, uint64_t& h, bool& quit); + uint64_t count_blocks(const std::string& dir_path, streampos& start_pos, uint64_t& seek_height); uint64_t count_blocks(const std::string& dir_path); uint64_t seek_to_first_chunk(std::ifstream& import_file); diff --git a/src/blocks/checkpoints.dat b/src/blocks/checkpoints.dat Binary files differindex 15fa042cf..c12c3d8a0 100644 --- a/src/blocks/checkpoints.dat +++ b/src/blocks/checkpoints.dat diff --git a/src/checkpoints/CMakeLists.txt b/src/checkpoints/CMakeLists.txt new file mode 100644 index 000000000..bc7a27e36 --- /dev/null +++ b/src/checkpoints/CMakeLists.txt @@ -0,0 +1,60 @@ +# Copyright (c) 2014-2017, 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. + +if(APPLE) + find_library(IOKIT_LIBRARY IOKit) + mark_as_advanced(IOKIT_LIBRARY) + list(APPEND EXTRA_LIBRARIES ${IOKIT_LIBRARY}) +endif() + +set(checkpoints_sources + checkpoints.cpp) + +set(checkpoints_headers) + +set(checkpoints_private_headers + checkpoints.h) + +monero_private_headers(checkpoints + ${checkpoints_private_headers}) +monero_add_library(checkpoints + ${checkpoints_sources} + ${checkpoints_headers} + ${checkpoints_private_headers}) +target_link_libraries(checkpoints + PUBLIC + common + cncrypto + ${Boost_DATE_TIME_LIBRARY} + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_SERIALIZATION_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ${Boost_SYSTEM_LIBRARY} + ${Boost_THREAD_LIBRARY} + PRIVATE + ${EXTRA_LIBRARIES}) diff --git a/src/cryptonote_basic/checkpoints.cpp b/src/checkpoints/checkpoints.cpp index 98e509561..bea392db0 100644 --- a/src/cryptonote_basic/checkpoints.cpp +++ b/src/checkpoints/checkpoints.cpp @@ -36,6 +36,7 @@ using namespace epee; #include "common/dns_utils.h" #include "include_base_utils.h" +#include "storages/portable_storage_template_helper.h" // epee json include #include <sstream> #include <random> @@ -51,7 +52,7 @@ namespace cryptonote //--------------------------------------------------------------------------- bool checkpoints::add_checkpoint(uint64_t height, const std::string& hash_str) { - crypto::hash h = null_hash; + crypto::hash h = crypto::null_hash; bool r = epee::string_tools::parse_tpod_from_hex_string(hash_str, h); CHECK_AND_ASSERT_MES(r, false, "Failed to parse checkpoint hash string into binary representation!"); @@ -135,8 +136,14 @@ namespace cryptonote return true; } - bool checkpoints::init_default_checkpoints() + bool checkpoints::init_default_checkpoints(bool testnet) { + if (testnet) + { + // just use the genesis block on testnet + ADD_CHECKPOINT(0, "48ca7cd3c8de5b6a4d53d2861fbdaedca141553559f9be9520068053cda8430b"); + return true; + } ADD_CHECKPOINT(1, "771fbcd656ec1464d3a02ead5e18644030007a0fc664c0a964d30922821a8148"); ADD_CHECKPOINT(10, "c0e3b387e47042f72d8ccdca88071ff96bff1ac7cde09ae113dbb7ad3fe92381"); ADD_CHECKPOINT(100, "ac3e11ca545e57c49fca2b4e8c48c03c23be047c43e471e1394528b1f9f80b2d"); diff --git a/src/cryptonote_basic/checkpoints.h b/src/checkpoints/checkpoints.h index 3a74d8a69..a643c5790 100644 --- a/src/cryptonote_basic/checkpoints.h +++ b/src/checkpoints/checkpoints.h @@ -31,9 +31,9 @@ #pragma once #include <map> #include <vector> -#include "cryptonote_basic_impl.h" #include "misc_log_ex.h" -#include "storages/portable_storage_template_helper.h" // epee json include +#include "crypto/hash.h" +#include "serialization/keyvalue_serialization.h" #define ADD_CHECKPOINT(h, hash) CHECK_AND_ASSERT(add_checkpoint(h, hash), false); #define JSON_HASH_FILE_NAME "checkpoints.json" @@ -148,10 +148,11 @@ namespace cryptonote /** * @brief loads the default main chain checkpoints + * @param testnet whether to load testnet checkpoints or mainnet * * @return true unless adding a checkpoint fails */ - bool init_default_checkpoints(); + bool init_default_checkpoints(bool testnet); /** * @brief load new checkpoints diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 55b8ad3e6..50887e35c 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -37,8 +37,7 @@ set(common_sources i18n.cpp password.cpp perf_timer.cpp - task_region.cpp - thread_group.cpp + threadpool.cpp updates.cpp) if (STACK_TRACE) @@ -48,6 +47,7 @@ endif() set(common_headers) set(common_private_headers + apply_permutation.h base58.h boost_serialization_helper.h command_line.h @@ -66,8 +66,7 @@ set(common_private_headers password.h perf_timer.h stack_trace.h - task_region.h - thread_group.h + threadpool.h updates.h) monero_private_headers(common diff --git a/src/common/apply_permutation.h b/src/common/apply_permutation.h new file mode 100644 index 000000000..4fd952686 --- /dev/null +++ b/src/common/apply_permutation.h @@ -0,0 +1,68 @@ +// Copyright (c) 2017, 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. +// +// Most of this file is originally copyright (c) 2017 Raymond Chen, Microsoft +// This algorithm is adapted from Raymond Chen's code: +// https://blogs.msdn.microsoft.com/oldnewthing/20170109-00/?p=95145 + +#include <vector> +#include <functional> +#include "misc_log_ex.h" + +namespace tools +{ + +template<typename F> +void apply_permutation(std::vector<size_t> permutation, const F &swap) +{ + //sanity check + for (size_t n = 0; n < permutation.size(); ++n) + CHECK_AND_ASSERT_THROW_MES(std::find(permutation.begin(), permutation.end(), n) != permutation.end(), "Bad permutation"); + + for (size_t i = 0; i < permutation.size(); ++i) + { + size_t current = i; + while (i != permutation[current]) + { + size_t next = permutation[current]; + swap(current, next); + permutation[current] = current; + current = next; + } + permutation[current] = current; + } +} + +template<typename T> +void apply_permutation(const std::vector<size_t> &permutation, std::vector<T> &v) +{ + CHECK_AND_ASSERT_THROW_MES(permutation.size() == v.size(), "Mismatched vector sizes"); + apply_permutation(permutation, [&v](size_t i0, size_t i1){ std::swap(v[i0], v[i1]); }); +} + +} diff --git a/src/common/base58.cpp b/src/common/base58.cpp index 64cb7c0de..941373443 100644 --- a/src/common/base58.cpp +++ b/src/common/base58.cpp @@ -111,13 +111,13 @@ namespace tools uint64_t res = 0; switch (9 - size) { - case 1: res |= *data++; - case 2: res <<= 8; res |= *data++; - case 3: res <<= 8; res |= *data++; - case 4: res <<= 8; res |= *data++; - case 5: res <<= 8; res |= *data++; - case 6: res <<= 8; res |= *data++; - case 7: res <<= 8; res |= *data++; + case 1: res |= *data++; /* FALLTHRU */ + case 2: res <<= 8; res |= *data++; /* FALLTHRU */ + case 3: res <<= 8; res |= *data++; /* FALLTHRU */ + case 4: res <<= 8; res |= *data++; /* FALLTHRU */ + case 5: res <<= 8; res |= *data++; /* FALLTHRU */ + case 6: res <<= 8; res |= *data++; /* FALLTHRU */ + case 7: res <<= 8; res |= *data++; /* FALLTHRU */ case 8: res <<= 8; res |= *data; break; default: assert(false); } diff --git a/src/common/command_line.cpp b/src/common/command_line.cpp index 8c03bed0d..666b3267f 100644 --- a/src/common/command_line.cpp +++ b/src/common/command_line.cpp @@ -120,4 +120,9 @@ namespace command_line , "Check for new versions of monero: [disabled|notify|download|update]" , "notify" }; + const arg_descriptor<bool> arg_fluffy_blocks = { + "fluffy-blocks" + , "Relay blocks as fluffy blocks where possible (automatic on testnet)" + , false + }; } diff --git a/src/common/command_line.h b/src/common/command_line.h index ac64f519c..d4231acd0 100644 --- a/src/common/command_line.h +++ b/src/common/command_line.h @@ -217,4 +217,5 @@ namespace command_line extern const arg_descriptor<uint64_t> arg_show_time_stats; extern const arg_descriptor<size_t> arg_block_sync_size; extern const arg_descriptor<std::string> arg_check_updates; + extern const arg_descriptor<bool> arg_fluffy_blocks; } diff --git a/src/common/common_fwd.h b/src/common/common_fwd.h index 5d67251b1..f33e185b5 100644 --- a/src/common/common_fwd.h +++ b/src/common/common_fwd.h @@ -36,6 +36,5 @@ namespace tools struct login; class password_container; class t_http_connection; - class task_region; - class thread_group; + class threadpool; } diff --git a/src/common/dns_utils.cpp b/src/common/dns_utils.cpp index e7ff11c5c..1310b8bfd 100644 --- a/src/common/dns_utils.cpp +++ b/src/common/dns_utils.cpp @@ -27,8 +27,6 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "common/dns_utils.h" -#include "common/i18n.h" -#include "cryptonote_basic/cryptonote_basic_impl.h" // check local first (in the event of static or in-source compilation of libunbound) #include "unbound.h" @@ -326,8 +324,6 @@ bool DNSResolver::check_address_syntax(const char *addr) const namespace dns_utils { -const char *tr(const char *str) { return i18n_translate(str, "tools::dns_utils"); } - //----------------------------------------------------------------------- // TODO: parse the string in a less stupid way, probably with regex std::string address_from_txt_record(const std::string& s) @@ -447,19 +443,28 @@ bool load_txt_records_from_dns(std::vector<std::string> &good_records, const std std::uniform_int_distribution<int> dis(0, dns_urls.size() - 1); size_t first_index = dis(gen); - bool avail, valid; + // send all requests in parallel + std::vector<boost::thread> threads(dns_urls.size()); + std::deque<bool> avail(dns_urls.size(), false), valid(dns_urls.size(), false); + for (size_t n = 0; n < dns_urls.size(); ++n) + { + threads[n] = boost::thread([n, dns_urls, &records, &avail, &valid](){ + records[n] = tools::DNSResolver::instance().get_txt_record(dns_urls[n], avail[n], valid[n]); + }); + } + for (size_t n = 0; n < dns_urls.size(); ++n) + threads[n].join(); + size_t cur_index = first_index; do { - std::string url = dns_urls[cur_index]; - - records[cur_index] = tools::DNSResolver::instance().get_txt_record(url, avail, valid); - if (!avail) + const std::string &url = dns_urls[cur_index]; + if (!avail[cur_index]) { records[cur_index].clear(); LOG_PRINT_L2("DNSSEC not available for checkpoint update at URL: " << url << ", skipping."); } - if (!valid) + if (!valid[cur_index]) { records[cur_index].clear(); LOG_PRINT_L2("DNSSEC validation failed for checkpoint update at URL: " << url << ", skipping."); diff --git a/src/common/sfinae_helpers.h b/src/common/sfinae_helpers.h new file mode 100644 index 000000000..ddd456dd2 --- /dev/null +++ b/src/common/sfinae_helpers.h @@ -0,0 +1,147 @@ +// Copyright (c) 2016-2017, 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. + +#pragma once + +// the loose definitions of types in this file are, well, loose. +// +// these helpers aren't here for absolute type certainty at compile-time, +// but rather to help with templated functions telling types apart. + +namespace sfinae +{ + + typedef char true_type; + + struct false_type { true_type a[2]; }; + + template <typename T> + struct is_not_container + { + private: + + // does not have const iterator + template <typename C> static false_type c_iter(typename C::const_iterator*); + template <typename C> static true_type c_iter(...); + + // does not have value_type + template <typename C> static false_type v_type(typename C::value_type*); + template <typename C> static true_type v_type(...); + + // does not have key_type + template <typename C> static false_type k_type(typename C::key_type*); + template <typename C> static true_type k_type(...); + + // does not have mapped_type + template <typename C> static false_type m_type(typename C::mapped_type*); + template <typename C> static true_type m_type(...); + + public: + + static const bool value = ( + ( + sizeof(c_iter<T>(0)) == sizeof(true_type) && + sizeof(v_type<T>(0)) == sizeof(true_type) && + sizeof(k_type<T>(0)) == sizeof(true_type) && + sizeof(m_type<T>(0)) == sizeof(true_type) + ) + || std::is_same<T, std::string>::value + ); + + typedef T type; + }; + + template <typename T> + struct is_vector_like + { + private: + + // has const iterator + template <typename C> static true_type c_iter(typename C::const_iterator*); + template <typename C> static false_type c_iter(...); + + // has value_type + template <typename C> static true_type v_type(typename C::value_type*); + template <typename C> static false_type v_type(...); + + // does not have key_type + template <typename C> static false_type k_type(typename C::key_type*); + template <typename C> static true_type k_type(...); + + // does not have mapped_type + template <typename C> static false_type m_type(typename C::mapped_type*); + template <typename C> static true_type m_type(...); + + public: + + static const bool value = ( + sizeof(c_iter<T>(0)) == sizeof(true_type) && + sizeof(v_type<T>(0)) == sizeof(true_type) && + sizeof(k_type<T>(0)) == sizeof(true_type) && + sizeof(m_type<T>(0)) == sizeof(true_type) && + !std::is_same<T, std::string>::value + ); + + typedef T type; + }; + + template <typename T> + struct is_map_like + { + private: + + // has const iterator + template <typename C> static true_type c_iter(typename C::const_iterator*); + template <typename C> static false_type c_iter(...); + + // has value_type + template <typename C> static true_type v_type(typename C::value_type*); + template <typename C> static false_type v_type(...); + + // has key_type + template <typename C> static true_type k_type(typename C::key_type*); + template <typename C> static false_type k_type(...); + + // has mapped_type + template <typename C> static true_type m_type(typename C::mapped_type*); + template <typename C> static false_type m_type(...); + + public: + + static const bool value = ( + sizeof(c_iter<T>(0)) == sizeof(true_type) && + sizeof(v_type<T>(0)) == sizeof(true_type) && + sizeof(k_type<T>(0)) == sizeof(true_type) && + sizeof(m_type<T>(0)) == sizeof(true_type) && + !std::is_same<T, std::string>::value + ); + + typedef T type; + }; + +} // namespace sfinae diff --git a/src/common/task_region.cpp b/src/common/task_region.cpp deleted file mode 100644 index 9b4620c6e..000000000 --- a/src/common/task_region.cpp +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) 2014-2017, 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 "common/task_region.h" - -#include <boost/thread/locks.hpp> -#include <cassert> - -/* `mark_completed` and `wait` can throw in the lock call, but its difficult to -recover from either. An exception in `wait` means the post condition of joining -all threads cannot be achieved, and an exception in `mark_completed` means -certain deadlock. `noexcept` qualifier will force a call to `std::terminate` if -locking throws an exception, which should only happen if a recursive lock -attempt is made (which is not possible since no external function is called -while holding the lock). */ - -namespace tools -{ -void task_region_handle::state::mark_completed(id task_id) noexcept { - assert(task_id != 0 && (task_id & (task_id - 1)) == 0); // power of 2 check - if (pending.fetch_and(~task_id) == task_id) { - // synchronize with wait call, but do not need to hold - boost::unique_lock<boost::mutex>{sync_on_complete}; - all_complete.notify_all(); - } -} - -void task_region_handle::state::abort() noexcept { - state* current = this; - while (current) { - current->ready = 0; - current = current->next.get(); - } -} - -void task_region_handle::state::wait() noexcept { - state* current = this; - while (current) { - { - boost::unique_lock<boost::mutex> lock{current->sync_on_complete}; - current->all_complete.wait(lock, [current] { return current->pending == 0; }); - } - current = current->next.get(); - } -} - -void task_region_handle::state::wait(thread_group& threads) noexcept { - state* current = this; - while (current) { - while (current->pending != 0) { - if (!threads.try_run_one()) { - current->wait(); - return; - } - } - current = current->next.get(); - } -} - -void task_region_handle::create_state() { - st = std::make_shared<state>(std::move(st)); - next_id = 1; -} - -void task_region_handle::do_wait() noexcept { - assert(st); - const std::shared_ptr<state> temp = std::move(st); - temp->wait(threads); -} -} diff --git a/src/common/task_region.h b/src/common/task_region.h deleted file mode 100644 index 30972cce3..000000000 --- a/src/common/task_region.h +++ /dev/null @@ -1,223 +0,0 @@ -// Copyright (c) 2014-2017, 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. -#pragma once - -#include <atomic> -#include <boost/thread/condition_variable.hpp> -#include <boost/thread/mutex.hpp> -#include <memory> -#include <type_traits> -#include <utility> - -#include "common/thread_group.h" - -namespace tools -{ - -/*! A model of the fork-join concept. `run(...)` "forks" (i.e. spawns new -tasks), and `~task_region_handle()` or `wait()` "joins" the spawned tasks. -`wait` will block until all tasks have completed, while `~task_region_handle()` -blocks until all tasks have completed or aborted. - -Do _NOT_ give this object to separate thread of execution (which includes -`task_region_handle::run(...)`) because joining on a different thread is -undesireable (potential deadlock). - -This class cannot be constructed directly, use the function -`task_region(...)` instead. -*/ -class task_region_handle -{ - struct state - { - using id = unsigned; - - explicit state(std::shared_ptr<state> next_src) noexcept - : next(std::move(next_src)) - , ready(0) - , pending(0) - , sync_on_complete() - , all_complete() { - } - - state(const state&) = default; - state(state&&) = default; - ~state() = default; - state& operator=(const state&) = default; - state& operator=(state&&) = default; - - void track_id(id task_id) noexcept { - pending |= task_id; - ready |= task_id; - } - - //! \return True only once whether a given id can execute - bool can_run(id task_id) noexcept { - return (ready.fetch_and(~task_id) & task_id); - } - - //! Mark id as completed, and synchronize with waiting threads - void mark_completed(id task_id) noexcept; - - //! Tell all unstarted functions in region to return immediately - void abort() noexcept; - - //! Blocks until all functions in region have aborted or completed. - void wait() noexcept; - - //! Same as `wait()`, except `this_thread` runs tasks while waiting. - void wait(thread_group& threads) noexcept; - - private: - /* This implementation is a bit pessimistic, it ensures that all copies - of a wrapped task can only be executed once. `thread_group` should never - do this, but some variable needs to track whether an abort should be done - anyway... */ - std::shared_ptr<state> next; - std::atomic<id> ready; //!< Tracks whether a task has been invoked - std::atomic<id> pending; //!< Tracks when a task has completed or aborted - boost::mutex sync_on_complete; - boost::condition_variable all_complete; - }; - - template<typename F> - struct wrapper - { - wrapper(state::id id_src, std::shared_ptr<state> st_src, F f_src) - : task_id(id_src), st(std::move(st_src)), f(std::move(f_src)) { - } - - wrapper(const wrapper&) = default; - wrapper(wrapper&&) = default; - wrapper& operator=(const wrapper&) = default; - wrapper& operator=(wrapper&&) = default; - - void operator()() { - if (st) { - if (st->can_run(task_id)) { - f(); - } - st->mark_completed(task_id); - } - } - - private: - const state::id task_id; - std::shared_ptr<state> st; - F f; - }; - -public: - friend struct task_region_; - - task_region_handle() = delete; - task_region_handle(const task_region_handle&) = delete; - task_region_handle(task_region_handle&&) = delete; - - //! Cancels unstarted pending tasks, and waits for them to respond. - ~task_region_handle() noexcept { - if (st) { - st->abort(); - st->wait(threads); - } - } - - task_region_handle& operator=(const task_region_handle&) = delete; - task_region_handle& operator=(task_region_handle&&) = delete; - - /*! If the group has no threads, `f` is immediately run before returning. - Otherwise, `f` is dispatched to the thread_group associated with `this` - region. If `f` is dispatched to another thread, and it throws, the process - will immediately terminate. See std::packaged_task for getting exceptions on - functions executed on other threads. */ - template<typename F> - void run(F&& f) { - if (threads.count() == 0) { - f(); - } else { - if (!st || next_id == 0) { - create_state(); - } - const state::id this_id = next_id; - next_id <<= 1; - - st->track_id(this_id); - threads.dispatch(wrapper<F>{this_id, st, std::move(f)}); - } - } - - //! Wait until all functions provided to `run` have completed. - void wait() noexcept { - if (st) { - do_wait(); - } - } - -private: - explicit task_region_handle(thread_group& threads_src) - : st(nullptr), threads(threads_src), next_id(0) { - } - - void create_state(); - void do_wait() noexcept; - - std::shared_ptr<state> st; - thread_group& threads; - state::id next_id; -}; - -/*! Function for creating a `task_region_handle`, which automatically calls -`task_region_handle::wait()` before returning. If a `thread_group` is not -provided, one is created with an optimal number of threads. The callback `f` -must have the signature `void(task_region_handle&)`. */ -struct task_region_ { - template<typename F> - void operator()(thread_group& threads, F&& f) const { - static_assert( - std::is_same<void, typename std::result_of<F(task_region_handle&)>::type>::value, - "f cannot have a return value" - ); - task_region_handle region{threads}; - f(region); - region.wait(); - } - - template<typename F> - void operator()(thread_group&& threads, F&& f) const { - (*this)(threads, std::forward<F>(f)); - } - - template<typename F> - void operator()(F&& f) const { - thread_group threads; - (*this)(threads, std::forward<F>(f)); - } -}; - -constexpr const task_region_ task_region{}; -} diff --git a/src/common/thread_group.cpp b/src/common/thread_group.cpp deleted file mode 100644 index 691a27a25..000000000 --- a/src/common/thread_group.cpp +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright (c) 2014-2017, 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 "common/thread_group.h" - -#include <boost/thread/locks.hpp> -#include <cassert> -#include <limits> -#include <stdexcept> - -#include "cryptonote_config.h" -#include "common/util.h" - -namespace tools -{ -std::size_t thread_group::optimal() { - static_assert( - std::numeric_limits<unsigned>::max() <= std::numeric_limits<std::size_t>::max(), - "unexpected truncation" - ); - const std::size_t hardware = get_max_concurrency(); - return hardware ? (hardware - 1) : 0; -} - -std::size_t thread_group::optimal_with_max(std::size_t count) { - return count ? std::min(count - 1, optimal()) : 0; -} - -thread_group::thread_group(std::size_t count) : internal() { - if (count) { - internal.emplace(count); - } -} - -thread_group::data::data(std::size_t count) - : threads() - , head{nullptr} - , last(std::addressof(head)) - , mutex() - , has_work() - , stop(false) { - threads.reserve(count); - boost::thread::attributes attrs; - attrs.set_stack_size(THREAD_STACK_SIZE); - while (count--) { - threads.push_back(boost::thread(attrs, boost::bind(&thread_group::data::run, this))); - } -} - -thread_group::data::~data() noexcept { - { - const boost::unique_lock<boost::mutex> lock(mutex); - stop = true; - } - has_work.notify_all(); - for (auto& worker : threads) { - try { - worker.join(); - } - catch(...) {} - } -} - -std::unique_ptr<thread_group::data::work> thread_group::data::get_next() noexcept { - std::unique_ptr<work> rc = std::move(head.ptr); - if (rc != nullptr) { - head.ptr = std::move(rc->next.ptr); - if (head.ptr == nullptr) { - last = std::addressof(head); - } - } - return rc; -} - -bool thread_group::data::try_run_one() noexcept { - /* This function and `run()` can both throw when acquiring the lock, or in - dispatched function. It is tough to recover from either, particularly the - lock case. These functions are marked as noexcept so that if either call - throws, the entire process is terminated. Users of the `dispatch` call are - expected to make their functions noexcept, or use std::packaged_task to copy - exceptions so that the process will continue in all but the most pessimistic - cases (std::bad_alloc). This was the existing behavior; - `asio::io_service::run` propogates errors from dispatched calls, and uncaught - exceptions on threads result in process termination. */ - std::unique_ptr<work> next = nullptr; - { - const boost::unique_lock<boost::mutex> lock(mutex); - next = get_next(); - } - if (next) { - assert(next->f); - next->f(); - return true; - } - return false; -} - -void thread_group::data::run() noexcept { - // see `try_run_one()` source for additional information - while (true) { - std::unique_ptr<work> next = nullptr; - { - boost::unique_lock<boost::mutex> lock(mutex); - has_work.wait(lock, [this] { return head.ptr != nullptr || stop; }); - if (stop) { - return; - } - next = get_next(); - } - assert(next != nullptr); - assert(next->f); - next->f(); - } -} - -void thread_group::data::dispatch(std::function<void()> f) { - std::unique_ptr<work> latest(new work{std::move(f), node{nullptr}}); - node* const latest_node = std::addressof(latest->next); - { - const boost::unique_lock<boost::mutex> lock(mutex); - assert(last != nullptr); - assert(last->ptr == nullptr); - - last->ptr = std::move(latest); - last = latest_node; - } - has_work.notify_one(); -} -} diff --git a/src/common/thread_group.h b/src/common/thread_group.h deleted file mode 100644 index 48fd4cd56..000000000 --- a/src/common/thread_group.h +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright (c) 2014-2017, 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. -#pragma once - -#include <boost/optional/optional.hpp> -#include <boost/thread/condition_variable.hpp> -#include <boost/thread/mutex.hpp> -#include <boost/thread/thread.hpp> -#include <cstddef> -#include <functional> -#include <thread> -#include <utility> -#include <vector> - -namespace tools -{ -//! Manages zero or more threads for work dispatching. -class thread_group -{ -public: - - //! \return `get_max_concurrency() ? get_max_concurrency() - 1 : 0` - static std::size_t optimal(); - - //! \return `count ? min(count - 1, optimal()) : 0` - static std::size_t optimal_with_max(std::size_t count); - - //! Create an optimal number of threads. - explicit thread_group() : thread_group(optimal()) {} - - //! Create exactly `count` threads. - explicit thread_group(std::size_t count); - - thread_group(thread_group const&) = delete; - thread_group(thread_group&&) = delete; - - //! Joins threads, but does not necessarily run all dispatched functions. - ~thread_group() = default; - - thread_group& operator=(thread_group const&) = delete; - thread_group& operator=(thread_group&&) = delete; - - //! \return Number of threads owned by `this` group. - std::size_t count() const noexcept { - if (internal) { - return internal->count(); - } - return 0; - } - - //! \return True iff a function was available and executed (on `this_thread`). - bool try_run_one() noexcept { - if (internal) { - return internal->try_run_one(); - } - return false; - } - - /*! `f` is invoked immediately if `count() == 0`, otherwise execution of `f` - is queued for next available thread. If `f` is queued, any exception leaving - that function will result in process termination. Use std::packaged_task if - exceptions need to be handled. */ - template<typename F> - void dispatch(F&& f) { - if (internal) { - internal->dispatch(std::forward<F>(f)); - } - else { - f(); - } - } - -private: - class data { - public: - data(std::size_t count); - ~data() noexcept; - - std::size_t count() const noexcept { - return threads.size(); - } - - bool try_run_one() noexcept; - void dispatch(std::function<void()> f); - - private: - struct work; - - struct node { - std::unique_ptr<work> ptr; - }; - - struct work { - std::function<void()> f; - node next; - }; - - //! Requires lock on `mutex`. - std::unique_ptr<work> get_next() noexcept; - - //! Blocks until destructor is invoked, only call from thread. - void run() noexcept; - - private: - std::vector<boost::thread> threads; - node head; - node* last; - boost::condition_variable has_work; - boost::mutex mutex; - bool stop; - }; - -private: - // optionally construct elements, without separate heap allocation - boost::optional<data> internal; -}; - -} diff --git a/src/common/threadpool.cpp b/src/common/threadpool.cpp new file mode 100644 index 000000000..41d0c25e0 --- /dev/null +++ b/src/common/threadpool.cpp @@ -0,0 +1,117 @@ +// Copyright (c) 2017, 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 "common/threadpool.h" + +#include <cassert> +#include <limits> +#include <stdexcept> + +#include "cryptonote_config.h" +#include "common/util.h" + +namespace tools +{ +threadpool::threadpool() : running(true), active(0) { + boost::thread::attributes attrs; + attrs.set_stack_size(THREAD_STACK_SIZE); + max = tools::get_max_concurrency() * 2; + size_t i = max; + while(i--) { + threads.push_back(boost::thread(attrs, boost::bind(&threadpool::run, this))); + } +} + +threadpool::~threadpool() { + { + const boost::unique_lock<boost::mutex> lock(mutex); + running = false; + has_work.notify_all(); + } + for (size_t i = 0; i<threads.size(); i++) { + threads[i].join(); + } +} + +void threadpool::submit(waiter *obj, std::function<void()> f) { + entry e = {obj, f}; + boost::unique_lock<boost::mutex> lock(mutex); + if (active == max && !queue.empty()) { + // if all available threads are already running + // and there's work waiting, just run in current thread + lock.unlock(); + f(); + } else { + if (obj) + obj->inc(); + queue.push_back(e); + has_work.notify_one(); + } +} + +int threadpool::get_max_concurrency() { + return max / 2; +} + +void threadpool::waiter::wait() { + boost::unique_lock<boost::mutex> lock(mt); + while(num) cv.wait(lock); +} + +void threadpool::waiter::inc() { + const boost::unique_lock<boost::mutex> lock(mt); + num++; +} + +void threadpool::waiter::dec() { + const boost::unique_lock<boost::mutex> lock(mt); + num--; + if (!num) + cv.notify_one(); +} + +void threadpool::run() { + boost::unique_lock<boost::mutex> lock(mutex); + while (running) { + entry e; + while(queue.empty() && running) + has_work.wait(lock); + if (!running) break; + + active++; + e = queue.front(); + queue.pop_front(); + lock.unlock(); + e.f(); + + if (e.wo) + e.wo->dec(); + lock.lock(); + active--; + } +} +} diff --git a/src/common/threadpool.h b/src/common/threadpool.h new file mode 100644 index 000000000..1d56d7605 --- /dev/null +++ b/src/common/threadpool.h @@ -0,0 +1,87 @@ +// Copyright (c) 2017, 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. +#pragma once + +#include <boost/thread/condition_variable.hpp> +#include <boost/thread/mutex.hpp> +#include <boost/thread/thread.hpp> +#include <cstddef> +#include <functional> +#include <utility> +#include <vector> + +namespace tools +{ +//! A global thread pool +class threadpool +{ +public: + static threadpool& getInstance() { + static threadpool instance; + return instance; + } + + // The waiter lets the caller know when all of its + // tasks are completed. + class waiter { + boost::mutex mt; + boost::condition_variable cv; + int num; + public: + void inc(); + void dec(); + void wait(); //! Wait for a set of tasks to finish. + waiter() : num(0){} + ~waiter() { wait(); } + }; + + // Submit a task to the pool. The waiter pointer may be + // NULL if the caller doesn't care to wait for the + // task to finish. + void submit(waiter *waiter, std::function<void()> f); + + int get_max_concurrency(); + + private: + threadpool(); + ~threadpool(); + typedef struct entry { + waiter *wo; + std::function<void()> f; + } entry; + std::deque<entry> queue; + boost::condition_variable has_work; + boost::mutex mutex; + std::vector<boost::thread> threads; + int active; + int max; + bool running; + void run(); +}; + +} diff --git a/src/common/util.cpp b/src/common/util.cpp index 046961b06..74a6babf1 100644 --- a/src/common/util.cpp +++ b/src/common/util.cpp @@ -39,11 +39,13 @@ using namespace epee; #include "net/http_client.h" // epee::net_utils::... #ifdef WIN32 -#include <windows.h> -#include <shlobj.h> -#include <strsafe.h> + #include <windows.h> + #include <shlobj.h> + #include <strsafe.h> #else -#include <sys/utsname.h> + #include <sys/file.h> + #include <sys/utsname.h> + #include <sys/stat.h> #endif #include <boost/filesystem.hpp> #include <boost/asio.hpp> @@ -53,7 +55,12 @@ namespace tools { std::function<void(int)> signal_handler::m_handler; - std::unique_ptr<std::FILE, tools::close_file> create_private_file(const std::string& name) + private_file::private_file() noexcept : m_handle(), m_filename() {} + + private_file::private_file(std::FILE* handle, std::string&& filename) noexcept + : m_handle(handle), m_filename(std::move(filename)) {} + + private_file private_file::create(std::string name) { #ifdef WIN32 struct close_handle @@ -70,17 +77,17 @@ namespace tools const bool fail = OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, std::addressof(temp)) == 0; process.reset(temp); if (fail) - return nullptr; + return {}; } DWORD sid_size = 0; GetTokenInformation(process.get(), TokenOwner, nullptr, 0, std::addressof(sid_size)); if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) - return nullptr; + return {}; std::unique_ptr<char[]> sid{new char[sid_size]}; if (!GetTokenInformation(process.get(), TokenOwner, sid.get(), sid_size, std::addressof(sid_size))) - return nullptr; + return {}; const PSID psid = reinterpret_cast<const PTOKEN_OWNER>(sid.get())->Owner; const DWORD daclSize = @@ -88,17 +95,17 @@ namespace tools const std::unique_ptr<char[]> dacl{new char[daclSize]}; if (!InitializeAcl(reinterpret_cast<PACL>(dacl.get()), daclSize, ACL_REVISION)) - return nullptr; + return {}; if (!AddAccessAllowedAce(reinterpret_cast<PACL>(dacl.get()), ACL_REVISION, (READ_CONTROL | FILE_GENERIC_READ | DELETE), psid)) - return nullptr; + return {}; SECURITY_DESCRIPTOR descriptor{}; if (!InitializeSecurityDescriptor(std::addressof(descriptor), SECURITY_DESCRIPTOR_REVISION)) - return nullptr; + return {}; if (!SetSecurityDescriptorDacl(std::addressof(descriptor), true, reinterpret_cast<PACL>(dacl.get()), false)) - return nullptr; + return {}; SECURITY_ATTRIBUTES attributes{sizeof(SECURITY_ATTRIBUTES), std::addressof(descriptor), false}; std::unique_ptr<void, close_handle> file{ @@ -106,7 +113,7 @@ namespace tools name.c_str(), GENERIC_WRITE, FILE_SHARE_READ, std::addressof(attributes), - CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY, + CREATE_NEW, (FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE), nullptr ) }; @@ -121,22 +128,49 @@ namespace tools { _close(fd); } - return {real_file, tools::close_file{}}; + return {real_file, std::move(name)}; } } #else - const int fd = open(name.c_str(), (O_RDWR | O_EXCL | O_CREAT), S_IRUSR); - if (0 <= fd) + const int fdr = open(name.c_str(), (O_RDONLY | O_CREAT), S_IRUSR); + if (0 <= fdr) { - std::FILE* file = fdopen(fd, "w"); - if (!file) + struct stat rstats = {}; + if (fstat(fdr, std::addressof(rstats)) != 0) { - close(fd); + close(fdr); + return {}; + } + fchmod(fdr, (S_IRUSR | S_IWUSR)); + const int fdw = open(name.c_str(), O_RDWR); + fchmod(fdr, rstats.st_mode); + close(fdr); + + if (0 <= fdw) + { + struct stat wstats = {}; + if (fstat(fdw, std::addressof(wstats)) == 0 && + rstats.st_dev == wstats.st_dev && rstats.st_ino == wstats.st_ino && + flock(fdw, (LOCK_EX | LOCK_NB)) == 0 && ftruncate(fdw, 0) == 0) + { + std::FILE* file = fdopen(fdw, "w"); + if (file) return {file, std::move(name)}; + } + close(fdw); } - return {file, tools::close_file{}}; } #endif - return nullptr; + return {}; + } + + private_file::~private_file() noexcept + { + try + { + boost::system::error_code ec{}; + boost::filesystem::remove(filename(), ec); + } + catch (...) {} } #ifdef WIN32 diff --git a/src/common/util.h b/src/common/util.h index 2452bc9d5..48bdbbc28 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -60,8 +60,30 @@ namespace tools } }; - //! \return File only readable by owner. nullptr if `filename` exists. - std::unique_ptr<std::FILE, close_file> create_private_file(const std::string& filename); + //! A file restricted to process owner AND process. Deletes file on destruction. + class private_file { + std::unique_ptr<std::FILE, close_file> m_handle; + std::string m_filename; + + private_file(std::FILE* handle, std::string&& filename) noexcept; + public: + + //! `handle() == nullptr && filename.empty()`. + private_file() noexcept; + + /*! \return File only readable by owner and only used by this process + OR `private_file{}` on error. */ + static private_file create(std::string filename); + + private_file(private_file&&) = default; + private_file& operator=(private_file&&) = default; + + //! Deletes `filename()` and closes `handle()`. + ~private_file() noexcept; + + std::FILE* handle() const noexcept { return m_handle.get(); } + const std::string& filename() const noexcept { return m_filename; } + }; /*! \brief Returns the default data directory. * diff --git a/src/crypto/crypto.cpp b/src/crypto/crypto.cpp index 5fb670f87..95ba34828 100644 --- a/src/crypto/crypto.cpp +++ b/src/crypto/crypto.cpp @@ -87,7 +87,7 @@ namespace crypto { random_scalar_not_thread_safe(res); } - static inline void hash_to_scalar(const void *data, size_t length, ec_scalar &res) { + void hash_to_scalar(const void *data, size_t length, ec_scalar &res) { cn_fast_hash(data, length, reinterpret_cast<hash &>(res)); sc_reduce32(&res); } @@ -189,6 +189,25 @@ namespace crypto { sc_add(&derived_key, &base, &scalar); } + bool crypto_ops::derive_subaddress_public_key(const public_key &out_key, const key_derivation &derivation, std::size_t output_index, public_key &derived_key) { + ec_scalar scalar; + ge_p3 point1; + ge_p3 point2; + ge_cached point3; + ge_p1p1 point4; + ge_p2 point5; + if (ge_frombytes_vartime(&point1, &out_key) != 0) { + return false; + } + derivation_to_scalar(derivation, output_index, scalar); + ge_scalarmult_base(&point2, &scalar); + ge_p3_to_cached(&point3, &point2); + ge_sub(&point4, &point1, &point3); + ge_p1p1_to_p2(&point5, &point4); + ge_tobytes(&derived_key, &point5); + return true; + } + struct s_comm { hash h; ec_point key; @@ -246,22 +265,33 @@ namespace crypto { return sc_isnonzero(&c) == 0; } - void crypto_ops::generate_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const public_key &D, const secret_key &r, signature &sig) { + void crypto_ops::generate_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional<public_key> &B, const public_key &D, const secret_key &r, signature &sig) { // sanity check ge_p3 R_p3; ge_p3 A_p3; + ge_p3 B_p3; ge_p3 D_p3; if (ge_frombytes_vartime(&R_p3, &R) != 0) throw std::runtime_error("tx pubkey is invalid"); if (ge_frombytes_vartime(&A_p3, &A) != 0) throw std::runtime_error("recipient view pubkey is invalid"); + if (B && ge_frombytes_vartime(&B_p3, &*B) != 0) throw std::runtime_error("recipient spend pubkey is invalid"); if (ge_frombytes_vartime(&D_p3, &D) != 0) throw std::runtime_error("key derivation is invalid"); #if !defined(NDEBUG) { assert(sc_check(&r) == 0); - // check R == r*G - ge_p3 dbg_R_p3; - ge_scalarmult_base(&dbg_R_p3, &r); + // check R == r*G or R == r*B public_key dbg_R; - ge_p3_tobytes(&dbg_R, &dbg_R_p3); + if (B) + { + ge_p2 dbg_R_p2; + ge_scalarmult(&dbg_R_p2, &r, &B_p3); + ge_tobytes(&dbg_R, &dbg_R_p2); + } + else + { + ge_p3 dbg_R_p3; + ge_scalarmult_base(&dbg_R_p3, &r); + ge_p3_tobytes(&dbg_R, &dbg_R_p3); + } assert(R == dbg_R); // check D == r*A ge_p2 dbg_D_p2; @@ -276,43 +306,84 @@ namespace crypto { ec_scalar k; random_scalar(k); - // compute X = k*G - ge_p3 X_p3; - ge_scalarmult_base(&X_p3, &k); + s_comm_2 buf; + buf.msg = prefix_hash; + buf.D = D; + + if (B) + { + // compute X = k*B + ge_p2 X_p2; + ge_scalarmult(&X_p2, &k, &B_p3); + ge_tobytes(&buf.X, &X_p2); + } + else + { + // compute X = k*G + ge_p3 X_p3; + ge_scalarmult_base(&X_p3, &k); + ge_p3_tobytes(&buf.X, &X_p3); + } // compute Y = k*A ge_p2 Y_p2; ge_scalarmult(&Y_p2, &k, &A_p3); + ge_tobytes(&buf.Y, &Y_p2); // sig.c = Hs(Msg || D || X || Y) - s_comm_2 buf; - buf.msg = prefix_hash; - buf.D = D; - ge_p3_tobytes(&buf.X, &X_p3); - ge_tobytes(&buf.Y, &Y_p2); - hash_to_scalar(&buf, sizeof(s_comm_2), sig.c); + hash_to_scalar(&buf, sizeof(buf), sig.c); // sig.r = k - sig.c*r sc_mulsub(&sig.r, &sig.c, &r, &k); } - bool crypto_ops::check_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const public_key &D, const signature &sig) { + bool crypto_ops::check_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional<public_key> &B, const public_key &D, const signature &sig) { // sanity check ge_p3 R_p3; ge_p3 A_p3; + ge_p3 B_p3; ge_p3 D_p3; if (ge_frombytes_vartime(&R_p3, &R) != 0) return false; if (ge_frombytes_vartime(&A_p3, &A) != 0) return false; + if (B && ge_frombytes_vartime(&B_p3, &*B) != 0) return false; if (ge_frombytes_vartime(&D_p3, &D) != 0) return false; if (sc_check(&sig.c) != 0 || sc_check(&sig.r) != 0) return false; // compute sig.c*R - ge_p2 cR_p2; - ge_scalarmult(&cR_p2, &sig.c, &R_p3); + ge_p3 cR_p3; + { + ge_p2 cR_p2; + ge_scalarmult(&cR_p2, &sig.c, &R_p3); + public_key cR; + ge_tobytes(&cR, &cR_p2); + if (ge_frombytes_vartime(&cR_p3, &cR) != 0) return false; + } - // compute sig.r*G - ge_p3 rG_p3; - ge_scalarmult_base(&rG_p3, &sig.r); + ge_p1p1 X_p1p1; + if (B) + { + // compute X = sig.c*R + sig.r*B + ge_p2 rB_p2; + ge_scalarmult(&rB_p2, &sig.r, &B_p3); + public_key rB; + ge_tobytes(&rB, &rB_p2); + ge_p3 rB_p3; + if (ge_frombytes_vartime(&rB_p3, &rB) != 0) return false; + ge_cached rB_cached; + ge_p3_to_cached(&rB_cached, &rB_p3); + ge_add(&X_p1p1, &cR_p3, &rB_cached); + } + else + { + // compute X = sig.c*R + sig.r*G + ge_p3 rG_p3; + ge_scalarmult_base(&rG_p3, &sig.r); + ge_cached rG_cached; + ge_p3_to_cached(&rG_cached, &rG_p3); + ge_add(&X_p1p1, &cR_p3, &rG_cached); + } + ge_p2 X_p2; + ge_p1p1_to_p2(&X_p2, &X_p1p1); // compute sig.c*D ge_p2 cD_p2; @@ -322,18 +393,6 @@ namespace crypto { ge_p2 rA_p2; ge_scalarmult(&rA_p2, &sig.r, &A_p3); - // compute X = sig.c*R + sig.r*G - public_key cR; - ge_tobytes(&cR, &cR_p2); - ge_p3 cR_p3; - if (ge_frombytes_vartime(&cR_p3, &cR) != 0) return false; - ge_cached rG_cached; - ge_p3_to_cached(&rG_cached, &rG_p3); - ge_p1p1 X_p1p1; - ge_add(&X_p1p1, &cR_p3, &rG_cached); - ge_p2 X_p2; - ge_p1p1_to_p2(&X_p2, &X_p1p1); - // compute Y = sig.c*D + sig.r*A public_key cD; public_key rA; diff --git a/src/crypto/crypto.h b/src/crypto/crypto.h index e99b6651f..abdea0165 100644 --- a/src/crypto/crypto.h +++ b/src/crypto/crypto.h @@ -31,12 +31,17 @@ #pragma once #include <cstddef> +#include <iostream> #include <boost/thread/mutex.hpp> #include <boost/thread/lock_guard.hpp> +#include <boost/utility/value_init.hpp> +#include <boost/optional.hpp> #include <vector> #include "common/pod-class.h" #include "generic-ops.h" +#include "hex.h" +#include "span.h" #include "hash.h" namespace crypto { @@ -94,6 +99,8 @@ namespace crypto { }; #pragma pack(pop) + void hash_to_scalar(const void *data, size_t length, ec_scalar &res); + static_assert(sizeof(ec_point) == 32 && sizeof(ec_scalar) == 32 && sizeof(public_key) == 32 && sizeof(secret_key) == 32 && sizeof(key_derivation) == 32 && sizeof(key_image) == 32 && @@ -119,14 +126,16 @@ namespace crypto { 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 &); friend void derive_secret_key(const key_derivation &, std::size_t, const secret_key &, secret_key &); + static bool derive_subaddress_public_key(const public_key &, const key_derivation &, std::size_t, public_key &); + friend bool derive_subaddress_public_key(const public_key &, const key_derivation &, std::size_t, public_key &); static void generate_signature(const hash &, const public_key &, const secret_key &, signature &); friend void generate_signature(const hash &, const public_key &, const secret_key &, signature &); static bool check_signature(const hash &, const public_key &, const signature &); friend bool check_signature(const hash &, const public_key &, const signature &); - static void generate_tx_proof(const hash &, const public_key &, const public_key &, const public_key &, const secret_key &, signature &); - friend void generate_tx_proof(const hash &, const public_key &, const public_key &, const public_key &, const secret_key &, signature &); - static bool check_tx_proof(const hash &, const public_key &, const public_key &, const public_key &, const signature &); - friend bool check_tx_proof(const hash &, const public_key &, const public_key &, const public_key &, const signature &); + static void generate_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional<public_key> &, const public_key &, const secret_key &, signature &); + friend void generate_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional<public_key> &, const public_key &, const secret_key &, signature &); + static bool check_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional<public_key> &, const public_key &, const signature &); + friend bool check_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional<public_key> &, const public_key &, const signature &); static void generate_key_image(const public_key &, const secret_key &, key_image &); friend void generate_key_image(const public_key &, const secret_key &, key_image &); static void generate_ring_signature(const hash &, const key_image &, @@ -194,6 +203,9 @@ namespace crypto { const secret_key &base, secret_key &derived_key) { crypto_ops::derive_secret_key(derivation, output_index, base, derived_key); } + inline bool derive_subaddress_public_key(const public_key &out_key, const key_derivation &derivation, std::size_t output_index, public_key &result) { + return crypto_ops::derive_subaddress_public_key(out_key, derivation, output_index, result); + } /* Generation and checking of a standard signature. */ @@ -206,12 +218,13 @@ namespace crypto { /* Generation and checking of a tx proof; given a tx pubkey R, the recipient's view pubkey A, and the key * derivation D, the signature proves the knowledge of the tx secret key r such that R=r*G and D=r*A + * When the recipient's address is a subaddress, the tx pubkey R is defined as R=r*B where B is the recipient's spend pubkey */ - inline void generate_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const public_key &D, const secret_key &r, signature &sig) { - crypto_ops::generate_tx_proof(prefix_hash, R, A, D, r, sig); + inline void generate_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional<public_key> &B, const public_key &D, const secret_key &r, signature &sig) { + crypto_ops::generate_tx_proof(prefix_hash, R, A, B, D, r, sig); } - inline bool check_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const public_key &D, const signature &sig) { - return crypto_ops::check_tx_proof(prefix_hash, R, A, D, sig); + inline bool check_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional<public_key> &B, const public_key &D, const signature &sig) { + return crypto_ops::check_tx_proof(prefix_hash, R, A, B, D, sig); } /* To send money to a key: @@ -248,8 +261,28 @@ namespace crypto { const signature *sig) { return check_ring_signature(prefix_hash, image, pubs.data(), pubs.size(), sig); } + + inline std::ostream &operator <<(std::ostream &o, const crypto::public_key &v) { + epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; + } + inline std::ostream &operator <<(std::ostream &o, const crypto::secret_key &v) { + epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; + } + inline std::ostream &operator <<(std::ostream &o, const crypto::key_derivation &v) { + epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; + } + inline std::ostream &operator <<(std::ostream &o, const crypto::key_image &v) { + epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; + } + inline std::ostream &operator <<(std::ostream &o, const crypto::signature &v) { + epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; + } + + const static crypto::public_key null_pkey = boost::value_initialized<crypto::public_key>(); + const static crypto::secret_key null_skey = boost::value_initialized<crypto::secret_key>(); } CRYPTO_MAKE_HASHABLE(public_key) +CRYPTO_MAKE_HASHABLE(secret_key) CRYPTO_MAKE_HASHABLE(key_image) CRYPTO_MAKE_COMPARABLE(signature) diff --git a/src/crypto/hash.h b/src/crypto/hash.h index 22991e513..610b4502f 100644 --- a/src/crypto/hash.h +++ b/src/crypto/hash.h @@ -31,9 +31,13 @@ #pragma once #include <stddef.h> +#include <iostream> +#include <boost/utility/value_init.hpp> #include "common/pod-class.h" #include "generic-ops.h" +#include "hex.h" +#include "span.h" namespace crypto { @@ -75,6 +79,15 @@ namespace crypto { tree_hash(reinterpret_cast<const char (*)[HASH_SIZE]>(hashes), count, reinterpret_cast<char *>(&root_hash)); } + inline std::ostream &operator <<(std::ostream &o, const crypto::hash &v) { + epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; + } + inline std::ostream &operator <<(std::ostream &o, const crypto::hash8 &v) { + epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; + } + + const static crypto::hash null_hash = boost::value_initialized<crypto::hash>(); + const static crypto::hash8 null_hash8 = boost::value_initialized<crypto::hash8>(); } CRYPTO_MAKE_HASHABLE(hash) diff --git a/src/cryptonote_basic/CMakeLists.txt b/src/cryptonote_basic/CMakeLists.txt index 1503b277e..750be69f1 100644 --- a/src/cryptonote_basic/CMakeLists.txt +++ b/src/cryptonote_basic/CMakeLists.txt @@ -34,7 +34,6 @@ endif() set(cryptonote_basic_sources account.cpp - checkpoints.cpp cryptonote_basic_impl.cpp cryptonote_format_utils.cpp difficulty.cpp @@ -46,7 +45,6 @@ set(cryptonote_basic_headers) set(cryptonote_basic_private_headers account.h account_boost_serialization.h - checkpoints.h connection_context.h cryptonote_basic.h cryptonote_basic_impl.h @@ -60,7 +58,7 @@ set(cryptonote_basic_private_headers verification_context.h) monero_private_headers(cryptonote_basic - ${crypto_private_headers}) + ${cryptonote_basic_private_headers}) monero_add_library(cryptonote_basic ${cryptonote_basic_sources} ${cryptonote_basic_headers} @@ -69,6 +67,7 @@ target_link_libraries(cryptonote_basic PUBLIC common cncrypto + checkpoints ${Boost_DATE_TIME_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_SERIALIZATION_LIBRARY} diff --git a/src/cryptonote_basic/account.cpp b/src/cryptonote_basic/account.cpp index dd875402f..fb832d88e 100644 --- a/src/cryptonote_basic/account.cpp +++ b/src/cryptonote_basic/account.cpp @@ -131,7 +131,7 @@ DISABLE_VS_WARNINGS(4244 4345) std::string account_base::get_public_address_str(bool testnet) const { //TODO: change this code into base 58 - return get_account_address_as_str(testnet, m_keys.m_account_address); + return get_account_address_as_str(testnet, false, m_keys.m_account_address); } //----------------------------------------------------------------- std::string account_base::get_public_integrated_address_str(const crypto::hash8 &payment_id, bool testnet) const diff --git a/src/cryptonote_basic/connection_context.h b/src/cryptonote_basic/connection_context.h index 3283543e2..e173348db 100644 --- a/src/cryptonote_basic/connection_context.h +++ b/src/cryptonote_basic/connection_context.h @@ -40,7 +40,7 @@ namespace cryptonote struct cryptonote_connection_context: public epee::net_utils::connection_context_base { cryptonote_connection_context(): m_state(state_before_handshake), m_remote_blockchain_height(0), m_last_response_height(0), - m_last_known_hash(cryptonote::null_hash) {} + m_callback_request_count(0), m_last_known_hash(crypto::null_hash) {} enum state { diff --git a/src/cryptonote_basic/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h index c4adf1fcb..89dda8c3d 100644 --- a/src/cryptonote_basic/cryptonote_basic.h +++ b/src/cryptonote_basic/cryptonote_basic.h @@ -53,11 +53,6 @@ namespace cryptonote { - - const static crypto::hash null_hash = AUTO_VAL_INIT(null_hash); - const static crypto::hash8 null_hash8 = AUTO_VAL_INIT(null_hash8); - const static crypto::public_key null_pkey = AUTO_VAL_INIT(null_pkey); - typedef std::vector<crypto::signature> ring_signature; @@ -416,6 +411,17 @@ namespace cryptonote KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(m_spend_public_key) KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(m_view_public_key) END_KV_SERIALIZE_MAP() + + bool operator==(const account_public_address& rhs) const + { + return m_spend_public_key == rhs.m_spend_public_key && + m_view_public_key == rhs.m_view_public_key; + } + + bool operator!=(const account_public_address& rhs) const + { + return !(*this == rhs); + } }; struct keypair @@ -434,6 +440,21 @@ namespace cryptonote } +namespace std { + template <> + struct hash<cryptonote::account_public_address> + { + std::size_t operator()(const cryptonote::account_public_address& addr) const + { + // https://stackoverflow.com/a/17017281 + size_t res = 17; + res = res * 31 + hash<crypto::public_key>()(addr.m_spend_public_key); + res = res * 31 + hash<crypto::public_key>()(addr.m_view_public_key); + return res; + } + }; +} + BLOB_SERIALIZER(cryptonote::txout_to_key); BLOB_SERIALIZER(cryptonote::txout_to_scripthash); diff --git a/src/cryptonote_basic/cryptonote_basic_impl.cpp b/src/cryptonote_basic/cryptonote_basic_impl.cpp index a59f96956..1183fda06 100644 --- a/src/cryptonote_basic/cryptonote_basic_impl.cpp +++ b/src/cryptonote_basic/cryptonote_basic_impl.cpp @@ -158,11 +158,13 @@ namespace cryptonote { //----------------------------------------------------------------------- std::string get_account_address_as_str( bool testnet + , bool subaddress , account_public_address const & adr ) { uint64_t address_prefix = testnet ? - config::testnet::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX : config::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX; + (subaddress ? config::testnet::CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX : config::testnet::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX) : + (subaddress ? config::CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX : config::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX); return tools::base58::encode_addr(address_prefix, t_serializable_object_to_blob(adr)); } @@ -173,8 +175,7 @@ namespace cryptonote { , crypto::hash8 const & payment_id ) { - uint64_t integrated_address_prefix = testnet ? - config::testnet::CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX : config::CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX; + uint64_t integrated_address_prefix = testnet ? config::testnet::CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX : config::CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX; integrated_address iadr = { adr, payment_id @@ -193,10 +194,8 @@ namespace cryptonote { return true; } //----------------------------------------------------------------------- - bool get_account_integrated_address_from_str( - account_public_address& adr - , bool& has_payment_id - , crypto::hash8& payment_id + bool get_account_address_from_str( + address_parse_info& info , bool testnet , std::string const & str ) @@ -205,6 +204,8 @@ namespace cryptonote { config::testnet::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX : config::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX; uint64_t integrated_address_prefix = testnet ? config::testnet::CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX : config::CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX; + uint64_t subaddress_prefix = testnet ? + config::testnet::CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX : config::CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX; if (2 * sizeof(public_address_outer_blob) != str.size()) { @@ -218,18 +219,27 @@ namespace cryptonote { if (integrated_address_prefix == prefix) { - has_payment_id = true; + info.is_subaddress = false; + info.has_payment_id = true; } else if (address_prefix == prefix) { - has_payment_id = false; + info.is_subaddress = false; + info.has_payment_id = false; + } + else if (subaddress_prefix == prefix) + { + info.is_subaddress = true; + info.has_payment_id = false; } else { - LOG_PRINT_L1("Wrong address prefix: " << prefix << ", expected " << address_prefix << " or " << integrated_address_prefix); + LOG_PRINT_L1("Wrong address prefix: " << prefix << ", expected " << address_prefix + << " or " << integrated_address_prefix + << " or " << subaddress_prefix); return false; } - if (has_payment_id) + if (info.has_payment_id) { integrated_address iadr; if (!::serialization::parse_binary(data, iadr)) @@ -237,19 +247,19 @@ namespace cryptonote { LOG_PRINT_L1("Account public address keys can't be parsed"); return false; } - adr = iadr.adr; - payment_id = iadr.payment_id; + info.address = iadr.adr; + info.payment_id = iadr.payment_id; } else { - if (!::serialization::parse_binary(data, adr)) + if (!::serialization::parse_binary(data, info.address)) { LOG_PRINT_L1("Account public address keys can't be parsed"); return false; } } - if (!crypto::check_key(adr.m_spend_public_key) || !crypto::check_key(adr.m_view_public_key)) + if (!crypto::check_key(info.address.m_spend_public_key) || !crypto::check_key(info.address.m_view_public_key)) { LOG_PRINT_L1("Failed to validate address keys"); return false; @@ -284,51 +294,27 @@ namespace cryptonote { } //we success - adr = blob.m_address; - has_payment_id = false; + info.address = blob.m_address; + info.is_subaddress = false; + info.has_payment_id = false; } return true; } - //----------------------------------------------------------------------- - bool get_account_address_from_str( - account_public_address& adr - , bool testnet - , std::string const & str - ) - { - bool has_payment_id; - crypto::hash8 payment_id; - return get_account_integrated_address_from_str(adr, has_payment_id, payment_id, testnet, str); - } //-------------------------------------------------------------------------------- bool get_account_address_from_str_or_url( - cryptonote::account_public_address& address - , bool& has_payment_id - , crypto::hash8& payment_id + address_parse_info& info , bool testnet , const std::string& str_or_url , std::function<std::string(const std::string&, const std::vector<std::string>&, bool)> dns_confirm ) { - if (get_account_integrated_address_from_str(address, has_payment_id, payment_id, testnet, str_or_url)) + if (get_account_address_from_str(info, testnet, str_or_url)) return true; bool dnssec_valid; std::string address_str = tools::dns_utils::get_account_address_as_str_from_url(str_or_url, dnssec_valid, dns_confirm); return !address_str.empty() && - get_account_integrated_address_from_str(address, has_payment_id, payment_id, testnet, address_str); - } - //-------------------------------------------------------------------------------- - bool get_account_address_from_str_or_url( - cryptonote::account_public_address& address - , bool testnet - , const std::string& str_or_url - , std::function<std::string(const std::string&, const std::vector<std::string>&, bool)> dns_confirm - ) - { - bool has_payment_id; - crypto::hash8 payment_id; - return get_account_address_from_str_or_url(address, has_payment_id, payment_id, testnet, str_or_url, dns_confirm); + get_account_address_from_str(info, testnet, address_str); } //-------------------------------------------------------------------------------- bool operator ==(const cryptonote::transaction& a, const cryptonote::transaction& b) { diff --git a/src/cryptonote_basic/cryptonote_basic_impl.h b/src/cryptonote_basic/cryptonote_basic_impl.h index 7a2259b32..08d966fed 100644 --- a/src/cryptonote_basic/cryptonote_basic_impl.h +++ b/src/cryptonote_basic/cryptonote_basic_impl.h @@ -33,8 +33,6 @@ #include "cryptonote_basic.h" #include "crypto/crypto.h" #include "crypto/hash.h" -#include "hex.h" -#include "span.h" namespace cryptonote { @@ -77,6 +75,14 @@ namespace cryptonote { } } + struct address_parse_info + { + account_public_address address; + bool is_subaddress; + bool has_payment_id; + crypto::hash8 payment_id; + }; + /************************************************************************/ /* Cryptonote helper functions */ /************************************************************************/ @@ -89,6 +95,7 @@ namespace cryptonote { std::string get_account_address_as_str( bool testnet + , bool subaddress , const account_public_address& adr ); @@ -98,31 +105,14 @@ namespace cryptonote { , const crypto::hash8& payment_id ); - bool get_account_integrated_address_from_str( - account_public_address& adr - , bool& has_payment_id - , crypto::hash8& payment_id - , bool testnet - , const std::string& str - ); - bool get_account_address_from_str( - account_public_address& adr + address_parse_info& info , bool testnet , const std::string& str ); bool get_account_address_from_str_or_url( - cryptonote::account_public_address& address - , bool& has_payment_id - , crypto::hash8& payment_id - , bool testnet - , const std::string& str_or_url - , std::function<std::string(const std::string&, const std::vector<std::string>&, bool)> dns_confirm = return_first_address - ); - - bool get_account_address_from_str_or_url( - cryptonote::account_public_address& address + address_parse_info& info , bool testnet , const std::string& str_or_url , std::function<std::string(const std::string&, const std::vector<std::string>&, bool)> dns_confirm = return_first_address @@ -136,26 +126,3 @@ namespace cryptonote { bool parse_hash256(const std::string str_hash, crypto::hash& hash); -namespace crypto { - inline std::ostream &operator <<(std::ostream &o, const crypto::public_key &v) { - epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; - } - inline std::ostream &operator <<(std::ostream &o, const crypto::secret_key &v) { - epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; - } - inline std::ostream &operator <<(std::ostream &o, const crypto::key_derivation &v) { - epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; - } - inline std::ostream &operator <<(std::ostream &o, const crypto::key_image &v) { - epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; - } - inline std::ostream &operator <<(std::ostream &o, const crypto::signature &v) { - epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; - } - inline std::ostream &operator <<(std::ostream &o, const crypto::hash &v) { - epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; - } - inline std::ostream &operator <<(std::ostream &o, const crypto::hash8 &v) { - epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; - } -} diff --git a/src/cryptonote_basic/cryptonote_boost_serialization.h b/src/cryptonote_basic/cryptonote_boost_serialization.h index 6e4ac9b72..a67fa0ae7 100644 --- a/src/cryptonote_basic/cryptonote_boost_serialization.h +++ b/src/cryptonote_basic/cryptonote_boost_serialization.h @@ -83,6 +83,11 @@ namespace boost { a & reinterpret_cast<char (&)[sizeof(crypto::hash)]>(x); } + template <class Archive> + inline void serialize(Archive &a, crypto::hash8 &x, const boost::serialization::version_type ver) + { + a & reinterpret_cast<char (&)[sizeof(crypto::hash8)]>(x); + } template <class Archive> inline void serialize(Archive &a, cryptonote::txout_to_script &x, const boost::serialization::version_type ver) diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index 745dfb72e..6f171f227 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -129,16 +129,69 @@ namespace cryptonote return true; } //--------------------------------------------------------------- - bool generate_key_image_helper(const account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, keypair& in_ephemeral, crypto::key_image& ki) + crypto::secret_key get_subaddress_secret_key(const crypto::secret_key& a, const subaddress_index& index) + { + const char prefix[] = "SubAddr"; + char data[sizeof(prefix) + sizeof(crypto::secret_key) + sizeof(subaddress_index)]; + memcpy(data, prefix, sizeof(prefix)); + memcpy(data + sizeof(prefix), &a, sizeof(crypto::secret_key)); + memcpy(data + sizeof(prefix) + sizeof(crypto::secret_key), &index, sizeof(subaddress_index)); + crypto::secret_key m; + crypto::hash_to_scalar(data, sizeof(data), m); + return m; + } + //--------------------------------------------------------------- + bool generate_key_image_helper(const account_keys& ack, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::public_key& tx_public_key, const std::vector<crypto::public_key>& additional_tx_public_keys, size_t real_output_index, keypair& in_ephemeral, crypto::key_image& ki) { crypto::key_derivation recv_derivation = AUTO_VAL_INIT(recv_derivation); bool r = crypto::generate_key_derivation(tx_public_key, ack.m_view_secret_key, recv_derivation); CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to generate_key_derivation(" << tx_public_key << ", " << ack.m_view_secret_key << ")"); - r = crypto::derive_public_key(recv_derivation, real_output_index, ack.m_account_address.m_spend_public_key, in_ephemeral.pub); - CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to derive_public_key(" << recv_derivation << ", " << real_output_index << ", " << ack.m_account_address.m_spend_public_key << ")"); + std::vector<crypto::key_derivation> additional_recv_derivations; + for (size_t i = 0; i < additional_tx_public_keys.size(); ++i) + { + crypto::key_derivation additional_recv_derivation = AUTO_VAL_INIT(additional_recv_derivation); + r = crypto::generate_key_derivation(additional_tx_public_keys[i], ack.m_view_secret_key, additional_recv_derivation); + CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to generate_key_derivation(" << additional_tx_public_keys[i] << ", " << ack.m_view_secret_key << ")"); + additional_recv_derivations.push_back(additional_recv_derivation); + } + + boost::optional<subaddress_receive_info> subaddr_recv_info = is_out_to_acc_precomp(subaddresses, out_key, recv_derivation, additional_recv_derivations, real_output_index); + CHECK_AND_ASSERT_MES(subaddr_recv_info, false, "key image helper: given output pubkey doesn't seem to belong to this address"); + + return generate_key_image_helper_precomp(ack, out_key, subaddr_recv_info->derivation, real_output_index, subaddr_recv_info->index, in_ephemeral, ki); + } + //--------------------------------------------------------------- + bool generate_key_image_helper_precomp(const account_keys& ack, const crypto::public_key& out_key, const crypto::key_derivation& recv_derivation, size_t real_output_index, const subaddress_index& received_index, keypair& in_ephemeral, crypto::key_image& ki) + { + if (ack.m_spend_secret_key == crypto::null_skey) + { + // for watch-only wallet, simply copy the known output pubkey + in_ephemeral.pub = out_key; + in_ephemeral.sec = crypto::null_skey; + } + else + { + // derive secret key with subaddress - step 1: original CN derivation + crypto::secret_key scalar_step1; + crypto::derive_secret_key(recv_derivation, real_output_index, ack.m_spend_secret_key, scalar_step1); // computes Hs(a*R || idx) + b + + // step 2: add Hs(a || index_major || index_minor) + crypto::secret_key scalar_step2; + if (received_index.is_zero()) + { + scalar_step2 = scalar_step1; // treat index=(0,0) as a special case representing the main address + } + else + { + crypto::secret_key m = get_subaddress_secret_key(ack.m_view_secret_key, received_index); + sc_add((unsigned char*)&scalar_step2, (unsigned char*)&scalar_step1, (unsigned char*)&m); + } - crypto::derive_secret_key(recv_derivation, real_output_index, ack.m_spend_secret_key, in_ephemeral.sec); + in_ephemeral.sec = scalar_step2; + crypto::secret_key_to_public_key(in_ephemeral.sec, in_ephemeral.pub); + CHECK_AND_ASSERT_MES(in_ephemeral.pub == out_key, false, "key image helper precomp: given output pubkey doesn't match the derived one"); + } crypto::generate_key_image(in_ephemeral.pub, in_ephemeral.sec, ki); return true; @@ -277,6 +330,40 @@ namespace cryptonote return true; } //--------------------------------------------------------------- + std::vector<crypto::public_key> get_additional_tx_pub_keys_from_extra(const std::vector<uint8_t>& tx_extra) + { + // parse + std::vector<tx_extra_field> tx_extra_fields; + parse_tx_extra(tx_extra, tx_extra_fields); + // find corresponding field + tx_extra_additional_pub_keys additional_pub_keys; + if(!find_tx_extra_field_by_type(tx_extra_fields, additional_pub_keys)) + return {}; + return additional_pub_keys.data; + } + //--------------------------------------------------------------- + std::vector<crypto::public_key> get_additional_tx_pub_keys_from_extra(const transaction_prefix& tx) + { + return get_additional_tx_pub_keys_from_extra(tx.extra); + } + //--------------------------------------------------------------- + bool add_additional_tx_pub_keys_to_extra(std::vector<uint8_t>& tx_extra, const std::vector<crypto::public_key>& additional_pub_keys) + { + // convert to variant + tx_extra_field field = tx_extra_additional_pub_keys{ additional_pub_keys }; + // serialize + std::ostringstream oss; + binary_archive<true> ar(oss); + bool r = ::do_serialize(ar, field); + CHECK_AND_NO_ASSERT_MES_L1(r, false, "failed to serialize tx extra additional tx pub keys"); + // append + std::string tx_extra_str = oss.str(); + size_t pos = tx_extra.size(); + tx_extra.resize(tx_extra.size() + tx_extra_str.size()); + memcpy(&tx_extra[pos], tx_extra_str.data(), tx_extra_str.size()); + return true; + } + //--------------------------------------------------------------- bool add_extra_nonce_to_tx_extra(std::vector<uint8_t>& tx_extra, const blobdata& extra_nonce) { CHECK_AND_ASSERT_MES(extra_nonce.size() <= TX_EXTRA_NONCE_MAX_COUNT, false, "extra nonce could be 255 bytes max"); @@ -480,20 +567,43 @@ namespace cryptonote return res; } //--------------------------------------------------------------- - bool is_out_to_acc(const account_keys& acc, const txout_to_key& out_key, const crypto::public_key& tx_pub_key, size_t output_index) + bool is_out_to_acc(const account_keys& acc, const txout_to_key& out_key, const crypto::public_key& tx_pub_key, const std::vector<crypto::public_key>& additional_tx_pub_keys, size_t output_index) { crypto::key_derivation derivation; generate_key_derivation(tx_pub_key, acc.m_view_secret_key, derivation); crypto::public_key pk; derive_public_key(derivation, output_index, acc.m_account_address.m_spend_public_key, pk); - return pk == out_key.key; + if (pk == out_key.key) + return true; + // try additional tx pubkeys if available + if (!additional_tx_pub_keys.empty()) + { + CHECK_AND_ASSERT_MES(output_index < additional_tx_pub_keys.size(), false, "wrong number of additional tx pubkeys"); + generate_key_derivation(additional_tx_pub_keys[output_index], acc.m_view_secret_key, derivation); + derive_public_key(derivation, output_index, acc.m_account_address.m_spend_public_key, pk); + return pk == out_key.key; + } + return false; } //--------------------------------------------------------------- - bool is_out_to_acc_precomp(const crypto::public_key& spend_public_key, const txout_to_key& out_key, const crypto::key_derivation& derivation, size_t output_index) + boost::optional<subaddress_receive_info> is_out_to_acc_precomp(const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::key_derivation& derivation, const std::vector<crypto::key_derivation>& additional_derivations, size_t output_index) { - crypto::public_key pk; - derive_public_key(derivation, output_index, spend_public_key, pk); - return pk == out_key.key; + // try the shared tx pubkey + crypto::public_key subaddress_spendkey; + derive_subaddress_public_key(out_key, derivation, output_index, subaddress_spendkey); + auto found = subaddresses.find(subaddress_spendkey); + if (found != subaddresses.end()) + return subaddress_receive_info{ found->second, derivation }; + // try additional tx pubkeys if available + if (!additional_derivations.empty()) + { + CHECK_AND_ASSERT_MES(output_index < additional_derivations.size(), boost::none, "wrong number of additional derivations"); + derive_subaddress_public_key(out_key, additional_derivations[output_index], output_index, subaddress_spendkey); + found = subaddresses.find(subaddress_spendkey); + if (found != subaddresses.end()) + return subaddress_receive_info{ found->second, additional_derivations[output_index] }; + } + return boost::none; } //--------------------------------------------------------------- bool lookup_acc_outs(const account_keys& acc, const transaction& tx, std::vector<size_t>& outs, uint64_t& money_transfered) @@ -501,17 +611,19 @@ namespace cryptonote crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); if(null_pkey == tx_pub_key) return false; - return lookup_acc_outs(acc, tx, tx_pub_key, outs, money_transfered); + std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(tx); + return lookup_acc_outs(acc, tx, tx_pub_key, additional_tx_pub_keys, outs, money_transfered); } //--------------------------------------------------------------- - bool lookup_acc_outs(const account_keys& acc, const transaction& tx, const crypto::public_key& tx_pub_key, std::vector<size_t>& outs, uint64_t& money_transfered) + bool lookup_acc_outs(const account_keys& acc, const transaction& tx, const crypto::public_key& tx_pub_key, const std::vector<crypto::public_key>& additional_tx_pub_keys, std::vector<size_t>& outs, uint64_t& money_transfered) { + CHECK_AND_ASSERT_MES(additional_tx_pub_keys.empty() || additional_tx_pub_keys.size() == tx.vout.size(), false, "wrong number of additional pubkeys" ); money_transfered = 0; size_t i = 0; for(const tx_out& o: tx.vout) { CHECK_AND_ASSERT_MES(o.target.type() == typeid(txout_to_key), false, "wrong type id in transaction out" ); - if(is_out_to_acc(acc, boost::get<txout_to_key>(o.target), tx_pub_key, i)) + if(is_out_to_acc(acc, boost::get<txout_to_key>(o.target), tx_pub_key, additional_tx_pub_keys, i)) { outs.push_back(i); money_transfered += o.amount; @@ -632,7 +744,7 @@ namespace cryptonote // prunable rct if (t.rct_signatures.type == rct::RCTTypeNull) { - hashes[2] = cryptonote::null_hash; + hashes[2] = crypto::null_hash; } else { @@ -869,4 +981,21 @@ namespace cryptonote block_hashes_calculated = block_hashes_calculated_count; block_hashes_cached = block_hashes_cached_count; } + //--------------------------------------------------------------- + crypto::secret_key encrypt_key(const crypto::secret_key &key, const std::string &passphrase) + { + crypto::hash hash; + crypto::cn_slow_hash(passphrase.data(), passphrase.size(), hash); + sc_add((unsigned char*)key.data, (const unsigned char*)key.data, (const unsigned char*)hash.data); + return key; + } + //--------------------------------------------------------------- + crypto::secret_key decrypt_key(const crypto::secret_key &key, const std::string &passphrase) + { + crypto::hash hash; + crypto::cn_slow_hash(passphrase.data(), passphrase.size(), hash); + sc_sub((unsigned char*)key.data, (const unsigned char*)key.data, (const unsigned char*)hash.data); + return key; + } + } diff --git a/src/cryptonote_basic/cryptonote_format_utils.h b/src/cryptonote_basic/cryptonote_format_utils.h index d8ccf8eec..078c8b8a0 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.h +++ b/src/cryptonote_basic/cryptonote_format_utils.h @@ -32,9 +32,11 @@ #include "cryptonote_protocol/cryptonote_protocol_defs.h" #include "cryptonote_basic_impl.h" #include "account.h" +#include "subaddress_index.h" #include "include_base_utils.h" #include "crypto/crypto.h" #include "crypto/hash.h" +#include <unordered_map> namespace cryptonote { @@ -63,19 +65,29 @@ namespace cryptonote crypto::public_key get_tx_pub_key_from_extra(const transaction_prefix& tx, size_t pk_index = 0); crypto::public_key get_tx_pub_key_from_extra(const transaction& tx, size_t pk_index = 0); bool add_tx_pub_key_to_extra(transaction& tx, const crypto::public_key& tx_pub_key); + std::vector<crypto::public_key> get_additional_tx_pub_keys_from_extra(const std::vector<uint8_t>& tx_extra); + std::vector<crypto::public_key> get_additional_tx_pub_keys_from_extra(const transaction_prefix& tx); + bool add_additional_tx_pub_keys_to_extra(std::vector<uint8_t>& tx_extra, const std::vector<crypto::public_key>& additional_pub_keys); bool add_extra_nonce_to_tx_extra(std::vector<uint8_t>& tx_extra, const blobdata& extra_nonce); bool remove_field_from_tx_extra(std::vector<uint8_t>& tx_extra, const std::type_info &type); void set_payment_id_to_tx_extra_nonce(blobdata& extra_nonce, const crypto::hash& payment_id); void set_encrypted_payment_id_to_tx_extra_nonce(blobdata& extra_nonce, const crypto::hash8& payment_id); bool get_payment_id_from_tx_extra_nonce(const blobdata& extra_nonce, crypto::hash& payment_id); bool get_encrypted_payment_id_from_tx_extra_nonce(const blobdata& extra_nonce, crypto::hash8& payment_id); - bool is_out_to_acc(const account_keys& acc, const txout_to_key& out_key, const crypto::public_key& tx_pub_key, size_t output_index); - bool is_out_to_acc_precomp(const crypto::public_key& spend_public_key, const txout_to_key& out_key, const crypto::key_derivation& derivation, size_t output_index); - bool lookup_acc_outs(const account_keys& acc, const transaction& tx, const crypto::public_key& tx_pub_key, std::vector<size_t>& outs, uint64_t& money_transfered); + bool is_out_to_acc(const account_keys& acc, const txout_to_key& out_key, const crypto::public_key& tx_pub_key, const std::vector<crypto::public_key>& additional_tx_public_keys, size_t output_index); + struct subaddress_receive_info + { + subaddress_index index; + crypto::key_derivation derivation; + }; + boost::optional<subaddress_receive_info> is_out_to_acc_precomp(const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::key_derivation& derivation, const std::vector<crypto::key_derivation>& additional_derivations, size_t output_index); + bool lookup_acc_outs(const account_keys& acc, const transaction& tx, const crypto::public_key& tx_pub_key, const std::vector<crypto::public_key>& additional_tx_public_keys, std::vector<size_t>& outs, uint64_t& money_transfered); bool lookup_acc_outs(const account_keys& acc, const transaction& tx, std::vector<size_t>& outs, uint64_t& money_transfered); bool get_tx_fee(const transaction& tx, uint64_t & fee); uint64_t get_tx_fee(const transaction& tx); - bool generate_key_image_helper(const account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, keypair& in_ephemeral, crypto::key_image& ki); + crypto::secret_key get_subaddress_secret_key(const crypto::secret_key& a, const subaddress_index& index); + bool generate_key_image_helper(const account_keys& ack, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::public_key& tx_public_key, const std::vector<crypto::public_key>& additional_tx_public_keys, size_t real_output_index, keypair& in_ephemeral, crypto::key_image& ki); + bool generate_key_image_helper_precomp(const account_keys& ack, const crypto::public_key& out_key, const crypto::key_derivation& recv_derivation, size_t real_output_index, const subaddress_index& received_index, keypair& in_ephemeral, crypto::key_image& ki); void get_blob_hash(const blobdata& blob, crypto::hash& res); crypto::hash get_blob_hash(const blobdata& blob); std::string short_hash_str(const crypto::hash& h); @@ -212,6 +224,8 @@ namespace cryptonote bool is_valid_decomposed_amount(uint64_t amount); void get_hash_stats(uint64_t &tx_hashes_calculated, uint64_t &tx_hashes_cached, uint64_t &block_hashes_calculated, uint64_t & block_hashes_cached); + crypto::secret_key encrypt_key(const crypto::secret_key &key, const std::string &passphrase); + crypto::secret_key decrypt_key(const crypto::secret_key &key, const std::string &passphrase); #define CHECKED_GET_SPECIFIC_VARIANT(variant_var, specific_type, variable_name, fail_return_val) \ CHECK_AND_ASSERT_MES(variant_var.type() == typeid(specific_type), fail_return_val, "wrong variant type: " << variant_var.type().name() << ", expected " << typeid(specific_type).name()); \ specific_type& variable_name = boost::get<specific_type>(variant_var); diff --git a/src/cryptonote_basic/miner.cpp b/src/cryptonote_basic/miner.cpp index 3c5811d61..8f53c2d3c 100644 --- a/src/cryptonote_basic/miner.cpp +++ b/src/cryptonote_basic/miner.cpp @@ -228,11 +228,13 @@ namespace cryptonote if(command_line::has_arg(vm, arg_start_mining)) { - if(!cryptonote::get_account_address_from_str(m_mine_address, testnet, command_line::get_arg(vm, arg_start_mining))) + address_parse_info info; + if(!cryptonote::get_account_address_from_str(info, testnet, command_line::get_arg(vm, arg_start_mining)) || info.is_subaddress) { LOG_ERROR("Target account address " << command_line::get_arg(vm, arg_start_mining) << " has wrong format, starting daemon canceled"); return false; } + m_mine_address = info.address; m_threads_total = 1; m_do_mining = true; if(command_line::has_arg(vm, arg_mining_threads)) @@ -858,19 +860,6 @@ namespace cryptonote const boost::filesystem::path& power_supply_path = iter->path(); if (boost::filesystem::is_directory(power_supply_path)) { - std::ifstream power_supply_present_stream((power_supply_path / "present").string()); - if (power_supply_present_stream.fail()) - { - LOG_PRINT_L0("Unable to read from " << power_supply_path << " to check if power supply present"); - continue; - } - - if (power_supply_present_stream.get() != '1') - { - LOG_PRINT_L4("Power supply not present at " << power_supply_path); - continue; - } - boost::filesystem::path power_supply_type_path = power_supply_path / "type"; if (boost::filesystem::is_regular_file(power_supply_type_path)) { diff --git a/src/cryptonote_basic/subaddress_index.h b/src/cryptonote_basic/subaddress_index.h new file mode 100644 index 000000000..07d13c503 --- /dev/null +++ b/src/cryptonote_basic/subaddress_index.h @@ -0,0 +1,102 @@ +// Copyright (c) 2017, 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. + +#pragma once + +#include "serialization/keyvalue_serialization.h" +#include <boost/serialization/serialization.hpp> +#include <boost/serialization/version.hpp> +#include <ostream> + +namespace cryptonote +{ + struct subaddress_index + { + uint32_t major; + uint32_t minor; + bool operator==(const subaddress_index& rhs) const { return !memcmp(this, &rhs, sizeof(subaddress_index)); } + bool operator!=(const subaddress_index& rhs) const { return !(*this == rhs); } + bool is_zero() const { return major == 0 && minor == 0; } + + BEGIN_SERIALIZE_OBJECT() + FIELD(major) + FIELD(minor) + END_SERIALIZE() + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(major) + KV_SERIALIZE(minor) + END_KV_SERIALIZE_MAP() + }; +} + +namespace cryptonote { + inline std::ostream& operator<<(std::ostream& out, const cryptonote::subaddress_index& subaddr_index) + { + return out << subaddr_index.major << '/' << subaddr_index.minor; + } +} + +namespace std +{ + template <> + struct hash<cryptonote::subaddress_index> + { + size_t operator()(const cryptonote::subaddress_index& index ) const + { + size_t res; + if (sizeof(size_t) == 8) + { + res = ((uint64_t)index.major << 32) | index.minor; + } + else + { + // https://stackoverflow.com/a/17017281 + res = 17; + res = res * 31 + hash<uint32_t>()(index.major); + res = res * 31 + hash<uint32_t>()(index.minor); + } + return res; + } + }; +} + +BOOST_CLASS_VERSION(cryptonote::subaddress_index, 0) + +namespace boost +{ + namespace serialization + { + template <class Archive> + inline void serialize(Archive &a, cryptonote::subaddress_index &x, const boost::serialization::version_type ver) + { + a & x.major; + a & x.minor; + } + } +} diff --git a/src/cryptonote_basic/tx_extra.h b/src/cryptonote_basic/tx_extra.h index 5a6c3176d..e12828a9f 100644 --- a/src/cryptonote_basic/tx_extra.h +++ b/src/cryptonote_basic/tx_extra.h @@ -38,6 +38,7 @@ #define TX_EXTRA_TAG_PUBKEY 0x01 #define TX_EXTRA_NONCE 0x02 #define TX_EXTRA_MERGE_MINING_TAG 0x03 +#define TX_EXTRA_TAG_ADDITIONAL_PUBKEYS 0x04 #define TX_EXTRA_MYSTERIOUS_MINERGATE_TAG 0xDE #define TX_EXTRA_NONCE_PAYMENT_ID 0x00 @@ -159,6 +160,16 @@ namespace cryptonote } }; + // per-output additional tx pubkey for multi-destination transfers involving at least one subaddress + struct tx_extra_additional_pub_keys + { + std::vector<crypto::public_key> data; + + BEGIN_SERIALIZE() + FIELD(data) + END_SERIALIZE() + }; + struct tx_extra_mysterious_minergate { std::string data; @@ -172,11 +183,12 @@ namespace cryptonote // varint tag; // varint size; // varint data[]; - typedef boost::variant<tx_extra_padding, tx_extra_pub_key, tx_extra_nonce, tx_extra_merge_mining_tag, tx_extra_mysterious_minergate> tx_extra_field; + typedef boost::variant<tx_extra_padding, tx_extra_pub_key, tx_extra_nonce, tx_extra_merge_mining_tag, tx_extra_additional_pub_keys, tx_extra_mysterious_minergate> tx_extra_field; } VARIANT_TAG(binary_archive, cryptonote::tx_extra_padding, TX_EXTRA_TAG_PADDING); VARIANT_TAG(binary_archive, cryptonote::tx_extra_pub_key, TX_EXTRA_TAG_PUBKEY); VARIANT_TAG(binary_archive, cryptonote::tx_extra_nonce, TX_EXTRA_NONCE); VARIANT_TAG(binary_archive, cryptonote::tx_extra_merge_mining_tag, TX_EXTRA_MERGE_MINING_TAG); +VARIANT_TAG(binary_archive, cryptonote::tx_extra_additional_pub_keys, TX_EXTRA_TAG_ADDITIONAL_PUBKEYS); VARIANT_TAG(binary_archive, cryptonote::tx_extra_mysterious_minergate, TX_EXTRA_MYSTERIOUS_MINERGATE_TAG); diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 8ff01cf8b..5a61fc78c 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -91,7 +91,6 @@ #define BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT 10000 //by default, blocks ids count in synchronizing #define BLOCKS_SYNCHRONIZING_DEFAULT_COUNT_PRE_V4 100 //by default, blocks count in blocks downloading #define BLOCKS_SYNCHRONIZING_DEFAULT_COUNT 20 //by default, blocks count in blocks downloading -#define CRYPTONOTE_PROTOCOL_HOP_RELAX_COUNT 3 //value of hop, after which we use only announce of new block #define CRYPTONOTE_MEMPOOL_TX_LIVETIME 86400 //seconds, one day #define CRYPTONOTE_MEMPOOL_TX_FROM_ALT_BLOCK_LIVETIME 604800 //seconds, one week @@ -137,6 +136,9 @@ #define PER_KB_FEE_QUANTIZATION_DECIMALS 8 +#define HASH_OF_HASHES_STEP 256 + + // New constants are intended to go here namespace config { @@ -148,8 +150,10 @@ namespace config uint64_t const CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX = 18; uint64_t const CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX = 19; + uint64_t const CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX = 26; uint16_t const P2P_DEFAULT_PORT = 18080; uint16_t const RPC_DEFAULT_PORT = 18081; + uint16_t const ZMQ_RPC_DEFAULT_PORT = 18082; boost::uuids::uuid const NETWORK_ID = { { 0x12 ,0x30, 0xF1, 0x71 , 0x61, 0x04 , 0x41, 0x61, 0x17, 0x31, 0x00, 0x82, 0x16, 0xA1, 0xA1, 0x10 } }; // Bender's nightmare @@ -160,8 +164,10 @@ namespace config { uint64_t const CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX = 53; uint64_t const CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX = 54; + uint64_t const CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX = 63; uint16_t const P2P_DEFAULT_PORT = 28080; uint16_t const RPC_DEFAULT_PORT = 28081; + uint16_t const ZMQ_RPC_DEFAULT_PORT = 28082; boost::uuids::uuid const NETWORK_ID = { { 0x12 ,0x30, 0xF1, 0x71 , 0x61, 0x04 , 0x41, 0x61, 0x17, 0x31, 0x00, 0x82, 0x16, 0xA1, 0xA1, 0x11 } }; // Bender's daydream diff --git a/src/cryptonote_core/CMakeLists.txt b/src/cryptonote_core/CMakeLists.txt index 7c43323d4..169a38f0a 100644 --- a/src/cryptonote_core/CMakeLists.txt +++ b/src/cryptonote_core/CMakeLists.txt @@ -48,13 +48,14 @@ else() endif() monero_private_headers(cryptonote_core - ${crypto_private_headers}) + ${cryptonote_core_private_headers}) monero_add_library(cryptonote_core ${cryptonote_core_sources} ${cryptonote_core_headers} ${cryptonote_core_private_headers}) target_link_libraries(cryptonote_core PUBLIC + version common cncrypto blockchain_db diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 69d2edf65..5e0dd33e6 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -45,10 +45,10 @@ #include "profile_tools.h" #include "file_io_utils.h" #include "common/int-util.h" +#include "common/threadpool.h" #include "common/boost_serialization_helper.h" #include "warnings.h" #include "crypto/hash.h" -#include "cryptonote_basic/checkpoints.h" #include "cryptonote_core.h" #include "ringct/rctSigs.h" #include "common/perf_timer.h" @@ -751,7 +751,7 @@ difficulty_type Blockchain::get_difficulty_for_next_block() m_timestamps = timestamps; m_difficulties = difficulties; } - size_t target = get_current_hard_fork_version() < 2 ? DIFFICULTY_TARGET_V1 : DIFFICULTY_TARGET_V2; + size_t target = get_difficulty_target(); return next_difficulty(timestamps, difficulties, target); } //------------------------------------------------------------------ @@ -1444,7 +1444,7 @@ bool Blockchain::get_blocks(uint64_t start_offset, size_t count, std::list<std:: { LOG_PRINT_L3("Blockchain::" << __func__); CRITICAL_REGION_LOCAL(m_blockchain_lock); - if(start_offset > m_db->height()) + if(start_offset >= m_db->height()) return false; if (!get_blocks(start_offset, count, blocks)) @@ -1466,7 +1466,7 @@ bool Blockchain::get_blocks(uint64_t start_offset, size_t count, std::list<std:: { LOG_PRINT_L3("Blockchain::" << __func__); CRITICAL_REGION_LOCAL(m_blockchain_lock); - if(start_offset > m_db->height()) + if(start_offset >= m_db->height()) return false; for(size_t i = start_offset; i < start_offset + count && i < m_db->height();i++) @@ -1571,6 +1571,98 @@ void Blockchain::add_out_to_get_random_outs(COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_A output_data_t data = m_db->get_output_key(amount, i); oen.out_key = data.pubkey; } + +uint64_t Blockchain::get_num_mature_outputs(uint64_t amount) const +{ + uint64_t num_outs = m_db->get_num_outputs(amount); + // 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(amount, 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; + } + + return num_outs; +} + +std::vector<uint64_t> Blockchain::get_random_outputs(uint64_t amount, uint64_t count) const +{ + uint64_t num_outs = get_num_mature_outputs(amount); + + std::vector<uint64_t> indices; + + 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 <= 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(amount, i); + + // if tx is unlocked, add output to indices + if (is_tx_spendtime_unlocked(m_db->get_tx_unlock_time(toi.first))) + { + indices.push_back(i); + } + } + } + else + { + // while we still need more mixins + while (indices.size() < 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(amount, 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))) + { + indices.push_back(i); + } + } + } + + return indices; +} + +crypto::public_key Blockchain::get_output_key(uint64_t amount, uint64_t global_index) const +{ + output_data_t data = m_db->get_output_key(amount, global_index); + return data.pubkey; +} + //------------------------------------------------------------------ // This function takes an RPC request for mixins and creates an RPC response // with the requested mixins. @@ -1585,80 +1677,18 @@ bool Blockchain::get_random_outs_for_amounts(const COMMAND_RPC_GET_RANDOM_OUTPUT // from BlockchainDB where <n> is req.outs_count (number of mixins). for (uint64_t amount : req.amounts) { - auto num_outs = m_db->get_num_outputs(amount); - // 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(amount, 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; - } - // create outs_for_amount struct and populate amount field 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; - std::unordered_set<uint64_t> seen_indices; + std::vector<uint64_t> indices = get_random_outputs(amount, req.outs_count); - // 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 (auto i : indices) { - 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(amount, i); + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry& oe = *result_outs.outs.insert(result_outs.outs.end(), COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry()); - // 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_random_outs(result_outs, amount, i); - } - - } - } - else - { - // while we still need more mixins - while (result_outs.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(amount, 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_random_outs(result_outs, amount, i); - } - } + oe.global_amount_index = i; + oe.out_key = get_output_key(amount, i); } } return true; @@ -1816,6 +1846,15 @@ bool Blockchain::get_outs(const COMMAND_RPC_GET_OUTPUTS_BIN::request& req, COMMA return true; } //------------------------------------------------------------------ +void Blockchain::get_output_key_mask_unlocked(const uint64_t& amount, const uint64_t& index, crypto::public_key& key, rct::key& mask, bool& unlocked) const +{ + const auto o_data = m_db->get_output_key(amount, index); + key = o_data.pubkey; + mask = o_data.commitment; + tx_out_index toi = m_db->get_output_tx_and_index(amount, index); + unlocked = is_tx_spendtime_unlocked(m_db->get_tx_unlock_time(toi.first)); +} +//------------------------------------------------------------------ // This function takes a list of block hashes from another node // on the network to find where the split point is between us and them. // This is used to see what to send another node that needs to sync. @@ -2025,28 +2064,39 @@ void Blockchain::print_blockchain_outs(const std::string& file) const // Find the split point between us and foreign blockchain and return // (by reference) the most recent common block hash along with up to // BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT additional (more recent) hashes. -bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) const +bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, std::list<crypto::hash>& hashes, uint64_t& start_height, uint64_t& current_height) const { LOG_PRINT_L3("Blockchain::" << __func__); CRITICAL_REGION_LOCAL(m_blockchain_lock); // if we can't find the split point, return false - if(!find_blockchain_supplement(qblock_ids, resp.start_height)) + if(!find_blockchain_supplement(qblock_ids, start_height)) { return false; } m_db->block_txn_start(true); - resp.total_height = get_current_blockchain_height(); + current_height = get_current_blockchain_height(); size_t count = 0; - for(size_t i = resp.start_height; i < resp.total_height && count < BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT; i++, count++) + for(size_t i = start_height; i < current_height && count < BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT; i++, count++) { - resp.m_block_ids.push_back(m_db->get_block_hash_from_height(i)); + hashes.push_back(m_db->get_block_hash_from_height(i)); } - resp.cumulative_difficulty = m_db->get_block_cumulative_difficulty(m_db->height() - 1); + m_db->block_txn_stop(); return true; } + +bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) const +{ + LOG_PRINT_L3("Blockchain::" << __func__); + CRITICAL_REGION_LOCAL(m_blockchain_lock); + + bool result = find_blockchain_supplement(qblock_ids, resp.m_block_ids, resp.start_height, resp.total_height); + resp.cumulative_difficulty = m_db->get_block_cumulative_difficulty(m_db->height() - 1); + + return result; +} //------------------------------------------------------------------ //FIXME: change argument to std::vector, low priority // find split point between ours and foreign blockchain (or start at @@ -2501,6 +2551,25 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, } } + // from v7, sorted ins + if (hf_version >= 7) { + const crypto::key_image *last_key_image = NULL; + for (size_t n = 0; n < tx.vin.size(); ++n) + { + const txin_v &txin = tx.vin[n]; + if (txin.type() == typeid(txin_to_key)) + { + const txin_to_key& in_to_key = boost::get<txin_to_key>(txin); + if (last_key_image && memcmp(&in_to_key.k_image, last_key_image, sizeof(*last_key_image)) >= 0) + { + MERROR_VER("transaction has unsorted inputs"); + tvc.m_verifivation_failed = true; + return false; + } + last_key_image = &in_to_key.k_image; + } + } + } auto it = m_check_txin_table.find(tx_prefix_hash); if(it == m_check_txin_table.end()) { @@ -2513,33 +2582,9 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, std::vector < uint64_t > results; results.resize(tx.vin.size(), 0); - int threads = tools::get_max_concurrency(); - - boost::asio::io_service ioservice; - boost::thread_group threadpool; - bool ioservice_active = false; - - std::unique_ptr < boost::asio::io_service::work > work(new boost::asio::io_service::work(ioservice)); - if(threads > 1) - { - for (int i = 0; i < threads; i++) - { - threadpool.create_thread(boost::bind(&boost::asio::io_service::run, &ioservice)); - } - ioservice_active = true; - } - -#define KILL_IOSERVICE() \ - if(ioservice_active) \ - { \ - work.reset(); \ - while (!ioservice.stopped()) ioservice.poll(); \ - threadpool.join_all(); \ - ioservice.stop(); \ - ioservice_active = false; \ - } - - epee::misc_utils::auto_scope_leave_caller ioservice_killer = epee::misc_utils::create_scope_leave_handler([&]() { KILL_IOSERVICE(); }); + tools::threadpool& tpool = tools::threadpool::getInstance(); + tools::threadpool::waiter waiter; + int threads = tpool.get_max_concurrency(); for (const auto& txin : tx.vin) { @@ -2600,7 +2645,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, { // 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]))); + tpool.submit(&waiter, 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 { @@ -2623,8 +2668,8 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, sig_index++; } - - KILL_IOSERVICE(); + if (tx.version == 1 && threads > 1) + waiter.wait(); if (tx.version == 1) { @@ -3179,13 +3224,21 @@ leave: if (m_db->height() < m_blocks_hash_check.size()) { auto hash = get_block_hash(bl); - if (memcmp(&hash, &m_blocks_hash_check[m_db->height()], sizeof(hash)) != 0) + const auto &expected_hash = m_blocks_hash_check[m_db->height()]; + if (expected_hash != cryptonote::null_hash) { - MERROR_VER("Block with id is INVALID: " << id); - bvc.m_verifivation_failed = true; - goto leave; + if (memcmp(&hash, &expected_hash, sizeof(hash)) != 0) + { + MERROR_VER("Block with id is INVALID: " << id); + bvc.m_verifivation_failed = true; + goto leave; + } + fast_check = true; + } + else + { + MCINFO("verify", "No pre-validated hash at height " << m_db->height() << ", verifying fully"); } - fast_check = true; } else #endif @@ -3249,7 +3302,7 @@ leave: // XXX old code adds miner tx here - int tx_index = 0; + size_t tx_index = 0; // Iterate over the block's transaction hashes, grabbing each // from the tx_pool and validating them. Each is then added // to txs. Keys spent in each are added to <keys> by the double spend check. @@ -3331,7 +3384,7 @@ leave: { // ND: if fast_check is enabled for blocks, there is no need to check // the transaction inputs, but do some sanity checks anyway. - if (memcmp(&m_blocks_txs_check[tx_index++], &tx_id, sizeof(tx_id)) != 0) + if (tx_index >= m_blocks_txs_check.size() || memcmp(&m_blocks_txs_check[tx_index++], &tx_id, sizeof(tx_id)) != 0) { MERROR_VER("Block with id: " << id << " has at least one transaction (id: " << tx_id << ") with wrong inputs."); //TODO: why is this done? make sure that keeping invalid blocks makes sense. @@ -3630,6 +3683,14 @@ bool Blockchain::cleanup_handle_incoming_blocks(bool force_sync) m_blocks_txs_check.clear(); m_check_txin_table.clear(); + // when we're well clear of the precomputed hashes, free the memory + if (!m_blocks_hash_check.empty() && m_db->height() > m_blocks_hash_check.size() + 4096) + { + MINFO("Dumping block hashes, we're now 4k past " << m_blocks_hash_check.size()); + m_blocks_hash_check.clear(); + m_blocks_hash_check.shrink_to_fit(); + } + CRITICAL_REGION_END(); m_tx_pool.unlock(); @@ -3654,6 +3715,98 @@ void Blockchain::output_scan_worker(const uint64_t amount, const std::vector<uin } } +uint64_t Blockchain::prevalidate_block_hashes(uint64_t height, const std::list<crypto::hash> &hashes) +{ + // new: . . . . . X X X X X . . . . . . + // pre: A A A A B B B B C C C C D D D D + + // easy case: height >= hashes + if (height >= m_blocks_hash_of_hashes.size() * HASH_OF_HASHES_STEP) + return hashes.size(); + + // find hashes encompassing those block + size_t first_index = height / HASH_OF_HASHES_STEP; + size_t last_index = (height + hashes.size() - 1) / HASH_OF_HASHES_STEP; + MDEBUG("Blocks " << height << " - " << (height + hashes.size() - 1) << " start at " << first_index << " and end at " << last_index); + + // case of not enough to calculate even a single hash + if (first_index == last_index && hashes.size() < HASH_OF_HASHES_STEP && (height + hashes.size()) % HASH_OF_HASHES_STEP) + return hashes.size(); + + // build hashes vector to hash hashes together + std::vector<crypto::hash> data; + data.reserve(hashes.size() + HASH_OF_HASHES_STEP - 1); // may be a bit too much + + // we expect height to be either equal or a bit below db height + bool disconnected = (height > m_db->height()); + size_t pop; + if (disconnected && height % HASH_OF_HASHES_STEP) + { + ++first_index; + pop = HASH_OF_HASHES_STEP - height % HASH_OF_HASHES_STEP; + } + else + { + // we might need some already in the chain for the first part of the first hash + for (uint64_t h = first_index * HASH_OF_HASHES_STEP; h < height; ++h) + { + data.push_back(m_db->get_block_hash_from_height(h)); + } + pop = 0; + } + + // push the data to check + for (const auto &h: hashes) + { + if (pop) + --pop; + else + data.push_back(h); + } + + // hash and check + uint64_t usable = first_index * HASH_OF_HASHES_STEP - height; // may start negative, but unsigned under/overflow is not UB + for (size_t n = first_index; n <= last_index; ++n) + { + if (n < m_blocks_hash_of_hashes.size()) + { + // if the last index isn't fully filled, we can't tell if valid + if (data.size() < (n - first_index) * HASH_OF_HASHES_STEP + HASH_OF_HASHES_STEP) + break; + + crypto::hash hash; + cn_fast_hash(data.data() + (n - first_index) * HASH_OF_HASHES_STEP, HASH_OF_HASHES_STEP * sizeof(crypto::hash), hash); + bool valid = hash == m_blocks_hash_of_hashes[n]; + + // add to the known hashes array + if (!valid) + { + MWARNING("invalid hash for blocks " << n * HASH_OF_HASHES_STEP << " - " << (n * HASH_OF_HASHES_STEP + HASH_OF_HASHES_STEP - 1)); + break; + } + + size_t end = n * HASH_OF_HASHES_STEP + HASH_OF_HASHES_STEP; + for (size_t i = n * HASH_OF_HASHES_STEP; i < end; ++i) + { + CHECK_AND_ASSERT_MES(m_blocks_hash_check[i] == cryptonote::null_hash || m_blocks_hash_check[i] == data[i - first_index * HASH_OF_HASHES_STEP], + 0, "Consistency failure in m_blocks_hash_check construction"); + m_blocks_hash_check[i] = data[i - first_index * HASH_OF_HASHES_STEP]; + } + usable += HASH_OF_HASHES_STEP; + } + else + { + // if after the end of the precomputed blocks, accept anything + usable += HASH_OF_HASHES_STEP; + if (usable > hashes.size()) + usable = hashes.size(); + } + } + MDEBUG("usable: " << usable << " / " << hashes.size()); + CHECK_AND_ASSERT_MES(usable < std::numeric_limits<uint64_t>::max() / 2, 0, "usable is negative"); + return usable; +} + //------------------------------------------------------------------ // ND: Speedups: // 1. Thread long_hash computations if possible (m_max_prepare_blocks_threads = nthreads, default = 4) @@ -3666,6 +3819,7 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::list<block_complete_e MTRACE("Blockchain::" << __func__); TIME_MEASURE_START(prepare); bool stop_batch; + uint64_t bytes = 0; // Order of locking must be: // m_incoming_tx_lock (optional) @@ -3687,7 +3841,15 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::list<block_complete_e if(blocks_entry.size() == 0) return false; - while (!(stop_batch = m_db->batch_start(blocks_entry.size()))) { + for (const auto &entry : blocks_entry) + { + bytes += entry.block.size(); + for (const auto &tx_blob : entry.txs) + { + bytes += tx_blob.size(); + } + } + while (!(stop_batch = m_db->batch_start(blocks_entry.size(), bytes))) { m_blockchain_lock.unlock(); m_tx_pool.unlock(); epee::misc_utils::sleep_no_w(1000); @@ -3699,7 +3861,8 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::list<block_complete_e return true; bool blocks_exist = false; - uint64_t threads = tools::get_max_concurrency(); + tools::threadpool& tpool = tools::threadpool::getInstance(); + uint64_t threads = tpool.get_max_concurrency(); if (blocks_entry.size() > 1 && threads > 1 && m_max_prepare_blocks_threads > 1) { @@ -3708,15 +3871,12 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::list<block_complete_e threads = m_max_prepare_blocks_threads; uint64_t height = m_db->height(); - std::vector<boost::thread *> thread_list; int batches = blocks_entry.size() / threads; int extra = blocks_entry.size() % threads; MDEBUG("block_batches: " << batches); std::vector<std::unordered_map<crypto::hash, crypto::hash>> maps(threads); std::vector < std::vector < block >> blocks(threads); auto it = blocks_entry.begin(); - boost::thread::attributes attrs; - attrs.set_stack_size(THREAD_STACK_SIZE); for (uint64_t i = 0; i < threads; i++) { @@ -3775,19 +3935,14 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::list<block_complete_e { m_blocks_longhash_table.clear(); uint64_t thread_height = height; + tools::threadpool::waiter waiter; for (uint64_t i = 0; i < threads; i++) { - thread_list.push_back(new boost::thread(attrs, boost::bind(&Blockchain::block_longhash_worker, this, thread_height, std::cref(blocks[i]), std::ref(maps[i])))); + tpool.submit(&waiter, boost::bind(&Blockchain::block_longhash_worker, this, thread_height, std::cref(blocks[i]), std::ref(maps[i]))); thread_height += blocks[i].size(); } - for (size_t j = 0; j < thread_list.size(); j++) - { - thread_list[j]->join(); - delete thread_list[j]; - } - - thread_list.clear(); + waiter.wait(); if (m_cancel) return false; @@ -3911,30 +4066,20 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::list<block_complete_e // [output] stores all transactions for each tx_out_index::hash found std::vector<std::unordered_map<crypto::hash, cryptonote::transaction>> transactions(amounts.size()); - threads = tools::get_max_concurrency(); + threads = tpool.get_max_concurrency(); if (!m_db->can_thread_bulk_indices()) threads = 1; if (threads > 1) { - boost::asio::io_service ioservice; - boost::thread_group threadpool; - std::unique_ptr < boost::asio::io_service::work > work(new boost::asio::io_service::work(ioservice)); - - for (uint64_t i = 0; i < threads; i++) - { - threadpool.create_thread(boost::bind(&boost::asio::io_service::run, &ioservice)); - } + tools::threadpool::waiter waiter; for (size_t i = 0; i < amounts.size(); i++) { uint64_t amount = amounts[i]; - ioservice.dispatch(boost::bind(&Blockchain::output_scan_worker, this, amount, std::cref(offset_map[amount]), std::ref(tx_map[amount]), std::ref(transactions[i]))); + tpool.submit(&waiter, boost::bind(&Blockchain::output_scan_worker, this, amount, std::cref(offset_map[amount]), std::ref(tx_map[amount]), std::ref(transactions[i]))); } - - work.reset(); - threadpool.join_all(); - ioservice.stop(); + waiter.wait(); } else { @@ -4086,6 +4231,11 @@ bool Blockchain::get_hard_fork_voting_info(uint8_t version, uint32_t &window, ui return m_hardfork->get_voting_info(version, window, votes, threshold, earliest_height, voting); } +uint64_t Blockchain::get_difficulty_target() const +{ + return get_current_hard_fork_version() < 2 ? DIFFICULTY_TARGET_V1 : DIFFICULTY_TARGET_V2; +} + std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t>> Blockchain:: get_output_histogram(const std::vector<uint64_t> &amounts, bool unlocked, uint64_t recent_cutoff) const { return m_db->get_output_histogram(amounts, unlocked, recent_cutoff); @@ -4129,7 +4279,7 @@ void Blockchain::cancel() } #if defined(PER_BLOCK_CHECKPOINT) -static const char expected_block_hashes_hash[] = "d3ca80d50661684cde0e715d46d7c19704d2e216b21ed088af9fd4ef37ed4d65"; +static const char expected_block_hashes_hash[] = "4b553162ee4e7af3c53666506591489c68560b9175e6e941dc96c89f96f0e35c"; void Blockchain::load_compiled_in_block_hashes() { if (m_fast_sync && get_blocks_dat_start(m_testnet) != nullptr && get_blocks_dat_size(m_testnet) > 0) @@ -4165,16 +4315,18 @@ void Blockchain::load_compiled_in_block_hashes() const unsigned char *p = get_blocks_dat_start(m_testnet); const uint32_t nblocks = *p | ((*(p+1))<<8) | ((*(p+2))<<16) | ((*(p+3))<<24); const size_t size_needed = 4 + nblocks * sizeof(crypto::hash); - if(nblocks > 0 && nblocks > m_db->height() && get_blocks_dat_size(m_testnet) >= size_needed) + if(nblocks > 0 && nblocks * HASH_OF_HASHES_STEP > m_db->height() && get_blocks_dat_size(m_testnet) >= size_needed) { p += sizeof(uint32_t); + m_blocks_hash_of_hashes.reserve(nblocks); for (uint32_t i = 0; i < nblocks; i++) { crypto::hash hash; memcpy(hash.data, p, sizeof(hash.data)); p += sizeof(hash.data); - m_blocks_hash_check.push_back(hash); + m_blocks_hash_of_hashes.push_back(hash); } + m_blocks_hash_check.resize(m_blocks_hash_of_hashes.size() * HASH_OF_HASHES_STEP, cryptonote::null_hash); MINFO(nblocks << " block hashes loaded"); // FIXME: clear tx_pool because the process might have been @@ -4205,7 +4357,7 @@ void Blockchain::load_compiled_in_block_hashes() bool Blockchain::is_within_compiled_block_hash_area(uint64_t height) const { #if defined(PER_BLOCK_CHECKPOINT) - return height < m_blocks_hash_check.size(); + return height < m_blocks_hash_of_hashes.size() * HASH_OF_HASHES_STEP; #else return false; #endif diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index b8ea657b4..d5bcad2e2 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -51,7 +51,7 @@ #include "cryptonote_tx_utils.h" #include "cryptonote_basic/verification_context.h" #include "crypto/hash.h" -#include "cryptonote_basic/checkpoints.h" +#include "checkpoints/checkpoints.h" #include "cryptonote_basic/hardfork.h" #include "blockchain_db/blockchain_db.h" @@ -374,6 +374,22 @@ namespace cryptonote * BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT additional (more recent) hashes. * * @param qblock_ids the foreign chain's "short history" (see get_short_chain_history) + * @param hashes the hashes to be returned, return-by-reference + * @param start_height the start height, return-by-reference + * @param current_height the current blockchain height, return-by-reference + * + * @return true if a block found in common, else false + */ + bool find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, std::list<crypto::hash>& hashes, uint64_t& start_height, uint64_t& current_height) const; + + /** + * @brief get recent block hashes for a foreign chain + * + * Find the split point between us and foreign blockchain and return + * (by reference) the most recent common block hash along with up to + * BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT additional (more recent) hashes. + * + * @param qblock_ids the foreign chain's "short history" (see get_short_chain_history) * @param resp return-by-reference the split height and subsequent blocks' hashes * * @return true if a block found in common, else false @@ -427,6 +443,35 @@ namespace cryptonote bool handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, NOTIFY_RESPONSE_GET_OBJECTS::request& rsp); /** + * @brief get number of outputs of an amount past the minimum spendable age + * + * @param amount the output amount + * + * @return the number of mature outputs + */ + uint64_t get_num_mature_outputs(uint64_t amount) const; + + /** + * @brief get random outputs (indices) for an amount + * + * @param amount the amount + * @param count the number of random outputs to choose + * + * @return the outputs' amount-global indices + */ + std::vector<uint64_t> get_random_outputs(uint64_t amount, uint64_t count) const; + + /** + * @brief get the public key for an output + * + * @param amount the output amount + * @param global_index the output amount-global index + * + * @return the public key + */ + crypto::public_key get_output_key(uint64_t amount, uint64_t global_index) const; + + /** * @brief gets random outputs to mix with * * This function takes an RPC request for outputs to mix with @@ -458,6 +503,17 @@ namespace cryptonote bool get_outs(const COMMAND_RPC_GET_OUTPUTS_BIN::request& req, COMMAND_RPC_GET_OUTPUTS_BIN::response& res) const; /** + * @brief gets an output's key and unlocked state + * + * @param amount in - the output amount + * @param index in - the output global amount index + * @param mask out - the output's RingCT mask + * @param key out - the output's key + * @param unlocked out - the output's unlocked state + */ + void get_output_key_mask_unlocked(const uint64_t& amount, const uint64_t& index, crypto::public_key& key, rct::key& mask, bool& unlocked) const; + + /** * @brief gets random ringct outputs to mix with * * This function takes an RPC request for outputs to mix with @@ -775,6 +831,13 @@ namespace cryptonote bool get_hard_fork_voting_info(uint8_t version, uint32_t &window, uint32_t &votes, uint32_t &threshold, uint64_t &earliest_height, uint8_t &voting) const; /** + * @brief get difficulty target based on chain and hardfork version + * + * @return difficulty target + */ + uint64_t get_difficulty_target() const; + + /** * @brief remove transactions from the transaction pool (if present) * * @param txids a list of hashes of transactions to be removed @@ -892,6 +955,7 @@ namespace cryptonote bool is_within_compiled_block_hash_area(uint64_t height) const; bool is_within_compiled_block_hash_area() const { return is_within_compiled_block_hash_area(m_db->height()); } + uint64_t prevalidate_block_hashes(uint64_t height, const std::list<crypto::hash> &hashes); void lock(); void unlock(); @@ -932,6 +996,7 @@ namespace cryptonote std::unordered_map<crypto::hash, std::unordered_map<crypto::key_image, bool>> m_check_txin_table; // SHA-3 hashes for each block and for fast pow checking + std::vector<crypto::hash> m_blocks_hash_of_hashes; std::vector<crypto::hash> m_blocks_hash_check; std::vector<crypto::hash> m_blocks_txs_check; diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 73ce88c79..bd01bc69a 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -37,7 +37,7 @@ using namespace epee; #include "common/util.h" #include "common/updates.h" #include "common/download.h" -#include "common/task_region.h" +#include "common/threadpool.h" #include "warnings.h" #include "crypto/crypto.h" #include "cryptonote_config.h" @@ -45,7 +45,7 @@ using namespace epee; #include "misc_language.h" #include <csignal> #include <p2p/net_node.h> -#include "cryptonote_basic/checkpoints.h" +#include "checkpoints/checkpoints.h" #include "ringct/rctTypes.h" #include "blockchain_db/blockchain_db.h" #include "ringct/rctSigs.h" @@ -74,7 +74,7 @@ namespace cryptonote m_last_dns_checkpoints_update(0), m_last_json_checkpoints_update(0), m_disable_dns_checkpoints(false), - m_threadpool(tools::thread_group::optimal()), + m_threadpool(tools::threadpool::getInstance()), m_update_download(0) { m_checkpoints_updating.clear(); @@ -161,6 +161,7 @@ namespace cryptonote command_line::add_arg(desc, command_line::arg_show_time_stats); command_line::add_arg(desc, command_line::arg_block_sync_size); command_line::add_arg(desc, command_line::arg_check_updates); + command_line::add_arg(desc, command_line::arg_fluffy_blocks); // we now also need some of net_node's options (p2p bind arg, for separate data dir) command_line::add_arg(desc, nodetool::arg_testnet_p2p_bind_port, false); @@ -182,7 +183,7 @@ namespace cryptonote if (!m_testnet && !m_fakechain) { cryptonote::checkpoints checkpoints; - if (!checkpoints.init_default_checkpoints()) + if (!checkpoints.init_default_checkpoints(m_testnet)) { throw std::runtime_error("Failed to initialize checkpoints"); } @@ -197,6 +198,7 @@ namespace cryptonote set_enforce_dns_checkpoints(command_line::get_arg(vm, command_line::arg_dns_checkpoints)); test_drop_download_height(command_line::get_arg(vm, command_line::arg_test_drop_download_height)); + m_fluffy_blocks_enabled = m_testnet || get_arg(vm, command_line::arg_fluffy_blocks); if (command_line::get_arg(vm, command_line::arg_test_drop_download) == true) test_drop_download(); @@ -209,10 +211,9 @@ namespace cryptonote return m_blockchain_storage.get_current_blockchain_height(); } //----------------------------------------------------------------------------------------------- - bool core::get_blockchain_top(uint64_t& height, crypto::hash& top_id) const + void core::get_blockchain_top(uint64_t& height, crypto::hash& top_id) const { top_id = m_blockchain_storage.get_tail_id(height); - return true; } //----------------------------------------------------------------------------------------------- bool core::get_blocks(uint64_t start_offset, size_t count, std::list<std::pair<cryptonote::blobdata,block>>& blocks, std::list<cryptonote::blobdata>& txs) const @@ -589,38 +590,53 @@ namespace cryptonote std::vector<result> results(tx_blobs.size()); tvc.resize(tx_blobs.size()); - tools::task_region(m_threadpool, [&] (tools::task_region_handle& region) { - std::list<blobdata>::const_iterator it = tx_blobs.begin(); - for (size_t i = 0; i < tx_blobs.size(); i++, ++it) { - region.run([&, i, it] { - results[i].res = handle_incoming_tx_pre(*it, tvc[i], results[i].tx, results[i].hash, results[i].prefix_hash, keeped_by_block, relayed, do_not_relay); - }); - } - }); - tools::task_region(m_threadpool, [&] (tools::task_region_handle& region) { - std::list<blobdata>::const_iterator it = tx_blobs.begin(); - for (size_t i = 0; i < tx_blobs.size(); i++, ++it) { - if (!results[i].res) - continue; - if(m_mempool.have_tx(results[i].hash)) + tools::threadpool::waiter waiter; + std::list<blobdata>::const_iterator it = tx_blobs.begin(); + for (size_t i = 0; i < tx_blobs.size(); i++, ++it) { + m_threadpool.submit(&waiter, [&, i, it] { + try { - LOG_PRINT_L2("tx " << results[i].hash << "already have transaction in tx_pool"); + results[i].res = handle_incoming_tx_pre(*it, tvc[i], results[i].tx, results[i].hash, results[i].prefix_hash, keeped_by_block, relayed, do_not_relay); } - else if(m_blockchain_storage.have_tx(results[i].hash)) + catch (const std::exception &e) { - LOG_PRINT_L2("tx " << results[i].hash << " already have transaction in blockchain"); + MERROR_VER("Exception in handle_incoming_tx_pre: " << e.what()); + results[i].res = false; } - else - { - region.run([&, i, it] { + }); + } + waiter.wait(); + it = tx_blobs.begin(); + for (size_t i = 0; i < tx_blobs.size(); i++, ++it) { + if (!results[i].res) + continue; + if(m_mempool.have_tx(results[i].hash)) + { + LOG_PRINT_L2("tx " << results[i].hash << "already have transaction in tx_pool"); + } + else if(m_blockchain_storage.have_tx(results[i].hash)) + { + LOG_PRINT_L2("tx " << results[i].hash << " already have transaction in blockchain"); + } + else + { + m_threadpool.submit(&waiter, [&, i, it] { + try + { results[i].res = handle_incoming_tx_post(*it, tvc[i], results[i].tx, results[i].hash, results[i].prefix_hash, keeped_by_block, relayed, do_not_relay); - }); - } + } + catch (const std::exception &e) + { + MERROR_VER("Exception in handle_incoming_tx_post: " << e.what()); + results[i].res = false; + } + }); } - }); + } + waiter.wait(); bool ok = true; - std::list<blobdata>::const_iterator it = tx_blobs.begin(); + it = tx_blobs.begin(); for (size_t i = 0; i < tx_blobs.size(); i++, ++it) { if (!results[i].res) { @@ -792,6 +808,13 @@ namespace cryptonote return BLOCKS_SYNCHRONIZING_DEFAULT_COUNT_PRE_V4; } //----------------------------------------------------------------------------------------------- + bool core::are_key_images_spent_in_pool(const std::vector<crypto::key_image>& key_im, std::vector<bool> &spent) const + { + spent.clear(); + + return m_mempool.check_for_key_images(key_im, spent); + } + //----------------------------------------------------------------------------------------------- std::pair<uint64_t, uint64_t> core::get_coinbase_tx_sum(const uint64_t start_offset, const size_t count) { uint64_t emission_amount = 0; @@ -1017,7 +1040,6 @@ namespace cryptonote { cryptonote_connection_context exclude_context = boost::value_initialized<cryptonote_connection_context>(); NOTIFY_NEW_BLOCK::request arg = AUTO_VAL_INIT(arg); - arg.hop = 0; arg.current_blockchain_height = m_blockchain_storage.get_current_blockchain_height(); std::list<crypto::hash> missed_txs; std::list<cryptonote::blobdata> txs; @@ -1182,6 +1204,11 @@ namespace cryptonote return m_mempool.get_transactions_and_spent_keys_info(tx_infos, key_image_infos); } //----------------------------------------------------------------------------------------------- + bool core::get_pool_for_rpc(std::vector<cryptonote::rpc::tx_in_pool>& tx_infos, cryptonote::rpc::key_images_with_tx_hashes& key_image_infos) const + { + return m_mempool.get_pool_for_rpc(tx_infos, key_image_infos); + } + //----------------------------------------------------------------------------------------------- bool core::get_short_chain_history(std::list<crypto::hash>& ids) const { return m_blockchain_storage.get_short_chain_history(ids); @@ -1271,11 +1298,12 @@ namespace cryptonote bool core::check_updates() { static const char software[] = "monero"; - static const char subdir[] = "cli"; // because it can never be simple #ifdef BUILD_TAG static const char buildtag[] = BOOST_PP_STRINGIZE(BUILD_TAG); + static const char subdir[] = "cli"; // because it can never be simple #else static const char buildtag[] = "source"; + static const char subdir[] = "source"; // because it can never be simple #endif if (check_updates_level == UPDATES_DISABLED) @@ -1371,6 +1399,11 @@ namespace cryptonote return m_target_blockchain_height; } //----------------------------------------------------------------------------------------------- + uint64_t core::prevalidate_block_hashes(uint64_t height, const std::list<crypto::hash> &hashes) + { + return get_blockchain_storage().prevalidate_block_hashes(height, hashes); + } + //----------------------------------------------------------------------------------------------- std::time_t core::get_start_time() const { return start_time; diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index f565afd87..7340e1024 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -40,7 +40,7 @@ #include "cryptonote_protocol/cryptonote_protocol_handler_common.h" #include "storages/portable_storage_template_helper.h" #include "common/download.h" -#include "common/thread_group.h" +#include "common/threadpool.h" #include "tx_pool.h" #include "blockchain.h" #include "cryptonote_basic/miner.h" @@ -299,10 +299,8 @@ namespace cryptonote * * @param height return-by-reference height of the block * @param top_id return-by-reference hash of the block - * - * @return true */ - bool get_blockchain_top(uint64_t& height, crypto::hash& top_id) const; + void get_blockchain_top(uint64_t& height, crypto::hash& top_id) const; /** * @copydoc Blockchain::get_blocks(uint64_t, size_t, std::list<std::pair<cryptonote::blobdata,block>>&, std::list<transaction>&) const @@ -463,6 +461,13 @@ namespace cryptonote bool get_pool_transactions_and_spent_keys_info(std::vector<tx_info>& tx_infos, std::vector<spent_key_image_info>& key_image_infos) const; /** + * @copydoc tx_memory_pool::get_pool_for_rpc + * + * @note see tx_memory_pool::get_pool_for_rpc + */ + bool get_pool_for_rpc(std::vector<cryptonote::rpc::tx_in_pool>& tx_infos, cryptonote::rpc::key_images_with_tx_hashes& key_image_infos) const; + + /** * @copydoc tx_memory_pool::get_transactions_count * * @note see tx_memory_pool::get_transactions_count @@ -708,6 +713,16 @@ namespace cryptonote bool are_key_images_spent(const std::vector<crypto::key_image>& key_im, std::vector<bool> &spent) const; /** + * @brief check if multiple key images are spent in the transaction pool + * + * @param key_im list of key images to check + * @param spent return-by-reference result for each image checked + * + * @return true + */ + bool are_key_images_spent_in_pool(const std::vector<crypto::key_image>& key_im, std::vector<bool> &spent) const; + + /** * @brief get the number of blocks to sync in one go * * @return the number of blocks to sync in one go @@ -728,6 +743,20 @@ namespace cryptonote */ bool get_testnet() const { return m_testnet; }; + /** + * @brief get whether fluffy blocks are enabled + * + * @return whether fluffy blocks are enabled + */ + bool fluffy_blocks_enabled() const { return m_fluffy_blocks_enabled; } + + /** + * @brief check a set of hashes against the precompiled hash set + * + * @return number of usable blocks + */ + uint64_t prevalidate_block_hashes(uint64_t height, const std::list<crypto::hash> &hashes); + private: /** @@ -933,7 +962,7 @@ namespace cryptonote std::unordered_set<crypto::hash> bad_semantics_txes[2]; boost::mutex bad_semantics_txes_lock; - tools::thread_group m_threadpool; + tools::threadpool& m_threadpool; enum { UPDATES_DISABLED, @@ -945,6 +974,8 @@ namespace cryptonote tools::download_async_handle m_update_download; size_t m_last_update_length; boost::mutex m_update_mutex; + + bool m_fluffy_blocks_enabled; }; } diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index 94f069827..586df9079 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -28,9 +28,11 @@ // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers +#include <unordered_set> #include "include_base_utils.h" using namespace epee; +#include "common/apply_permutation.h" #include "cryptonote_tx_utils.h" #include "cryptonote_config.h" #include "cryptonote_basic/miner.h" @@ -156,8 +158,14 @@ namespace cryptonote return destinations[0].addr.m_view_public_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) + bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, const cryptonote::account_public_address& change_addr, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct) { + if (destinations.empty()) + { + LOG_ERROR("The destinations must be non-empty"); + return false; + } + std::vector<rct::key> amount_keys; tx.set_null(); amount_keys.clear(); @@ -236,15 +244,19 @@ namespace cryptonote in_contexts.push_back(input_generation_context_data()); keypair& in_ephemeral = in_contexts.back().in_ephemeral; crypto::key_image img; - if(!generate_key_image_helper(sender_account_keys, src_entr.real_out_tx_key, src_entr.real_output_in_tx_index, in_ephemeral, img)) + const auto& out_key = reinterpret_cast<const crypto::public_key&>(src_entr.outputs[src_entr.real_output].second.dest); + if(!generate_key_image_helper(sender_account_keys, subaddresses, out_key, src_entr.real_out_tx_key, src_entr.real_out_additional_tx_keys, src_entr.real_output_in_tx_index, in_ephemeral, img)) + { + LOG_ERROR("Key image generation failed!"); return false; + } //check that derivated key is equal with real output key if( !(in_ephemeral.pub == src_entr.outputs[src_entr.real_output].second.dest) ) { LOG_ERROR("derived public key mismatch with output public key at index " << idx << ", real out " << src_entr.real_output << "! "<< ENDL << "derived_key:" << string_tools::pod_to_hex(in_ephemeral.pub) << ENDL << "real output_public_key:" - << string_tools::pod_to_hex(src_entr.outputs[src_entr.real_output].second) ); + << string_tools::pod_to_hex(src_entr.outputs[src_entr.real_output].second.dest) ); LOG_ERROR("amount " << src_entr.amount << ", rct " << src_entr.rct); LOG_ERROR("tx pubkey " << src_entr.real_out_tx_key << ", real_output_in_tx_index " << src_entr.real_output_in_tx_index); return false; @@ -267,16 +279,99 @@ namespace cryptonote std::vector<tx_destination_entry> shuffled_dsts(destinations); std::random_shuffle(shuffled_dsts.begin(), shuffled_dsts.end(), [](unsigned int i) { return crypto::rand<unsigned int>() % i; }); + // sort ins by their key image + std::vector<size_t> ins_order(sources.size()); + for (size_t n = 0; n < sources.size(); ++n) + ins_order[n] = n; + std::sort(ins_order.begin(), ins_order.end(), [&](const size_t i0, const size_t i1) { + const txin_to_key &tk0 = boost::get<txin_to_key>(tx.vin[i0]); + const txin_to_key &tk1 = boost::get<txin_to_key>(tx.vin[i1]); + return memcmp(&tk0.k_image, &tk1.k_image, sizeof(tk0.k_image)) < 0; + }); + tools::apply_permutation(ins_order, [&] (size_t i0, size_t i1) { + std::swap(tx.vin[i0], tx.vin[i1]); + std::swap(in_contexts[i0], in_contexts[i1]); + std::swap(sources[i0], sources[i1]); + }); + + // figure out if we need to make additional tx pubkeys + size_t num_stdaddresses = 0; + size_t num_subaddresses = 0; + std::unordered_set<cryptonote::account_public_address> unique_dst_addresses; + account_public_address single_dest_subaddress; + for(const tx_destination_entry& dst_entr: destinations) + { + if (dst_entr.addr == change_addr) + continue; + if (unique_dst_addresses.count(dst_entr.addr) == 0) + { + unique_dst_addresses.insert(dst_entr.addr); + if (dst_entr.is_subaddress) + { + ++num_subaddresses; + single_dest_subaddress = dst_entr.addr; + } + else + { + ++num_stdaddresses; + } + } + } + LOG_PRINT_L2("destinations include " << num_stdaddresses << " standard addresses and " << num_subaddresses << "subaddresses"); + + // if this is a single-destination transfer to a subaddress, we set the tx pubkey to R=s*D + if (num_stdaddresses == 0 && num_subaddresses == 1) + { + txkey.pub = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(single_dest_subaddress.m_spend_public_key), rct::sk2rct(txkey.sec))); + remove_field_from_tx_extra(tx.extra, typeid(tx_extra_pub_key)); + add_tx_pub_key_to_extra(tx, txkey.pub); + } + + std::vector<crypto::public_key> additional_tx_public_keys; + additional_tx_keys.clear(); + + // we don't need to include additional tx keys if: + // - all the destinations are standard addresses + // - there's only one destination which is a subaddress + bool need_additional_txkeys = num_subaddresses > 0 && (num_stdaddresses > 0 || num_subaddresses > 1); + uint64_t summary_outs_money = 0; //fill outputs size_t output_index = 0; - for(const tx_destination_entry& dst_entr: shuffled_dsts) + for(const tx_destination_entry& dst_entr: destinations) { 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 << ")"); + + // make additional tx pubkey if necessary + keypair additional_txkey; + if (need_additional_txkeys) + { + additional_txkey = keypair::generate(); + if (dst_entr.is_subaddress) + additional_txkey.pub = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(dst_entr.addr.m_spend_public_key), rct::sk2rct(additional_txkey.sec))); + } + + bool r; + if (dst_entr.addr == change_addr) + { + // sending change to yourself; derivation = a*R + r = crypto::generate_key_derivation(txkey.pub, sender_account_keys.m_view_secret_key, derivation); + CHECK_AND_ASSERT_MES(r, false, "at creation outs: failed to generate_key_derivation(" << txkey.pub << ", " << sender_account_keys.m_view_secret_key << ")"); + } + else + { + // sending to the recipient; derivation = r*A (or s*C in the subaddress scheme) + r = crypto::generate_key_derivation(dst_entr.addr.m_view_public_key, dst_entr.is_subaddress && need_additional_txkeys ? additional_txkey.sec : txkey.sec, derivation); + CHECK_AND_ASSERT_MES(r, false, "at creation outs: failed to generate_key_derivation(" << dst_entr.addr.m_view_public_key << ", " << (dst_entr.is_subaddress && need_additional_txkeys ? additional_txkey.sec : txkey.sec) << ")"); + } + + if (need_additional_txkeys) + { + additional_tx_public_keys.push_back(additional_txkey.pub); + additional_tx_keys.push_back(additional_txkey.sec); + } if (tx.version > 1) { @@ -297,6 +392,17 @@ namespace cryptonote summary_outs_money += dst_entr.amount; } + remove_field_from_tx_extra(tx.extra, typeid(tx_extra_additional_pub_keys)); + add_additional_tx_pub_keys_to_extra(tx.extra, additional_tx_public_keys); + + LOG_PRINT_L2("tx pubkey: " << txkey.pub); + if (need_additional_txkeys) + { + LOG_PRINT_L2("additional tx pubkeys: "); + for (size_t i = 0; i < additional_tx_public_keys.size(); ++i) + LOG_PRINT_L2(additional_tx_public_keys[i]); + } + //check money if(summary_outs_money > summary_inputs_money ) { @@ -459,10 +565,13 @@ namespace cryptonote return true; } //--------------------------------------------------------------- - 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(const account_keys& sender_account_keys, std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time) { + std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses; + subaddresses[sender_account_keys.m_account_address.m_spend_public_key] = {0,0}; crypto::secret_key tx_key; - return construct_tx_and_get_tx_key(sender_account_keys, sources, destinations, extra, tx, unlock_time, tx_key); + std::vector<crypto::secret_key> additional_tx_keys; + return construct_tx_and_get_tx_key(sender_account_keys, subaddresses, sources, destinations, destinations.back().addr, extra, tx, unlock_time, tx_key, additional_tx_keys); } //--------------------------------------------------------------- bool generate_genesis_block( @@ -484,8 +593,9 @@ namespace cryptonote std::string genesis_coinbase_tx_hex = config::GENESIS_TX; blobdata tx_bl; - string_tools::parse_hexstr_to_binbuff(genesis_coinbase_tx_hex, tx_bl); - bool r = parse_and_validate_tx_from_blob(tx_bl, bl.miner_tx); + bool r = string_tools::parse_hexstr_to_binbuff(genesis_coinbase_tx_hex, tx_bl); + CHECK_AND_ASSERT_MES(r, false, "failed to parse coinbase tx from hard coded blob"); + r = parse_and_validate_tx_from_blob(tx_bl, bl.miner_tx); CHECK_AND_ASSERT_MES(r, false, "failed to parse coinbase tx from hard coded blob"); bl.major_version = CURRENT_BLOCK_MAJOR_VERSION; bl.minor_version = CURRENT_BLOCK_MINOR_VERSION; diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h index 7aa7c280d..b5de44b88 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.h +++ b/src/cryptonote_core/cryptonote_tx_utils.h @@ -46,6 +46,7 @@ namespace cryptonote 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 + std::vector<crypto::public_key> real_out_additional_tx_keys; //incoming real tx additional public keys size_t real_output_in_tx_index; //index in transaction outputs vector uint64_t amount; //money bool rct; //true if the output is rct @@ -58,20 +59,22 @@ namespace cryptonote { uint64_t amount; //money account_public_address addr; //destination address + bool is_subaddress; - tx_destination_entry() : amount(0), addr(AUTO_VAL_INIT(addr)) { } - tx_destination_entry(uint64_t a, const account_public_address &ad) : amount(a), addr(ad) { } + tx_destination_entry() : amount(0), addr(AUTO_VAL_INIT(addr)), is_subaddress(false) { } + tx_destination_entry(uint64_t a, const account_public_address &ad, bool is_subaddress) : amount(a), addr(ad), is_subaddress(is_subaddress) { } BEGIN_SERIALIZE_OBJECT() VARINT_FIELD(amount) FIELD(addr) + FIELD(is_subaddress) END_SERIALIZE() }; //--------------------------------------------------------------- crypto::public_key get_destination_view_key_pub(const std::vector<tx_destination_entry> &destinations, const account_keys &sender_keys); - 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 &tx_key, bool rct = false); + bool construct_tx(const account_keys& sender_account_keys, 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::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, const cryptonote::account_public_address& change_addr, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false); bool generate_genesis_block( block& bl @@ -82,6 +85,7 @@ namespace cryptonote } BOOST_CLASS_VERSION(cryptonote::tx_source_entry, 0) +BOOST_CLASS_VERSION(cryptonote::tx_destination_entry, 1) namespace boost { @@ -98,5 +102,15 @@ namespace boost a & x.rct; a & x.mask; } + + template <class Archive> + inline void serialize(Archive& a, cryptonote::tx_destination_entry& x, const boost::serialization::version_type ver) + { + a & x.amount; + a & x.addr; + if (ver < 1) + return; + a & x.is_subaddress; + } } } diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index 68e0b556f..9071c330c 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -80,7 +80,7 @@ namespace cryptonote uint64_t get_transaction_size_limit(uint8_t version) { - return get_min_block_size(version) * 125 / 100 - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE; + return get_min_block_size(version) - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE; } // This class is meant to create a batch when none currently exists. @@ -92,7 +92,7 @@ namespace cryptonote LockedTXN(Blockchain &b): m_blockchain(b), m_batch(false) { m_batch = m_blockchain.get_db().batch_start(); } - ~LockedTXN() { if (m_batch) { m_blockchain.get_db().batch_stop(); } } + ~LockedTXN() { try { if (m_batch) { m_blockchain.get_db().batch_stop(); } } catch (const std::exception &e) { MWARNING("LockedTXN dtor filtering exception: " << e.what()); } } private: Blockchain &m_blockchain; bool m_batch; @@ -202,6 +202,9 @@ namespace cryptonote return false; } + // assume failure during verification steps until success is certain + tvc.m_verifivation_failed = true; + time_t receive_time = time(nullptr); crypto::hash max_used_block_id = null_hash; @@ -246,6 +249,7 @@ namespace cryptonote { LOG_PRINT_L1("tx used wrong inputs, rejected"); tvc.m_verifivation_failed = true; + tvc.m_invalid_input = true; return false; } }else @@ -285,9 +289,6 @@ namespace cryptonote tvc.m_should_be_relayed = true; } - // assume failure during verification steps until success is certain - tvc.m_verifivation_failed = true; - tvc.m_verifivation_failed = false; MINFO("Transaction added to pool: txid " << id << " bytes: " << blob_size << " fee/byte: " << (fee / (double)blob_size)); @@ -298,7 +299,8 @@ namespace cryptonote { crypto::hash h = null_hash; size_t blob_size = 0; - get_transaction_hash(tx, h, blob_size); + if (!get_transaction_hash(tx, h, blob_size) || blob_size == 0) + return false; return add_tx(tx, h, blob_size, tvc, keeped_by_block, relayed, do_not_relay, version); } //--------------------------------------------------------------------------------- @@ -684,6 +686,65 @@ namespace cryptonote return true; } //--------------------------------------------------------------------------------- + bool tx_memory_pool::get_pool_for_rpc(std::vector<cryptonote::rpc::tx_in_pool>& tx_infos, cryptonote::rpc::key_images_with_tx_hashes& key_image_infos) const + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + CRITICAL_REGION_LOCAL1(m_blockchain); + m_blockchain.for_all_txpool_txes([&tx_infos, key_image_infos](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *bd){ + cryptonote::rpc::tx_in_pool txi; + txi.tx_hash = txid; + transaction tx; + if (!parse_and_validate_tx_from_blob(*bd, tx)) + { + MERROR("Failed to parse tx from txpool"); + // continue + return true; + } + txi.tx = tx; + txi.blob_size = meta.blob_size; + txi.fee = meta.fee; + txi.kept_by_block = meta.kept_by_block; + txi.max_used_block_height = meta.max_used_block_height; + txi.max_used_block_hash = meta.max_used_block_id; + txi.last_failed_block_height = meta.last_failed_height; + txi.last_failed_block_hash = meta.last_failed_id; + txi.receive_time = meta.receive_time; + txi.relayed = meta.relayed; + txi.last_relayed_time = meta.last_relayed_time; + txi.do_not_relay = meta.do_not_relay; + tx_infos.push_back(txi); + return true; + }, true); + + for (const key_images_container::value_type& kee : m_spent_key_images) { + std::vector<crypto::hash> tx_hashes; + const std::unordered_set<crypto::hash>& kei_image_set = kee.second; + for (const crypto::hash& tx_id_hash : kei_image_set) + { + tx_hashes.push_back(tx_id_hash); + } + + const crypto::key_image& k_image = kee.first; + key_image_infos[k_image] = tx_hashes; + } + return true; + } + //--------------------------------------------------------------------------------- + bool tx_memory_pool::check_for_key_images(const std::vector<crypto::key_image>& key_images, std::vector<bool> spent) const + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + CRITICAL_REGION_LOCAL1(m_blockchain); + + spent.clear(); + + for (const auto& image : key_images) + { + spent.push_back(m_spent_key_images.find(image) == m_spent_key_images.end() ? false : true); + } + + return true; + } + //--------------------------------------------------------------------------------- bool tx_memory_pool::get_transaction(const crypto::hash& id, cryptonote::blobdata& txblob) const { CRITICAL_REGION_LOCAL(m_transactions_lock); @@ -1031,12 +1092,13 @@ namespace cryptonote m_txs_by_fee_and_receive_time.clear(); m_spent_key_images.clear(); - return m_blockchain.for_all_txpool_txes([this](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *bd) { + std::vector<crypto::hash> remove; + bool r = m_blockchain.for_all_txpool_txes([this, &remove](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *bd) { cryptonote::transaction tx; if (!parse_and_validate_tx_from_blob(*bd, tx)) { - MERROR("Failed to parse tx from txpool"); - return false; + MWARNING("Failed to parse tx from txpool, removing"); + remove.push_back(txid); } if (!insert_key_images(tx, meta.kept_by_block)) { @@ -1046,6 +1108,25 @@ namespace cryptonote m_txs_by_fee_and_receive_time.emplace(std::pair<double, time_t>(meta.fee / (double)meta.blob_size, meta.receive_time), txid); return true; }, true); + if (!r) + return false; + if (!remove.empty()) + { + LockedTXN lock(m_blockchain); + for (const auto &txid: remove) + { + try + { + m_blockchain.remove_txpool_tx(txid); + } + catch (const std::exception &e) + { + MWARNING("Failed to remove corrupt transaction: " << txid); + // ignore error + } + } + } + return true; } //--------------------------------------------------------------------------------- diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h index 6414620c9..3e4ccb338 100644 --- a/src/cryptonote_core/tx_pool.h +++ b/src/cryptonote_core/tx_pool.h @@ -46,6 +46,7 @@ #include "blockchain_db/blockchain_db.h" #include "crypto/hash.h" #include "rpc/core_rpc_server_commands_defs.h" +#include "rpc/message_data_structs.h" namespace cryptonote { @@ -269,6 +270,28 @@ namespace cryptonote bool get_transactions_and_spent_keys_info(std::vector<tx_info>& tx_infos, std::vector<spent_key_image_info>& key_image_infos) const; /** + * @brief get information about all transactions and key images in the pool + * + * see documentation on tx_in_pool and key_images_with_tx_hashes for more details + * + * @param tx_infos [out] the transactions' information + * @param key_image_infos [out] the spent key images' information + * + * @return true + */ + bool get_pool_for_rpc(std::vector<cryptonote::rpc::tx_in_pool>& tx_infos, cryptonote::rpc::key_images_with_tx_hashes& key_image_infos) const; + + /** + * @brief check for presence of key images in the pool + * + * @param key_images [in] vector of key images to check + * @param spent [out] vector of bool to return + * + * @return true + */ + bool check_for_key_images(const std::vector<crypto::key_image>& key_images, std::vector<bool> spent) const; + + /** * @brief get a specific transaction from the pool * * @param h the hash of the transaction to get diff --git a/src/cryptonote_protocol/CMakeLists.txt b/src/cryptonote_protocol/CMakeLists.txt index 4ce380a48..347e48eee 100644 --- a/src/cryptonote_protocol/CMakeLists.txt +++ b/src/cryptonote_protocol/CMakeLists.txt @@ -39,5 +39,3 @@ target_link_libraries(cryptonote_protocol p2p PRIVATE ${EXTRA_LIBRARIES}) -add_dependencies(cryptonote_protocol - version) diff --git a/src/cryptonote_protocol/block_queue.cpp b/src/cryptonote_protocol/block_queue.cpp index 02a8e3ec2..64dd1fb50 100644 --- a/src/cryptonote_protocol/block_queue.cpp +++ b/src/cryptonote_protocol/block_queue.cpp @@ -340,7 +340,7 @@ size_t block_queue::get_num_filled_spans() const crypto::hash block_queue::get_last_known_hash(const boost::uuids::uuid &connection_id) const { boost::unique_lock<boost::recursive_mutex> lock(mutex); - crypto::hash hash = cryptonote::null_hash; + crypto::hash hash = crypto::null_hash; uint64_t highest_height = 0; for (const auto &span: blocks) { diff --git a/src/cryptonote_protocol/cryptonote_protocol_defs.h b/src/cryptonote_protocol/cryptonote_protocol_defs.h index 71e205c23..1804cc101 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_defs.h +++ b/src/cryptonote_protocol/cryptonote_protocol_defs.h @@ -128,12 +128,10 @@ namespace cryptonote { block_complete_entry b; uint64_t current_blockchain_height; - uint32_t hop; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(b) KV_SERIALIZE(current_blockchain_height) - KV_SERIALIZE(hop) END_KV_SERIALIZE_MAP() }; }; @@ -254,12 +252,10 @@ namespace cryptonote { block_complete_entry b; uint64_t current_blockchain_height; - uint32_t hop; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(b) KV_SERIALIZE(current_blockchain_height) - KV_SERIALIZE(hop) END_KV_SERIALIZE_MAP() }; }; @@ -276,13 +272,11 @@ namespace cryptonote crypto::hash block_hash; uint64_t current_blockchain_height; std::vector<size_t> missing_tx_indices; - uint32_t hop; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE_VAL_POD_AS_BLOB(block_hash) KV_SERIALIZE(current_blockchain_height) KV_SERIALIZE_CONTAINER_POD_AS_BLOB(missing_tx_indices) - KV_SERIALIZE(hop) END_KV_SERIALIZE_MAP() }; }; diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index 7d95f134b..f53b2ee4a 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -336,7 +336,7 @@ namespace cryptonote template<class t_core> int t_cryptonote_protocol_handler<t_core>::handle_notify_new_block(int command, NOTIFY_NEW_BLOCK::request& arg, cryptonote_connection_context& context) { - MLOG_P2P_MESSAGE("Received NOTIFY_NEW_BLOCK (hop " << arg.hop << ", " << arg.b.txs.size() << " txes)"); + MLOG_P2P_MESSAGE("Received NOTIFY_NEW_BLOCK (" << arg.b.txs.size() << " txes)"); if(context.m_state != cryptonote_connection_context::state_normal) return 1; if(!is_synchronized()) // can happen if a peer connection goes to normal but another thread still hasn't finished adding queued blocks @@ -379,7 +379,6 @@ namespace cryptonote } if(bvc.m_added_to_main_chain) { - ++arg.hop; //TODO: Add here announce protocol usage relay_block(arg, context); }else if(bvc.m_marked_as_orphaned) @@ -397,7 +396,7 @@ namespace cryptonote template<class t_core> int t_cryptonote_protocol_handler<t_core>::handle_notify_new_fluffy_block(int command, NOTIFY_NEW_FLUFFY_BLOCK::request& arg, cryptonote_connection_context& context) { - MLOG_P2P_MESSAGE("Received NOTIFY_NEW_FLUFFY_BLOCK (height " << arg.current_blockchain_height << ", hop " << arg.hop << ", " << arg.b.txs.size() << " txes)"); + MLOG_P2P_MESSAGE("Received NOTIFY_NEW_FLUFFY_BLOCK (height " << arg.current_blockchain_height << ", " << arg.b.txs.size() << " txes)"); if(context.m_state != cryptonote_connection_context::state_normal) return 1; if(!is_synchronized()) // can happen if a peer connection goes to normal but another thread still hasn't finished adding queued blocks @@ -607,7 +606,6 @@ namespace cryptonote MDEBUG(" tx " << new_block.tx_hashes[txidx]); NOTIFY_REQUEST_FLUFFY_MISSING_TX::request missing_tx_req; missing_tx_req.block_hash = get_block_hash(new_block); - missing_tx_req.hop = arg.hop; missing_tx_req.current_blockchain_height = arg.current_blockchain_height; missing_tx_req.missing_tx_indices = std::move(need_tx_indices); @@ -644,10 +642,8 @@ namespace cryptonote } if( bvc.m_added_to_main_chain ) { - ++arg.hop; //TODO: Add here announce protocol usage NOTIFY_NEW_BLOCK::request reg_arg = AUTO_VAL_INIT(reg_arg); - reg_arg.hop = arg.hop; reg_arg.current_blockchain_height = arg.current_blockchain_height; reg_arg.b = b; relay_block(reg_arg, context); @@ -700,7 +696,6 @@ namespace cryptonote NOTIFY_NEW_FLUFFY_BLOCK::request fluffy_response; fluffy_response.b.block = t_serializable_object_to_blob(b); fluffy_response.current_blockchain_height = arg.current_blockchain_height; - fluffy_response.hop = arg.hop; for(auto& tx_idx: arg.missing_tx_indices) { if(tx_idx < b.tx_hashes.size()) @@ -936,7 +931,8 @@ namespace cryptonote } // get the last parsed block, which should be the highest one - if(m_core.have_block(cryptonote::get_block_hash(b))) + const crypto::hash last_block_hash = cryptonote::get_block_hash(b); + if(m_core.have_block(last_block_hash)) { const uint64_t subchain_height = start_height + arg.blocks.size(); LOG_DEBUG_CC(context, "These are old blocks, ignoring: blocks " << start_height << " - " << (subchain_height-1) << ", blockchain height " << m_core.get_current_blockchain_height()); @@ -954,7 +950,7 @@ namespace cryptonote MDEBUG(context << " adding span: " << arg.blocks.size() << " at height " << start_height << ", " << dt.total_microseconds()/1e6 << " seconds, " << (rate/1e3) << " kB/s, size now " << (m_block_queue.get_data_size() + blocks_size) / 1048576.f << " MB"); m_block_queue.add_blocks(start_height, arg.blocks, context.m_connection_id, rate, blocks_size); - context.m_last_known_hash = cryptonote::get_blob_hash(arg.blocks.back().block); + context.m_last_known_hash = last_block_hash; if (!m_core.get_test_drop_download() || !m_core.get_test_drop_download_height()) { // DISCARD BLOCKS for testing return 1; @@ -1488,7 +1484,7 @@ skip: if (!start_from_current_chain) { // we'll want to start off from where we are on that peer, which may not be added yet - if (context.m_last_known_hash != cryptonote::null_hash && r.block_ids.front() != context.m_last_known_hash) + if (context.m_last_known_hash != crypto::null_hash && r.block_ids.front() != context.m_last_known_hash) r.block_ids.push_front(context.m_last_known_hash); } @@ -1583,10 +1579,22 @@ skip: return 1; } + uint64_t n_use_blocks = m_core.prevalidate_block_hashes(arg.start_height, arg.m_block_ids); + if (n_use_blocks == 0) + { + LOG_ERROR_CCONTEXT("Peer yielded no usable blocks, dropping connection"); + drop_connection(context, false, false); + return 1; + } + + uint64_t added = 0; for(auto& bl_id: arg.m_block_ids) { context.m_needed_objects.push_back(bl_id); + if (++added == n_use_blocks) + break; } + context.m_last_response_height -= arg.m_block_ids.size() - n_use_blocks; if (!request_missing_objects(context, false)) { @@ -1605,7 +1613,6 @@ skip: bool t_cryptonote_protocol_handler<t_core>::relay_block(NOTIFY_NEW_BLOCK::request& arg, cryptonote_connection_context& exclude_context) { NOTIFY_NEW_FLUFFY_BLOCK::request fluffy_arg = AUTO_VAL_INIT(fluffy_arg); - fluffy_arg.hop = arg.hop; fluffy_arg.current_blockchain_height = arg.current_blockchain_height; std::list<blobdata> fluffy_txs; fluffy_arg.b = arg.b; @@ -1622,7 +1629,7 @@ skip: { if (peer_id && exclude_context.m_connection_id != context.m_connection_id) { - if(m_core.get_testnet() && (support_flags & P2P_SUPPORT_FLAG_FLUFFY_BLOCKS)) + if(m_core.fluffy_blocks_enabled() && (support_flags & P2P_SUPPORT_FLAG_FLUFFY_BLOCKS)) { LOG_DEBUG_CC(context, "PEER SUPPORTS FLUFFY BLOCKS - RELAYING THIN/COMPACT WHATEVER BLOCK"); fluffyConnections.push_back(context.m_connection_id); diff --git a/src/daemon/CMakeLists.txt b/src/daemon/CMakeLists.txt index 795442a2d..d0fc1d846 100644 --- a/src/daemon/CMakeLists.txt +++ b/src/daemon/CMakeLists.txt @@ -92,14 +92,17 @@ target_link_libraries(daemon p2p cryptonote_protocol daemonizer + serialization + daemon_rpc_server + version ${Boost_CHRONO_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_REGEX_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} + ${ZMQ_LIB} ${EXTRA_LIBRARIES}) -add_dependencies(daemon version) set_property(TARGET daemon PROPERTY OUTPUT_NAME "monerod") diff --git a/src/daemon/command_line_args.h b/src/daemon/command_line_args.h index 8eb3db195..7fa58f9d8 100644 --- a/src/daemon/command_line_args.h +++ b/src/daemon/command_line_args.h @@ -46,6 +46,11 @@ namespace daemon_args , "Specify log file" , "" }; + const command_line::arg_descriptor<std::size_t> arg_max_log_file_size = { + "max-log-file-size" + , "Specify maximum log file size [B]" + , MAX_LOG_FILE_SIZE + }; const command_line::arg_descriptor<std::string> arg_log_level = { "log-level" , "" @@ -64,6 +69,25 @@ namespace daemon_args , "Max number of threads to use for a parallel job" , 0 }; + + const command_line::arg_descriptor<std::string> arg_zmq_rpc_bind_ip = { + "zmq-rpc-bind-ip" + , "IP for ZMQ RPC server to listen on" + , "127.0.0.1" + }; + + const command_line::arg_descriptor<std::string> arg_zmq_rpc_bind_port = { + "zmq-rpc-bind-port" + , "Port for ZMQ RPC server to listen on" + , std::to_string(config::ZMQ_RPC_DEFAULT_PORT) + }; + + const command_line::arg_descriptor<std::string> arg_zmq_testnet_rpc_bind_port = { + "zmq-testnet-rpc-bind-port" + , "Port for testnet ZMQ RPC server to listen on" + , std::to_string(config::testnet::ZMQ_RPC_DEFAULT_PORT) + }; + } // namespace daemon_args #endif // DAEMON_COMMAND_LINE_ARGS_H diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index d949a57b1..50a63d955 100644 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -125,12 +125,17 @@ bool t_command_parser_executor::print_blockchain_info(const std::vector<std::str bool t_command_parser_executor::set_log_level(const std::vector<std::string>& args) { - if(args.size() != 1) + if(args.size() > 1) { std::cout << "use: set_log [<log_level_number_0-4> | <categories>]" << std::endl; return true; } + if (args.empty()) + { + return m_executor.set_log_categories("+"); + } + uint16_t l = 0; if(epee::string_tools::get_xtype_from_string(l, args[0])) { @@ -247,20 +252,18 @@ bool t_command_parser_executor::start_mining(const std::vector<std::string>& arg return true; } - cryptonote::account_public_address adr; - bool has_payment_id; - crypto::hash8 payment_id; + cryptonote::address_parse_info info; bool testnet = false; - if(!cryptonote::get_account_integrated_address_from_str(adr, has_payment_id, payment_id, false, args.front())) + if(!cryptonote::get_account_address_from_str(info, false, args.front())) { - if(!cryptonote::get_account_integrated_address_from_str(adr, has_payment_id, payment_id, true, args.front())) + if(!cryptonote::get_account_address_from_str(info, true, args.front())) { bool dnssec_valid; std::string address_str = tools::dns_utils::get_account_address_as_str_from_url(args.front(), dnssec_valid, [](const std::string &url, const std::vector<std::string> &addresses, bool dnssec_valid){return addresses[0];}); - if(!cryptonote::get_account_integrated_address_from_str(adr, has_payment_id, payment_id, false, address_str)) + if(!cryptonote::get_account_address_from_str(info, false, address_str)) { - if(!cryptonote::get_account_integrated_address_from_str(adr, has_payment_id, payment_id, true, address_str)) + if(!cryptonote::get_account_address_from_str(info, true, address_str)) { std::cout << "target account address has wrong format" << std::endl; return true; @@ -276,6 +279,11 @@ bool t_command_parser_executor::start_mining(const std::vector<std::string>& arg testnet = true; } } + if (info.is_subaddress) + { + tools::fail_msg_writer() << "subaddress for mining reward is not yet supported!" << std::endl; + return true; + } if(testnet) std::cout << "Mining to a testnet address, make sure this is intentional!" << std::endl; uint64_t threads_count = 1; @@ -302,7 +310,7 @@ bool t_command_parser_executor::start_mining(const std::vector<std::string>& arg threads_count = (ok && 0 < threads_count) ? threads_count : 1; } - m_executor.start_mining(adr, threads_count, testnet, do_background_mining, ignore_battery); + m_executor.start_mining(info.address, threads_count, testnet, do_background_mining, ignore_battery); return true; } @@ -334,17 +342,18 @@ bool t_command_parser_executor::set_limit(const std::vector<std::string>& args) if(args.size()==0) { return m_executor.get_limit(); } - int limit; + int64_t limit; try { - limit = std::stoi(args[0]); + limit = std::stoll(args[0]); } - catch(std::invalid_argument& ex) { + catch(const std::invalid_argument& ex) { + std::cout << "failed to parse argument" << std::endl; return false; } - if (limit==-1) limit=128; - limit *= 1024; + if (limit > 0) + limit *= 1024; - return m_executor.set_limit(limit); + return m_executor.set_limit(limit, limit); } bool t_command_parser_executor::set_limit_up(const std::vector<std::string>& args) @@ -353,17 +362,18 @@ bool t_command_parser_executor::set_limit_up(const std::vector<std::string>& arg if(args.size()==0) { return m_executor.get_limit_up(); } - int limit; + int64_t limit; try { - limit = std::stoi(args[0]); + limit = std::stoll(args[0]); } - catch(std::invalid_argument& ex) { + catch(const std::invalid_argument& ex) { + std::cout << "failed to parse argument" << std::endl; return false; } - if (limit==-1) limit=128; - limit *= 1024; + if (limit > 0) + limit *= 1024; - return m_executor.set_limit_up(limit); + return m_executor.set_limit(0, limit); } bool t_command_parser_executor::set_limit_down(const std::vector<std::string>& args) @@ -372,17 +382,18 @@ bool t_command_parser_executor::set_limit_down(const std::vector<std::string>& a if(args.size()==0) { return m_executor.get_limit_down(); } - int limit; + int64_t limit; try { - limit = std::stoi(args[0]); + limit = std::stoll(args[0]); } - catch(std::invalid_argument& ex) { + catch(const std::invalid_argument& ex) { + std::cout << "failed to parse argument" << std::endl; return false; } - if (limit==-1) limit=128; - limit *= 1024; + if (limit > 0) + limit *= 1024; - return m_executor.set_limit_down(limit); + return m_executor.set_limit(limit, 0); } bool t_command_parser_executor::out_peers(const std::vector<std::string>& args) diff --git a/src/daemon/command_parser_executor.h b/src/daemon/command_parser_executor.h index f301ef14a..6443d9be0 100644 --- a/src/daemon/command_parser_executor.h +++ b/src/daemon/command_parser_executor.h @@ -36,7 +36,7 @@ #pragma once -#include <boost/optional/optional_fwd.hpp> +#include <boost/optional/optional.hpp> #include "daemon/rpc_command_executor.h" #include "common/common_fwd.h" diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp index 9df698547..3f1543857 100644 --- a/src/daemon/command_server.cpp +++ b/src/daemon/command_server.cpp @@ -141,7 +141,7 @@ t_command_server::t_command_server( m_command_lookup.set_handler( "set_log" , std::bind(&t_command_parser_executor::set_log_level, &m_parser, p::_1) - , "set_log <level>|<categories> - Change current loglevel, <level> is a number 0-4" + , "set_log <level>|<{+,-,}categories> - Change current log level/categories, <level> is a number 0-4" ); m_command_lookup.set_handler( "diff" @@ -241,7 +241,7 @@ t_command_server::t_command_server( m_command_lookup.set_handler( "bc_dyn_stats" , std::bind(&t_command_parser_executor::print_blockchain_dynamic_stats, &m_parser, p::_1) - , "Print information about current blockchain dynamic state" + , "Print information about current blockchain dynamic state, bc_dyn_stats <last n blocks>" ); m_command_lookup.set_handler( "update" diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 683eaf4ff..faa620c54 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -32,6 +32,8 @@ #include <stdexcept> #include "misc_log_ex.h" #include "daemon/daemon.h" +#include "rpc/daemon_handler.h" +#include "rpc/zmq_server.h" #include "common/password.h" #include "common/util.h" @@ -85,7 +87,18 @@ t_daemon::t_daemon( boost::program_options::variables_map const & vm ) : mp_internals{new t_internals{vm}} -{} +{ + bool testnet = command_line::get_arg(vm, command_line::arg_testnet_on); + if (testnet) + { + zmq_rpc_bind_port = command_line::get_arg(vm, daemon_args::arg_zmq_testnet_rpc_bind_port); + } + else + { + zmq_rpc_bind_port = command_line::get_arg(vm, daemon_args::arg_zmq_rpc_bind_port); + } + zmq_rpc_bind_address = command_line::get_arg(vm, daemon_args::arg_zmq_rpc_bind_ip); +} t_daemon::~t_daemon() = default; @@ -133,6 +146,30 @@ bool t_daemon::run(bool interactive) rpc_commands->start_handling(std::bind(&daemonize::t_daemon::stop_p2p, this)); } + cryptonote::rpc::DaemonHandler rpc_daemon_handler(mp_internals->core.get(), mp_internals->p2p.get()); + cryptonote::rpc::ZmqServer zmq_server(rpc_daemon_handler); + + if (!zmq_server.addTCPSocket(zmq_rpc_bind_address, zmq_rpc_bind_port)) + { + LOG_ERROR(std::string("Failed to add TCP Socket (") + zmq_rpc_bind_address + + ":" + zmq_rpc_bind_port + ") to ZMQ RPC Server"); + + if (interactive) + { + rpc_commands->stop_handling(); + } + + mp_internals->rpc.stop(); + + return false; + } + + MINFO("Starting ZMQ server..."); + zmq_server.run(); + + MINFO(std::string("ZMQ server started at ") + zmq_rpc_bind_address + + ":" + zmq_rpc_bind_port + "."); + mp_internals->p2p.run(); // blocks until p2p goes down if (rpc_commands) @@ -140,6 +177,8 @@ bool t_daemon::run(bool interactive) rpc_commands->stop_handling(); } + zmq_server.stop(); + mp_internals->rpc.stop(); mp_internals->core.get().get_miner().stop(); MGINFO("Node stopped."); diff --git a/src/daemon/daemon.h b/src/daemon/daemon.h index 2b9f18669..8c7547f62 100644 --- a/src/daemon/daemon.h +++ b/src/daemon/daemon.h @@ -43,6 +43,8 @@ private: void stop_p2p(); private: std::unique_ptr<t_internals> mp_internals; + std::string zmq_rpc_bind_address; + std::string zmq_rpc_bind_port; public: t_daemon( boost::program_options::variables_map const & vm diff --git a/src/daemon/main.cpp b/src/daemon/main.cpp index 44d2dae43..acc23b9f9 100644 --- a/src/daemon/main.cpp +++ b/src/daemon/main.cpp @@ -88,7 +88,11 @@ int main(int argc, char const * argv[]) bf::path default_log = default_data_dir / std::string(CRYPTONOTE_NAME ".log"); command_line::add_arg(core_settings, daemon_args::arg_log_file, default_log.string()); command_line::add_arg(core_settings, daemon_args::arg_log_level); + command_line::add_arg(core_settings, daemon_args::arg_max_log_file_size); command_line::add_arg(core_settings, daemon_args::arg_max_concurrency); + command_line::add_arg(core_settings, daemon_args::arg_zmq_rpc_bind_ip); + command_line::add_arg(core_settings, daemon_args::arg_zmq_rpc_bind_port); + command_line::add_arg(core_settings, daemon_args::arg_zmq_testnet_rpc_bind_port); daemonizer::init_options(hidden_options, visible_options); daemonize::t_executor::init_options(core_settings); @@ -204,7 +208,7 @@ int main(int argc, char const * argv[]) if (! vm["log-file"].defaulted()) log_file_path = command_line::get_arg(vm, daemon_args::arg_log_file); log_file_path = bf::absolute(log_file_path, relative_path_base); - mlog_configure(log_file_path.string(), true); + mlog_configure(log_file_path.string(), true, command_line::get_arg(vm, daemon_args::arg_max_log_file_size)); // Set log level if (!vm["log-level"].defaulted()) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 3d6a01cd1..be1ed20ba 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -420,16 +420,17 @@ bool t_rpc_command_executor::show_status() { } std::time_t uptime = std::time(nullptr) - ires.start_time; + uint64_t net_height = ires.target_height > ires.height ? ires.target_height : ires.height; tools::success_msg_writer() << boost::format("Height: %llu/%llu (%.1f%%) on %s, %s, net hash %s, v%u%s, %s, %u(out)+%u(in) connections, uptime %ud %uh %um %us") % (unsigned long long)ires.height - % (unsigned long long)(ires.target_height >= ires.height ? ires.target_height : ires.height) + % (unsigned long long)net_height % get_sync_percentage(ires) % (ires.testnet ? "testnet" : "mainnet") % (!has_mining_info ? "mining info unavailable" : mining_busy ? "syncing" : mres.active ? ( ( mres.is_background_mining_enabled ? "smart " : "" ) + std::string("mining at ") + get_mining_speed(mres.speed) ) : "not mining") % get_mining_speed(ires.difficulty / ires.target) % (unsigned)hfres.version - % get_fork_extra_info(hfres.earliest_height, ires.height, ires.target) + % get_fork_extra_info(hfres.earliest_height, net_height, ires.target) % (hfres.state == cryptonote::HardFork::Ready ? "up to date" : hfres.state == cryptonote::HardFork::UpdateNeeded ? "update needed" : "out of date, likely forked") % (unsigned)ires.outgoing_connections_count % (unsigned)ires.incoming_connections_count @@ -598,7 +599,7 @@ bool t_rpc_command_executor::set_log_categories(const std::string &categories) { } } - tools::success_msg_writer() << "Log categories are now " << categories; + tools::success_msg_writer() << "Log categories are now " << res.categories; return true; } @@ -699,6 +700,7 @@ bool t_rpc_command_executor::print_transaction(crypto::hash transaction_hash) { std::string fail_message = "Problem fetching transaction"; req.txs_hashes.push_back(epee::string_tools::pod_to_hex(transaction_hash)); + req.decode_as_json = false; if (m_is_rpc) { if (!m_rpc_client->rpc_request(req, res, "/gettransactions", fail_message.c_str())) @@ -781,7 +783,7 @@ bool t_rpc_command_executor::is_key_image_spent(const crypto::key_image &ki) { if (1 == res.spent_status.size()) { // first as hex - tools::success_msg_writer() << ki << ": " << (res.spent_status.front() ? "spent" : "unspent"); + tools::success_msg_writer() << ki << ": " << (res.spent_status.front() ? "spent" : "unspent") << (res.spent_status.front() == cryptonote::COMMAND_RPC_IS_KEY_IMAGE_SPENT::SPENT_IN_POOL ? " (in pool)" : ""); } else { @@ -1012,7 +1014,7 @@ bool t_rpc_command_executor::print_transaction_pool_stats() { bool t_rpc_command_executor::start_mining(cryptonote::account_public_address address, uint64_t num_threads, bool testnet, bool do_background_mining, bool ignore_battery) { cryptonote::COMMAND_RPC_START_MINING::request req; cryptonote::COMMAND_RPC_START_MINING::response res; - req.miner_address = cryptonote::get_account_address_as_str(testnet, address); + req.miner_address = cryptonote::get_account_address_as_str(testnet, false, address); req.threads_count = num_threads; req.do_background_mining = do_background_mining; req.ignore_battery = ignore_battery; @@ -1128,48 +1130,115 @@ bool t_rpc_command_executor::print_status() bool t_rpc_command_executor::get_limit() { - int limit_down = epee::net_utils::connection_basic::get_rate_down_limit( ); - int limit_up = epee::net_utils::connection_basic::get_rate_up_limit( ); - std::cout << "limit-down is " << limit_down/1024 << " kB/s" << std::endl; - std::cout << "limit-up is " << limit_up/1024 << " kB/s" << std::endl; - return true; + cryptonote::COMMAND_RPC_GET_LIMIT::request req; + cryptonote::COMMAND_RPC_GET_LIMIT::response res; + + std::string failure_message = "Couldn't get limit"; + + if (m_is_rpc) + { + if (!m_rpc_client->rpc_request(req, res, "/get_limit", failure_message.c_str())) + { + return true; + } + } + else + { + if (!m_rpc_server->on_get_limit(req, res) || res.status != CORE_RPC_STATUS_OK) + { + tools::fail_msg_writer() << make_error(failure_message, res.status); + return true; + } + } + + tools::msg_writer() << "limit-down is " << res.limit_down/1024 << " kB/s"; + tools::msg_writer() << "limit-up is " << res.limit_up/1024 << " kB/s"; + return true; } -bool t_rpc_command_executor::set_limit(int limit) +bool t_rpc_command_executor::set_limit(int64_t limit_down, int64_t limit_up) { - epee::net_utils::connection_basic::set_rate_down_limit( limit ); - epee::net_utils::connection_basic::set_rate_up_limit( limit ); - std::cout << "Set limit-down to " << limit/1024 << " kB/s" << std::endl; - std::cout << "Set limit-up to " << limit/1024 << " kB/s" << std::endl; - return true; + cryptonote::COMMAND_RPC_SET_LIMIT::request req; + cryptonote::COMMAND_RPC_SET_LIMIT::response res; + + req.limit_down = limit_down; + req.limit_up = limit_up; + + std::string failure_message = "Couldn't set limit"; + + if (m_is_rpc) + { + if (!m_rpc_client->rpc_request(req, res, "/set_limit", failure_message.c_str())) + { + return true; + } + } + else + { + if (!m_rpc_server->on_set_limit(req, res) || res.status != CORE_RPC_STATUS_OK) + { + tools::fail_msg_writer() << make_error(failure_message, res.status); + return true; + } + } + + tools::msg_writer() << "Set limit-down to " << res.limit_down/1024 << " kB/s"; + tools::msg_writer() << "Set limit-up to " << res.limit_up/1024 << " kB/s"; + return true; } bool t_rpc_command_executor::get_limit_up() { - int limit_up = epee::net_utils::connection_basic::get_rate_up_limit( ); - std::cout << "limit-up is " << limit_up/1024 << " kB/s" << std::endl; - return true; -} + cryptonote::COMMAND_RPC_GET_LIMIT::request req; + cryptonote::COMMAND_RPC_GET_LIMIT::response res; -bool t_rpc_command_executor::set_limit_up(int limit) -{ - epee::net_utils::connection_basic::set_rate_up_limit( limit ); - std::cout << "Set limit-up to " << limit/1024 << " kB/s" << std::endl; - return true; + std::string failure_message = "Couldn't get limit"; + + if (m_is_rpc) + { + if (!m_rpc_client->rpc_request(req, res, "/get_limit", failure_message.c_str())) + { + return true; + } + } + else + { + if (!m_rpc_server->on_get_limit(req, res) || res.status != CORE_RPC_STATUS_OK) + { + tools::fail_msg_writer() << make_error(failure_message, res.status); + return true; + } + } + + tools::msg_writer() << "limit-up is " << res.limit_up/1024 << " kB/s"; + return true; } bool t_rpc_command_executor::get_limit_down() { - int limit_down = epee::net_utils::connection_basic::get_rate_down_limit( ); - std::cout << "limit-down is " << limit_down/1024 << " kB/s" << std::endl; - return true; -} + cryptonote::COMMAND_RPC_GET_LIMIT::request req; + cryptonote::COMMAND_RPC_GET_LIMIT::response res; -bool t_rpc_command_executor::set_limit_down(int limit) -{ - epee::net_utils::connection_basic::set_rate_down_limit( limit ); - std::cout << "Set limit-down to " << limit/1024 << " kB/s" << std::endl; - return true; + std::string failure_message = "Couldn't get limit"; + + if (m_is_rpc) + { + if (!m_rpc_client->rpc_request(req, res, "/get_limit", failure_message.c_str())) + { + return true; + } + } + else + { + if (!m_rpc_server->on_get_limit(req, res) || res.status != CORE_RPC_STATUS_OK) + { + tools::fail_msg_writer() << make_error(failure_message, res.status); + return true; + } + } + + tools::msg_writer() << "limit-down is " << res.limit_down/1024 << " kB/s"; + return true; } bool t_rpc_command_executor::out_peers(uint64_t limit) diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h index fc0b39654..d79707a6f 100644 --- a/src/daemon/rpc_command_executor.h +++ b/src/daemon/rpc_command_executor.h @@ -119,11 +119,7 @@ public: bool get_limit_down(); - bool set_limit(int limit); - - bool set_limit_up(int limit); - - bool set_limit_down(int limit); + bool set_limit(int64_t limit_down, int64_t limit_up); bool out_peers(uint64_t limit); diff --git a/src/debug_utilities/CMakeLists.txt b/src/debug_utilities/CMakeLists.txt index 99198dc57..7a82c12d9 100644 --- a/src/debug_utilities/CMakeLists.txt +++ b/src/debug_utilities/CMakeLists.txt @@ -42,8 +42,6 @@ target_link_libraries(cn_deserialize epee ${CMAKE_THREAD_LIBS_INIT}) -add_dependencies(cn_deserialize - version) set_property(TARGET cn_deserialize PROPERTY OUTPUT_NAME "monero-utils-deserialize") @@ -65,8 +63,6 @@ target_link_libraries(object_sizes epee ${CMAKE_THREAD_LIBS_INIT}) -add_dependencies(object_sizes - version) set_property(TARGET object_sizes PROPERTY OUTPUT_NAME "monero-utils-object-sizes") diff --git a/src/mnemonics/CMakeLists.txt b/src/mnemonics/CMakeLists.txt index 942b6eca3..5ce2198ae 100644 --- a/src/mnemonics/CMakeLists.txt +++ b/src/mnemonics/CMakeLists.txt @@ -46,7 +46,8 @@ set(mnemonics_private_headers russian.h singleton.h spanish.h - esperanto.h) + esperanto.h + lojban.h) monero_private_headers(mnemonics ${mnemonics_private_headers}) diff --git a/src/mnemonics/electrum-words.cpp b/src/mnemonics/electrum-words.cpp index 2fe5d9985..6ae33a743 100644 --- a/src/mnemonics/electrum-words.cpp +++ b/src/mnemonics/electrum-words.cpp @@ -62,6 +62,7 @@ #include "japanese.h" #include "russian.h" #include "esperanto.h" +#include "lojban.h" #include "english_old.h" #include "language_base.h" #include "singleton.h" @@ -97,6 +98,7 @@ namespace Language::Singleton<Language::Japanese>::instance(), Language::Singleton<Language::Russian>::instance(), Language::Singleton<Language::Esperanto>::instance(), + Language::Singleton<Language::Lojban>::instance(), Language::Singleton<Language::EnglishOld>::instance() }); Language::Base *fallback = NULL; @@ -360,6 +362,10 @@ namespace crypto { language = Language::Singleton<Language::Esperanto>::instance(); } + else if (language_name == "Lojban") + { + language = Language::Singleton<Language::Lojban>::instance(); + } else { return false; @@ -415,7 +421,8 @@ namespace crypto Language::Singleton<Language::Russian>::instance(), Language::Singleton<Language::Japanese>::instance(), Language::Singleton<Language::Chinese_Simplified>::instance(), - Language::Singleton<Language::Esperanto>::instance() + Language::Singleton<Language::Esperanto>::instance(), + Language::Singleton<Language::Lojban>::instance() }); for (std::vector<Language::Base*>::iterator it = language_instances.begin(); it != language_instances.end(); it++) diff --git a/src/mnemonics/lojban.h b/src/mnemonics/lojban.h new file mode 100644 index 000000000..8ea9510a3 --- /dev/null +++ b/src/mnemonics/lojban.h @@ -0,0 +1,1693 @@ +// Copyright (c) 2014-2017, 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.
+
+/*!
+ * \file lojban.h
+ *
+ * \brief New Lojban word list and map.
+ */
+
+/*
+ * Word list authored by: sorpaas
+ * Sources:
+ * lo gimste jo'u lo ma'oste (http://guskant.github.io/lojbo/gismu-cmavo.html)
+ * N-grams of Lojban corpus (https://mw.lojban.org/papri/N-grams_of_Lojban_corpus)
+ */
+
+#ifndef LOJBAN_H
+#define LOJBAN_H
+
+#include <vector>
+#include <unordered_map>
+#include "language_base.h"
+#include <string>
+
+/*!
+ * \namespace Language
+ * \brief Mnemonic language related namespace.
+ */
+namespace Language
+{
+ class Lojban: public Base
+ {
+ public:
+ Lojban(): Base("Lojban", std::vector<std::string>({
+ "backi",
+ "bacru",
+ "badna",
+ "badri",
+ "bajra",
+ "bakfu",
+ "bakni",
+ "bakri",
+ "baktu",
+ "balji",
+ "balni",
+ "balre",
+ "balvi",
+ "bambu",
+ "bancu",
+ "bandu",
+ "banfi",
+ "bangu",
+ "banli",
+ "banro",
+ "banxa",
+ "banzu",
+ "bapli",
+ "barda",
+ "bargu",
+ "barja",
+ "barna",
+ "bartu",
+ "basfa",
+ "basna",
+ "basti",
+ "batci",
+ "batke",
+ "bavmi",
+ "baxso",
+ "bebna",
+ "bekpi",
+ "bemro",
+ "bende",
+ "bengo",
+ "benji",
+ "benre",
+ "benzo",
+ "bergu",
+ "bersa",
+ "berti",
+ "besna",
+ "besto",
+ "betfu",
+ "betri",
+ "bevri",
+ "bidju",
+ "bifce",
+ "bikla",
+ "bilga",
+ "bilma",
+ "bilni",
+ "bindo",
+ "binra",
+ "binxo",
+ "birje",
+ "birka",
+ "birti",
+ "bisli",
+ "bitmu",
+ "bitni",
+ "blabi",
+ "blaci",
+ "blanu",
+ "bliku",
+ "bloti",
+ "bolci",
+ "bongu",
+ "boske",
+ "botpi",
+ "boxfo",
+ "boxna",
+ "bradi",
+ "brano",
+ "bratu",
+ "brazo",
+ "bredi",
+ "bridi",
+ "brife",
+ "briju",
+ "brito",
+ "brivo",
+ "broda",
+ "bruna",
+ "budjo",
+ "bukpu",
+ "bumru",
+ "bunda",
+ "bunre",
+ "burcu",
+ "burna",
+ "cabna",
+ "cabra",
+ "cacra",
+ "cadga",
+ "cadzu",
+ "cafne",
+ "cagna",
+ "cakla",
+ "calku",
+ "calse",
+ "canci",
+ "cando",
+ "cange",
+ "canja",
+ "canko",
+ "canlu",
+ "canpa",
+ "canre",
+ "canti",
+ "carce",
+ "carfu",
+ "carmi",
+ "carna",
+ "cartu",
+ "carvi",
+ "casnu",
+ "catke",
+ "catlu",
+ "catni",
+ "catra",
+ "caxno",
+ "cecla",
+ "cecmu",
+ "cedra",
+ "cenba",
+ "censa",
+ "centi",
+ "cerda",
+ "cerni",
+ "certu",
+ "cevni",
+ "cfale",
+ "cfari",
+ "cfika",
+ "cfila",
+ "cfine",
+ "cfipu",
+ "ciblu",
+ "cicna",
+ "cidja",
+ "cidni",
+ "cidro",
+ "cifnu",
+ "cigla",
+ "cikna",
+ "cikre",
+ "ciksi",
+ "cilce",
+ "cilfu",
+ "cilmo",
+ "cilre",
+ "cilta",
+ "cimde",
+ "cimni",
+ "cinba",
+ "cindu",
+ "cinfo",
+ "cinje",
+ "cinki",
+ "cinla",
+ "cinmo",
+ "cinri",
+ "cinse",
+ "cinta",
+ "cinza",
+ "cipni",
+ "cipra",
+ "cirko",
+ "cirla",
+ "ciska",
+ "cisma",
+ "cisni",
+ "ciste",
+ "citka",
+ "citno",
+ "citri",
+ "citsi",
+ "civla",
+ "cizra",
+ "ckabu",
+ "ckafi",
+ "ckaji",
+ "ckana",
+ "ckape",
+ "ckasu",
+ "ckeji",
+ "ckiku",
+ "ckilu",
+ "ckini",
+ "ckire",
+ "ckule",
+ "ckunu",
+ "cladu",
+ "clani",
+ "claxu",
+ "cletu",
+ "clika",
+ "clinu",
+ "clira",
+ "clite",
+ "cliva",
+ "clupa",
+ "cmaci",
+ "cmalu",
+ "cmana",
+ "cmavo",
+ "cmene",
+ "cmeta",
+ "cmevo",
+ "cmila",
+ "cmima",
+ "cmoni",
+ "cnano",
+ "cnebo",
+ "cnemu",
+ "cnici",
+ "cnino",
+ "cnisa",
+ "cnita",
+ "cokcu",
+ "condi",
+ "conka",
+ "corci",
+ "cortu",
+ "cpacu",
+ "cpana",
+ "cpare",
+ "cpedu",
+ "cpina",
+ "cradi",
+ "crane",
+ "creka",
+ "crepu",
+ "cribe",
+ "crida",
+ "crino",
+ "cripu",
+ "crisa",
+ "critu",
+ "ctaru",
+ "ctebi",
+ "cteki",
+ "ctile",
+ "ctino",
+ "ctuca",
+ "cukla",
+ "cukre",
+ "cukta",
+ "culno",
+ "cumki",
+ "cumla",
+ "cunmi",
+ "cunso",
+ "cuntu",
+ "cupra",
+ "curmi",
+ "curnu",
+ "curve",
+ "cusku",
+ "cusna",
+ "cutci",
+ "cutne",
+ "cuxna",
+ "dacru",
+ "dacti",
+ "dadjo",
+ "dakfu",
+ "dakli",
+ "damba",
+ "damri",
+ "dandu",
+ "danfu",
+ "danlu",
+ "danmo",
+ "danre",
+ "dansu",
+ "danti",
+ "daplu",
+ "dapma",
+ "darca",
+ "dargu",
+ "darlu",
+ "darno",
+ "darsi",
+ "darxi",
+ "daski",
+ "dasni",
+ "daspo",
+ "dasri",
+ "datka",
+ "datni",
+ "datro",
+ "decti",
+ "degji",
+ "dejni",
+ "dekpu",
+ "dekto",
+ "delno",
+ "dembi",
+ "denci",
+ "denmi",
+ "denpa",
+ "dertu",
+ "derxi",
+ "desku",
+ "detri",
+ "dicma",
+ "dicra",
+ "didni",
+ "digno",
+ "dikca",
+ "diklo",
+ "dikni",
+ "dilcu",
+ "dilma",
+ "dilnu",
+ "dimna",
+ "dindi",
+ "dinju",
+ "dinko",
+ "dinso",
+ "dirba",
+ "dirce",
+ "dirgo",
+ "disko",
+ "ditcu",
+ "divzi",
+ "dizlo",
+ "djacu",
+ "djedi",
+ "djica",
+ "djine",
+ "djuno",
+ "donri",
+ "dotco",
+ "draci",
+ "drani",
+ "drata",
+ "drudi",
+ "dugri",
+ "dukse",
+ "dukti",
+ "dunda",
+ "dunja",
+ "dunku",
+ "dunli",
+ "dunra",
+ "dutso",
+ "dzena",
+ "dzipo",
+ "facki",
+ "fadni",
+ "fagri",
+ "falnu",
+ "famti",
+ "fancu",
+ "fange",
+ "fanmo",
+ "fanri",
+ "fanta",
+ "fanva",
+ "fanza",
+ "fapro",
+ "farka",
+ "farlu",
+ "farna",
+ "farvi",
+ "fasnu",
+ "fatci",
+ "fatne",
+ "fatri",
+ "febvi",
+ "fegli",
+ "femti",
+ "fendi",
+ "fengu",
+ "fenki",
+ "fenra",
+ "fenso",
+ "fepni",
+ "fepri",
+ "ferti",
+ "festi",
+ "fetsi",
+ "figre",
+ "filso",
+ "finpe",
+ "finti",
+ "firca",
+ "fisli",
+ "fizbu",
+ "flaci",
+ "flalu",
+ "flani",
+ "flecu",
+ "flese",
+ "fliba",
+ "flira",
+ "foldi",
+ "fonmo",
+ "fonxa",
+ "forca",
+ "forse",
+ "fraso",
+ "frati",
+ "fraxu",
+ "frica",
+ "friko",
+ "frili",
+ "frinu",
+ "friti",
+ "frumu",
+ "fukpi",
+ "fulta",
+ "funca",
+ "fusra",
+ "fuzme",
+ "gacri",
+ "gadri",
+ "galfi",
+ "galtu",
+ "galxe",
+ "ganlo",
+ "ganra",
+ "ganse",
+ "ganti",
+ "ganxo",
+ "ganzu",
+ "gapci",
+ "gapru",
+ "garna",
+ "gasnu",
+ "gaspo",
+ "gasta",
+ "genja",
+ "gento",
+ "genxu",
+ "gerku",
+ "gerna",
+ "gidva",
+ "gigdo",
+ "ginka",
+ "girzu",
+ "gismu",
+ "glare",
+ "gleki",
+ "gletu",
+ "glico",
+ "glife",
+ "glosa",
+ "gluta",
+ "gocti",
+ "gomsi",
+ "gotro",
+ "gradu",
+ "grafu",
+ "grake",
+ "grana",
+ "grasu",
+ "grava",
+ "greku",
+ "grusi",
+ "grute",
+ "gubni",
+ "gugde",
+ "gugle",
+ "gumri",
+ "gundi",
+ "gunka",
+ "gunma",
+ "gunro",
+ "gunse",
+ "gunta",
+ "gurni",
+ "guska",
+ "gusni",
+ "gusta",
+ "gutci",
+ "gutra",
+ "guzme",
+ "jabre",
+ "jadni",
+ "jakne",
+ "jalge",
+ "jalna",
+ "jalra",
+ "jamfu",
+ "jamna",
+ "janbe",
+ "janco",
+ "janli",
+ "jansu",
+ "janta",
+ "jarbu",
+ "jarco",
+ "jarki",
+ "jaspu",
+ "jatna",
+ "javni",
+ "jbama",
+ "jbari",
+ "jbena",
+ "jbera",
+ "jbini",
+ "jdari",
+ "jdice",
+ "jdika",
+ "jdima",
+ "jdini",
+ "jduli",
+ "jecta",
+ "jeftu",
+ "jegvo",
+ "jelca",
+ "jemna",
+ "jenca",
+ "jendu",
+ "jenmi",
+ "jensi",
+ "jerna",
+ "jersi",
+ "jerxo",
+ "jesni",
+ "jetce",
+ "jetnu",
+ "jgalu",
+ "jganu",
+ "jgari",
+ "jgena",
+ "jgina",
+ "jgira",
+ "jgita",
+ "jibni",
+ "jibri",
+ "jicla",
+ "jicmu",
+ "jijnu",
+ "jikca",
+ "jikfi",
+ "jikni",
+ "jikru",
+ "jilka",
+ "jilra",
+ "jimca",
+ "jimpe",
+ "jimte",
+ "jinci",
+ "jinda",
+ "jinga",
+ "jinku",
+ "jinme",
+ "jinru",
+ "jinsa",
+ "jinto",
+ "jinvi",
+ "jinzi",
+ "jipci",
+ "jipno",
+ "jirna",
+ "jisra",
+ "jitfa",
+ "jitro",
+ "jivbu",
+ "jivna",
+ "jmaji",
+ "jmifa",
+ "jmina",
+ "jmive",
+ "jonse",
+ "jordo",
+ "jorne",
+ "jubme",
+ "judri",
+ "jufra",
+ "jukni",
+ "jukpa",
+ "julne",
+ "julro",
+ "jundi",
+ "jungo",
+ "junla",
+ "junri",
+ "junta",
+ "jurme",
+ "jursa",
+ "jutsi",
+ "juxre",
+ "jvinu",
+ "jviso",
+ "kabri",
+ "kacma",
+ "kadno",
+ "kafke",
+ "kagni",
+ "kajde",
+ "kajna",
+ "kakne",
+ "kakpa",
+ "kalci",
+ "kalri",
+ "kalsa",
+ "kalte",
+ "kamju",
+ "kamni",
+ "kampu",
+ "kamre",
+ "kanba",
+ "kancu",
+ "kandi",
+ "kanji",
+ "kanla",
+ "kanpe",
+ "kanro",
+ "kansa",
+ "kantu",
+ "kanxe",
+ "karbi",
+ "karce",
+ "karda",
+ "kargu",
+ "karli",
+ "karni",
+ "katci",
+ "katna",
+ "kavbu",
+ "kazra",
+ "kecti",
+ "kekli",
+ "kelci",
+ "kelvo",
+ "kenka",
+ "kenra",
+ "kensa",
+ "kerfa",
+ "kerlo",
+ "kesri",
+ "ketco",
+ "ketsu",
+ "kevna",
+ "kibro",
+ "kicne",
+ "kijno",
+ "kilto",
+ "kinda",
+ "kinli",
+ "kisto",
+ "klaji",
+ "klaku",
+ "klama",
+ "klani",
+ "klesi",
+ "kliki",
+ "klina",
+ "kliru",
+ "kliti",
+ "klupe",
+ "kluza",
+ "kobli",
+ "kogno",
+ "kojna",
+ "kokso",
+ "kolme",
+ "komcu",
+ "konju",
+ "korbi",
+ "korcu",
+ "korka",
+ "korvo",
+ "kosmu",
+ "kosta",
+ "krali",
+ "kramu",
+ "krasi",
+ "krati",
+ "krefu",
+ "krici",
+ "krili",
+ "krinu",
+ "krixa",
+ "kruca",
+ "kruji",
+ "kruvi",
+ "kubli",
+ "kucli",
+ "kufra",
+ "kukte",
+ "kulnu",
+ "kumfa",
+ "kumte",
+ "kunra",
+ "kunti",
+ "kurfa",
+ "kurji",
+ "kurki",
+ "kuspe",
+ "kusru",
+ "labno",
+ "lacni",
+ "lacpu",
+ "lacri",
+ "ladru",
+ "lafti",
+ "lakne",
+ "lakse",
+ "laldo",
+ "lalxu",
+ "lamji",
+ "lanbi",
+ "lanci",
+ "landa",
+ "lanka",
+ "lanli",
+ "lanme",
+ "lante",
+ "lanxe",
+ "lanzu",
+ "larcu",
+ "larva",
+ "lasna",
+ "lastu",
+ "latmo",
+ "latna",
+ "lazni",
+ "lebna",
+ "lelxe",
+ "lenga",
+ "lenjo",
+ "lenku",
+ "lerci",
+ "lerfu",
+ "libjo",
+ "lidne",
+ "lifri",
+ "lijda",
+ "limfa",
+ "limna",
+ "lince",
+ "lindi",
+ "linga",
+ "linji",
+ "linsi",
+ "linto",
+ "lisri",
+ "liste",
+ "litce",
+ "litki",
+ "litru",
+ "livga",
+ "livla",
+ "logji",
+ "loglo",
+ "lojbo",
+ "loldi",
+ "lorxu",
+ "lubno",
+ "lujvo",
+ "luksi",
+ "lumci",
+ "lunbe",
+ "lunra",
+ "lunsa",
+ "luska",
+ "lusto",
+ "mabla",
+ "mabru",
+ "macnu",
+ "majga",
+ "makcu",
+ "makfa",
+ "maksi",
+ "malsi",
+ "mamta",
+ "manci",
+ "manfo",
+ "mango",
+ "manku",
+ "manri",
+ "mansa",
+ "manti",
+ "mapku",
+ "mapni",
+ "mapra",
+ "mapti",
+ "marbi",
+ "marce",
+ "marde",
+ "margu",
+ "marji",
+ "marna",
+ "marxa",
+ "masno",
+ "masti",
+ "matci",
+ "matli",
+ "matne",
+ "matra",
+ "mavji",
+ "maxri",
+ "mebri",
+ "megdo",
+ "mekso",
+ "melbi",
+ "meljo",
+ "melmi",
+ "menli",
+ "menre",
+ "mensi",
+ "mentu",
+ "merko",
+ "merli",
+ "metfo",
+ "mexno",
+ "midju",
+ "mifra",
+ "mikce",
+ "mikri",
+ "milti",
+ "milxe",
+ "minde",
+ "minji",
+ "minli",
+ "minra",
+ "mintu",
+ "mipri",
+ "mirli",
+ "misno",
+ "misro",
+ "mitre",
+ "mixre",
+ "mlana",
+ "mlatu",
+ "mleca",
+ "mledi",
+ "mluni",
+ "mogle",
+ "mokca",
+ "moklu",
+ "molki",
+ "molro",
+ "morji",
+ "morko",
+ "morna",
+ "morsi",
+ "mosra",
+ "mraji",
+ "mrilu",
+ "mruli",
+ "mucti",
+ "mudri",
+ "mugle",
+ "mukti",
+ "mulno",
+ "munje",
+ "mupli",
+ "murse",
+ "murta",
+ "muslo",
+ "mutce",
+ "muvdu",
+ "muzga",
+ "nabmi",
+ "nakni",
+ "nalci",
+ "namcu",
+ "nanba",
+ "nanca",
+ "nandu",
+ "nanla",
+ "nanmu",
+ "nanvi",
+ "narge",
+ "narju",
+ "natfe",
+ "natmi",
+ "natsi",
+ "navni",
+ "naxle",
+ "nazbi",
+ "nejni",
+ "nelci",
+ "nenri",
+ "nerde",
+ "nibli",
+ "nicfa",
+ "nicte",
+ "nikle",
+ "nilce",
+ "nimre",
+ "ninja",
+ "ninmu",
+ "nirna",
+ "nitcu",
+ "nivji",
+ "nixli",
+ "nobli",
+ "norgo",
+ "notci",
+ "nudle",
+ "nukni",
+ "nunmu",
+ "nupre",
+ "nurma",
+ "nusna",
+ "nutka",
+ "nutli",
+ "nuzba",
+ "nuzlo",
+ "pacna",
+ "pagbu",
+ "pagre",
+ "pajni",
+ "palci",
+ "palku",
+ "palma",
+ "palne",
+ "palpi",
+ "palta",
+ "pambe",
+ "pamga",
+ "panci",
+ "pandi",
+ "panje",
+ "panka",
+ "panlo",
+ "panpi",
+ "panra",
+ "pante",
+ "panzi",
+ "papri",
+ "parbi",
+ "pardu",
+ "parji",
+ "pastu",
+ "patfu",
+ "patlu",
+ "patxu",
+ "paznu",
+ "pelji",
+ "pelxu",
+ "pemci",
+ "penbi",
+ "pencu",
+ "pendo",
+ "penmi",
+ "pensi",
+ "pentu",
+ "perli",
+ "pesxu",
+ "petso",
+ "pevna",
+ "pezli",
+ "picti",
+ "pijne",
+ "pikci",
+ "pikta",
+ "pilda",
+ "pilji",
+ "pilka",
+ "pilno",
+ "pimlu",
+ "pinca",
+ "pindi",
+ "pinfu",
+ "pinji",
+ "pinka",
+ "pinsi",
+ "pinta",
+ "pinxe",
+ "pipno",
+ "pixra",
+ "plana",
+ "platu",
+ "pleji",
+ "plibu",
+ "plini",
+ "plipe",
+ "plise",
+ "plita",
+ "plixa",
+ "pluja",
+ "pluka",
+ "pluta",
+ "pocli",
+ "polje",
+ "polno",
+ "ponjo",
+ "ponse",
+ "poplu",
+ "porpi",
+ "porsi",
+ "porto",
+ "prali",
+ "prami",
+ "prane",
+ "preja",
+ "prenu",
+ "preri",
+ "preti",
+ "prije",
+ "prina",
+ "pritu",
+ "proga",
+ "prosa",
+ "pruce",
+ "pruni",
+ "pruri",
+ "pruxi",
+ "pulce",
+ "pulji",
+ "pulni",
+ "punji",
+ "punli",
+ "pupsu",
+ "purci",
+ "purdi",
+ "purmo",
+ "racli",
+ "ractu",
+ "radno",
+ "rafsi",
+ "ragbi",
+ "ragve",
+ "rakle",
+ "rakso",
+ "raktu",
+ "ralci",
+ "ralju",
+ "ralte",
+ "randa",
+ "rango",
+ "ranji",
+ "ranmi",
+ "ransu",
+ "ranti",
+ "ranxi",
+ "rapli",
+ "rarna",
+ "ratcu",
+ "ratni",
+ "rebla",
+ "rectu",
+ "rekto",
+ "remna",
+ "renro",
+ "renvi",
+ "respa",
+ "rexsa",
+ "ricfu",
+ "rigni",
+ "rijno",
+ "rilti",
+ "rimni",
+ "rinci",
+ "rindo",
+ "rinju",
+ "rinka",
+ "rinsa",
+ "rirci",
+ "rirni",
+ "rirxe",
+ "rismi",
+ "risna",
+ "ritli",
+ "rivbi",
+ "rokci",
+ "romge",
+ "romlo",
+ "ronte",
+ "ropno",
+ "rorci",
+ "rotsu",
+ "rozgu",
+ "ruble",
+ "rufsu",
+ "runme",
+ "runta",
+ "rupnu",
+ "rusko",
+ "rutni",
+ "sabji",
+ "sabnu",
+ "sacki",
+ "saclu",
+ "sadjo",
+ "sakci",
+ "sakli",
+ "sakta",
+ "salci",
+ "salpo",
+ "salri",
+ "salta",
+ "samcu",
+ "sampu",
+ "sanbu",
+ "sance",
+ "sanga",
+ "sanji",
+ "sanli",
+ "sanmi",
+ "sanso",
+ "santa",
+ "sarcu",
+ "sarji",
+ "sarlu",
+ "sarni",
+ "sarxe",
+ "saske",
+ "satci",
+ "satre",
+ "savru",
+ "sazri",
+ "sefsi",
+ "sefta",
+ "sekre",
+ "selci",
+ "selfu",
+ "semto",
+ "senci",
+ "sengi",
+ "senpi",
+ "senta",
+ "senva",
+ "sepli",
+ "serti",
+ "sesre",
+ "setca",
+ "sevzi",
+ "sfani",
+ "sfasa",
+ "sfofa",
+ "sfubu",
+ "sibli",
+ "siclu",
+ "sicni",
+ "sicpi",
+ "sidbo",
+ "sidju",
+ "sigja",
+ "sigma",
+ "sikta",
+ "silka",
+ "silna",
+ "simlu",
+ "simsa",
+ "simxu",
+ "since",
+ "sinma",
+ "sinso",
+ "sinxa",
+ "sipna",
+ "sirji",
+ "sirxo",
+ "sisku",
+ "sisti",
+ "sitna",
+ "sivni",
+ "skaci",
+ "skami",
+ "skapi",
+ "skari",
+ "skicu",
+ "skiji",
+ "skina",
+ "skori",
+ "skoto",
+ "skuba",
+ "skuro",
+ "slabu",
+ "slaka",
+ "slami",
+ "slanu",
+ "slari",
+ "slasi",
+ "sligu",
+ "slilu",
+ "sliri",
+ "slovo",
+ "sluji",
+ "sluni",
+ "smacu",
+ "smadi",
+ "smaji",
+ "smaka",
+ "smani",
+ "smela",
+ "smoka",
+ "smuci",
+ "smuni",
+ "smusu",
+ "snada",
+ "snanu",
+ "snidu",
+ "snime",
+ "snipa",
+ "snuji",
+ "snura",
+ "snuti",
+ "sobde",
+ "sodna",
+ "sodva",
+ "softo",
+ "solji",
+ "solri",
+ "sombo",
+ "sonci",
+ "sorcu",
+ "sorgu",
+ "sorni",
+ "sorta",
+ "sovda",
+ "spaji",
+ "spali",
+ "spano",
+ "spati",
+ "speni",
+ "spero",
+ "spisa",
+ "spita",
+ "spofu",
+ "spoja",
+ "spuda",
+ "sputu",
+ "sraji",
+ "sraku",
+ "sralo",
+ "srana",
+ "srasu",
+ "srera",
+ "srito",
+ "sruma",
+ "sruri",
+ "stace",
+ "stagi",
+ "staku",
+ "stali",
+ "stani",
+ "stapa",
+ "stasu",
+ "stati",
+ "steba",
+ "steci",
+ "stedu",
+ "stela",
+ "stero",
+ "stici",
+ "stidi",
+ "stika",
+ "stizu",
+ "stodi",
+ "stuna",
+ "stura",
+ "stuzi",
+ "sucta",
+ "sudga",
+ "sufti",
+ "suksa",
+ "sumji",
+ "sumne",
+ "sumti",
+ "sunga",
+ "sunla",
+ "surla",
+ "sutra",
+ "tabno",
+ "tabra",
+ "tadji",
+ "tadni",
+ "tagji",
+ "taksi",
+ "talsa",
+ "tamca",
+ "tamji",
+ "tamne",
+ "tanbo",
+ "tance",
+ "tanjo",
+ "tanko",
+ "tanru",
+ "tansi",
+ "tanxe",
+ "tapla",
+ "tarbi",
+ "tarci",
+ "tarla",
+ "tarmi",
+ "tarti",
+ "taske",
+ "tasmi",
+ "tasta",
+ "tatpi",
+ "tatru",
+ "tavla",
+ "taxfu",
+ "tcaci",
+ "tcadu",
+ "tcana",
+ "tcati",
+ "tcaxe",
+ "tcena",
+ "tcese",
+ "tcica",
+ "tcidu",
+ "tcika",
+ "tcila",
+ "tcima",
+ "tcini",
+ "tcita",
+ "temci",
+ "temse",
+ "tende",
+ "tenfa",
+ "tengu",
+ "terdi",
+ "terpa",
+ "terto",
+ "tifri",
+ "tigni",
+ "tigra",
+ "tikpa",
+ "tilju",
+ "tinbe",
+ "tinci",
+ "tinsa",
+ "tirna",
+ "tirse",
+ "tirxu",
+ "tisna",
+ "titla",
+ "tivni",
+ "tixnu",
+ "toknu",
+ "toldi",
+ "tonga",
+ "tordu",
+ "torni",
+ "torso",
+ "traji",
+ "trano",
+ "trati",
+ "trene",
+ "tricu",
+ "trina",
+ "trixe",
+ "troci",
+ "tsaba",
+ "tsali",
+ "tsani",
+ "tsapi",
+ "tsiju",
+ "tsina",
+ "tsuku",
+ "tubnu",
+ "tubra",
+ "tugni",
+ "tujli",
+ "tumla",
+ "tunba",
+ "tunka",
+ "tunlo",
+ "tunta",
+ "tuple",
+ "turko",
+ "turni",
+ "tutci",
+ "tutle",
+ "tutra",
+ "vacri",
+ "vajni",
+ "valsi",
+ "vamji",
+ "vamtu",
+ "vanbi",
+ "vanci",
+ "vanju",
+ "vasru",
+ "vasxu",
+ "vecnu",
+ "vedli",
+ "venfu",
+ "vensa",
+ "vente",
+ "vepre",
+ "verba",
+ "vibna",
+ "vidni",
+ "vidru",
+ "vifne",
+ "vikmi",
+ "viknu",
+ "vimcu",
+ "vindu",
+ "vinji",
+ "vinta",
+ "vipsi",
+ "virnu",
+ "viska",
+ "vitci",
+ "vitke",
+ "vitno",
+ "vlagi",
+ "vlile",
+ "vlina",
+ "vlipa",
+ "vofli",
+ "voksa",
+ "volve",
+ "vorme",
+ "vraga",
+ "vreji",
+ "vreta",
+ "vrici",
+ "vrude",
+ "vrusi",
+ "vubla",
+ "vujnu",
+ "vukna",
+ "vukro",
+ "xabju",
+ "xadba",
+ "xadji",
+ "xadni",
+ "xagji",
+ "xagri",
+ "xajmi",
+ "xaksu",
+ "xalbo",
+ "xalka",
+ "xalni",
+ "xamgu",
+ "xampo",
+ "xamsi",
+ "xance",
+ "xango",
+ "xanka",
+ "xanri",
+ "xansa",
+ "xanto",
+ "xarci",
+ "xarju",
+ "xarnu",
+ "xasli",
+ "xasne",
+ "xatra",
+ "xatsi",
+ "xazdo",
+ "xebni",
+ "xebro",
+ "xecto",
+ "xedja",
+ "xekri",
+ "xelso",
+ "xendo",
+ "xenru",
+ "xexso",
+ "xigzo",
+ "xindo",
+ "xinmo",
+ "xirma",
+ "xislu",
+ "xispo",
+ "xlali",
+ "xlura",
+ "xorbo",
+ "xorlo",
+ "xotli",
+ "xrabo",
+ "xrani",
+ "xriso",
+ "xrotu",
+ "xruba",
+ "xruki",
+ "xrula",
+ "xruti",
+ "xukmi",
+ "xulta",
+ "xunre",
+ "xurdo",
+ "xusra",
+ "xutla",
+ "zabna",
+ "zajba",
+ "zalvi",
+ "zanru",
+ "zarci",
+ "zargu",
+ "zasni",
+ "zasti",
+ "zbabu",
+ "zbani",
+ "zbasu",
+ "zbepi",
+ "zdani",
+ "zdile",
+ "zekri",
+ "zenba",
+ "zepti",
+ "zetro",
+ "zevla",
+ "zgadi",
+ "zgana",
+ "zgike",
+ "zifre",
+ "zinki",
+ "zirpu",
+ "zivle",
+ "zmadu",
+ "zmiku",
+ "zucna",
+ "zukte",
+ "zumri",
+ "zungi",
+ "zunle",
+ "zunti",
+ "zutse",
+ "zvati",
+ "zviki",
+ "jbobau",
+ "jbopre",
+ "karsna",
+ "cabdei",
+ "zunsna",
+ "gendra",
+ "glibau",
+ "nintadni",
+ "pavyseljirna",
+ "vlaste",
+ "selbri",
+ "latro'a",
+ "zdakemkulgu'a",
+ "mriste",
+ "selsku",
+ "fu'ivla",
+ "tolmo'i",
+ "snavei",
+ "xagmau",
+ "retsku",
+ "ckupau",
+ "skudji",
+ "smudra",
+ "prulamdei",
+ "vokta'a",
+ "tinju'i",
+ "jefyfa'o",
+ "bavlamdei",
+ "kinzga",
+ "jbocre",
+ "jbovla",
+ "xauzma",
+ "selkei",
+ "xuncku",
+ "spusku",
+ "jbogu'e",
+ "pampe'o",
+ "bripre",
+ "jbosnu",
+ "zi'evla",
+ "gimste",
+ "tolzdi",
+ "velski",
+ "samselpla",
+ "cnegau",
+ "velcki",
+ "selja'e",
+ "fasybau",
+ "zanfri",
+ "reisku",
+ "favgau",
+ "jbota'a",
+ "rejgau",
+ "malgli",
+ "zilkai",
+ "keidji",
+ "tersu'i",
+ "jbofi'e",
+ "cnima'o",
+ "mulgau",
+ "ningau",
+ "ponbau",
+ "mrobi'o",
+ "rarbau",
+ "zmanei",
+ "famyma'o",
+ "vacysai",
+ "jetmlu",
+ "jbonunsla",
+ "nunpe'i",
+ "fa'orma'o",
+ "crezenzu'e",
+ "jbojbe",
+ "cmicu'a",
+ "zilcmi",
+ "tolcando",
+ "zukcfu",
+ "depybu'i",
+ "mencre",
+ "matmau",
+ "nunctu",
+ "selma'o",
+ "titnanba",
+ "naldra",
+ "jvajvo",
+ "nunsnu",
+ "nerkla",
+ "cimjvo",
+ "muvgau",
+ "zipcpi",
+ "runbau",
+ "faumlu",
+ "terbri",
+ "balcu'e",
+ "dragau",
+ "smuvelcki",
+ "piksku",
+ "selpli",
+ "bregau",
+ "zvafa'i",
+ "ci'izra",
+ "noltruti'u",
+ "samtci",
+ "snaxa'a",
+ }), 4)
+ {
+ populate_maps();
+ }
+ };
+}
+
+#endif
diff --git a/src/p2p/CMakeLists.txt b/src/p2p/CMakeLists.txt index 0704940b5..123b0a272 100644 --- a/src/p2p/CMakeLists.txt +++ b/src/p2p/CMakeLists.txt @@ -39,6 +39,7 @@ monero_add_library(p2p ${P2P}) target_link_libraries(p2p PUBLIC epee + version ${UPNP_LIBRARIES} ${Boost_CHRONO_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} @@ -47,5 +48,3 @@ target_link_libraries(p2p ${Boost_THREAD_LIBRARY} PRIVATE ${EXTRA_LIBRARIES}) -add_dependencies(p2p - version) diff --git a/src/p2p/net_node.h b/src/p2p/net_node.h index 22ae5ec26..8bbaa9138 100644 --- a/src/p2p/net_node.h +++ b/src/p2p/net_node.h @@ -105,7 +105,7 @@ namespace nodetool bool init(const boost::program_options::variables_map& vm); bool deinit(); bool send_stop_signal(); - uint32_t get_this_peer_port(){return m_listenning_port;} + uint32_t get_this_peer_port(){return m_listening_port;} t_payload_net_handler& get_payload_object(); template <class Archive, class t_version_type> @@ -218,6 +218,8 @@ namespace nodetool bool is_peer_used(const peerlist_entry& peer); bool is_peer_used(const anchor_peerlist_entry& peer); bool is_addr_connected(const epee::net_utils::network_address& peer); + void add_upnp_port_mapping(uint32_t port); + void delete_upnp_port_mapping(uint32_t port); template<class t_callback> bool try_ping(basic_node_data& node_data, p2p_connection_context& context, t_callback cb); bool try_get_support_flags(const p2p_connection_context& context, std::function<void(p2p_connection_context&, const uint32_t&)> f); @@ -287,7 +289,7 @@ namespace nodetool bool m_have_address; bool m_first_connection_maker_call; - uint32_t m_listenning_port; + uint32_t m_listening_port; uint32_t m_external_port; uint32_t m_ip_address; bool m_allow_local_ip; diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index 889cfaf9d..6162d649b 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -390,7 +390,7 @@ namespace nodetool ip::tcp::endpoint endpoint = *i; if (endpoint.address().is_v4()) { - epee::net_utils::network_address na(new epee::net_utils::ipv4_network_address(boost::asio::detail::socket_ops::host_to_network_long(endpoint.address().to_v4().to_ulong()), endpoint.port())); + epee::net_utils::network_address na{epee::net_utils::ipv4_network_address{boost::asio::detail::socket_ops::host_to_network_long(endpoint.address().to_v4().to_ulong()), endpoint.port()}}; seed_nodes.push_back(na); MINFO("Added seed node: " << na.str()); } @@ -573,55 +573,15 @@ namespace nodetool res = m_net_server.init_server(m_port, m_bind_ip); CHECK_AND_ASSERT_MES(res, false, "Failed to bind server"); - m_listenning_port = m_net_server.get_binded_port(); - MLOG_GREEN(el::Level::Info, "Net service bound to " << m_bind_ip << ":" << m_listenning_port); + m_listening_port = m_net_server.get_binded_port(); + MLOG_GREEN(el::Level::Info, "Net service bound to " << m_bind_ip << ":" << m_listening_port); if(m_external_port) MDEBUG("External port defined as " << m_external_port); - // Add UPnP port mapping - if(m_no_igd == false) { - MDEBUG("Attempting to add IGD port mapping."); - int result; -#if MINIUPNPC_API_VERSION > 13 - // default according to miniupnpc.h - unsigned char ttl = 2; - UPNPDev* deviceList = upnpDiscover(1000, NULL, NULL, 0, 0, ttl, &result); -#else - UPNPDev* deviceList = upnpDiscover(1000, NULL, NULL, 0, 0, &result); -#endif - UPNPUrls urls; - IGDdatas igdData; - char lanAddress[64]; - result = UPNP_GetValidIGD(deviceList, &urls, &igdData, lanAddress, sizeof lanAddress); - freeUPNPDevlist(deviceList); - if (result != 0) { - if (result == 1) { - std::ostringstream portString; - portString << m_listenning_port; - - // Delete the port mapping before we create it, just in case we have dangling port mapping from the daemon not being shut down correctly - UPNP_DeletePortMapping(urls.controlURL, igdData.first.servicetype, portString.str().c_str(), "TCP", 0); - - int portMappingResult; - portMappingResult = UPNP_AddPortMapping(urls.controlURL, igdData.first.servicetype, portString.str().c_str(), portString.str().c_str(), lanAddress, CRYPTONOTE_NAME, "TCP", 0, "0"); - if (portMappingResult != 0) { - LOG_ERROR("UPNP_AddPortMapping failed, error: " << strupnperror(portMappingResult)); - } else { - MLOG_GREEN(el::Level::Info, "Added IGD port mapping."); - } - } else if (result == 2) { - MWARNING("IGD was found but reported as not connected."); - } else if (result == 3) { - MWARNING("UPnP device was found but not recognized as IGD."); - } else { - MWARNING("UPNP_GetValidIGD returned an unknown result code."); - } + // add UPnP port mapping + if(!m_no_igd) + add_upnp_port_mapping(m_listening_port); - FreeUPNPUrls(&urls); - } else { - MINFO("No IGD was found."); - } - } return res; } //----------------------------------------------------------------------------------- @@ -688,6 +648,9 @@ namespace nodetool kill(); m_peerlist.deinit(); m_net_server.deinit_server(); + // remove UPnP port mapping + if(!m_no_igd) + delete_upnp_port_mapping(m_listening_port); return store_config(); } //----------------------------------------------------------------------------------- @@ -1127,6 +1090,8 @@ namespace nodetool if (use_white_list) { local_peers_count = m_peerlist.get_white_peers_count(); + if (!local_peers_count) + return false; max_random_index = std::min<uint64_t>(local_peers_count -1, 20); random_index = get_random_index_with_fixed_probability(max_random_index); } else { @@ -1389,7 +1354,7 @@ namespace nodetool node_data.local_time = local_time; node_data.peer_id = m_config.m_peer_id; if(!m_hide_my_port) - node_data.my_port = m_external_port ? m_external_port : m_listenning_port; + node_data.my_port = m_external_port ? m_external_port : m_listening_port; else node_data.my_port = 0; node_data.network_id = m_network_id; @@ -1556,7 +1521,7 @@ namespace nodetool return false; std::string ip = epee::string_tools::get_ip_string_from_int32(actual_ip); std::string port = epee::string_tools::num_to_string_fast(node_data.my_port); - epee::net_utils::network_address address(new epee::net_utils::ipv4_network_address(actual_ip, node_data.my_port)); + epee::net_utils::network_address address{epee::net_utils::ipv4_network_address(actual_ip, node_data.my_port)}; peerid_type pr = node_data.peer_id; bool r = m_net_server.connect_async(ip, port, m_config.m_net_config.ping_connection_timeout, [cb, /*context,*/ address, pr, this]( const typename net_server::t_connection_context& ping_context, @@ -1704,7 +1669,7 @@ namespace nodetool if(arg.node_data.peer_id != m_config.m_peer_id && arg.node_data.my_port) { peerid_type peer_id_l = arg.node_data.peer_id; - uint32_t port_l = arg.node_data.my_port; + uint32_t port_l = arg.node_data.my_port; //try ping to be sure that we can add this peer to peer_list try_ping(arg.node_data, context, [peer_id_l, port_l, context, this]() { @@ -1713,7 +1678,7 @@ namespace nodetool //called only(!) if success pinged, update local peerlist peerlist_entry pe; const epee::net_utils::network_address na = context.m_remote_address; - pe.adr.reset(new epee::net_utils::ipv4_network_address(na.as<epee::net_utils::ipv4_network_address>().ip(), port_l)); + pe.adr = epee::net_utils::ipv4_network_address(na.as<epee::net_utils::ipv4_network_address>().ip(), port_l); time_t last_seen; time(&last_seen); pe.last_seen = static_cast<int64_t>(last_seen); @@ -1951,8 +1916,13 @@ namespace nodetool template<class t_payload_net_handler> bool node_server<t_payload_net_handler>::gray_peerlist_housekeeping() { + if (!m_exclusive_peers.empty()) return true; + peerlist_entry pe = AUTO_VAL_INIT(pe); + if (m_net_server.is_stop_signal_sent()) + return false; + if (!m_peerlist.get_random_gray_peer(pe)) { return false; } @@ -1973,4 +1943,93 @@ namespace nodetool return true; } + + template<class t_payload_net_handler> + void node_server<t_payload_net_handler>::add_upnp_port_mapping(uint32_t port) + { + MDEBUG("Attempting to add IGD port mapping."); + int result; +#if MINIUPNPC_API_VERSION > 13 + // default according to miniupnpc.h + unsigned char ttl = 2; + UPNPDev* deviceList = upnpDiscover(1000, NULL, NULL, 0, 0, ttl, &result); +#else + UPNPDev* deviceList = upnpDiscover(1000, NULL, NULL, 0, 0, &result); +#endif + UPNPUrls urls; + IGDdatas igdData; + char lanAddress[64]; + result = UPNP_GetValidIGD(deviceList, &urls, &igdData, lanAddress, sizeof lanAddress); + freeUPNPDevlist(deviceList); + if (result != 0) { + if (result == 1) { + std::ostringstream portString; + portString << port; + + // Delete the port mapping before we create it, just in case we have dangling port mapping from the daemon not being shut down correctly + UPNP_DeletePortMapping(urls.controlURL, igdData.first.servicetype, portString.str().c_str(), "TCP", 0); + + int portMappingResult; + portMappingResult = UPNP_AddPortMapping(urls.controlURL, igdData.first.servicetype, portString.str().c_str(), portString.str().c_str(), lanAddress, CRYPTONOTE_NAME, "TCP", 0, "0"); + if (portMappingResult != 0) { + LOG_ERROR("UPNP_AddPortMapping failed, error: " << strupnperror(portMappingResult)); + } else { + MLOG_GREEN(el::Level::Info, "Added IGD port mapping."); + } + } else if (result == 2) { + MWARNING("IGD was found but reported as not connected."); + } else if (result == 3) { + MWARNING("UPnP device was found but not recognized as IGD."); + } else { + MWARNING("UPNP_GetValidIGD returned an unknown result code."); + } + + FreeUPNPUrls(&urls); + } else { + MINFO("No IGD was found."); + } + } + + template<class t_payload_net_handler> + void node_server<t_payload_net_handler>::delete_upnp_port_mapping(uint32_t port) + { + MDEBUG("Attempting to delete IGD port mapping."); + int result; +#if MINIUPNPC_API_VERSION > 13 + // default according to miniupnpc.h + unsigned char ttl = 2; + UPNPDev* deviceList = upnpDiscover(1000, NULL, NULL, 0, 0, ttl, &result); +#else + UPNPDev* deviceList = upnpDiscover(1000, NULL, NULL, 0, 0, &result); +#endif + UPNPUrls urls; + IGDdatas igdData; + char lanAddress[64]; + result = UPNP_GetValidIGD(deviceList, &urls, &igdData, lanAddress, sizeof lanAddress); + freeUPNPDevlist(deviceList); + if (result != 0) { + if (result == 1) { + std::ostringstream portString; + portString << port; + + int portMappingResult; + portMappingResult = UPNP_DeletePortMapping(urls.controlURL, igdData.first.servicetype, portString.str().c_str(), "TCP", 0); + if (portMappingResult != 0) { + LOG_ERROR("UPNP_DeletePortMapping failed, error: " << strupnperror(portMappingResult)); + } else { + MLOG_GREEN(el::Level::Info, "Deleted IGD port mapping."); + } + } else if (result == 2) { + MWARNING("IGD was found but reported as not connected."); + } else if (result == 3) { + MWARNING("UPnP device was found but not recognized as IGD."); + } else { + MWARNING("UPNP_GetValidIGD returned an unknown result code."); + } + + FreeUPNPUrls(&urls); + } else { + MINFO("No IGD was found."); + } + } } diff --git a/src/p2p/net_peerlist.h b/src/p2p/net_peerlist.h index a30a05422..8372445aa 100644 --- a/src/p2p/net_peerlist.h +++ b/src/p2p/net_peerlist.h @@ -190,35 +190,16 @@ namespace nodetool if (ver < 6) return; - if(ver < 3) - return; CRITICAL_REGION_LOCAL(m_peerlist_lock); - if(ver < 4) - { - //loading data from old storage - peers_indexed_old pio; - a & pio; - peers_indexed_from_old(pio, m_peers_white); - return; - } #if 0 // trouble loading more than one peer, can't find why a & m_peers_white; a & m_peers_gray; + a & m_peers_anchor; #else serialize_peers(a, m_peers_white, peerlist_entry(), ver); serialize_peers(a, m_peers_gray, peerlist_entry(), ver); -#endif - - if(ver < 5) { - return; - } - -#if 0 - // trouble loading more than one peer, can't find why - a & m_peers_anchor; -#else serialize_peers(a, m_peers_anchor, anchor_peerlist_entry(), ver); #endif } diff --git a/src/p2p/net_peerlist_boost_serialization.h b/src/p2p/net_peerlist_boost_serialization.h index 6ea2d48fd..079524aa1 100644 --- a/src/p2p/net_peerlist_boost_serialization.h +++ b/src/p2p/net_peerlist_boost_serialization.h @@ -31,11 +31,19 @@ #pragma once #include "net/net_utils_base.h" +#include "p2p/p2p_protocol_defs.h" namespace boost { namespace serialization { + template <class T, class Archive> + inline void do_serialize(Archive &a, epee::net_utils::network_address& na, T local) + { + if (typename Archive::is_saving()) local = na.as<T>(); + a & local; + if (!typename Archive::is_saving()) na = local; + } template <class Archive, class ver_type> inline void serialize(Archive &a, epee::net_utils::network_address& na, const ver_type ver) { @@ -46,10 +54,8 @@ namespace boost switch (type) { case epee::net_utils::ipv4_network_address::ID: - if (!typename Archive::is_saving()) - na.reset(new epee::net_utils::ipv4_network_address(0, 0)); - a & na.as<epee::net_utils::ipv4_network_address>(); - break; + do_serialize(a, na, epee::net_utils::ipv4_network_address{0, 0}); + break; default: throw std::runtime_error("Unsupported network address type"); } @@ -57,13 +63,14 @@ namespace boost template <class Archive, class ver_type> inline void serialize(Archive &a, epee::net_utils::ipv4_network_address& na, const ver_type ver) { - a & na.m_ip; - a & na.m_port; + uint32_t ip{na.ip()}; + uint16_t port{na.port()}; + a & ip; + a & port; if (!typename Archive::is_saving()) - na.init_ids(); + na = epee::net_utils::ipv4_network_address{ip, port}; } - template <class Archive, class ver_type> inline void serialize(Archive &a, nodetool::peerlist_entry& pl, const ver_type ver) { diff --git a/src/p2p/p2p_protocol_defs.h b/src/p2p/p2p_protocol_defs.h index f2b2cd1da..d49d83989 100644 --- a/src/p2p/p2p_protocol_defs.h +++ b/src/p2p/p2p_protocol_defs.h @@ -120,7 +120,7 @@ namespace nodetool ss << std::setfill ('0') << std::setw (8) << std::hex << std::noshowbase; for(const peerlist_entry& pe: pl) { - ss << pe.id << "\t" << pe.adr->str() << " \tlast_seen: " << epee::misc_utils::get_time_interval_string(now_time - pe.last_seen) << std::endl; + ss << pe.id << "\t" << pe.adr.str() << " \tlast_seen: " << epee::misc_utils::get_time_interval_string(now_time - pe.last_seen) << std::endl; } return ss.str(); } @@ -216,7 +216,7 @@ namespace nodetool std::list<peerlist_entry_base<network_address_old>> local_peerlist; epee::serialization::selector<is_store>::serialize_stl_container_pod_val_as_blob(local_peerlist, stg, hparent_section, "local_peerlist"); for (const auto &p: local_peerlist) - ((response&)this_ref).local_peerlist_new.push_back(peerlist_entry({new epee::net_utils::ipv4_network_address(p.adr.ip, p.adr.port), p.id, p.last_seen})); + ((response&)this_ref).local_peerlist_new.push_back(peerlist_entry({epee::net_utils::ipv4_network_address(p.adr.ip, p.adr.port), p.id, p.last_seen})); } } END_KV_SERIALIZE_MAP() @@ -275,7 +275,7 @@ namespace nodetool std::list<peerlist_entry_base<network_address_old>> local_peerlist; epee::serialization::selector<is_store>::serialize_stl_container_pod_val_as_blob(local_peerlist, stg, hparent_section, "local_peerlist"); for (const auto &p: local_peerlist) - ((response&)this_ref).local_peerlist_new.push_back(peerlist_entry({new epee::net_utils::ipv4_network_address(p.adr.ip, p.adr.port), p.id, p.last_seen})); + ((response&)this_ref).local_peerlist_new.push_back(peerlist_entry({epee::net_utils::ipv4_network_address(p.adr.ip, p.adr.port), p.id, p.last_seen})); } } END_KV_SERIALIZE_MAP() diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index 8efd6a07c..946325367 100644 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -30,8 +30,7 @@ #include "misc_log_ex.h" #include "common/perf_timer.h" -#include "common/task_region.h" -#include "common/thread_group.h" +#include "common/threadpool.h" #include "common/util.h" #include "rctSigs.h" #include "cryptonote_basic/cryptonote_format_utils.h" @@ -731,17 +730,16 @@ namespace rct { try { if (semantics) { + tools::threadpool& tpool = tools::threadpool::getInstance(); + tools::threadpool::waiter waiter; std::deque<bool> results(rv.outPk.size(), false); - tools::thread_group threadpool(tools::thread_group::optimal_with_max(rv.outPk.size())); - - tools::task_region(threadpool, [&] (tools::task_region_handle& region) { - DP("range proofs verified?"); - for (size_t i = 0; i < rv.outPk.size(); i++) { - region.run([&, i] { - results[i] = verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]); - }); - } - }); + DP("range proofs verified?"); + for (size_t i = 0; i < rv.outPk.size(); i++) { + tpool.submit(&waiter, [&, i] { + results[i] = verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]); + }); + } + waiter.wait(); for (size_t i = 0; i < rv.outPk.size(); ++i) { if (!results[i]) { @@ -794,7 +792,8 @@ namespace rct { const size_t threads = std::max(rv.outPk.size(), rv.mixRing.size()); std::deque<bool> results(threads); - tools::thread_group threadpool(tools::thread_group::optimal_with_max(threads)); + tools::threadpool& tpool = tools::threadpool::getInstance(); + tools::threadpool::waiter waiter; if (semantics) { key sumOutpks = identity(); @@ -819,13 +818,12 @@ namespace rct { results.clear(); results.resize(rv.outPk.size()); - tools::task_region(threadpool, [&] (tools::task_region_handle& region) { - for (size_t i = 0; i < rv.outPk.size(); i++) { - region.run([&, i] { - results[i] = verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]); - }); - } - }); + for (size_t i = 0; i < rv.outPk.size(); i++) { + tpool.submit(&waiter, [&, i] { + results[i] = verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]); + }); + } + waiter.wait(); for (size_t i = 0; i < results.size(); ++i) { if (!results[i]) { @@ -839,13 +837,12 @@ namespace rct { results.clear(); results.resize(rv.mixRing.size()); - tools::task_region(threadpool, [&] (tools::task_region_handle& region) { - for (size_t i = 0 ; i < rv.mixRing.size() ; i++) { - region.run([&, i] { + for (size_t i = 0 ; i < rv.mixRing.size() ; i++) { + tpool.submit(&waiter, [&, i] { results[i] = verRctMGSimple(message, rv.p.MGs[i], rv.mixRing[i], rv.pseudoOuts[i]); - }); - } - }); + }); + } + waiter.wait(); for (size_t i = 0; i < results.size(); ++i) { if (!results[i]) { diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h index c820fb297..cc0000ad6 100644 --- a/src/ringct/rctTypes.h +++ b/src/ringct/rctTypes.h @@ -445,9 +445,11 @@ namespace cryptonote { static inline bool operator!=(const crypto::secret_key &k0, const rct::key &k1) { return memcmp(&k0, &k1, 32); } } +namespace rct { inline std::ostream &operator <<(std::ostream &o, const rct::key &v) { epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; } +} BLOB_SERIALIZER(rct::key); diff --git a/src/rpc/CMakeLists.txt b/src/rpc/CMakeLists.txt index f6037d7e3..23bb6aaae 100644 --- a/src/rpc/CMakeLists.txt +++ b/src/rpc/CMakeLists.txt @@ -30,20 +30,61 @@ set(rpc_sources core_rpc_server.cpp rpc_args.cpp) +set(daemon_messages_sources + message.cpp + daemon_messages.cpp) + +set(daemon_rpc_server_sources + daemon_handler.cpp + zmq_server.cpp) + + set(rpc_headers rpc_args.h) -set(rpc_private_headers +set(daemon_rpc_server_headers) + + +set(rpc_daemon_private_headers core_rpc_server.h core_rpc_server_commands_defs.h core_rpc_server_error_codes.h) +set(daemon_messages_private_headers + message.h + daemon_messages.h) + +set(daemon_rpc_server_private_headers + message.h + daemon_messages.h + daemon_handler.h + rpc_handler.h + zmq_server.h) + + monero_private_headers(rpc ${rpc_private_headers}) + +monero_private_headers(daemon_rpc_server + ${daemon_rpc_server_private_headers}) + + monero_add_library(rpc ${rpc_sources} ${rpc_headers} ${rpc_private_headers}) + +monero_add_library(daemon_messages + ${daemon_messages_sources} + ${daemon_messages_headers} + ${daemon_messages_private_headers}) + +monero_add_library(daemon_rpc_server + ${daemon_rpc_server_sources} + ${daemon_rpc_server_headers} + ${daemon_rpc_server_private_headers}) + + target_link_libraries(rpc PUBLIC common @@ -54,5 +95,25 @@ target_link_libraries(rpc ${Boost_THREAD_LIBRARY} PRIVATE ${EXTRA_LIBRARIES}) -add_dependencies(rpc - version) + +target_link_libraries(daemon_messages + LINK_PRIVATE + cryptonote_core + cryptonote_protocol + serialization + ${EXTRA_LIBRARIES}) + +target_link_libraries(daemon_rpc_server + LINK_PRIVATE + cryptonote_core + cryptonote_protocol + daemon_messages + serialization + ${Boost_CHRONO_LIBRARY} + ${Boost_REGEX_LIBRARY} + ${Boost_SYSTEM_LIBRARY} + ${Boost_THREAD_LIBRARY} + ${ZMQ_LIB} + ${EXTRA_LIBRARIES}) +target_include_directories(daemon_rpc_server PUBLIC ${ZMQ_INCLUDE_PATH}) +target_include_directories(obj_daemon_rpc_server PUBLIC ${ZMQ_INCLUDE_PATH}) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index a6a61705b..da02e15a1 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -50,6 +50,16 @@ using namespace epee; #define MAX_RESTRICTED_FAKE_OUTS_COUNT 40 #define MAX_RESTRICTED_GLOBAL_FAKE_OUTS_COUNT 500 +namespace +{ + void add_reason(std::string &reasons, const char *reason) + { + if (!reasons.empty()) + reasons += ", "; + reasons += reason; + } +} + namespace cryptonote { @@ -128,16 +138,12 @@ namespace cryptonote { CHECK_CORE_BUSY(); crypto::hash top_hash; - if (!m_core.get_blockchain_top(res.height, top_hash)) - { - res.status = "Failed"; - return false; - } + m_core.get_blockchain_top(res.height, top_hash); ++res.height; // turn top block height into blockchain height res.top_block_hash = string_tools::pod_to_hex(top_hash); res.target_height = m_core.get_target_blockchain_height(); res.difficulty = m_core.get_blockchain_storage().get_difficulty_for_next_block(); - res.target = m_core.get_blockchain_storage().get_current_hard_fork_version() < 2 ? DIFFICULTY_TARGET_V1 : DIFFICULTY_TARGET_V2; + res.target = m_core.get_blockchain_storage().get_difficulty_target(); res.tx_count = m_core.get_blockchain_storage().get_total_transactions() - res.height; //without coinbase res.tx_pool_size = m_core.get_pool_transactions_count(); res.alt_blocks_count = m_core.get_blockchain_storage().get_alternative_blocks_count(); @@ -478,18 +484,31 @@ namespace cryptonote bool r = m_core.get_pool_transactions(pool_txs); if(r) { - for (std::list<transaction>::const_iterator i = pool_txs.begin(); i != pool_txs.end(); ++i) + // sort to match original request + std::list<transaction> sorted_txs; + std::list<cryptonote::transaction>::const_iterator i; + for (const crypto::hash &h: vh) { - crypto::hash tx_hash = get_transaction_hash(*i); - std::list<crypto::hash>::iterator mi = std::find(missed_txs.begin(), missed_txs.end(), tx_hash); - if (mi != missed_txs.end()) + if (std::find(missed_txs.begin(), missed_txs.end(), h) == missed_txs.end()) { - pool_tx_hashes.insert(tx_hash); - missed_txs.erase(mi); - txs.push_back(*i); + // core returns the ones it finds in the right order + if (get_transaction_hash(txs.front()) != h) + { + res.status = "Failed: tx hash mismatch"; + return true; + } + sorted_txs.push_back(std::move(txs.front())); + txs.pop_front(); + } + else if ((i = std::find_if(pool_txs.begin(), pool_txs.end(), [h](cryptonote::transaction &tx) { return h == cryptonote::get_transaction_hash(tx); })) != pool_txs.end()) + { + sorted_txs.push_back(*i); + missed_txs.remove(h); + pool_tx_hashes.insert(h); ++found_in_pool; } } + txs = sorted_txs; } LOG_PRINT_L2("Found " << found_in_pool << "/" << vh.size() << " transactions in the pool"); } @@ -510,11 +529,12 @@ namespace cryptonote e.in_pool = pool_tx_hashes.find(tx_hash) != pool_tx_hashes.end(); if (e.in_pool) { - e.block_height = std::numeric_limits<uint64_t>::max(); + e.block_height = e.block_timestamp = std::numeric_limits<uint64_t>::max(); } else { e.block_height = m_core.get_blockchain_storage().get_db().get_tx_block_height(tx_hash); + e.block_timestamp = m_core.get_blockchain_storage().get_db().get_block_timestamp(e.block_height); } // fill up old style responses too, in case an old wallet asks @@ -623,31 +643,33 @@ namespace cryptonote tx_verification_context tvc = AUTO_VAL_INIT(tvc); if(!m_core.handle_incoming_tx(tx_blob, tvc, false, false, req.do_not_relay) || tvc.m_verifivation_failed) { - if (tvc.m_verifivation_failed) - { - LOG_PRINT_L0("[on_send_raw_tx]: tx verification failed"); - } - else - { - LOG_PRINT_L0("[on_send_raw_tx]: Failed to process tx"); - } res.status = "Failed"; + res.reason = ""; if ((res.low_mixin = tvc.m_low_mixin)) - res.reason = "ring size too small"; + add_reason(res.reason, "ring size too small"); if ((res.double_spend = tvc.m_double_spend)) - res.reason = "double spend"; + add_reason(res.reason, "double spend"); if ((res.invalid_input = tvc.m_invalid_input)) - res.reason = "invalid input"; + add_reason(res.reason, "invalid input"); if ((res.invalid_output = tvc.m_invalid_output)) - res.reason = "invalid output"; + add_reason(res.reason, "invalid output"); if ((res.too_big = tvc.m_too_big)) - res.reason = "too big"; + add_reason(res.reason, "too big"); if ((res.overspend = tvc.m_overspend)) - res.reason = "overspend"; + add_reason(res.reason, "overspend"); if ((res.fee_too_low = tvc.m_fee_too_low)) - res.reason = "fee too low"; + add_reason(res.reason, "fee too low"); if ((res.not_rct = tvc.m_not_rct)) - res.reason = "tx is not ringct"; + add_reason(res.reason, "tx is not ringct"); + const std::string punctuation = res.reason.empty() ? "" : ": "; + if (tvc.m_verifivation_failed) + { + LOG_PRINT_L0("[on_send_raw_tx]: tx verification failed" << punctuation << res.reason); + } + else + { + LOG_PRINT_L0("[on_send_raw_tx]: Failed to process tx" << punctuation << res.reason); + } return true; } @@ -671,13 +693,19 @@ namespace cryptonote bool core_rpc_server::on_start_mining(const COMMAND_RPC_START_MINING::request& req, COMMAND_RPC_START_MINING::response& res) { CHECK_CORE_READY(); - account_public_address adr; - if(!get_account_address_from_str(adr, m_testnet, req.miner_address)) + cryptonote::address_parse_info info; + if(!get_account_address_from_str(info, m_testnet, req.miner_address)) { res.status = "Failed, wrong address"; LOG_PRINT_L0(res.status); return true; } + if (info.is_subaddress) + { + res.status = "Mining to subaddress isn't supported yet"; + LOG_PRINT_L0(res.status); + return true; + } unsigned int concurrency_count = boost::thread::hardware_concurrency() * 4; @@ -699,7 +727,7 @@ namespace cryptonote boost::thread::attributes attrs; attrs.set_stack_size(THREAD_STACK_SIZE); - if(!m_core.get_miner().start(adr, static_cast<size_t>(req.threads_count), attrs, req.do_background_mining, req.ignore_battery)) + if(!m_core.get_miner().start(info.address, static_cast<size_t>(req.threads_count), attrs, req.do_background_mining, req.ignore_battery)) { res.status = "Failed, mining not started"; LOG_PRINT_L0(res.status); @@ -733,7 +761,7 @@ namespace cryptonote res.speed = lMiner.get_speed(); res.threads_count = lMiner.get_threads_count(); const account_public_address& lMiningAdr = lMiner.get_mining_address(); - res.address = get_account_address_as_str(m_testnet, lMiningAdr); + res.address = get_account_address_as_str(m_testnet, false, lMiningAdr); } res.status = CORE_RPC_STATUS_OK; @@ -810,6 +838,7 @@ namespace cryptonote bool core_rpc_server::on_set_log_categories(const COMMAND_RPC_SET_LOG_CATEGORIES::request& req, COMMAND_RPC_SET_LOG_CATEGORIES::response& res) { mlog_set_log(req.categories.c_str()); + res.categories = mlog_get_categories(); res.status = CORE_RPC_STATUS_OK; return true; } @@ -912,19 +941,25 @@ namespace cryptonote return false; } - cryptonote::account_public_address acc = AUTO_VAL_INIT(acc); + cryptonote::address_parse_info info; - if(!req.wallet_address.size() || !cryptonote::get_account_address_from_str(acc, m_testnet, req.wallet_address)) + if(!req.wallet_address.size() || !cryptonote::get_account_address_from_str(info, m_testnet, req.wallet_address)) { error_resp.code = CORE_RPC_ERROR_CODE_WRONG_WALLET_ADDRESS; error_resp.message = "Failed to parse wallet address"; return false; } + if (info.is_subaddress) + { + error_resp.code = CORE_RPC_ERROR_CODE_MINING_TO_SUBADDRESS; + error_resp.message = "Mining to subaddress is not supported yet"; + return false; + } block b = AUTO_VAL_INIT(b); cryptonote::blobdata blob_reserve; blob_reserve.resize(req.reserve_size, 0); - if(!m_core.get_block_template(b, acc, res.difficulty, res.height, res.expected_reward, blob_reserve)) + if(!m_core.get_block_template(b, info.address, res.difficulty, res.height, res.expected_reward, blob_reserve)) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; error_resp.message = "Internal error: failed to create block template"; @@ -948,7 +983,7 @@ namespace cryptonote LOG_ERROR("Failed to find tx pub key in blockblob"); return false; } - res.reserved_offset += sizeof(tx_pub_key) + 3; //3 bytes: tag for TX_EXTRA_TAG_PUBKEY(1 byte), tag for TX_EXTRA_NONCE(1 byte), counter in TX_EXTRA_NONCE(1 byte) + res.reserved_offset += sizeof(tx_pub_key) + 2; //2 bytes: tag for TX_EXTRA_NONCE(1 byte), counter in TX_EXTRA_NONCE(1 byte) if(res.reserved_offset + req.reserve_size > block_blob.size()) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; @@ -1048,13 +1083,7 @@ namespace cryptonote } uint64_t last_block_height; crypto::hash last_block_hash; - bool have_last_block_hash = m_core.get_blockchain_top(last_block_height, last_block_hash); - if (!have_last_block_hash) - { - error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; - error_resp.message = "Internal error: can't get last block hash."; - return false; - } + m_core.get_blockchain_top(last_block_height, last_block_hash); block last_block; bool have_last_block = m_core.get_block_by_hash(last_block_hash, last_block); if (!have_last_block) @@ -1287,11 +1316,7 @@ namespace cryptonote } crypto::hash top_hash; - if (!m_core.get_blockchain_top(res.height, top_hash)) - { - res.status = "Failed"; - return false; - } + m_core.get_blockchain_top(res.height, top_hash); ++res.height; // turn top block height into blockchain height res.top_block_hash = string_tools::pod_to_hex(top_hash); res.target_height = m_core.get_target_blockchain_height(); @@ -1383,7 +1408,7 @@ namespace cryptonote } else { - na.reset(new epee::net_utils::ipv4_network_address(i->ip, 0)); + na = epee::net_utils::ipv4_network_address{i->ip, 0}; } if (i->ban) m_p2p.block_host(na, i->seconds); @@ -1522,6 +1547,53 @@ namespace cryptonote return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::on_get_limit(const COMMAND_RPC_GET_LIMIT::request& req, COMMAND_RPC_GET_LIMIT::response& res) + { + res.limit_down = epee::net_utils::connection_basic::get_rate_down_limit(); + res.limit_up = epee::net_utils::connection_basic::get_rate_up_limit(); + res.status = CORE_RPC_STATUS_OK; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::on_set_limit(const COMMAND_RPC_SET_LIMIT::request& req, COMMAND_RPC_SET_LIMIT::response& res) + { + // -1 = reset to default + // 0 = do not modify + + if (req.limit_down > 0) + { + epee::net_utils::connection_basic::set_rate_down_limit(req.limit_down); + } + else if (req.limit_down < 0) + { + if (req.limit_down != -1) + { + res.status = CORE_RPC_ERROR_CODE_WRONG_PARAM; + return false; + } + epee::net_utils::connection_basic::set_rate_down_limit(nodetool::default_limit_down * 1024); + } + + if (req.limit_up > 0) + { + epee::net_utils::connection_basic::set_rate_up_limit(req.limit_up); + } + else if (req.limit_up < 0) + { + if (req.limit_up != -1) + { + res.status = CORE_RPC_ERROR_CODE_WRONG_PARAM; + return false; + } + epee::net_utils::connection_basic::set_rate_up_limit(nodetool::default_limit_up * 1024); + } + + res.limit_down = epee::net_utils::connection_basic::get_rate_down_limit(); + res.limit_up = epee::net_utils::connection_basic::get_rate_up_limit(); + res.status = CORE_RPC_STATUS_OK; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_out_peers(const COMMAND_RPC_OUT_PEERS::request& req, COMMAND_RPC_OUT_PEERS::response& res) { // TODO @@ -1560,8 +1632,10 @@ namespace cryptonote static const char software[] = "monero"; #ifdef BUILD_TAG static const char buildtag[] = BOOST_PP_STRINGIZE(BUILD_TAG); + static const char subdir[] = "cli"; #else static const char buildtag[] = "source"; + static const char subdir[] = "source"; #endif if (req.command != "check" && req.command != "download" && req.command != "update") @@ -1584,8 +1658,8 @@ namespace cryptonote } res.update = true; res.version = version; - res.user_uri = tools::get_update_url(software, "cli", buildtag, version, true); - res.auto_uri = tools::get_update_url(software, "cli", buildtag, version, false); + res.user_uri = tools::get_update_url(software, subdir, buildtag, version, true); + res.auto_uri = tools::get_update_url(software, subdir, buildtag, version, false); res.hash = hash; if (req.command == "check") { @@ -1703,11 +1777,7 @@ namespace cryptonote } crypto::hash top_hash; - if (!m_core.get_blockchain_top(res.height, top_hash)) - { - res.status = "Failed"; - return false; - } + m_core.get_blockchain_top(res.height, top_hash); ++res.height; // turn top block height into blockchain height res.target_height = m_core.get_target_blockchain_height(); diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index b526277a8..73a308a72 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -97,6 +97,8 @@ namespace cryptonote MAP_URI_AUTO_JON2("/get_transaction_pool_stats", on_get_transaction_pool_stats, COMMAND_RPC_GET_TRANSACTION_POOL_STATS) MAP_URI_AUTO_JON2_IF("/stop_daemon", on_stop_daemon, COMMAND_RPC_STOP_DAEMON, !m_restricted) MAP_URI_AUTO_JON2("/getinfo", on_get_info, COMMAND_RPC_GET_INFO) + MAP_URI_AUTO_JON2("/get_limit", on_get_limit, COMMAND_RPC_GET_LIMIT) + MAP_URI_AUTO_JON2_IF("/set_limit", on_set_limit, COMMAND_RPC_SET_LIMIT, !m_restricted) MAP_URI_AUTO_JON2_IF("/out_peers", on_out_peers, COMMAND_RPC_OUT_PEERS, !m_restricted) MAP_URI_AUTO_JON2_IF("/start_save_graph", on_start_save_graph, COMMAND_RPC_START_SAVE_GRAPH, !m_restricted) MAP_URI_AUTO_JON2_IF("/stop_save_graph", on_stop_save_graph, COMMAND_RPC_STOP_SAVE_GRAPH, !m_restricted) @@ -125,7 +127,7 @@ namespace cryptonote MAP_JON_RPC_WE_IF("get_alternate_chains",on_get_alternate_chains, COMMAND_RPC_GET_ALTERNATE_CHAINS, !m_restricted) MAP_JON_RPC_WE_IF("relay_tx", on_relay_tx, COMMAND_RPC_RELAY_TX, !m_restricted) MAP_JON_RPC_WE_IF("sync_info", on_sync_info, COMMAND_RPC_SYNC_INFO, !m_restricted) - MAP_JON_RPC_WE_IF("get_txpool_backlog", on_get_txpool_backlog, COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG, !m_restricted) + MAP_JON_RPC_WE("get_txpool_backlog", on_get_txpool_backlog, COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG) END_JSON_RPC_MAP() END_URI_MAP2() @@ -155,6 +157,8 @@ namespace cryptonote bool on_get_transaction_pool_hashes(const COMMAND_RPC_GET_TRANSACTION_POOL_HASHES::request& req, COMMAND_RPC_GET_TRANSACTION_POOL_HASHES::response& res); bool on_get_transaction_pool_stats(const COMMAND_RPC_GET_TRANSACTION_POOL_STATS::request& req, COMMAND_RPC_GET_TRANSACTION_POOL_STATS::response& res); bool on_stop_daemon(const COMMAND_RPC_STOP_DAEMON::request& req, COMMAND_RPC_STOP_DAEMON::response& res); + bool on_get_limit(const COMMAND_RPC_GET_LIMIT::request& req, COMMAND_RPC_GET_LIMIT::response& res); + bool on_set_limit(const COMMAND_RPC_SET_LIMIT::request& req, COMMAND_RPC_SET_LIMIT::response& res); bool on_out_peers(const COMMAND_RPC_OUT_PEERS::request& req, COMMAND_RPC_OUT_PEERS::response& res); bool on_start_save_graph(const COMMAND_RPC_START_SAVE_GRAPH::request& req, COMMAND_RPC_START_SAVE_GRAPH::response& res); bool on_stop_save_graph(const COMMAND_RPC_STOP_SAVE_GRAPH::request& req, COMMAND_RPC_STOP_SAVE_GRAPH::response& res); diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 88327dd75..85ac2ca30 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -49,7 +49,7 @@ namespace cryptonote // advance which version they will stop working with // Don't go over 32767 for any of these #define CORE_RPC_VERSION_MAJOR 1 -#define CORE_RPC_VERSION_MINOR 13 +#define CORE_RPC_VERSION_MINOR 14 #define MAKE_CORE_RPC_VERSION(major,minor) (((major)<<16)|(minor)) #define CORE_RPC_VERSION MAKE_CORE_RPC_VERSION(CORE_RPC_VERSION_MAJOR, CORE_RPC_VERSION_MINOR) @@ -215,6 +215,7 @@ namespace cryptonote std::string as_json; bool in_pool; uint64_t block_height; + uint64_t block_timestamp; std::vector<uint64_t> output_indices; BEGIN_KV_SERIALIZE_MAP() @@ -223,6 +224,7 @@ namespace cryptonote KV_SERIALIZE(as_json) KV_SERIALIZE(in_pool) KV_SERIALIZE(block_height) + KV_SERIALIZE(block_timestamp) KV_SERIALIZE(output_indices) END_KV_SERIALIZE_MAP() }; @@ -979,8 +981,11 @@ namespace cryptonote struct response { std::string status; + std::string categories; + BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(status) + KV_SERIALIZE(categories) END_KV_SERIALIZE_MAP() }; }; @@ -1242,6 +1247,55 @@ namespace cryptonote }; }; + struct COMMAND_RPC_GET_LIMIT + { + struct request + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string status; + uint64_t limit_up; + uint64_t limit_down; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(status) + KV_SERIALIZE(limit_up) + KV_SERIALIZE(limit_down) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_SET_LIMIT + { + struct request + { + int64_t limit_down; + int64_t limit_up; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(limit_down) + KV_SERIALIZE(limit_up) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string status; + uint64_t limit_up; + uint64_t limit_down; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(status) + KV_SERIALIZE(limit_up) + KV_SERIALIZE(limit_down) + END_KV_SERIALIZE_MAP() + }; + }; + struct COMMAND_RPC_OUT_PEERS { struct request diff --git a/src/rpc/core_rpc_server_error_codes.h b/src/rpc/core_rpc_server_error_codes.h index 269cce2b1..bd90d37aa 100644 --- a/src/rpc/core_rpc_server_error_codes.h +++ b/src/rpc/core_rpc_server_error_codes.h @@ -41,5 +41,6 @@ #define CORE_RPC_ERROR_CODE_CORE_BUSY -9 #define CORE_RPC_ERROR_CODE_WRONG_BLOCKBLOB_SIZE -10 #define CORE_RPC_ERROR_CODE_UNSUPPORTED_RPC -11 +#define CORE_RPC_ERROR_CODE_MINING_TO_SUBADDRESS -12 diff --git a/src/rpc/daemon_handler.cpp b/src/rpc/daemon_handler.cpp new file mode 100644 index 000000000..4d3fbf491 --- /dev/null +++ b/src/rpc/daemon_handler.cpp @@ -0,0 +1,894 @@ +// Copyright (c) 2017, 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 "daemon_handler.h" + +// likely included by daemon_handler.h's includes, +// but including here for clarity +#include "cryptonote_core/cryptonote_core.h" +#include "cryptonote_basic/cryptonote_format_utils.h" +#include "cryptonote_protocol/blobdatatype.h" +#include "ringct/rctSigs.h" + +namespace cryptonote +{ + +namespace rpc +{ + + void DaemonHandler::handle(const GetHeight::Request& req, GetHeight::Response& res) + { + res.height = m_core.get_current_blockchain_height(); + + res.status = Message::STATUS_OK; + } + + void DaemonHandler::handle(const GetBlocksFast::Request& req, GetBlocksFast::Response& res) + { + std::list<std::pair<blobdata, std::list<blobdata> > > blocks; + + if(!m_core.find_blockchain_supplement(req.start_height, req.block_ids, blocks, res.current_height, res.start_height, COMMAND_RPC_GET_BLOCKS_FAST_MAX_COUNT)) + { + res.status = Message::STATUS_FAILED; + res.error_details = "core::find_blockchain_supplement() returned false"; + return; + } + + res.blocks.resize(blocks.size()); + res.output_indices.resize(blocks.size()); + + //TODO: really need to switch uses of std::list to std::vector unless + // it's a huge performance concern + + auto it = blocks.begin(); + + uint64_t block_count = 0; + while (it != blocks.end()) + { + cryptonote::rpc::block_with_transactions& bwt = res.blocks[block_count]; + + if (!parse_and_validate_block_from_blob(it->first, bwt.block)) + { + res.blocks.clear(); + res.output_indices.clear(); + res.status = Message::STATUS_FAILED; + res.error_details = "failed retrieving a requested block"; + return; + } + + if (it->second.size() != bwt.block.tx_hashes.size()) + { + res.blocks.clear(); + res.output_indices.clear(); + res.status = Message::STATUS_FAILED; + res.error_details = "incorrect number of transactions retrieved for block"; + return; + } + std::list<transaction> txs; + for (const auto& blob : it->second) + { + txs.resize(txs.size() + 1); + if (!parse_and_validate_tx_from_blob(blob, txs.back())) + { + res.blocks.clear(); + res.output_indices.clear(); + res.status = Message::STATUS_FAILED; + res.error_details = "failed retrieving a requested transaction"; + return; + } + } + + cryptonote::rpc::block_output_indices& indices = res.output_indices[block_count]; + + // miner tx output indices + { + cryptonote::rpc::tx_output_indices tx_indices; + bool r = m_core.get_tx_outputs_gindexs(get_transaction_hash(bwt.block.miner_tx), tx_indices); + if (!r) + { + res.status = Message::STATUS_FAILED; + res.error_details = "core::get_tx_outputs_gindexs() returned false"; + return; + } + indices.push_back(tx_indices); + } + + // assume each block returned is returned with all its transactions + // in the correct order. + auto tx_it = txs.begin(); + for (const crypto::hash& h : bwt.block.tx_hashes) + { + bwt.transactions.emplace(h, *tx_it); + tx_it++; + + cryptonote::rpc::tx_output_indices tx_indices; + bool r = m_core.get_tx_outputs_gindexs(h, tx_indices); + if (!r) + { + res.status = Message::STATUS_FAILED; + res.error_details = "core::get_tx_outputs_gindexs() returned false"; + return; + } + + indices.push_back(tx_indices); + } + + it++; + block_count++; + } + + res.status = Message::STATUS_OK; + } + + void DaemonHandler::handle(const GetHashesFast::Request& req, GetHashesFast::Response& res) + { + res.start_height = req.start_height; + + auto& chain = m_core.get_blockchain_storage(); + + if (!chain.find_blockchain_supplement(req.known_hashes, res.hashes, res.start_height, res.current_height)) + { + res.status = Message::STATUS_FAILED; + res.error_details = "Blockchain::find_blockchain_supplement() returned false"; + return; + } + + res.status = Message::STATUS_OK; + } + + void DaemonHandler::handle(const GetTransactions::Request& req, GetTransactions::Response& res) + { + std::list<cryptonote::transaction> found_txs; + std::list<crypto::hash> missed_hashes; + + bool r = m_core.get_transactions(req.tx_hashes, found_txs, missed_hashes); + + // TODO: consider fixing core::get_transactions to not hide exceptions + if (!r) + { + res.status = Message::STATUS_FAILED; + res.error_details = "core::get_transactions() returned false (exception caught there)"; + return; + } + + size_t num_found = found_txs.size(); + + // std::list is annoying + std::vector<cryptonote::transaction> found_txs_vec + { + std::make_move_iterator(std::begin(found_txs)), + std::make_move_iterator(std::end(found_txs)) + }; + + std::vector<crypto::hash> missed_vec + { + std::make_move_iterator(std::begin(missed_hashes)), + std::make_move_iterator(std::end(missed_hashes)) + }; + + std::vector<uint64_t> heights(num_found); + std::vector<bool> in_pool(num_found, false); + std::vector<crypto::hash> found_hashes(num_found); + + for (size_t i=0; i < num_found; i++) + { + found_hashes[i] = get_transaction_hash(found_txs_vec[i]); + heights[i] = m_core.get_blockchain_storage().get_db().get_tx_block_height(found_hashes[i]); + } + + // if any missing from blockchain, check in tx pool + if (!missed_vec.empty()) + { + std::list<cryptonote::transaction> pool_txs; + + m_core.get_pool_transactions(pool_txs); + + for (const auto& tx : pool_txs) + { + crypto::hash h = get_transaction_hash(tx); + + auto itr = std::find(missed_vec.begin(), missed_vec.end(), h); + + if (itr != missed_vec.end()) + { + found_hashes.push_back(h); + found_txs_vec.push_back(tx); + heights.push_back(std::numeric_limits<uint64_t>::max()); + in_pool.push_back(true); + missed_vec.erase(itr); + } + } + } + + for (size_t i=0; i < found_hashes.size(); i++) + { + cryptonote::rpc::transaction_info info; + info.height = heights[i]; + info.in_pool = in_pool[i]; + info.transaction = std::move(found_txs_vec[i]); + + res.txs.emplace(found_hashes[i], std::move(info)); + } + + res.missed_hashes = std::move(missed_vec); + res.status = Message::STATUS_OK; + } + + void DaemonHandler::handle(const KeyImagesSpent::Request& req, KeyImagesSpent::Response& res) + { + res.spent_status.resize(req.key_images.size(), KeyImagesSpent::STATUS::UNSPENT); + + std::vector<bool> chain_spent_status; + std::vector<bool> pool_spent_status; + + m_core.are_key_images_spent(req.key_images, chain_spent_status); + m_core.are_key_images_spent_in_pool(req.key_images, pool_spent_status); + + if ((chain_spent_status.size() != req.key_images.size()) || (pool_spent_status.size() != req.key_images.size())) + { + res.status = Message::STATUS_FAILED; + res.error_details = "tx_pool::have_key_images_as_spent() gave vectors of wrong size(s)."; + return; + } + + for(size_t i=0; i < req.key_images.size(); i++) + { + if ( chain_spent_status[i] ) + { + res.spent_status[i] = KeyImagesSpent::STATUS::SPENT_IN_BLOCKCHAIN; + } + else if ( pool_spent_status[i] ) + { + res.spent_status[i] = KeyImagesSpent::STATUS::SPENT_IN_POOL; + } + } + + res.status = Message::STATUS_OK; + } + + void DaemonHandler::handle(const GetTxGlobalOutputIndices::Request& req, GetTxGlobalOutputIndices::Response& res) + { + if (!m_core.get_tx_outputs_gindexs(req.tx_hash, res.output_indices)) + { + res.status = Message::STATUS_FAILED; + res.error_details = "core::get_tx_outputs_gindexs() returned false"; + return; + } + + res.status = Message::STATUS_OK; + + } + + //TODO: handle "restricted" RPC + void DaemonHandler::handle(const GetRandomOutputsForAmounts::Request& req, GetRandomOutputsForAmounts::Response& res) + { + auto& chain = m_core.get_blockchain_storage(); + + try + { + for (const uint64_t& amount : req.amounts) + { + std::vector<uint64_t> indices = chain.get_random_outputs(amount, req.count); + + outputs_for_amount ofa; + + ofa.resize(indices.size()); + + for (size_t i = 0; i < indices.size(); i++) + { + crypto::public_key key = chain.get_output_key(amount, indices[i]); + ofa[i].amount_index = indices[i]; + ofa[i].key = key; + } + + amount_with_random_outputs amt; + amt.amount = amount; + amt.outputs = ofa; + + res.amounts_with_outputs.push_back(amt); + } + + res.status = Message::STATUS_OK; + } + catch (const std::exception& e) + { + res.status = Message::STATUS_FAILED; + res.error_details = e.what(); + } + } + + void DaemonHandler::handle(const SendRawTx::Request& req, SendRawTx::Response& res) + { + auto tx_blob = cryptonote::tx_to_blob(req.tx); + + cryptonote_connection_context fake_context = AUTO_VAL_INIT(fake_context); + tx_verification_context tvc = AUTO_VAL_INIT(tvc); + + if(!m_core.handle_incoming_tx(tx_blob, tvc, false, false, !req.relay) || tvc.m_verifivation_failed) + { + if (tvc.m_verifivation_failed) + { + LOG_PRINT_L0("[on_send_raw_tx]: tx verification failed"); + } + else + { + LOG_PRINT_L0("[on_send_raw_tx]: Failed to process tx"); + } + res.status = Message::STATUS_FAILED; + res.error_details = ""; + + if (tvc.m_low_mixin) + { + res.error_details = "mixin too low"; + } + if (tvc.m_double_spend) + { + if (!res.error_details.empty()) res.error_details += " and "; + res.error_details = "double spend"; + } + if (tvc.m_invalid_input) + { + if (!res.error_details.empty()) res.error_details += " and "; + res.error_details = "invalid input"; + } + if (tvc.m_invalid_output) + { + if (!res.error_details.empty()) res.error_details += " and "; + res.error_details = "invalid output"; + } + if (tvc.m_too_big) + { + if (!res.error_details.empty()) res.error_details += " and "; + res.error_details = "too big"; + } + if (tvc.m_overspend) + { + if (!res.error_details.empty()) res.error_details += " and "; + res.error_details = "overspend"; + } + if (tvc.m_fee_too_low) + { + if (!res.error_details.empty()) res.error_details += " and "; + res.error_details = "fee too low"; + } + if (tvc.m_not_rct) + { + if (!res.error_details.empty()) res.error_details += " and "; + res.error_details = "tx is not ringct"; + } + if (res.error_details.empty()) + { + res.error_details = "an unknown issue was found with the transaction"; + } + + return; + } + + if(!tvc.m_should_be_relayed || !req.relay) + { + LOG_PRINT_L0("[on_send_raw_tx]: tx accepted, but not relayed"); + res.error_details = "Not relayed"; + res.relayed = false; + res.status = Message::STATUS_OK; + + return; + } + + NOTIFY_NEW_TRANSACTIONS::request r; + r.txs.push_back(tx_blob); + m_core.get_protocol()->relay_transactions(r, fake_context); + + //TODO: make sure that tx has reached other nodes here, probably wait to receive reflections from other nodes + res.status = Message::STATUS_OK; + res.relayed = true; + + return; + } + + void DaemonHandler::handle(const StartMining::Request& req, StartMining::Response& res) + { + cryptonote::address_parse_info info; + if(!get_account_address_from_str(info, m_core.get_testnet(), req.miner_address)) + { + res.error_details = "Failed, wrong address"; + LOG_PRINT_L0(res.error_details); + res.status = Message::STATUS_FAILED; + return; + } + if (info.is_subaddress) + { + res.error_details = "Failed, mining to subaddress isn't supported yet"; + LOG_PRINT_L0(res.error_details); + res.status = Message::STATUS_FAILED; + return; + } + + unsigned int concurrency_count = boost::thread::hardware_concurrency() * 4; + + // if we couldn't detect threads, set it to a ridiculously high number + if(concurrency_count == 0) + { + concurrency_count = 257; + } + + // if there are more threads requested than the hardware supports + // then we fail and log that. + if(req.threads_count > concurrency_count) + { + res.error_details = "Failed, too many threads relative to CPU cores."; + LOG_PRINT_L0(res.error_details); + res.status = Message::STATUS_FAILED; + return; + } + + boost::thread::attributes attrs; + attrs.set_stack_size(THREAD_STACK_SIZE); + + if(!m_core.get_miner().start(info.address, static_cast<size_t>(req.threads_count), attrs, req.do_background_mining, req.ignore_battery)) + { + res.error_details = "Failed, mining not started"; + LOG_PRINT_L0(res.error_details); + res.status = Message::STATUS_FAILED; + return; + } + res.status = Message::STATUS_OK; + res.error_details = ""; + + } + + void DaemonHandler::handle(const GetInfo::Request& req, GetInfo::Response& res) + { + res.info.height = m_core.get_current_blockchain_height(); + + res.info.target_height = m_core.get_target_blockchain_height(); + + if (res.info.height > res.info.target_height) + { + res.info.target_height = res.info.height; + } + + auto& chain = m_core.get_blockchain_storage(); + + res.info.difficulty = chain.get_difficulty_for_next_block(); + + res.info.target = chain.get_difficulty_target(); + + res.info.tx_count = chain.get_total_transactions() - res.info.height; //without coinbase + + res.info.tx_pool_size = m_core.get_pool_transactions_count(); + + res.info.alt_blocks_count = chain.get_alternative_blocks_count(); + + uint64_t total_conn = m_p2p.get_connections_count(); + res.info.outgoing_connections_count = m_p2p.get_outgoing_connections_count(); + res.info.incoming_connections_count = total_conn - res.info.outgoing_connections_count; + + res.info.white_peerlist_size = m_p2p.get_peerlist_manager().get_white_peers_count(); + + res.info.grey_peerlist_size = m_p2p.get_peerlist_manager().get_gray_peers_count(); + + res.info.testnet = m_core.get_testnet(); + res.info.cumulative_difficulty = m_core.get_blockchain_storage().get_db().get_block_cumulative_difficulty(res.info.height - 1); + res.info.block_size_limit = m_core.get_blockchain_storage().get_current_cumulative_blocksize_limit(); + res.info.start_time = (uint64_t)m_core.get_start_time(); + + res.status = Message::STATUS_OK; + res.error_details = ""; + } + + void DaemonHandler::handle(const StopMining::Request& req, StopMining::Response& res) + { + if(!m_core.get_miner().stop()) + { + res.error_details = "Failed, mining not stopped"; + LOG_PRINT_L0(res.error_details); + res.status = Message::STATUS_FAILED; + return; + } + + res.status = Message::STATUS_OK; + res.error_details = ""; + } + + void DaemonHandler::handle(const MiningStatus::Request& req, MiningStatus::Response& res) + { + const cryptonote::miner& lMiner = m_core.get_miner(); + res.active = lMiner.is_mining(); + res.is_background_mining_enabled = lMiner.get_is_background_mining_enabled(); + + if ( lMiner.is_mining() ) { + res.speed = lMiner.get_speed(); + res.threads_count = lMiner.get_threads_count(); + const account_public_address& lMiningAdr = lMiner.get_mining_address(); + res.address = get_account_address_as_str(m_core.get_testnet(), false, lMiningAdr); + } + + res.status = Message::STATUS_OK; + res.error_details = ""; + } + + void DaemonHandler::handle(const SaveBC::Request& req, SaveBC::Response& res) + { + if (!m_core.get_blockchain_storage().store_blockchain()) + { + res.status = Message::STATUS_FAILED; + res.error_details = "Error storing the blockchain"; + } + else + { + res.status = Message::STATUS_OK; + } + } + + void DaemonHandler::handle(const GetBlockHash::Request& req, GetBlockHash::Response& res) + { + if (m_core.get_current_blockchain_height() <= req.height) + { + res.hash = crypto::null_hash; + res.status = Message::STATUS_FAILED; + res.error_details = "height given is higher than current chain height"; + return; + } + + res.hash = m_core.get_block_id_by_height(req.height); + + res.status = Message::STATUS_OK; + } + + void DaemonHandler::handle(const GetBlockTemplate::Request& req, GetBlockTemplate::Response& res) + { + res.status = Message::STATUS_FAILED; + res.error_details = "RPC method not yet implemented."; + } + + void DaemonHandler::handle(const SubmitBlock::Request& req, SubmitBlock::Response& res) + { + res.status = Message::STATUS_FAILED; + res.error_details = "RPC method not yet implemented."; + } + + void DaemonHandler::handle(const GetLastBlockHeader::Request& req, GetLastBlockHeader::Response& res) + { + const crypto::hash block_hash = m_core.get_tail_id(); + + if (!getBlockHeaderByHash(block_hash, res.header)) + { + res.status = Message::STATUS_FAILED; + res.error_details = "Requested block does not exist"; + return; + } + + res.status = Message::STATUS_OK; + } + + void DaemonHandler::handle(const GetBlockHeaderByHash::Request& req, GetBlockHeaderByHash::Response& res) + { + if (!getBlockHeaderByHash(req.hash, res.header)) + { + res.status = Message::STATUS_FAILED; + res.error_details = "Requested block does not exist"; + return; + } + + res.status = Message::STATUS_OK; + } + + void DaemonHandler::handle(const GetBlockHeaderByHeight::Request& req, GetBlockHeaderByHeight::Response& res) + { + const crypto::hash block_hash = m_core.get_block_id_by_height(req.height); + + if (!getBlockHeaderByHash(block_hash, res.header)) + { + res.status = Message::STATUS_FAILED; + res.error_details = "Requested block does not exist"; + return; + } + + res.status = Message::STATUS_OK; + } + + void DaemonHandler::handle(const GetBlockHeadersByHeight::Request& req, GetBlockHeadersByHeight::Response& res) + { + res.headers.resize(req.heights.size()); + + for (size_t i=0; i < req.heights.size(); i++) + { + const crypto::hash block_hash = m_core.get_block_id_by_height(req.heights[i]); + + if (!getBlockHeaderByHash(block_hash, res.headers[i])) + { + res.status = Message::STATUS_FAILED; + res.error_details = "A requested block does not exist"; + return; + } + } + + res.status = Message::STATUS_OK; + } + + void DaemonHandler::handle(const GetBlock::Request& req, GetBlock::Response& res) + { + res.status = Message::STATUS_FAILED; + res.error_details = "RPC method not yet implemented."; + } + + void DaemonHandler::handle(const GetPeerList::Request& req, GetPeerList::Response& res) + { + res.status = Message::STATUS_FAILED; + res.error_details = "RPC method not yet implemented."; + } + + void DaemonHandler::handle(const SetLogHashRate::Request& req, SetLogHashRate::Response& res) + { + res.status = Message::STATUS_FAILED; + res.error_details = "RPC method not yet implemented."; + } + + void DaemonHandler::handle(const SetLogLevel::Request& req, SetLogLevel::Response& res) + { + if (req.level < 0 || req.level > 4) + { + res.status = Message::STATUS_FAILED; + res.error_details = "Error: log level not valid"; + } + else + { + res.status = Message::STATUS_OK; + mlog_set_log_level(req.level); + } + } + + void DaemonHandler::handle(const GetTransactionPool::Request& req, GetTransactionPool::Response& res) + { + bool r = m_core.get_pool_for_rpc(res.transactions, res.key_images); + + if (!r) res.status = Message::STATUS_FAILED; + else res.status = Message::STATUS_OK; + } + + void DaemonHandler::handle(const GetConnections::Request& req, GetConnections::Response& res) + { + res.status = Message::STATUS_FAILED; + res.error_details = "RPC method not yet implemented."; + } + + void DaemonHandler::handle(const GetBlockHeadersRange::Request& req, GetBlockHeadersRange::Response& res) + { + res.status = Message::STATUS_FAILED; + res.error_details = "RPC method not yet implemented."; + } + + void DaemonHandler::handle(const StopDaemon::Request& req, StopDaemon::Response& res) + { + res.status = Message::STATUS_FAILED; + res.error_details = "RPC method not yet implemented."; + } + + void DaemonHandler::handle(const StartSaveGraph::Request& req, StartSaveGraph::Response& res) + { + res.status = Message::STATUS_FAILED; + res.error_details = "RPC method not yet implemented."; + } + + void DaemonHandler::handle(const StopSaveGraph::Request& req, StopSaveGraph::Response& res) + { + res.status = Message::STATUS_FAILED; + res.error_details = "RPC method not yet implemented."; + } + + void DaemonHandler::handle(const HardForkInfo::Request& req, HardForkInfo::Response& res) + { + const Blockchain &blockchain = m_core.get_blockchain_storage(); + uint8_t version = req.version > 0 ? req.version : blockchain.get_ideal_hard_fork_version(); + res.info.version = blockchain.get_current_hard_fork_version(); + res.info.enabled = blockchain.get_hard_fork_voting_info(version, res.info.window, res.info.votes, res.info.threshold, res.info.earliest_height, res.info.voting); + res.info.state = blockchain.get_hard_fork_state(); + res.status = Message::STATUS_OK; + } + + void DaemonHandler::handle(const GetBans::Request& req, GetBans::Response& res) + { + res.status = Message::STATUS_FAILED; + res.error_details = "RPC method not yet implemented."; + } + + void DaemonHandler::handle(const SetBans::Request& req, SetBans::Response& res) + { + res.status = Message::STATUS_FAILED; + res.error_details = "RPC method not yet implemented."; + } + + void DaemonHandler::handle(const FlushTransactionPool::Request& req, FlushTransactionPool::Response& res) + { + res.status = Message::STATUS_FAILED; + res.error_details = "RPC method not yet implemented."; + } + + void DaemonHandler::handle(const GetOutputHistogram::Request& req, GetOutputHistogram::Response& res) + { + std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t> > histogram; + try + { + histogram = m_core.get_blockchain_storage().get_output_histogram(req.amounts, req.unlocked, req.recent_cutoff); + } + catch (const std::exception &e) + { + res.status = Message::STATUS_FAILED; + res.error_details = e.what(); + return; + } + + res.histogram.clear(); + res.histogram.reserve(histogram.size()); + for (const auto &i: histogram) + { + if (std::get<0>(i.second) >= req.min_count && (std::get<0>(i.second) <= req.max_count || req.max_count == 0)) + res.histogram.emplace_back(output_amount_count{i.first, std::get<0>(i.second), std::get<1>(i.second), std::get<2>(i.second)}); + } + + res.status = Message::STATUS_OK; + } + + void DaemonHandler::handle(const GetOutputKeys::Request& req, GetOutputKeys::Response& res) + { + try + { + for (const auto& i : req.outputs) + { + crypto::public_key key; + rct::key mask; + bool unlocked; + m_core.get_blockchain_storage().get_output_key_mask_unlocked(i.amount, i.index, key, mask, unlocked); + res.keys.emplace_back(output_key_mask_unlocked{key, mask, unlocked}); + } + } + catch (const std::exception& e) + { + res.status = Message::STATUS_FAILED; + res.error_details = e.what(); + return; + } + + res.status = Message::STATUS_OK; + } + + void DaemonHandler::handle(const GetRPCVersion::Request& req, GetRPCVersion::Response& res) + { + res.version = DAEMON_RPC_VERSION_ZMQ; + res.status = Message::STATUS_OK; + } + + void DaemonHandler::handle(const GetPerKBFeeEstimate::Request& req, GetPerKBFeeEstimate::Response& res) + { + res.estimated_fee_per_kb = m_core.get_blockchain_storage().get_dynamic_per_kb_fee_estimate(req.num_grace_blocks); + res.status = Message::STATUS_OK; + } + + bool DaemonHandler::getBlockHeaderByHash(const crypto::hash& hash_in, cryptonote::rpc::BlockHeaderResponse& header) + { + block b; + + if (!m_core.get_block_by_hash(hash_in, b)) + { + return false; + } + + header.hash = hash_in; + header.height = boost::get<txin_gen>(b.miner_tx.vin.front()).height; + + header.major_version = b.major_version; + header.minor_version = b.minor_version; + header.timestamp = b.timestamp; + header.nonce = b.nonce; + header.prev_id = b.prev_id; + + header.depth = m_core.get_current_blockchain_height() - header.height - 1; + + header.reward = 0; + for (const auto& out : b.miner_tx.vout) + { + header.reward += out.amount; + } + + header.difficulty = m_core.get_blockchain_storage().block_difficulty(header.height); + + return true; + } + + std::string DaemonHandler::handle(const std::string& request) + { + MDEBUG("Handling RPC request: " << request); + + Message* resp_message = NULL; + + try + { + FullMessage req_full(request, true); + + rapidjson::Value& req_json = req_full.getMessage(); + + const std::string request_type = req_full.getRequestType(); + + // create correct Message subclass and call handle() on it + REQ_RESP_TYPES_MACRO(request_type, GetHeight, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, GetBlocksFast, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, GetHashesFast, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, GetTransactions, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, KeyImagesSpent, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, GetTxGlobalOutputIndices, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, GetRandomOutputsForAmounts, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, SendRawTx, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, GetInfo, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, StartMining, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, StopMining, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, MiningStatus, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, SaveBC, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, GetBlockHash, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, GetLastBlockHeader, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, GetBlockHeaderByHash, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, GetBlockHeaderByHeight, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, GetBlockHeadersByHeight, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, GetPeerList, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, SetLogLevel, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, GetTransactionPool, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, HardForkInfo, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, GetOutputHistogram, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, GetOutputKeys, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, GetRPCVersion, req_json, resp_message, handle); + REQ_RESP_TYPES_MACRO(request_type, GetPerKBFeeEstimate, req_json, resp_message, handle); + + // if none of the request types matches + if (resp_message == NULL) + { + return BAD_REQUEST(request_type, req_full.getID()); + } + + FullMessage resp_full = FullMessage::responseMessage(resp_message, req_full.getID()); + + const std::string response = resp_full.getJson(); + delete resp_message; + resp_message = NULL; + + MDEBUG("Returning RPC response: " << response); + + return response; + } + catch (const std::exception& e) + { + if (resp_message) + { + delete resp_message; + } + + return BAD_JSON(e.what()); + } + } + +} // namespace rpc + +} // namespace cryptonote diff --git a/src/rpc/daemon_handler.h b/src/rpc/daemon_handler.h new file mode 100644 index 000000000..0d356bad2 --- /dev/null +++ b/src/rpc/daemon_handler.h @@ -0,0 +1,145 @@ +// Copyright (c) 2017, 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. + +#pragma once + +#include "daemon_messages.h" +#include "daemon_rpc_version.h" +#include "rpc_handler.h" +#include "cryptonote_core/cryptonote_core.h" +#include "cryptonote_protocol/cryptonote_protocol_handler.h" +#include "p2p/net_node.h" + +namespace +{ + typedef nodetool::node_server<cryptonote::t_cryptonote_protocol_handler<cryptonote::core> > t_p2p; +} // anonymous namespace + +namespace cryptonote +{ + +namespace rpc +{ + +class DaemonHandler : public RpcHandler +{ + public: + + DaemonHandler(cryptonote::core& c, t_p2p& p2p) : m_core(c), m_p2p(p2p) { } + + ~DaemonHandler() { } + + void handle(const GetHeight::Request& req, GetHeight::Response& res); + + void handle(const GetBlocksFast::Request& req, GetBlocksFast::Response& res); + + void handle(const GetHashesFast::Request& req, GetHashesFast::Response& res); + + void handle(const GetTransactions::Request& req, GetTransactions::Response& res); + + void handle(const KeyImagesSpent::Request& req, KeyImagesSpent::Response& res); + + void handle(const GetTxGlobalOutputIndices::Request& req, GetTxGlobalOutputIndices::Response& res); + + void handle(const GetRandomOutputsForAmounts::Request& req, GetRandomOutputsForAmounts::Response& res); + + void handle(const SendRawTx::Request& req, SendRawTx::Response& res); + + void handle(const StartMining::Request& req, StartMining::Response& res); + + void handle(const GetInfo::Request& req, GetInfo::Response& res); + + void handle(const StopMining::Request& req, StopMining::Response& res); + + void handle(const MiningStatus::Request& req, MiningStatus::Response& res); + + void handle(const SaveBC::Request& req, SaveBC::Response& res); + + void handle(const GetBlockHash::Request& req, GetBlockHash::Response& res); + + void handle(const GetBlockTemplate::Request& req, GetBlockTemplate::Response& res); + + void handle(const SubmitBlock::Request& req, SubmitBlock::Response& res); + + void handle(const GetLastBlockHeader::Request& req, GetLastBlockHeader::Response& res); + + void handle(const GetBlockHeaderByHash::Request& req, GetBlockHeaderByHash::Response& res); + + void handle(const GetBlockHeaderByHeight::Request& req, GetBlockHeaderByHeight::Response& res); + + void handle(const GetBlockHeadersByHeight::Request& req, GetBlockHeadersByHeight::Response& res); + + void handle(const GetBlock::Request& req, GetBlock::Response& res); + + void handle(const GetPeerList::Request& req, GetPeerList::Response& res); + + void handle(const SetLogHashRate::Request& req, SetLogHashRate::Response& res); + + void handle(const SetLogLevel::Request& req, SetLogLevel::Response& res); + + void handle(const GetTransactionPool::Request& req, GetTransactionPool::Response& res); + + void handle(const GetConnections::Request& req, GetConnections::Response& res); + + void handle(const GetBlockHeadersRange::Request& req, GetBlockHeadersRange::Response& res); + + void handle(const StopDaemon::Request& req, StopDaemon::Response& res); + + void handle(const StartSaveGraph::Request& req, StartSaveGraph::Response& res); + + void handle(const StopSaveGraph::Request& req, StopSaveGraph::Response& res); + + void handle(const HardForkInfo::Request& req, HardForkInfo::Response& res); + + void handle(const GetBans::Request& req, GetBans::Response& res); + + void handle(const SetBans::Request& req, SetBans::Response& res); + + void handle(const FlushTransactionPool::Request& req, FlushTransactionPool::Response& res); + + void handle(const GetOutputHistogram::Request& req, GetOutputHistogram::Response& res); + + void handle(const GetOutputKeys::Request& req, GetOutputKeys::Response& res); + + void handle(const GetRPCVersion::Request& req, GetRPCVersion::Response& res); + + void handle(const GetPerKBFeeEstimate::Request& req, GetPerKBFeeEstimate::Response& res); + + std::string handle(const std::string& request); + + private: + + bool getBlockHeaderByHash(const crypto::hash& hash_in, cryptonote::rpc::BlockHeaderResponse& response); + + cryptonote::core& m_core; + t_p2p& m_p2p; +}; + +} // namespace rpc + +} // namespace cryptonote diff --git a/src/rpc/daemon_messages.cpp b/src/rpc/daemon_messages.cpp new file mode 100644 index 000000000..640058df9 --- /dev/null +++ b/src/rpc/daemon_messages.cpp @@ -0,0 +1,900 @@ +// Copyright (c) 2016-2017, 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 "daemon_messages.h" +#include "serialization/json_object.h" + +namespace cryptonote +{ + +namespace rpc +{ + +const char* const GetHeight::name = "get_height"; +const char* const GetBlocksFast::name = "get_blocks_fast"; +const char* const GetHashesFast::name = "get_hashes_fast"; +const char* const GetTransactions::name = "get_transactions"; +const char* const KeyImagesSpent::name = "key_images_spent"; +const char* const GetTxGlobalOutputIndices::name = "get_tx_global_output_indices"; +const char* const GetRandomOutputsForAmounts::name = "get_random_outputs_for_amounts"; +const char* const SendRawTx::name = "send_raw_tx"; +const char* const StartMining::name = "start_mining"; +const char* const StopMining::name = "stop_mining"; +const char* const MiningStatus::name = "mining_status"; +const char* const GetInfo::name = "get_info"; +const char* const SaveBC::name = "save_bc"; +const char* const GetBlockHash::name = "get_block_hash"; +const char* const GetLastBlockHeader::name = "get_last_block_header"; +const char* const GetBlockHeaderByHash::name = "get_block_header_by_hash"; +const char* const GetBlockHeaderByHeight::name = "get_block_header_by_height"; +const char* const GetBlockHeadersByHeight::name = "get_block_headers_by_height"; +const char* const GetPeerList::name = "get_peer_list"; +const char* const SetLogLevel::name = "set_log_level"; +const char* const GetTransactionPool::name = "get_transaction_pool"; +const char* const HardForkInfo::name = "hard_fork_info"; +const char* const GetOutputHistogram::name = "get_output_histogram"; +const char* const GetOutputKeys::name = "get_output_keys"; +const char* const GetRPCVersion::name = "get_rpc_version"; +const char* const GetPerKBFeeEstimate::name = "get_dynamic_per_kb_fee_estimate"; + + + + +rapidjson::Value GetHeight::Request::toJson(rapidjson::Document& doc) const +{ + return Message::toJson(doc); +} + +void GetHeight::Request::fromJson(rapidjson::Value& val) +{ +} + +rapidjson::Value GetHeight::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + val.AddMember("height", height, al); + + return val; +} + +void GetHeight::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, height, height); +} + + +rapidjson::Value GetBlocksFast::Request::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, block_ids, block_ids); + val.AddMember("start_height", start_height, al); + val.AddMember("prune", prune, al); + + return val; +} + +void GetBlocksFast::Request::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, block_ids, block_ids); + GET_FROM_JSON_OBJECT(val, start_height, start_height); + GET_FROM_JSON_OBJECT(val, prune, prune); +} + +rapidjson::Value GetBlocksFast::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, blocks, blocks); + val.AddMember("start_height", start_height, al); + val.AddMember("current_height", current_height, al); + INSERT_INTO_JSON_OBJECT(val, doc, output_indices, output_indices); + + return val; +} + +void GetBlocksFast::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, blocks, blocks); + GET_FROM_JSON_OBJECT(val, start_height, start_height); + GET_FROM_JSON_OBJECT(val, current_height, current_height); + GET_FROM_JSON_OBJECT(val, output_indices, output_indices); +} + + +rapidjson::Value GetHashesFast::Request::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, known_hashes, known_hashes); + val.AddMember("start_height", start_height, al); + + return val; +} + +void GetHashesFast::Request::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, known_hashes, known_hashes); + GET_FROM_JSON_OBJECT(val, start_height, start_height); +} + +rapidjson::Value GetHashesFast::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, hashes, hashes); + val.AddMember("start_height", start_height, al); + val.AddMember("current_height", current_height, al); + + return val; +} + +void GetHashesFast::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, hashes, hashes); + GET_FROM_JSON_OBJECT(val, start_height, start_height); + GET_FROM_JSON_OBJECT(val, current_height, current_height); +} + + +rapidjson::Value GetTransactions::Request::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, tx_hashes, tx_hashes); + + return val; +} + +void GetTransactions::Request::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, tx_hashes, tx_hashes); +} + +rapidjson::Value GetTransactions::Response::toJson(rapidjson::Document& doc) const +{ + rapidjson::Value val(rapidjson::kObjectType); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, txs, txs); + INSERT_INTO_JSON_OBJECT(val, doc, missed_hashes, missed_hashes); + + return val; +} + +void GetTransactions::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, txs, txs); + GET_FROM_JSON_OBJECT(val, missed_hashes, missed_hashes); +} + + +rapidjson::Value KeyImagesSpent::Request::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, key_images, key_images); + + return val; +} + +void KeyImagesSpent::Request::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, key_images, key_images); +} + +rapidjson::Value KeyImagesSpent::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, spent_status, spent_status); + + return val; +} + +void KeyImagesSpent::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, spent_status, spent_status); +} + + +rapidjson::Value GetTxGlobalOutputIndices::Request::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, tx_hash, tx_hash); + + return val; +} + +void GetTxGlobalOutputIndices::Request::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, tx_hash, tx_hash); +} + +rapidjson::Value GetTxGlobalOutputIndices::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, output_indices, output_indices); + + return val; +} + +void GetTxGlobalOutputIndices::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, output_indices, output_indices); +} + + +rapidjson::Value GetRandomOutputsForAmounts::Request::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, amounts, amounts); + INSERT_INTO_JSON_OBJECT(val, doc, count, count); + + return val; +} + +void GetRandomOutputsForAmounts::Request::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, amounts, amounts); + GET_FROM_JSON_OBJECT(val, count, count); +} + +rapidjson::Value GetRandomOutputsForAmounts::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, amounts_with_outputs, amounts_with_outputs); + + return val; +} + +void GetRandomOutputsForAmounts::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, amounts_with_outputs, amounts_with_outputs); +} + + +rapidjson::Value SendRawTx::Request::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, tx, tx); + INSERT_INTO_JSON_OBJECT(val, doc, relay, relay); + + return val; +} + +void SendRawTx::Request::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, tx, tx); + GET_FROM_JSON_OBJECT(val, relay, relay); +} + +rapidjson::Value SendRawTx::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, relayed, relayed); + + return val; +} + + +void SendRawTx::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, relayed, relayed); +} + +rapidjson::Value StartMining::Request::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, miner_address, miner_address); + INSERT_INTO_JSON_OBJECT(val, doc, threads_count, threads_count); + INSERT_INTO_JSON_OBJECT(val, doc, do_background_mining, do_background_mining); + INSERT_INTO_JSON_OBJECT(val, doc, ignore_battery, ignore_battery); + + return val; +} + +void StartMining::Request::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, miner_address, miner_address); + GET_FROM_JSON_OBJECT(val, threads_count, threads_count); + GET_FROM_JSON_OBJECT(val, do_background_mining, do_background_mining); + GET_FROM_JSON_OBJECT(val, ignore_battery, ignore_battery); +} + +rapidjson::Value StartMining::Response::toJson(rapidjson::Document& doc) const +{ + return Message::toJson(doc); +} + +void StartMining::Response::fromJson(rapidjson::Value& val) +{ +} + + +rapidjson::Value StopMining::Request::toJson(rapidjson::Document& doc) const +{ + return Message::toJson(doc); +} + +void StopMining::Request::fromJson(rapidjson::Value& val) +{ +} + +rapidjson::Value StopMining::Response::toJson(rapidjson::Document& doc) const +{ + return Message::toJson(doc); +} + +void StopMining::Response::fromJson(rapidjson::Value& val) +{ +} + + +rapidjson::Value MiningStatus::Request::toJson(rapidjson::Document& doc) const +{ + return Message::toJson(doc); +} + +void MiningStatus::Request::fromJson(rapidjson::Value& val) +{ +} + +rapidjson::Value MiningStatus::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, active, active); + INSERT_INTO_JSON_OBJECT(val, doc, speed, speed); + INSERT_INTO_JSON_OBJECT(val, doc, threads_count, threads_count); + INSERT_INTO_JSON_OBJECT(val, doc, address, address); + INSERT_INTO_JSON_OBJECT(val, doc, is_background_mining_enabled, is_background_mining_enabled); + + return val; +} + +void MiningStatus::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, active, active); + GET_FROM_JSON_OBJECT(val, speed, speed); + GET_FROM_JSON_OBJECT(val, threads_count, threads_count); + GET_FROM_JSON_OBJECT(val, address, address); + GET_FROM_JSON_OBJECT(val, is_background_mining_enabled, is_background_mining_enabled); +} + + +rapidjson::Value GetInfo::Request::toJson(rapidjson::Document& doc) const +{ + return Message::toJson(doc); +} + +void GetInfo::Request::fromJson(rapidjson::Value& val) +{ +} + +rapidjson::Value GetInfo::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, info, info); + + return val; +} + +void GetInfo::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, info, info); +} + + +rapidjson::Value SaveBC::Request::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + return val; +} + +void SaveBC::Request::fromJson(rapidjson::Value& val) +{ +} + +rapidjson::Value SaveBC::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + return val; +} + +void SaveBC::Response::fromJson(rapidjson::Value& val) +{ +} + + +rapidjson::Value GetBlockHash::Request::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, height, height); + + return val; +} + +void GetBlockHash::Request::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, height, height); +} + +rapidjson::Value GetBlockHash::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, hash, hash); + + return val; +} + +void GetBlockHash::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, hash, hash); +} + + +rapidjson::Value GetLastBlockHeader::Request::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + return val; +} + +void GetLastBlockHeader::Request::fromJson(rapidjson::Value& val) +{ +} + +rapidjson::Value GetLastBlockHeader::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, header, header); + + return val; +} + +void GetLastBlockHeader::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, header, header); +} + + +rapidjson::Value GetBlockHeaderByHash::Request::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, hash, hash); + + return val; +} + +void GetBlockHeaderByHash::Request::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, hash, hash); +} + +rapidjson::Value GetBlockHeaderByHash::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, header, header); + + return val; +} + +void GetBlockHeaderByHash::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, header, header); +} + + +rapidjson::Value GetBlockHeaderByHeight::Request::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, height, height); + + return val; +} + +void GetBlockHeaderByHeight::Request::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, height, height); +} + +rapidjson::Value GetBlockHeaderByHeight::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, header, header); + + return val; +} + +void GetBlockHeaderByHeight::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, header, header); +} + + +rapidjson::Value GetBlockHeadersByHeight::Request::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, heights, heights); + + return val; +} + +void GetBlockHeadersByHeight::Request::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, heights, heights); +} + +rapidjson::Value GetBlockHeadersByHeight::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, headers, headers); + + return val; +} + +void GetBlockHeadersByHeight::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, headers, headers); +} + + +rapidjson::Value GetPeerList::Request::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + return val; +} + +void GetPeerList::Request::fromJson(rapidjson::Value& val) +{ +} + +rapidjson::Value GetPeerList::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, white_list, white_list); + INSERT_INTO_JSON_OBJECT(val, doc, gray_list, gray_list); + + return val; +} + +void GetPeerList::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, white_list, white_list); + GET_FROM_JSON_OBJECT(val, gray_list, gray_list); +} + + +rapidjson::Value SetLogLevel::Request::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + val.AddMember("level", level, al); + + return val; +} + +void SetLogLevel::Request::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, level, level); +} + +rapidjson::Value SetLogLevel::Response::toJson(rapidjson::Document& doc) const +{ + return Message::toJson(doc); +} + +void SetLogLevel::Response::fromJson(rapidjson::Value& val) +{ +} + + +rapidjson::Value GetTransactionPool::Request::toJson(rapidjson::Document& doc) const +{ + return Message::toJson(doc); +} + +void GetTransactionPool::Request::fromJson(rapidjson::Value& val) +{ +} + +rapidjson::Value GetTransactionPool::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, transactions, transactions); + INSERT_INTO_JSON_OBJECT(val, doc, key_images, key_images); + + return val; +} + +void GetTransactionPool::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, transactions, transactions); + GET_FROM_JSON_OBJECT(val, key_images, key_images); +} + + +rapidjson::Value HardForkInfo::Request::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, version, version); + + return val; +} + +void HardForkInfo::Request::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, version, version); +} + +rapidjson::Value HardForkInfo::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, info, info); + + return val; +} + +void HardForkInfo::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, info, info); +} + + +rapidjson::Value GetOutputHistogram::Request::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, amounts, amounts); + INSERT_INTO_JSON_OBJECT(val, doc, min_count, min_count); + INSERT_INTO_JSON_OBJECT(val, doc, max_count, max_count); + INSERT_INTO_JSON_OBJECT(val, doc, unlocked, unlocked); + INSERT_INTO_JSON_OBJECT(val, doc, recent_cutoff, recent_cutoff); + + return val; +} + +void GetOutputHistogram::Request::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, amounts, amounts); + GET_FROM_JSON_OBJECT(val, min_count, min_count); + GET_FROM_JSON_OBJECT(val, max_count, max_count); + GET_FROM_JSON_OBJECT(val, unlocked, unlocked); + GET_FROM_JSON_OBJECT(val, recent_cutoff, recent_cutoff); +} + +rapidjson::Value GetOutputHistogram::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, histogram, histogram); + + return val; +} + +void GetOutputHistogram::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, histogram, histogram); +} + + +rapidjson::Value GetOutputKeys::Request::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, outputs, outputs); + + return val; +} + +void GetOutputKeys::Request::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, outputs, outputs); +} + +rapidjson::Value GetOutputKeys::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, keys, keys); + + return val; +} + +void GetOutputKeys::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, keys, keys); +} + + +rapidjson::Value GetRPCVersion::Request::toJson(rapidjson::Document& doc) const +{ + return Message::toJson(doc); +} + +void GetRPCVersion::Request::fromJson(rapidjson::Value& val) +{ +} + +rapidjson::Value GetRPCVersion::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, version, version); + + return val; +} + +void GetRPCVersion::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, version, version); +} + +rapidjson::Value GetPerKBFeeEstimate::Request::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, num_grace_blocks, num_grace_blocks); + + return val; +} + +void GetPerKBFeeEstimate::Request::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, num_grace_blocks, num_grace_blocks); +} + +rapidjson::Value GetPerKBFeeEstimate::Response::toJson(rapidjson::Document& doc) const +{ + auto val = Message::toJson(doc); + + auto& al = doc.GetAllocator(); + + INSERT_INTO_JSON_OBJECT(val, doc, estimated_fee_per_kb, estimated_fee_per_kb); + + return val; +} + +void GetPerKBFeeEstimate::Response::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, estimated_fee_per_kb, estimated_fee_per_kb); +} + + +} // namespace rpc + +} // namespace cryptonote diff --git a/src/rpc/daemon_messages.h b/src/rpc/daemon_messages.h new file mode 100644 index 000000000..685f66843 --- /dev/null +++ b/src/rpc/daemon_messages.h @@ -0,0 +1,421 @@ +// Copyright (c) 2016-2017, 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. + +#pragma once + +#include "message.h" +#include "cryptonote_protocol/cryptonote_protocol_defs.h" +#include "rpc/message_data_structs.h" +#include "rpc/daemon_rpc_version.h" +#include "cryptonote_basic/cryptonote_basic.h" + +#define BEGIN_RPC_MESSAGE_CLASS(classname) \ +class classname \ +{ \ + public: \ + static const char* const name; + +#define BEGIN_RPC_MESSAGE_REQUEST \ + class Request : public Message \ + { \ + public: \ + Request() { } \ + ~Request() { } \ + rapidjson::Value toJson(rapidjson::Document& doc) const; \ + void fromJson(rapidjson::Value& val); + +#define BEGIN_RPC_MESSAGE_RESPONSE \ + class Response : public Message \ + { \ + public: \ + Response() { } \ + ~Response() { } \ + rapidjson::Value toJson(rapidjson::Document& doc) const; \ + void fromJson(rapidjson::Value& val); + +#define END_RPC_MESSAGE_REQUEST }; +#define END_RPC_MESSAGE_RESPONSE }; +#define END_RPC_MESSAGE_CLASS }; + +#define COMMA() , + +// NOTE: when using a type with multiple template parameters, +// replace any comma in the template specifier with the macro +// above, or the preprocessor will eat the comma in a bad way. +#define RPC_MESSAGE_MEMBER(type, name) type name; + + +namespace cryptonote +{ + +namespace rpc +{ + +BEGIN_RPC_MESSAGE_CLASS(GetHeight); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(uint64_t, height); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + + +BEGIN_RPC_MESSAGE_CLASS(GetBlocksFast); + BEGIN_RPC_MESSAGE_REQUEST; + RPC_MESSAGE_MEMBER(std::list<crypto::hash>, block_ids); + RPC_MESSAGE_MEMBER(uint64_t, start_height); + RPC_MESSAGE_MEMBER(bool, prune); + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(std::vector<cryptonote::rpc::block_with_transactions>, blocks); + RPC_MESSAGE_MEMBER(uint64_t, start_height); + RPC_MESSAGE_MEMBER(uint64_t, current_height); + RPC_MESSAGE_MEMBER(std::vector<cryptonote::rpc::block_output_indices>, output_indices); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + + +BEGIN_RPC_MESSAGE_CLASS(GetHashesFast); + BEGIN_RPC_MESSAGE_REQUEST; + RPC_MESSAGE_MEMBER(std::list<crypto::hash>, known_hashes); + RPC_MESSAGE_MEMBER(uint64_t, start_height); + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(std::list<crypto::hash>, hashes); + RPC_MESSAGE_MEMBER(uint64_t, start_height); + RPC_MESSAGE_MEMBER(uint64_t, current_height); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + + +BEGIN_RPC_MESSAGE_CLASS(GetTransactions); + BEGIN_RPC_MESSAGE_REQUEST; + RPC_MESSAGE_MEMBER(std::vector<crypto::hash>, tx_hashes); + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(std::unordered_map<crypto::hash COMMA() cryptonote::rpc::transaction_info>, txs); + RPC_MESSAGE_MEMBER(std::vector<crypto::hash>, missed_hashes); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + + +BEGIN_RPC_MESSAGE_CLASS(KeyImagesSpent); + enum STATUS { + UNSPENT = 0, + SPENT_IN_BLOCKCHAIN = 1, + SPENT_IN_POOL = 2, + }; + BEGIN_RPC_MESSAGE_REQUEST; + RPC_MESSAGE_MEMBER(std::vector<crypto::key_image>, key_images); + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(std::vector<uint64_t>, spent_status); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + + +BEGIN_RPC_MESSAGE_CLASS(GetTxGlobalOutputIndices); + BEGIN_RPC_MESSAGE_REQUEST; + RPC_MESSAGE_MEMBER(crypto::hash, tx_hash); + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(std::vector<uint64_t>, output_indices); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + + +BEGIN_RPC_MESSAGE_CLASS(GetRandomOutputsForAmounts); + BEGIN_RPC_MESSAGE_REQUEST; + RPC_MESSAGE_MEMBER(std::vector<uint64_t>, amounts); + RPC_MESSAGE_MEMBER(uint64_t, count); + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(std::vector<amount_with_random_outputs>, amounts_with_outputs); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(SendRawTx); + BEGIN_RPC_MESSAGE_REQUEST; + RPC_MESSAGE_MEMBER(cryptonote::transaction, tx); + RPC_MESSAGE_MEMBER(bool, relay); + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(bool, relayed); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(StartMining); + BEGIN_RPC_MESSAGE_REQUEST; + RPC_MESSAGE_MEMBER(std::string, miner_address); + RPC_MESSAGE_MEMBER(uint64_t, threads_count); + RPC_MESSAGE_MEMBER(bool, do_background_mining); + RPC_MESSAGE_MEMBER(bool, ignore_battery); + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(GetInfo); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(DaemonInfo, info); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(StopMining); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(MiningStatus); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(bool, active); + RPC_MESSAGE_MEMBER(uint64_t, speed); + RPC_MESSAGE_MEMBER(uint64_t, threads_count); + RPC_MESSAGE_MEMBER(std::string, address); + RPC_MESSAGE_MEMBER(bool, is_background_mining_enabled); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(SaveBC); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(GetBlockHash); + BEGIN_RPC_MESSAGE_REQUEST; + RPC_MESSAGE_MEMBER(uint64_t, height); + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(crypto::hash, hash); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(GetBlockTemplate); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(SubmitBlock); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(GetLastBlockHeader); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(cryptonote::rpc::BlockHeaderResponse, header); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(GetBlockHeaderByHash); + BEGIN_RPC_MESSAGE_REQUEST; + RPC_MESSAGE_MEMBER(crypto::hash, hash); + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(cryptonote::rpc::BlockHeaderResponse, header); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(GetBlockHeaderByHeight); + BEGIN_RPC_MESSAGE_REQUEST; + RPC_MESSAGE_MEMBER(uint64_t, height); + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(cryptonote::rpc::BlockHeaderResponse, header); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(GetBlockHeadersByHeight); + BEGIN_RPC_MESSAGE_REQUEST; + RPC_MESSAGE_MEMBER(std::vector<uint64_t>, heights); + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(std::vector<cryptonote::rpc::BlockHeaderResponse>, headers); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(GetBlock); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(GetPeerList); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(std::vector<peer>, white_list); + RPC_MESSAGE_MEMBER(std::vector<peer>, gray_list); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(SetLogHashRate); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(SetLogLevel); + BEGIN_RPC_MESSAGE_REQUEST; + RPC_MESSAGE_MEMBER(int8_t, level); + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(GetTransactionPool); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(std::vector<cryptonote::rpc::tx_in_pool>, transactions); + RPC_MESSAGE_MEMBER(key_images_with_tx_hashes, key_images); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(GetConnections); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(GetBlockHeadersRange); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(StopDaemon); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(StartSaveGraph); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(StopSaveGraph); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(HardForkInfo); + BEGIN_RPC_MESSAGE_REQUEST; + RPC_MESSAGE_MEMBER(uint8_t, version); + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(hard_fork_info, info); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(GetBans); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(SetBans); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(FlushTransactionPool); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(GetOutputHistogram); + BEGIN_RPC_MESSAGE_REQUEST; + RPC_MESSAGE_MEMBER(std::vector<uint64_t>, amounts); + RPC_MESSAGE_MEMBER(uint64_t, min_count); + RPC_MESSAGE_MEMBER(uint64_t, max_count); + RPC_MESSAGE_MEMBER(bool, unlocked); + RPC_MESSAGE_MEMBER(uint64_t, recent_cutoff); + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(std::vector<output_amount_count>, histogram); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(GetOutputKeys); + BEGIN_RPC_MESSAGE_REQUEST; + RPC_MESSAGE_MEMBER(std::vector<output_amount_and_index>, outputs); + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(std::vector<output_key_mask_unlocked>, keys); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(GetRPCVersion); + BEGIN_RPC_MESSAGE_REQUEST; + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(uint32_t, version); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +BEGIN_RPC_MESSAGE_CLASS(GetPerKBFeeEstimate); + BEGIN_RPC_MESSAGE_REQUEST; + RPC_MESSAGE_MEMBER(uint64_t, num_grace_blocks); + END_RPC_MESSAGE_REQUEST; + BEGIN_RPC_MESSAGE_RESPONSE; + RPC_MESSAGE_MEMBER(uint64_t, estimated_fee_per_kb); + END_RPC_MESSAGE_RESPONSE; +END_RPC_MESSAGE_CLASS; + +} // namespace rpc + +} // namespace cryptonote diff --git a/src/rpc/daemon_rpc_version.h b/src/rpc/daemon_rpc_version.h new file mode 100644 index 000000000..bb735fe7c --- /dev/null +++ b/src/rpc/daemon_rpc_version.h @@ -0,0 +1,44 @@ +// Copyright (c) 2016-2017, 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. + +#pragma once + +namespace cryptonote +{ + +namespace rpc +{ + +static const uint32_t DAEMON_RPC_VERSION_ZMQ_MINOR = 0; +static const uint32_t DAEMON_RPC_VERSION_ZMQ_MAJOR = 1; + +static const uint32_t DAEMON_RPC_VERSION_ZMQ = DAEMON_RPC_VERSION_ZMQ_MINOR + (DAEMON_RPC_VERSION_ZMQ_MAJOR << 16); + +} // namespace rpc + +} // namespace cryptonote diff --git a/src/rpc/message.cpp b/src/rpc/message.cpp new file mode 100644 index 000000000..086820180 --- /dev/null +++ b/src/rpc/message.cpp @@ -0,0 +1,286 @@ +// Copyright (c) 2016-2017, 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 "message.h" +#include "daemon_rpc_version.h" +#include "serialization/json_object.h" + +#include "rapidjson/writer.h" +#include "rapidjson/stringbuffer.h" + +namespace cryptonote +{ + +namespace rpc +{ + +const char* Message::STATUS_OK = "OK"; +const char* Message::STATUS_RETRY = "Retry"; +const char* Message::STATUS_FAILED = "Failed"; +const char* Message::STATUS_BAD_REQUEST = "Invalid request type"; +const char* Message::STATUS_BAD_JSON = "Malformed json"; + +rapidjson::Value Message::toJson(rapidjson::Document& doc) const +{ + rapidjson::Value val(rapidjson::kObjectType); + + auto& al = doc.GetAllocator(); + + val.AddMember("status", rapidjson::StringRef(status.c_str()), al); + val.AddMember("error_details", rapidjson::StringRef(error_details.c_str()), al); + INSERT_INTO_JSON_OBJECT(val, doc, rpc_version, DAEMON_RPC_VERSION_ZMQ); + + return val; +} + +void Message::fromJson(rapidjson::Value& val) +{ + GET_FROM_JSON_OBJECT(val, status, status); + GET_FROM_JSON_OBJECT(val, error_details, error_details); + GET_FROM_JSON_OBJECT(val, rpc_version, rpc_version); +} + + +FullMessage::FullMessage(const std::string& request, Message* message) +{ + doc.SetObject(); + + doc.AddMember("method", rapidjson::StringRef(request.c_str()), doc.GetAllocator()); + doc.AddMember("params", message->toJson(doc), doc.GetAllocator()); + + // required by JSON-RPC 2.0 spec + doc.AddMember("jsonrpc", rapidjson::Value("2.0"), doc.GetAllocator()); +} + +FullMessage::FullMessage(Message* message) +{ + doc.SetObject(); + + // required by JSON-RPC 2.0 spec + doc.AddMember("jsonrpc", "2.0", doc.GetAllocator()); + + if (message->status == Message::STATUS_OK) + { + doc.AddMember("response", message->toJson(doc), doc.GetAllocator()); + } + else + { + cryptonote::rpc::error err; + + err.error_str = message->status; + err.message = message->error_details; + + INSERT_INTO_JSON_OBJECT(doc, doc, error, err); + } +} + +FullMessage::FullMessage(const std::string& json_string, bool request) +{ + doc.Parse(json_string.c_str()); + if (doc.HasParseError()) + { + throw cryptonote::json::PARSE_FAIL(); + } + + OBJECT_HAS_MEMBER_OR_THROW(doc, "jsonrpc") + + if (request) + { + OBJECT_HAS_MEMBER_OR_THROW(doc, "method") + OBJECT_HAS_MEMBER_OR_THROW(doc, "params") + } + else + { + if (!doc.HasMember("response") && !doc.HasMember("error")) + { + throw cryptonote::json::MISSING_KEY("error/response"); + } + } +} + +std::string FullMessage::getJson() +{ + + if (!doc.HasMember("id")) + { + doc.AddMember("id", rapidjson::Value("unused"), doc.GetAllocator()); + } + + rapidjson::StringBuffer buf; + + rapidjson::Writer<rapidjson::StringBuffer> writer(buf); + + doc.Accept(writer); + + return std::string(buf.GetString(), buf.GetSize()); +} + +std::string FullMessage::getRequestType() const +{ + OBJECT_HAS_MEMBER_OR_THROW(doc, "method") + return doc["method"].GetString(); +} + +rapidjson::Value& FullMessage::getMessage() +{ + if (doc.HasMember("params")) + { + return doc["params"]; + } + else if (doc.HasMember("response")) + { + return doc["response"]; + } + + //else + OBJECT_HAS_MEMBER_OR_THROW(doc, "error") + return doc["error"]; + +} + +rapidjson::Value FullMessage::getMessageCopy() +{ + rapidjson::Value& val = getMessage(); + + return rapidjson::Value(val, doc.GetAllocator()); +} + +rapidjson::Value& FullMessage::getID() +{ + OBJECT_HAS_MEMBER_OR_THROW(doc, "id") + return doc["id"]; +} + +void FullMessage::setID(rapidjson::Value& id) +{ + auto itr = doc.FindMember("id"); + if (itr != doc.MemberEnd()) + { + itr->value = id; + } + else + { + doc.AddMember("id", id, doc.GetAllocator()); + } +} + +cryptonote::rpc::error FullMessage::getError() +{ + cryptonote::rpc::error err; + err.use = false; + if (doc.HasMember("error")) + { + GET_FROM_JSON_OBJECT(doc, err, error); + err.use = true; + } + + return err; +} + +FullMessage FullMessage::requestMessage(const std::string& request, Message* message) +{ + return FullMessage(request, message); +} + +FullMessage FullMessage::requestMessage(const std::string& request, Message* message, rapidjson::Value& id) +{ + auto mes = requestMessage(request, message); + mes.setID(id); + return mes; +} + +FullMessage FullMessage::responseMessage(Message* message) +{ + return FullMessage(message); +} + +FullMessage FullMessage::responseMessage(Message* message, rapidjson::Value& id) +{ + auto mes = responseMessage(message); + mes.setID(id); + return mes; +} + +FullMessage* FullMessage::timeoutMessage() +{ + auto *full_message = new FullMessage(); + + auto& doc = full_message->doc; + auto& al = full_message->doc.GetAllocator(); + + doc.SetObject(); + + // required by JSON-RPC 2.0 spec + doc.AddMember("jsonrpc", "2.0", al); + + cryptonote::rpc::error err; + + err.error_str = "RPC request timed out."; + INSERT_INTO_JSON_OBJECT(doc, doc, err, err); + + return full_message; +} + +// convenience functions for bad input +std::string BAD_REQUEST(const std::string& request) +{ + Message fail; + fail.status = Message::STATUS_BAD_REQUEST; + fail.error_details = std::string("\"") + request + "\" is not a valid request."; + + FullMessage fail_response = FullMessage::responseMessage(&fail); + + return fail_response.getJson(); +} + +std::string BAD_REQUEST(const std::string& request, rapidjson::Value& id) +{ + Message fail; + fail.status = Message::STATUS_BAD_REQUEST; + fail.error_details = std::string("\"") + request + "\" is not a valid request."; + + FullMessage fail_response = FullMessage::responseMessage(&fail, id); + + return fail_response.getJson(); +} + +std::string BAD_JSON(const std::string& error_details) +{ + Message fail; + fail.status = Message::STATUS_BAD_JSON; + fail.error_details = error_details; + + FullMessage fail_response = FullMessage::responseMessage(&fail); + + return fail_response.getJson(); +} + + +} // namespace rpc + +} // namespace cryptonote diff --git a/src/rpc/message.h b/src/rpc/message.h new file mode 100644 index 000000000..d1abe3fbe --- /dev/null +++ b/src/rpc/message.h @@ -0,0 +1,131 @@ +// Copyright (c) 2016-2017, 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. + +#pragma once + +#include "rapidjson/document.h" +#include "rpc/message_data_structs.h" +#include <string> + +/* I normally hate using macros, but in this case it would be untenably + * verbose to not use a macro. This macro saves the trouble of explicitly + * writing the below if block for every single RPC call. + */ +#define REQ_RESP_TYPES_MACRO( runtime_str, type, reqjson, resp_message_ptr, handler) \ + \ + if (runtime_str == type::name) \ + { \ + type::Request reqvar; \ + type::Response *respvar = new type::Response(); \ + \ + reqvar.fromJson(reqjson); \ + \ + handler(reqvar, *respvar); \ + \ + resp_message_ptr = respvar; \ + } + +namespace cryptonote +{ + +namespace rpc +{ + + class Message + { + public: + static const char* STATUS_OK; + static const char* STATUS_RETRY; + static const char* STATUS_FAILED; + static const char* STATUS_BAD_REQUEST; + static const char* STATUS_BAD_JSON; + + Message() : status(STATUS_OK) { } + + virtual ~Message() { } + + virtual rapidjson::Value toJson(rapidjson::Document& doc) const; + + virtual void fromJson(rapidjson::Value& val); + + std::string status; + std::string error_details; + uint32_t rpc_version; + }; + + class FullMessage + { + public: + ~FullMessage() { } + + FullMessage(FullMessage&& rhs) noexcept : doc(std::move(rhs.doc)) { } + + FullMessage(const std::string& json_string, bool request=false); + + std::string getJson(); + + std::string getRequestType() const; + + rapidjson::Value& getMessage(); + + rapidjson::Value getMessageCopy(); + + rapidjson::Value& getID(); + + void setID(rapidjson::Value& id); + + cryptonote::rpc::error getError(); + + static FullMessage requestMessage(const std::string& request, Message* message); + static FullMessage requestMessage(const std::string& request, Message* message, rapidjson::Value& id); + + static FullMessage responseMessage(Message* message); + static FullMessage responseMessage(Message* message, rapidjson::Value& id); + + static FullMessage* timeoutMessage(); + private: + + FullMessage() = default; + + FullMessage(const std::string& request, Message* message); + FullMessage(Message* message); + + rapidjson::Document doc; + }; + + + // convenience functions for bad input + std::string BAD_REQUEST(const std::string& request); + std::string BAD_REQUEST(const std::string& request, rapidjson::Value& id); + + std::string BAD_JSON(const std::string& error_details); + + +} // namespace rpc + +} // namespace cryptonote diff --git a/src/rpc/message_data_structs.h b/src/rpc/message_data_structs.h new file mode 100644 index 000000000..00f1e0caa --- /dev/null +++ b/src/rpc/message_data_structs.h @@ -0,0 +1,189 @@ +// Copyright (c) 2016-2017, 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. + +#pragma once + +#include "crypto/hash.h" +#include "cryptonote_basic/cryptonote_basic.h" +#include "ringct/rctSigs.h" + +#include <unordered_map> +#include <vector> + +namespace cryptonote +{ + +namespace rpc +{ + + struct block_with_transactions + { + cryptonote::block block; + std::unordered_map<crypto::hash, cryptonote::transaction> transactions; + }; + + typedef std::vector<uint64_t> tx_output_indices; + + typedef std::vector<tx_output_indices> block_output_indices; + + struct transaction_info + { + cryptonote::transaction transaction; + bool in_pool; + uint64_t height; + }; + + struct output_key_and_amount_index + { + uint64_t amount_index; + crypto::public_key key; + }; + + typedef std::vector<output_key_and_amount_index> outputs_for_amount; + + struct amount_with_random_outputs + { + uint64_t amount; + outputs_for_amount outputs; + }; + + struct peer + { + uint64_t id; + uint32_t ip; + uint16_t port; + uint64_t last_seen; + }; + + struct tx_in_pool + { + cryptonote::transaction tx; + crypto::hash tx_hash; + uint64_t blob_size; + uint64_t fee; + crypto::hash max_used_block_hash; + uint64_t max_used_block_height; + bool kept_by_block; + crypto::hash last_failed_block_hash; + uint64_t last_failed_block_height; + uint64_t receive_time; + uint64_t last_relayed_time; + bool relayed; + bool do_not_relay; + }; + + typedef std::unordered_map<crypto::key_image, std::vector<crypto::hash> > key_images_with_tx_hashes; + + struct output_amount_count + { + uint64_t amount; + uint64_t total_count; + uint64_t unlocked_count; + uint64_t recent_count; + }; + + struct output_amount_and_index + { + uint64_t amount; + uint64_t index; + }; + + struct output_key_mask_unlocked + { + crypto::public_key key; + rct::key mask; + bool unlocked; + }; + + struct hard_fork_info + { + uint8_t version; + bool enabled; + uint32_t window; + uint32_t votes; + uint32_t threshold; + uint8_t voting; + uint32_t state; + uint64_t earliest_height; + }; + + //required by JSON-RPC 2.0 spec + struct error + { + // not really using code, maybe later. + error() : use(false), code(1) { } + + bool use; // do not serialize + + int32_t code; + + // not required by spec, but int error codes aren't perfect + std::string error_str; + + std::string message; + + //TODO: data member? not required, may want later. + }; + + struct BlockHeaderResponse + { + uint64_t major_version; + uint64_t minor_version; + uint64_t timestamp; + crypto::hash prev_id; + uint32_t nonce; + uint64_t height; + uint64_t depth; + crypto::hash hash; + uint64_t difficulty; + uint64_t reward; + }; + + struct DaemonInfo + { + uint64_t height; + uint64_t target_height; + uint64_t difficulty; + uint64_t target; + uint64_t tx_count; + uint64_t tx_pool_size; + uint64_t alt_blocks_count; + uint64_t outgoing_connections_count; + uint64_t incoming_connections_count; + uint64_t white_peerlist_size; + uint64_t grey_peerlist_size; + bool testnet; + crypto::hash top_block_hash; + uint64_t cumulative_difficulty; + uint64_t block_size_limit; + uint64_t start_time; + }; + +} // namespace rpc + +} // namespace cryptonote diff --git a/src/rpc/rpc_handler.h b/src/rpc/rpc_handler.h new file mode 100644 index 000000000..d75180199 --- /dev/null +++ b/src/rpc/rpc_handler.h @@ -0,0 +1,54 @@ +// 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. + +#pragma once + +#include <string> + +namespace cryptonote +{ + +namespace rpc +{ + + +class RpcHandler +{ + public: + + virtual std::string handle(const std::string& request) = 0; + + RpcHandler() { } + + virtual ~RpcHandler() { } +}; + + +} // rpc + +} // cryptonote diff --git a/src/rpc/zmq_server.cpp b/src/rpc/zmq_server.cpp new file mode 100644 index 000000000..afdff2328 --- /dev/null +++ b/src/rpc/zmq_server.cpp @@ -0,0 +1,141 @@ +// 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. + +#include "zmq_server.h" +#include <boost/chrono/chrono.hpp> + +namespace cryptonote +{ + +namespace rpc +{ + +ZmqServer::ZmqServer(RpcHandler& h) : + handler(h), + stop_signal(false), + running(false), + context(DEFAULT_NUM_ZMQ_THREADS) // TODO: make this configurable +{ +} + +ZmqServer::~ZmqServer() +{ +} + +void ZmqServer::serve() +{ + + while (1) + { + try + { + zmq::message_t message; + + if (!rep_socket) + { + throw std::runtime_error("ZMQ RPC server reply socket is null"); + } + while (rep_socket->recv(&message)) + { + std::string message_string(reinterpret_cast<const char *>(message.data()), message.size()); + + MDEBUG(std::string("Received RPC request: \"") + message_string + "\""); + + std::string response = handler.handle(message_string); + + zmq::message_t reply(response.size()); + memcpy((void *) reply.data(), response.c_str(), response.size()); + + rep_socket->send(reply); + MDEBUG(std::string("Sent RPC reply: \"") + response + "\""); + + } + } + catch (const boost::thread_interrupted& e) + { + MDEBUG("ZMQ Server thread interrupted."); + } + catch (const zmq::error_t& e) + { + MERROR(std::string("ZMQ error: ") + e.what()); + } + boost::this_thread::interruption_point(); + } +} + +bool ZmqServer::addIPCSocket(std::string address, std::string port) +{ + MERROR("ZmqServer::addIPCSocket not yet implemented!"); + return false; +} + +bool ZmqServer::addTCPSocket(std::string address, std::string port) +{ + try + { + std::string addr_prefix("tcp://"); + + rep_socket.reset(new zmq::socket_t(context, ZMQ_REP)); + + rep_socket->setsockopt(ZMQ_RCVTIMEO, DEFAULT_RPC_RECV_TIMEOUT_MS); + + std::string bind_address = addr_prefix + address + std::string(":") + port; + rep_socket->bind(bind_address.c_str()); + } + catch (const std::exception& e) + { + MERROR(std::string("Error creating ZMQ Socket: ") + e.what()); + return false; + } + return true; +} + +void ZmqServer::run() +{ + running = true; + run_thread = boost::thread(boost::bind(&ZmqServer::serve, this)); +} + +void ZmqServer::stop() +{ + if (!running) return; + + stop_signal = true; + + run_thread.interrupt(); + run_thread.join(); + + running = false; + + return; +} + + +} // namespace cryptonote + +} // namespace rpc diff --git a/src/rpc/zmq_server.h b/src/rpc/zmq_server.h new file mode 100644 index 000000000..c278ff759 --- /dev/null +++ b/src/rpc/zmq_server.h @@ -0,0 +1,83 @@ +// 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. + +#pragma once + +#include <boost/thread/thread.hpp> +#include <zmq.hpp> +#include <string> +#include <memory> + +#include "common/command_line.h" + +#include "rpc_handler.h" + +namespace cryptonote +{ + +namespace rpc +{ + +static constexpr int DEFAULT_NUM_ZMQ_THREADS = 1; +static constexpr int DEFAULT_RPC_RECV_TIMEOUT_MS = 1000; + +class ZmqServer +{ + public: + + ZmqServer(RpcHandler& h); + + ~ZmqServer(); + + static void init_options(boost::program_options::options_description& desc); + + void serve(); + + bool addIPCSocket(std::string address, std::string port); + bool addTCPSocket(std::string address, std::string port); + + void run(); + void stop(); + + private: + RpcHandler& handler; + + volatile bool stop_signal; + volatile bool running; + + zmq::context_t context; + + boost::thread run_thread; + + std::unique_ptr<zmq::socket_t> rep_socket; +}; + + +} // namespace cryptonote + +} // namespace rpc diff --git a/src/serialization/CMakeLists.txt b/src/serialization/CMakeLists.txt new file mode 100644 index 000000000..e4aa0b6a2 --- /dev/null +++ b/src/serialization/CMakeLists.txt @@ -0,0 +1,54 @@ +# 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(serialization_sources + json_object.cpp) + +set(serialization_headers) + +set(serialization_private_headers + json_object.h) + +monero_private_headers(serialization + ${serialization_private_headers}) +monero_add_library(serialization + ${serialization_sources} + ${serialization_headers} + ${serialization_private_headers}) +target_link_libraries(serialization + LINK_PRIVATE + cryptonote_core + cryptonote_protocol + ${Boost_CHRONO_LIBRARY} + ${Boost_REGEX_LIBRARY} + ${Boost_SYSTEM_LIBRARY} + ${Boost_THREAD_LIBRARY} + ${EXTRA_LIBRARIES}) +add_dependencies(serialization + version) + diff --git a/src/serialization/json_object.cpp b/src/serialization/json_object.cpp new file mode 100644 index 000000000..a40821d19 --- /dev/null +++ b/src/serialization/json_object.cpp @@ -0,0 +1,1168 @@ +// Copyright (c) 2016-2017, 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 "json_object.h" + +#include <limits> +#include <type_traits> +#include "string_tools.h" + +namespace cryptonote +{ + +namespace json +{ + +namespace +{ + template<typename Source, typename Destination> + constexpr bool precision_loss() + { + return + std::numeric_limits<Destination>::is_signed != std::numeric_limits<Source>::is_signed || + std::numeric_limits<Destination>::min() > std::numeric_limits<Source>::min() || + std::numeric_limits<Destination>::max() < std::numeric_limits<Source>::max(); + } + + template<typename Source, typename Type> + void convert_numeric(Source source, Type& i) + { + static_assert( + (std::is_same<Type, char>() && std::is_same<Source, int>()) || + std::numeric_limits<Source>::is_signed == std::numeric_limits<Type>::is_signed, + "comparisons below may have undefined behavior" + ); + if (source < std::numeric_limits<Type>::min()) + { + throw WRONG_TYPE{"numeric underflow"}; + } + if (std::numeric_limits<Type>::max() < source) + { + throw WRONG_TYPE{"numeric overflow"}; + } + i = Type(source); + } + + template<typename Type> + void to_int(const rapidjson::Value& val, Type& i) + { + if (!val.IsInt()) + { + throw WRONG_TYPE{"integer"}; + } + convert_numeric(val.GetInt(), i); + } + template<typename Type> + void to_int64(const rapidjson::Value& val, Type& i) + { + if (!val.IsInt64()) + { + throw WRONG_TYPE{"integer"}; + } + convert_numeric(val.GetInt64(), i); + } + + template<typename Type> + void to_uint(const rapidjson::Value& val, Type& i) + { + if (!val.IsUint()) + { + throw WRONG_TYPE{"unsigned integer"}; + } + convert_numeric(val.GetUint(), i); + } + template<typename Type> + void to_uint64(const rapidjson::Value& val, Type& i) + { + if (!val.IsUint64()) + { + throw WRONG_TYPE{"unsigned integer"}; + } + convert_numeric(val.GetUint64(), i); + } +} + +void toJsonValue(rapidjson::Document& doc, const std::string& i, rapidjson::Value& val) +{ + val = rapidjson::Value(i.c_str(), doc.GetAllocator()); +} + +void fromJsonValue(const rapidjson::Value& val, std::string& str) +{ + if (!val.IsString()) + { + throw WRONG_TYPE("string"); + } + + str = val.GetString(); +} + +void toJsonValue(rapidjson::Document& doc, bool i, rapidjson::Value& val) +{ + val.SetBool(i); +} + +void fromJsonValue(const rapidjson::Value& val, bool& b) +{ + if (!val.IsBool()) + { + throw WRONG_TYPE("boolean"); + } + b = val.GetBool(); +} + +void fromJsonValue(const rapidjson::Value& val, unsigned char& i) +{ + to_uint(val, i); +} + +void fromJsonValue(const rapidjson::Value& val, char& i) +{ + to_int(val, i); +} + +void fromJsonValue(const rapidjson::Value& val, signed char& i) +{ + to_int(val, i); +} + +void fromJsonValue(const rapidjson::Value& val, unsigned short& i) +{ + to_uint(val, i); +} + +void fromJsonValue(const rapidjson::Value& val, short& i) +{ + to_int(val, i); +} + +void toJsonValue(rapidjson::Document& doc, const unsigned int i, rapidjson::Value& val) +{ + val = rapidjson::Value(i); +} + +void fromJsonValue(const rapidjson::Value& val, unsigned int& i) +{ + to_uint(val, i); +} + +void toJsonValue(rapidjson::Document& doc, const int i, rapidjson::Value& val) +{ + val = rapidjson::Value(i); +} + +void fromJsonValue(const rapidjson::Value& val, int& i) +{ + to_int(val, i); +} + +void toJsonValue(rapidjson::Document& doc, const unsigned long long i, rapidjson::Value& val) +{ + static_assert(!precision_loss<unsigned long long, std::uint64_t>(), "precision loss"); + val = rapidjson::Value(std::uint64_t(i)); +} + +void fromJsonValue(const rapidjson::Value& val, unsigned long long& i) +{ + to_uint64(val, i); +} + +void toJsonValue(rapidjson::Document& doc, const long long i, rapidjson::Value& val) +{ + static_assert(!precision_loss<long long, std::int64_t>(), "precision loss"); + val = rapidjson::Value(std::int64_t(i)); +} + +void fromJsonValue(const rapidjson::Value& val, long long& i) +{ + to_int64(val, i); +} + +void fromJsonValue(const rapidjson::Value& val, unsigned long& i) +{ + to_uint64(val, i); +} + +void fromJsonValue(const rapidjson::Value& val, long& i) +{ + to_int64(val, i); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::transaction& tx, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, version, tx.version); + INSERT_INTO_JSON_OBJECT(val, doc, unlock_time, tx.unlock_time); + INSERT_INTO_JSON_OBJECT(val, doc, vin, tx.vin); + INSERT_INTO_JSON_OBJECT(val, doc, vout, tx.vout); + INSERT_INTO_JSON_OBJECT(val, doc, extra, tx.extra); + INSERT_INTO_JSON_OBJECT(val, doc, signatures, tx.signatures); + INSERT_INTO_JSON_OBJECT(val, doc, rct_signatures, tx.rct_signatures); +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::transaction& tx) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, tx.version, version); + GET_FROM_JSON_OBJECT(val, tx.unlock_time, unlock_time); + GET_FROM_JSON_OBJECT(val, tx.vin, vin); + GET_FROM_JSON_OBJECT(val, tx.vout, vout); + GET_FROM_JSON_OBJECT(val, tx.extra, extra); + GET_FROM_JSON_OBJECT(val, tx.signatures, signatures); + GET_FROM_JSON_OBJECT(val, tx.rct_signatures, rct_signatures); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::block& b, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, major_version, b.major_version); + INSERT_INTO_JSON_OBJECT(val, doc, minor_version, b.minor_version); + INSERT_INTO_JSON_OBJECT(val, doc, timestamp, b.timestamp); + INSERT_INTO_JSON_OBJECT(val, doc, prev_id, b.prev_id); + INSERT_INTO_JSON_OBJECT(val, doc, nonce, b.nonce); + INSERT_INTO_JSON_OBJECT(val, doc, miner_tx, b.miner_tx); + INSERT_INTO_JSON_OBJECT(val, doc, tx_hashes, b.tx_hashes); +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::block& b) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, b.major_version, major_version); + GET_FROM_JSON_OBJECT(val, b.minor_version, minor_version); + GET_FROM_JSON_OBJECT(val, b.timestamp, timestamp); + GET_FROM_JSON_OBJECT(val, b.prev_id, prev_id); + GET_FROM_JSON_OBJECT(val, b.nonce, nonce); + GET_FROM_JSON_OBJECT(val, b.miner_tx, miner_tx); + GET_FROM_JSON_OBJECT(val, b.tx_hashes, tx_hashes); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::txin_v& txin, rapidjson::Value& val) +{ + val.SetObject(); + + if (txin.type() == typeid(cryptonote::txin_gen)) + { + val.AddMember("type", "txin_gen", doc.GetAllocator()); + INSERT_INTO_JSON_OBJECT(val, doc, value, boost::get<cryptonote::txin_gen>(txin)); + } + else if (txin.type() == typeid(cryptonote::txin_to_script)) + { + val.AddMember("type", "txin_to_script", doc.GetAllocator()); + INSERT_INTO_JSON_OBJECT(val, doc, value, boost::get<cryptonote::txin_to_script>(txin)); + } + else if (txin.type() == typeid(cryptonote::txin_to_scripthash)) + { + val.AddMember("type", "txin_to_scripthash", doc.GetAllocator()); + INSERT_INTO_JSON_OBJECT(val, doc, value, boost::get<cryptonote::txin_to_scripthash>(txin)); + } + else if (txin.type() == typeid(cryptonote::txin_to_key)) + { + val.AddMember("type", "txin_to_key", doc.GetAllocator()); + INSERT_INTO_JSON_OBJECT(val, doc, value, boost::get<cryptonote::txin_to_key>(txin)); + } +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::txin_v& txin) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + OBJECT_HAS_MEMBER_OR_THROW(val, "type") + OBJECT_HAS_MEMBER_OR_THROW(val, "value") + if (val["type"]== "txin_gen") + { + cryptonote::txin_gen tmpVal; + fromJsonValue(val["value"], tmpVal); + txin = tmpVal; + } + else if (val["type"]== "txin_to_script") + { + cryptonote::txin_to_script tmpVal; + fromJsonValue(val["value"], tmpVal); + txin = tmpVal; + } + else if (val["type"] == "txin_to_scripthash") + { + cryptonote::txin_to_scripthash tmpVal; + fromJsonValue(val["value"], tmpVal); + txin = tmpVal; + } + else if (val["type"] == "txin_to_key") + { + cryptonote::txin_to_key tmpVal; + fromJsonValue(val["value"], tmpVal); + txin = tmpVal; + } +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::txin_gen& txin, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, height, txin.height); +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::txin_gen& txin) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, txin.height, height); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::txin_to_script& txin, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, prev, txin.prev); + INSERT_INTO_JSON_OBJECT(val, doc, prevout, txin.prevout); + INSERT_INTO_JSON_OBJECT(val, doc, sigset, txin.sigset); +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::txin_to_script& txin) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, txin.prev, prev); + GET_FROM_JSON_OBJECT(val, txin.prevout, prevout); + GET_FROM_JSON_OBJECT(val, txin.sigset, sigset); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::txin_to_scripthash& txin, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, prev, txin.prev); + INSERT_INTO_JSON_OBJECT(val, doc, prevout, txin.prevout); + INSERT_INTO_JSON_OBJECT(val, doc, script, txin.script); + INSERT_INTO_JSON_OBJECT(val, doc, sigset, txin.sigset); +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::txin_to_scripthash& txin) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, txin.prev, prev); + GET_FROM_JSON_OBJECT(val, txin.prevout, prevout); + GET_FROM_JSON_OBJECT(val, txin.script, script); + GET_FROM_JSON_OBJECT(val, txin.sigset, sigset); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::txin_to_key& txin, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, amount, txin.amount); + INSERT_INTO_JSON_OBJECT(val, doc, key_offsets, txin.key_offsets); + INSERT_INTO_JSON_OBJECT(val, doc, k_image, txin.k_image); +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::txin_to_key& txin) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, txin.amount, amount); + GET_FROM_JSON_OBJECT(val, txin.key_offsets, key_offsets); + GET_FROM_JSON_OBJECT(val, txin.k_image, k_image); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::txout_target_v& txout, rapidjson::Value& val) +{ + val.SetObject(); + + if (txout.type() == typeid(cryptonote::txout_to_script)) + { + val.AddMember("type", "txout_to_script", doc.GetAllocator()); + INSERT_INTO_JSON_OBJECT(val, doc, value, boost::get<cryptonote::txout_to_script>(txout)); + } + else if (txout.type() == typeid(cryptonote::txout_to_scripthash)) + { + val.AddMember("type", "txout_to_scripthash", doc.GetAllocator()); + INSERT_INTO_JSON_OBJECT(val, doc, value, boost::get<cryptonote::txout_to_scripthash>(txout)); + } + else if (txout.type() == typeid(cryptonote::txout_to_key)) + { + val.AddMember("type", "txout_to_key", doc.GetAllocator()); + INSERT_INTO_JSON_OBJECT(val, doc, value, boost::get<cryptonote::txout_to_key>(txout)); + } +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::txout_target_v& txout) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + OBJECT_HAS_MEMBER_OR_THROW(val, "type") + OBJECT_HAS_MEMBER_OR_THROW(val, "value") + if (val["type"]== "txout_to_script") + { + cryptonote::txout_to_script tmpVal; + fromJsonValue(val["value"], tmpVal); + txout = tmpVal; + } + else if (val["type"] == "txout_to_scripthash") + { + cryptonote::txout_to_scripthash tmpVal; + fromJsonValue(val["value"], tmpVal); + txout = tmpVal; + } + else if (val["type"] == "txout_to_key") + { + cryptonote::txout_to_key tmpVal; + fromJsonValue(val["value"], tmpVal); + txout = tmpVal; + } +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::txout_to_script& txout, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, keys, txout.keys); + INSERT_INTO_JSON_OBJECT(val, doc, script, txout.script); +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::txout_to_script& txout) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, txout.keys, keys); + GET_FROM_JSON_OBJECT(val, txout.script, script); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::txout_to_scripthash& txout, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, hash, txout.hash); +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::txout_to_scripthash& txout) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, txout.hash, hash); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::txout_to_key& txout, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, key, txout.key); +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::txout_to_key& txout) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, txout.key, key); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::tx_out& txout, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, amount, txout.amount); + INSERT_INTO_JSON_OBJECT(val, doc, target, txout.target); +} + +void fromJsonValue(const rapidjson::Value& val, cryptonote::tx_out& txout) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, txout.amount, amount); + GET_FROM_JSON_OBJECT(val, txout.target, target); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::connection_info& info, rapidjson::Value& val) +{ + val.SetObject(); + + auto& al = doc.GetAllocator(); + INSERT_INTO_JSON_OBJECT(val, doc, incoming, info.incoming); + INSERT_INTO_JSON_OBJECT(val, doc, localhost, info.localhost); + INSERT_INTO_JSON_OBJECT(val, doc, local_ip, info.local_ip); + + INSERT_INTO_JSON_OBJECT(val, doc, ip, info.ip); + INSERT_INTO_JSON_OBJECT(val, doc, port, info.port); + + INSERT_INTO_JSON_OBJECT(val, doc, peer_id, info.peer_id); + + INSERT_INTO_JSON_OBJECT(val, doc, recv_count, info.recv_count); + INSERT_INTO_JSON_OBJECT(val, doc, recv_idle_time, info.recv_idle_time); + + INSERT_INTO_JSON_OBJECT(val, doc, send_count, info.send_count); + INSERT_INTO_JSON_OBJECT(val, doc, send_idle_time, info.send_idle_time); + + INSERT_INTO_JSON_OBJECT(val, doc, state, info.state); + + INSERT_INTO_JSON_OBJECT(val, doc, live_time, info.live_time); + + INSERT_INTO_JSON_OBJECT(val, doc, avg_download, info.avg_download); + INSERT_INTO_JSON_OBJECT(val, doc, current_download, info.current_download); + + INSERT_INTO_JSON_OBJECT(val, doc, avg_upload, info.avg_upload); + INSERT_INTO_JSON_OBJECT(val, doc, current_upload, info.current_upload); +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::connection_info& info) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, info.incoming, incoming); + GET_FROM_JSON_OBJECT(val, info.localhost, localhost); + GET_FROM_JSON_OBJECT(val, info.local_ip, local_ip); + + GET_FROM_JSON_OBJECT(val, info.ip, ip); + GET_FROM_JSON_OBJECT(val, info.port, port); + + GET_FROM_JSON_OBJECT(val, info.peer_id, peer_id); + + GET_FROM_JSON_OBJECT(val, info.recv_count, recv_count); + GET_FROM_JSON_OBJECT(val, info.recv_idle_time, recv_idle_time); + + GET_FROM_JSON_OBJECT(val, info.send_count, send_count); + GET_FROM_JSON_OBJECT(val, info.send_idle_time, send_idle_time); + + GET_FROM_JSON_OBJECT(val, info.state, state); + + GET_FROM_JSON_OBJECT(val, info.live_time, live_time); + + GET_FROM_JSON_OBJECT(val, info.avg_download, avg_download); + GET_FROM_JSON_OBJECT(val, info.current_download, current_download); + + GET_FROM_JSON_OBJECT(val, info.avg_upload, avg_upload); + GET_FROM_JSON_OBJECT(val, info.current_upload, current_upload); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::block_complete_entry& blk, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, block, blk.block); + INSERT_INTO_JSON_OBJECT(val, doc, txs, blk.txs); +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::block_complete_entry& blk) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, blk.block, block); + GET_FROM_JSON_OBJECT(val, blk.txs, txs); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::block_with_transactions& blk, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, block, blk.block); + INSERT_INTO_JSON_OBJECT(val, doc, transactions, blk.transactions); +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::block_with_transactions& blk) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, blk.block, block); + GET_FROM_JSON_OBJECT(val, blk.transactions, transactions); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::transaction_info& tx_info, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, height, tx_info.height); + INSERT_INTO_JSON_OBJECT(val, doc, in_pool, tx_info.in_pool); + INSERT_INTO_JSON_OBJECT(val, doc, transaction, tx_info.transaction); +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::transaction_info& tx_info) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, tx_info.height, height); + GET_FROM_JSON_OBJECT(val, tx_info.in_pool, in_pool); + GET_FROM_JSON_OBJECT(val, tx_info.transaction, transaction); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::output_key_and_amount_index& out, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, amount_index, out.amount_index); + INSERT_INTO_JSON_OBJECT(val, doc, key, out.key); +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::output_key_and_amount_index& out) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, out.amount_index, amount_index); + GET_FROM_JSON_OBJECT(val, out.key, key); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::amount_with_random_outputs& out, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, amount, out.amount); + INSERT_INTO_JSON_OBJECT(val, doc, outputs, out.outputs); +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::amount_with_random_outputs& out) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, out.amount, amount); + GET_FROM_JSON_OBJECT(val, out.outputs, outputs); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::peer& peer, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, id, peer.id); + INSERT_INTO_JSON_OBJECT(val, doc, ip, peer.ip); + INSERT_INTO_JSON_OBJECT(val, doc, port, peer.port); + INSERT_INTO_JSON_OBJECT(val, doc, last_seen, peer.last_seen); +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::peer& peer) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, peer.id, id); + GET_FROM_JSON_OBJECT(val, peer.ip, ip); + GET_FROM_JSON_OBJECT(val, peer.port, port); + GET_FROM_JSON_OBJECT(val, peer.last_seen, last_seen); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::tx_in_pool& tx, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, tx, tx.tx); + INSERT_INTO_JSON_OBJECT(val, doc, tx_hash, tx.tx_hash); + INSERT_INTO_JSON_OBJECT(val, doc, blob_size, tx.blob_size); + INSERT_INTO_JSON_OBJECT(val, doc, fee, tx.fee); + INSERT_INTO_JSON_OBJECT(val, doc, max_used_block_hash, tx.max_used_block_hash); + INSERT_INTO_JSON_OBJECT(val, doc, max_used_block_height, tx.max_used_block_height); + INSERT_INTO_JSON_OBJECT(val, doc, kept_by_block, tx.kept_by_block); + INSERT_INTO_JSON_OBJECT(val, doc, last_failed_block_hash, tx.last_failed_block_hash); + INSERT_INTO_JSON_OBJECT(val, doc, last_failed_block_height, tx.last_failed_block_height); + INSERT_INTO_JSON_OBJECT(val, doc, receive_time, tx.receive_time); + INSERT_INTO_JSON_OBJECT(val, doc, last_relayed_time, tx.last_relayed_time); + INSERT_INTO_JSON_OBJECT(val, doc, relayed, tx.relayed); + INSERT_INTO_JSON_OBJECT(val, doc, do_not_relay, tx.do_not_relay); +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::tx_in_pool& tx) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, tx.tx, tx); + GET_FROM_JSON_OBJECT(val, tx.blob_size, blob_size); + GET_FROM_JSON_OBJECT(val, tx.fee, fee); + GET_FROM_JSON_OBJECT(val, tx.max_used_block_hash, max_used_block_hash); + GET_FROM_JSON_OBJECT(val, tx.max_used_block_height, max_used_block_height); + GET_FROM_JSON_OBJECT(val, tx.kept_by_block, kept_by_block); + GET_FROM_JSON_OBJECT(val, tx.last_failed_block_hash, last_failed_block_hash); + GET_FROM_JSON_OBJECT(val, tx.last_failed_block_height, last_failed_block_height); + GET_FROM_JSON_OBJECT(val, tx.receive_time, receive_time); + GET_FROM_JSON_OBJECT(val, tx.last_relayed_time, last_relayed_time); + GET_FROM_JSON_OBJECT(val, tx.relayed, relayed); + GET_FROM_JSON_OBJECT(val, tx.do_not_relay, do_not_relay); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::hard_fork_info& info, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, version, info.version); + INSERT_INTO_JSON_OBJECT(val, doc, enabled, info.enabled); + INSERT_INTO_JSON_OBJECT(val, doc, window, info.window); + INSERT_INTO_JSON_OBJECT(val, doc, votes, info.votes); + INSERT_INTO_JSON_OBJECT(val, doc, threshold, info.threshold); + INSERT_INTO_JSON_OBJECT(val, doc, voting, info.voting); + INSERT_INTO_JSON_OBJECT(val, doc, state, info.state); + INSERT_INTO_JSON_OBJECT(val, doc, earliest_height, info.earliest_height); +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::hard_fork_info& info) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, info.version, version); + GET_FROM_JSON_OBJECT(val, info.enabled, enabled); + GET_FROM_JSON_OBJECT(val, info.window, window); + GET_FROM_JSON_OBJECT(val, info.votes, votes); + GET_FROM_JSON_OBJECT(val, info.threshold, threshold); + GET_FROM_JSON_OBJECT(val, info.voting, voting); + GET_FROM_JSON_OBJECT(val, info.state, state); + GET_FROM_JSON_OBJECT(val, info.earliest_height, earliest_height); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::output_amount_count& out, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, amount, out.amount); + INSERT_INTO_JSON_OBJECT(val, doc, total_count, out.total_count); + INSERT_INTO_JSON_OBJECT(val, doc, unlocked_count, out.unlocked_count); + INSERT_INTO_JSON_OBJECT(val, doc, recent_count, out.recent_count); +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::output_amount_count& out) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, out.amount, amount); + GET_FROM_JSON_OBJECT(val, out.total_count, total_count); + GET_FROM_JSON_OBJECT(val, out.unlocked_count, unlocked_count); + GET_FROM_JSON_OBJECT(val, out.recent_count, recent_count); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::output_amount_and_index& out, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, amount, out.amount); + INSERT_INTO_JSON_OBJECT(val, doc, index, out.index); +} + + +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::output_amount_and_index& out) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, out.amount, amount); + GET_FROM_JSON_OBJECT(val, out.index, index); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::output_key_mask_unlocked& out, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, key, out.key); + INSERT_INTO_JSON_OBJECT(val, doc, mask, out.mask); + INSERT_INTO_JSON_OBJECT(val, doc, unlocked, out.unlocked); +} + +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::output_key_mask_unlocked& out) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, out.key, key); + GET_FROM_JSON_OBJECT(val, out.mask, mask); + GET_FROM_JSON_OBJECT(val, out.unlocked, unlocked); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::error& err, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, code, err.code); + INSERT_INTO_JSON_OBJECT(val, doc, error_str, err.error_str); + INSERT_INTO_JSON_OBJECT(val, doc, message, err.message); +} + +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::error& error) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, error.code, code); + GET_FROM_JSON_OBJECT(val, error.error_str, error_str); + GET_FROM_JSON_OBJECT(val, error.message, message); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::BlockHeaderResponse& response, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, major_version, response.major_version); + INSERT_INTO_JSON_OBJECT(val, doc, minor_version, response.minor_version); + INSERT_INTO_JSON_OBJECT(val, doc, timestamp, response.timestamp); + INSERT_INTO_JSON_OBJECT(val, doc, prev_id, response.prev_id); + INSERT_INTO_JSON_OBJECT(val, doc, nonce, response.nonce); + INSERT_INTO_JSON_OBJECT(val, doc, height, response.height); + INSERT_INTO_JSON_OBJECT(val, doc, depth, response.depth); + INSERT_INTO_JSON_OBJECT(val, doc, hash, response.hash); + INSERT_INTO_JSON_OBJECT(val, doc, difficulty, response.difficulty); + INSERT_INTO_JSON_OBJECT(val, doc, reward, response.reward); +} + +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::BlockHeaderResponse& response) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, response.major_version, major_version); + GET_FROM_JSON_OBJECT(val, response.minor_version, minor_version); + GET_FROM_JSON_OBJECT(val, response.timestamp, timestamp); + GET_FROM_JSON_OBJECT(val, response.prev_id, prev_id); + GET_FROM_JSON_OBJECT(val, response.nonce, nonce); + GET_FROM_JSON_OBJECT(val, response.height, height); + GET_FROM_JSON_OBJECT(val, response.depth, depth); + GET_FROM_JSON_OBJECT(val, response.hash, hash); + GET_FROM_JSON_OBJECT(val, response.difficulty, difficulty); + GET_FROM_JSON_OBJECT(val, response.reward, reward); +} + +void toJsonValue(rapidjson::Document& doc, const rct::rctSig& sig, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, type, sig.type); + INSERT_INTO_JSON_OBJECT(val, doc, message, sig.message); + INSERT_INTO_JSON_OBJECT(val, doc, mixRing, sig.mixRing); + INSERT_INTO_JSON_OBJECT(val, doc, pseudoOuts, sig.pseudoOuts); + INSERT_INTO_JSON_OBJECT(val, doc, ecdhInfo, sig.ecdhInfo); + INSERT_INTO_JSON_OBJECT(val, doc, outPk, sig.outPk); + INSERT_INTO_JSON_OBJECT(val, doc, txnFee, sig.txnFee); + INSERT_INTO_JSON_OBJECT(val, doc, p, sig.p); +} + +void fromJsonValue(const rapidjson::Value& val, rct::rctSig& sig) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, sig.type, type); + GET_FROM_JSON_OBJECT(val, sig.message, message); + GET_FROM_JSON_OBJECT(val, sig.mixRing, mixRing); + GET_FROM_JSON_OBJECT(val, sig.pseudoOuts, pseudoOuts); + GET_FROM_JSON_OBJECT(val, sig.ecdhInfo, ecdhInfo); + GET_FROM_JSON_OBJECT(val, sig.outPk, outPk); + GET_FROM_JSON_OBJECT(val, sig.txnFee, txnFee); + GET_FROM_JSON_OBJECT(val, sig.p, p); +} + +void toJsonValue(rapidjson::Document& doc, const rct::ctkey& key, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, dest, key.dest); + INSERT_INTO_JSON_OBJECT(val, doc, mask, key.mask); +} + +void fromJsonValue(const rapidjson::Value& val, rct::ctkey& key) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + GET_FROM_JSON_OBJECT(val, key.dest, dest); + GET_FROM_JSON_OBJECT(val, key.mask, mask); +} + +void toJsonValue(rapidjson::Document& doc, const rct::ecdhTuple& tuple, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, mask, tuple.mask); + INSERT_INTO_JSON_OBJECT(val, doc, amount, tuple.amount); +} + +void fromJsonValue(const rapidjson::Value& val, rct::ecdhTuple& tuple) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, tuple.mask, mask); + GET_FROM_JSON_OBJECT(val, tuple.amount, amount); +} + +void toJsonValue(rapidjson::Document& doc, const rct::rctSigPrunable& sig, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, rangeSigs, sig.rangeSigs); + INSERT_INTO_JSON_OBJECT(val, doc, MGs, sig.MGs); +} + +void fromJsonValue(const rapidjson::Value& val, rct::rctSigPrunable& sig) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, sig.rangeSigs, rangeSigs); + GET_FROM_JSON_OBJECT(val, sig.MGs, MGs); +} + +void toJsonValue(rapidjson::Document& doc, const rct::rangeSig& sig, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, asig, sig.asig); + + std::vector<rct::key> keyVector(sig.Ci, std::end(sig.Ci)); + INSERT_INTO_JSON_OBJECT(val, doc, Ci, keyVector); +} + +void fromJsonValue(const rapidjson::Value& val, rct::rangeSig& sig) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, sig.asig, asig); + + std::vector<rct::key> keyVector; + cryptonote::json::fromJsonValue(val["Ci"], keyVector); + if (!(keyVector.size() == 64)) + { + throw WRONG_TYPE("key64 (rct::key[64])"); + } + for (size_t i=0; i < 64; i++) + { + sig.Ci[i] = keyVector[i]; + } +} + +void toJsonValue(rapidjson::Document& doc, const rct::boroSig& sig, rapidjson::Value& val) +{ + val.SetObject(); + + std::vector<rct::key> keyVector(sig.s0, std::end(sig.s0)); + INSERT_INTO_JSON_OBJECT(val, doc, s0, sig.s0); + + keyVector.assign(sig.s1, std::end(sig.s1)); + INSERT_INTO_JSON_OBJECT(val, doc, s1, sig.s1); + + INSERT_INTO_JSON_OBJECT(val, doc, ee, sig.ee); +} + +void fromJsonValue(const rapidjson::Value& val, rct::boroSig& sig) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + OBJECT_HAS_MEMBER_OR_THROW(val, "s0") + std::vector<rct::key> keyVector; + cryptonote::json::fromJsonValue(val["s0"], keyVector); + if (!(keyVector.size() == 64)) + { + throw WRONG_TYPE("key64 (rct::key[64])"); + } + for (size_t i=0; i < 64; i++) + { + sig.s0[i] = keyVector[i]; + } + + OBJECT_HAS_MEMBER_OR_THROW(val, "s1") + keyVector.clear(); + cryptonote::json::fromJsonValue(val["s1"], keyVector); + if (!(keyVector.size() == 64)) + { + throw WRONG_TYPE("key64 (rct::key[64])"); + } + for (size_t i=0; i < 64; i++) + { + sig.s1[i] = keyVector[i]; + } + + GET_FROM_JSON_OBJECT(val, sig.ee, ee); +} + +void toJsonValue(rapidjson::Document& doc, const rct::mgSig& sig, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, ss, sig.ss); + INSERT_INTO_JSON_OBJECT(val, doc, cc, sig.cc); +} + +void fromJsonValue(const rapidjson::Value& val, rct::mgSig& sig) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("key64 (rct::key[64])"); + } + + GET_FROM_JSON_OBJECT(val, sig.ss, ss); + GET_FROM_JSON_OBJECT(val, sig.cc, cc); +} + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::DaemonInfo& info, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, height, info.height); + INSERT_INTO_JSON_OBJECT(val, doc, target_height, info.target_height); + INSERT_INTO_JSON_OBJECT(val, doc, difficulty, info.difficulty); + INSERT_INTO_JSON_OBJECT(val, doc, target, info.target); + INSERT_INTO_JSON_OBJECT(val, doc, tx_count, info.tx_count); + INSERT_INTO_JSON_OBJECT(val, doc, tx_pool_size, info.tx_pool_size); + INSERT_INTO_JSON_OBJECT(val, doc, alt_blocks_count, info.alt_blocks_count); + INSERT_INTO_JSON_OBJECT(val, doc, outgoing_connections_count, info.outgoing_connections_count); + INSERT_INTO_JSON_OBJECT(val, doc, incoming_connections_count, info.incoming_connections_count); + INSERT_INTO_JSON_OBJECT(val, doc, white_peerlist_size, info.white_peerlist_size); + INSERT_INTO_JSON_OBJECT(val, doc, grey_peerlist_size, info.grey_peerlist_size); + INSERT_INTO_JSON_OBJECT(val, doc, testnet, info.testnet); + INSERT_INTO_JSON_OBJECT(val, doc, top_block_hash, info.top_block_hash); + INSERT_INTO_JSON_OBJECT(val, doc, cumulative_difficulty, info.cumulative_difficulty); + INSERT_INTO_JSON_OBJECT(val, doc, block_size_limit, info.block_size_limit); + INSERT_INTO_JSON_OBJECT(val, doc, start_time, info.start_time); +} + +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::DaemonInfo& info) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, info.height, height); + GET_FROM_JSON_OBJECT(val, info.target_height, target_height); + GET_FROM_JSON_OBJECT(val, info.difficulty, difficulty); + GET_FROM_JSON_OBJECT(val, info.target, target); + GET_FROM_JSON_OBJECT(val, info.tx_count, tx_count); + GET_FROM_JSON_OBJECT(val, info.tx_pool_size, tx_pool_size); + GET_FROM_JSON_OBJECT(val, info.alt_blocks_count, alt_blocks_count); + GET_FROM_JSON_OBJECT(val, info.outgoing_connections_count, outgoing_connections_count); + GET_FROM_JSON_OBJECT(val, info.incoming_connections_count, incoming_connections_count); + GET_FROM_JSON_OBJECT(val, info.white_peerlist_size, white_peerlist_size); + GET_FROM_JSON_OBJECT(val, info.grey_peerlist_size, grey_peerlist_size); + GET_FROM_JSON_OBJECT(val, info.testnet, testnet); + GET_FROM_JSON_OBJECT(val, info.top_block_hash, top_block_hash); + GET_FROM_JSON_OBJECT(val, info.cumulative_difficulty, cumulative_difficulty); + GET_FROM_JSON_OBJECT(val, info.block_size_limit, block_size_limit); + GET_FROM_JSON_OBJECT(val, info.start_time, start_time); +} + +} // namespace json + +} // namespace cryptonote diff --git a/src/serialization/json_object.h b/src/serialization/json_object.h new file mode 100644 index 000000000..7b9519c48 --- /dev/null +++ b/src/serialization/json_object.h @@ -0,0 +1,371 @@ +// Copyright (c) 2016-2017, 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. + +#pragma once + +#include "rapidjson/document.h" +#include "cryptonote_basic/cryptonote_basic.h" +#include "rpc/message_data_structs.h" +#include "cryptonote_protocol/cryptonote_protocol_defs.h" +#include "common/sfinae_helpers.h" + +#define OBJECT_HAS_MEMBER_OR_THROW(val, key) \ + do \ + { \ + if (!val.HasMember(key)) \ + { \ + throw cryptonote::json::MISSING_KEY(key); \ + } \ + } while (0); + +#define INSERT_INTO_JSON_OBJECT(jsonVal, doc, key, source) \ + rapidjson::Value key##Val; \ + cryptonote::json::toJsonValue(doc, source, key##Val); \ + jsonVal.AddMember(#key, key##Val, doc.GetAllocator()); + +#define GET_FROM_JSON_OBJECT(source, dst, key) \ + OBJECT_HAS_MEMBER_OR_THROW(source, #key) \ + decltype(dst) dstVal##key; \ + cryptonote::json::fromJsonValue(source[#key], dstVal##key); \ + dst = dstVal##key; + +namespace cryptonote +{ + +namespace json +{ + +struct JSON_ERROR : public std::exception +{ + protected: + JSON_ERROR() { } + std::string m; + + public: + virtual ~JSON_ERROR() { } + + const char* what() const throw() + { + return m.c_str(); + } +}; + +struct MISSING_KEY : public JSON_ERROR +{ + MISSING_KEY(const char* key) + { + m = std::string("Key \"") + key + "\" missing from object."; + } +}; + +struct WRONG_TYPE : public JSON_ERROR +{ + WRONG_TYPE(const char* type) + { + m = std::string("Json value has incorrect type, expected: ") + type; + } +}; + +struct BAD_INPUT : public JSON_ERROR +{ + BAD_INPUT() + { + m = "An item failed to convert from json object to native object"; + } +}; + +struct PARSE_FAIL : public JSON_ERROR +{ + PARSE_FAIL() + { + m = "Failed to parse the json request"; + } +}; + +template<typename Type> +inline constexpr bool is_to_hex() +{ + return std::is_pod<Type>() && !std::is_integral<Type>(); +} + + +// POD to json value +template <class Type> +typename std::enable_if<is_to_hex<Type>()>::type toJsonValue(rapidjson::Document& doc, const Type& pod, rapidjson::Value& value) +{ + value = rapidjson::Value(epee::string_tools::pod_to_hex(pod).c_str(), doc.GetAllocator()); +} + +template <class Type> +typename std::enable_if<is_to_hex<Type>()>::type fromJsonValue(const rapidjson::Value& val, Type& t) +{ + if (!val.IsString()) + { + throw WRONG_TYPE("string"); + } + + //TODO: handle failure to convert hex string to POD type + bool success = epee::string_tools::hex_to_pod(val.GetString(), t); + + if (!success) + { + throw BAD_INPUT(); + } +} + +void toJsonValue(rapidjson::Document& doc, const std::string& i, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, std::string& str); + +void toJsonValue(rapidjson::Document& doc, bool i, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, bool& b); + +// integers overloads for toJsonValue are not needed for standard promotions + +void fromJsonValue(const rapidjson::Value& val, unsigned char& i); + +void fromJsonValue(const rapidjson::Value& val, signed char& i); + +void fromJsonValue(const rapidjson::Value& val, char& i); + +void fromJsonValue(const rapidjson::Value& val, unsigned short& i); + +void fromJsonValue(const rapidjson::Value& val, short& i); + +void toJsonValue(rapidjson::Document& doc, const unsigned i, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, unsigned& i); + +void toJsonValue(rapidjson::Document& doc, const int, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, int& i); + + +void toJsonValue(rapidjson::Document& doc, const unsigned long long i, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, unsigned long long& i); + +void toJsonValue(rapidjson::Document& doc, const long long i, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, long long& i); + +inline void toJsonValue(rapidjson::Document& doc, const unsigned long i, rapidjson::Value& val) { + toJsonValue(doc, static_cast<unsigned long long>(i), val); +} +void fromJsonValue(const rapidjson::Value& val, unsigned long& i); + +inline void toJsonValue(rapidjson::Document& doc, const long i, rapidjson::Value& val) { + toJsonValue(doc, static_cast<long long>(i), val); +} +void fromJsonValue(const rapidjson::Value& val, long& i); + +// end integers + +void toJsonValue(rapidjson::Document& doc, const cryptonote::transaction& tx, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::transaction& tx); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::block& b, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::block& b); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::txin_v& txin, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::txin_v& txin); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::txin_gen& txin, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::txin_gen& txin); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::txin_to_script& txin, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::txin_to_script& txin); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::txin_to_scripthash& txin, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::txin_to_scripthash& txin); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::txin_to_key& txin, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::txin_to_key& txin); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::txout_target_v& txout, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::txout_target_v& txout); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::txout_to_script& txout, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::txout_to_script& txout); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::txout_to_scripthash& txout, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::txout_to_scripthash& txout); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::txout_to_key& txout, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::txout_to_key& txout); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::tx_out& txout, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::tx_out& txout); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::connection_info& info, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::connection_info& info); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::block_complete_entry& blk, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::block_complete_entry& blk); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::block_with_transactions& blk, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::block_with_transactions& blk); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::transaction_info& tx_info, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::transaction_info& tx_info); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::output_key_and_amount_index& out, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::output_key_and_amount_index& out); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::amount_with_random_outputs& out, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::amount_with_random_outputs& out); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::peer& peer, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::peer& peer); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::tx_in_pool& tx, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::tx_in_pool& tx); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::hard_fork_info& info, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::hard_fork_info& info); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::output_amount_count& out, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::output_amount_count& out); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::output_amount_and_index& out, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::output_amount_and_index& out); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::output_key_mask_unlocked& out, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::output_key_mask_unlocked& out); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::error& err, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::error& error); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::BlockHeaderResponse& response, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::BlockHeaderResponse& response); + +void toJsonValue(rapidjson::Document& doc, const rct::rctSig& i, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& i, rct::rctSig& sig); + +void toJsonValue(rapidjson::Document& doc, const rct::ctkey& key, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, rct::ctkey& key); + +void toJsonValue(rapidjson::Document& doc, const rct::ecdhTuple& tuple, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, rct::ecdhTuple& tuple); + +void toJsonValue(rapidjson::Document& doc, const rct::rctSigPrunable& sig, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, rct::rctSigPrunable& sig); + +void toJsonValue(rapidjson::Document& doc, const rct::rangeSig& sig, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, rct::rangeSig& sig); + +void toJsonValue(rapidjson::Document& doc, const rct::boroSig& sig, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, rct::boroSig& sig); + +void toJsonValue(rapidjson::Document& doc, const rct::mgSig& sig, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, rct::mgSig& sig); + +void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::DaemonInfo& info, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::DaemonInfo& info); + +template <typename Map> +typename std::enable_if<sfinae::is_map_like<Map>::value, void>::type toJsonValue(rapidjson::Document& doc, const Map& map, rapidjson::Value& val); + +template <typename Map> +typename std::enable_if<sfinae::is_map_like<Map>::value, void>::type fromJsonValue(const rapidjson::Value& val, Map& map); + +template <typename Vec> +typename std::enable_if<sfinae::is_vector_like<Vec>::value, void>::type toJsonValue(rapidjson::Document& doc, const Vec &vec, rapidjson::Value& val); + +template <typename Vec> +typename std::enable_if<sfinae::is_vector_like<Vec>::value, void>::type fromJsonValue(const rapidjson::Value& val, Vec& vec); + + +// ideally would like to have the below functions in the .cpp file, but +// unfortunately because of how templates work they have to be here. + +template <typename Map> +typename std::enable_if<sfinae::is_map_like<Map>::value, void>::type toJsonValue(rapidjson::Document& doc, const Map& map, rapidjson::Value& val) +{ + val.SetObject(); + + auto& al = doc.GetAllocator(); + + for (const auto& i : map) + { + rapidjson::Value k; + rapidjson::Value m; + toJsonValue(doc, i.first, k); + toJsonValue(doc, i.second, m); + val.AddMember(k, m, al); + } +} + +template <typename Map> +typename std::enable_if<sfinae::is_map_like<Map>::value, void>::type fromJsonValue(const rapidjson::Value& val, Map& map) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + auto itr = val.MemberBegin(); + + while (itr != val.MemberEnd()) + { + typename Map::key_type k; + typename Map::mapped_type m; + fromJsonValue(itr->name, k); + fromJsonValue(itr->value, m); + map.emplace(k, m); + ++itr; + } +} + +template <typename Vec> +typename std::enable_if<sfinae::is_vector_like<Vec>::value, void>::type toJsonValue(rapidjson::Document& doc, const Vec &vec, rapidjson::Value& val) +{ + val.SetArray(); + + for (const auto& t : vec) + { + rapidjson::Value v; + toJsonValue(doc, t, v); + val.PushBack(v, doc.GetAllocator()); + } +} + +template <typename Vec> +typename std::enable_if<sfinae::is_vector_like<Vec>::value, void>::type fromJsonValue(const rapidjson::Value& val, Vec& vec) +{ + if (!val.IsArray()) + { + throw WRONG_TYPE("json array"); + } + + for (rapidjson::SizeType i=0; i < val.Size(); i++) + { + typename Vec::value_type v; + fromJsonValue(val[i], v); + vec.push_back(v); + } +} + +} // namespace json + +} // namespace cryptonote diff --git a/src/serialization/serialization.h b/src/serialization/serialization.h index 639240820..869f5d10e 100644 --- a/src/serialization/serialization.h +++ b/src/serialization/serialization.h @@ -41,6 +41,7 @@ #pragma once #include <vector> +#include <deque> #include <list> #include <string> #include <boost/type_traits/is_integral.hpp> @@ -198,6 +199,11 @@ inline bool do_serialize(Archive &ar, bool &v) #define PREPARE_CUSTOM_VECTOR_SERIALIZATION(size, vec) \ ::serialization::detail::prepare_custom_vector_serialization(size, vec, typename Archive<W>::is_saving()) +/*! \macro PREPARE_CUSTOM_DEQUE_SERIALIZATION + */ +#define PREPARE_CUSTOM_DEQUE_SERIALIZATION(size, vec) \ + ::serialization::detail::prepare_custom_deque_serialization(size, vec, typename Archive<W>::is_saving()) + /*! \macro END_SERIALIZE * \brief self-explanatory */ @@ -292,6 +298,17 @@ namespace serialization { vec.resize(size); } + template <typename T> + void prepare_custom_deque_serialization(size_t size, std::deque<T>& vec, const boost::mpl::bool_<true>& /*is_saving*/) + { + } + + template <typename T> + void prepare_custom_deque_serialization(size_t size, std::deque<T>& vec, const boost::mpl::bool_<false>& /*is_saving*/) + { + vec.resize(size); + } + /*! \fn do_check_stream_state * * \brief self explanatory diff --git a/src/serialization/vector.h b/src/serialization/vector.h index 598cfb92e..12fd59558 100644 --- a/src/serialization/vector.h +++ b/src/serialization/vector.h @@ -37,6 +37,11 @@ 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); +template <template <bool> class Archive, class T> +bool do_serialize(Archive<false> &ar, std::deque<T> &v); +template <template <bool> class Archive, class T> +bool do_serialize(Archive<true> &ar, std::deque<T> &v); + namespace serialization { namespace detail @@ -64,7 +69,7 @@ namespace serialization } template <template <bool> class Archive, class T> -bool do_serialize(Archive<false> &ar, std::vector<T> &v) +bool do_serialize_vd(Archive<false> &ar, T &v) { size_t cnt; ar.begin_array(cnt); @@ -93,7 +98,7 @@ 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) +bool do_serialize_vd(Archive<true> &ar, T &v) { size_t cnt = v.size(); ar.begin_array(cnt); @@ -110,3 +115,13 @@ bool do_serialize(Archive<true> &ar, std::vector<T> &v) ar.end_array(); return true; } + +template <template <bool> class Archive, class T> +bool do_serialize(Archive<false> &ar, std::vector<T> &v) { return do_serialize_vd(ar, v); } +template <template <bool> class Archive, class T> +bool do_serialize(Archive<true> &ar, std::vector<T> &v) { return do_serialize_vd(ar, v); } + +template <template <bool> class Archive, class T> +bool do_serialize(Archive<false> &ar, std::deque<T> &v) { return do_serialize_vd(ar, v); } +template <template <bool> class Archive, class T> +bool do_serialize(Archive<true> &ar, std::deque<T> &v) { return do_serialize_vd(ar, v); } diff --git a/src/simplewallet/CMakeLists.txt b/src/simplewallet/CMakeLists.txt index 443e9b87e..b56085b8f 100644 --- a/src/simplewallet/CMakeLists.txt +++ b/src/simplewallet/CMakeLists.txt @@ -49,14 +49,13 @@ target_link_libraries(simplewallet common mnemonics p2p + version ${Boost_CHRONO_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_THREAD_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ${EXTRA_LIBRARIES}) -add_dependencies(simplewallet - version) set_property(TARGET simplewallet PROPERTY OUTPUT_NAME "monero-wallet-cli") diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 857e2af6e..48739bc2d 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -251,6 +251,30 @@ namespace } return addresses[0]; } + + bool parse_subaddress_indices(const std::string& arg, std::set<uint32_t>& subaddr_indices) + { + subaddr_indices.clear(); + + if (arg.substr(0, 6) != "index=") + return false; + std::string subaddr_indices_str_unsplit = arg.substr(6, arg.size() - 6); + std::vector<std::string> subaddr_indices_str; + boost::split(subaddr_indices_str, subaddr_indices_str_unsplit, boost::is_any_of(",")); + + for (const auto& subaddr_index_str : subaddr_indices_str) + { + uint32_t subaddr_index; + if(!epee::string_tools::get_xtype_from_string(subaddr_index, subaddr_index_str)) + { + fail_msg_writer() << tr("failed to parse index: ") << subaddr_index_str; + subaddr_indices.clear(); + return false; + } + subaddr_indices.insert(subaddr_index); + } + return true; + } } @@ -290,7 +314,7 @@ bool simple_wallet::spendkey(const std::vector<std::string> &args/* = std::vecto return true; } -bool simple_wallet::seed(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +bool simple_wallet::print_seed(bool encrypted) { bool success = false; std::string electrum_words; @@ -311,7 +335,16 @@ bool simple_wallet::seed(const std::vector<std::string> &args/* = std::vector<st m_wallet->set_seed_language(mnemonic_language); } - success = m_wallet->get_seed(electrum_words); + std::string seed_pass; + if (encrypted) + { + auto pwd_container = tools::password_container::prompt(true, tr("Enter optional seed encryption passphrase, empty to see raw seed")); + if (std::cin.eof() || !pwd_container) + return true; + seed_pass = pwd_container->password(); + } + + success = m_wallet->get_seed(electrum_words, seed_pass); } if (success) @@ -325,6 +358,16 @@ bool simple_wallet::seed(const std::vector<std::string> &args/* = std::vector<st return true; } +bool simple_wallet::seed(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +{ + return print_seed(false); +} + +bool simple_wallet::encrypted_seed(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +{ + return print_seed(true); +} + bool simple_wallet::seed_set_language(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { if (m_wallet->watch_only()) @@ -716,6 +759,24 @@ bool simple_wallet::set_confirm_backlog(const std::vector<std::string> &args/* = return true; } +bool simple_wallet::set_confirm_backlog_threshold(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +{ + uint32_t threshold; + if (!string_tools::get_xtype_from_string(threshold, args[1])) + { + fail_msg_writer() << tr("invalid count: must be an unsigned integer"); + return true; + } + + const auto pwd_container = get_and_verify_password(); + if (pwd_container) + { + m_wallet->set_confirm_backlog_threshold(threshold); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); + } + return true; +} + bool simple_wallet::help(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { success_msg_writer() << get_commands_str(); @@ -729,26 +790,28 @@ simple_wallet::simple_wallet() , m_auto_refresh_enabled(false) , m_auto_refresh_refreshing(false) , m_in_manual_refresh(false) + , m_current_subaddress_account(0) { m_cmd_binder.set_handler("start_mining", boost::bind(&simple_wallet::start_mining, this, _1), tr("start_mining [<number_of_threads>] - Start mining in daemon")); m_cmd_binder.set_handler("stop_mining", boost::bind(&simple_wallet::stop_mining, this, _1), tr("Stop mining in daemon")); m_cmd_binder.set_handler("save_bc", boost::bind(&simple_wallet::save_bc, this, _1), tr("Save current blockchain data")); m_cmd_binder.set_handler("refresh", boost::bind(&simple_wallet::refresh, this, _1), tr("Synchronize transactions and balance")); - m_cmd_binder.set_handler("balance", boost::bind(&simple_wallet::show_balance, this, _1), tr("Show current wallet balance")); - 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("balance", boost::bind(&simple_wallet::show_balance, this, _1), tr("balance [detail] - Show wallet balance of currently selected account")); + m_cmd_binder.set_handler("incoming_transfers", boost::bind(&simple_wallet::show_incoming_transfers, this, _1), tr("incoming_transfers [available|unavailable] [verbose] [index=<N1>[,<N2>,...]] - Show incoming transfers, all or filtered by availability and address index")); 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_original", boost::bind(&simple_wallet::transfer, this, _1), tr("Same as transfer, but using an older transaction building algorithm")); - m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::transfer_new, this, _1), tr("transfer [<priority>] [<ring_size>] <address> <amount> [<payment_id>] - Transfer <amount> to <address>. <priority> is the priority of the transaction. The higher the priority, the higher the fee of the transaction. Valid values in priority order (from lowest to highest) are: unimportant, normal, elevated, priority. If omitted, the default value (see the command \"set priority\") is used. <ring_size> is the number of inputs to include for untraceability. Multiple payments can be made at once by adding <address_2> <amount_2> etcetera (before the payment ID, if it's included)")); - m_cmd_binder.set_handler("locked_transfer", boost::bind(&simple_wallet::locked_transfer, this, _1), tr("locked_transfer [<ring_size>] <addr> <amount> <lockblocks>(Number of blocks to lock the transaction for, max 1000000) [<payment_id>]")); + m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::transfer_new, this, _1), tr("transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> <amount> [<payment_id>] - Transfer <amount> to <address>. If the parameter \"index=<N1>[,<N2>,...]\" is specified, the wallet uses outputs received by addresses of those indices. If omitted, the wallet randomly chooses address indices to be used. In any case, it tries its best not to combine outputs across multiple addresses. <priority> is the priority of the transaction. The higher the priority, the higher the fee of the transaction. Valid values in priority order (from lowest to highest) are: unimportant, normal, elevated, priority. If omitted, the default value (see the command \"set priority\") is used. <ring_size> is the number of inputs to include for untraceability. Multiple payments can be made at once by adding <address_2> <amount_2> etcetera (before the payment ID, if it's included)")); + m_cmd_binder.set_handler("locked_transfer", boost::bind(&simple_wallet::locked_transfer, this, _1), tr("locked_transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <addr> <amount> <lockblocks> [<payment_id>] - Same as transfer, but with number of blocks to lock the transaction for, max 1000000")); m_cmd_binder.set_handler("sweep_unmixable", boost::bind(&simple_wallet::sweep_unmixable, this, _1), tr("Send all unmixable outputs to yourself with ring_size 1")); - m_cmd_binder.set_handler("sweep_all", boost::bind(&simple_wallet::sweep_all, this, _1), tr("sweep_all [ring_size] address [payment_id] - Send all unlocked balance to an address")); - m_cmd_binder.set_handler("sweep_below", boost::bind(&simple_wallet::sweep_below, this, _1), tr("sweep_below <amount_threshold> [ring_size] address [payment_id] - Send all unlocked outputs below the threshold to an address")); - m_cmd_binder.set_handler("donate", boost::bind(&simple_wallet::donate, this, _1), tr("donate [<ring_size>] <amount> [payment_id] - Donate <amount> to the development team (donate.getmonero.org)")); + m_cmd_binder.set_handler("sweep_all", boost::bind(&simple_wallet::sweep_all, this, _1), tr("sweep_all [index=<N1>[,<N2>,...]] [<ring_size>] <address> [<payment_id>] - Send all unlocked balance to an address. If the parameter \"index<N1>[,<N2>,...]\" is specified, the wallet sweeps outputs received by those address indices. If omitted, the wallet randomly chooses an address index to be used.")); + m_cmd_binder.set_handler("sweep_below", boost::bind(&simple_wallet::sweep_below, this, _1), tr("sweep_below <amount_threshold> [index=<N1>[,<N2>,...]] [<ring_size>] <address> [<payment_id>] - Send all unlocked outputs below the threshold to an address")); + m_cmd_binder.set_handler("donate", boost::bind(&simple_wallet::donate, this, _1), tr("donate [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <amount> [<payment_id>] - Donate <amount> to the development team (donate.getmonero.org)")); m_cmd_binder.set_handler("sign_transfer", boost::bind(&simple_wallet::sign_transfer, this, _1), tr("Sign a transaction from a file")); m_cmd_binder.set_handler("submit_transfer", boost::bind(&simple_wallet::submit_transfer, this, _1), tr("Submit a signed transaction from a file")); - m_cmd_binder.set_handler("set_log", boost::bind(&simple_wallet::set_log, this, _1), tr("set_log <level>|<categories> - Change current log detail (level must be <0-4>)")); - m_cmd_binder.set_handler("address", boost::bind(&simple_wallet::print_address, this, _1), tr("Show current wallet public address")); + m_cmd_binder.set_handler("set_log", boost::bind(&simple_wallet::set_log, this, _1), tr("set_log <level>|{+,-,}<categories> - Change current log detail (level must be <0-4>)")); + m_cmd_binder.set_handler("account", boost::bind(&simple_wallet::account, this, _1), tr("account [new <label text with white spaces allowed> | switch <index> | label <index> <label text with white spaces allowed>] - If no argments are specified, the wallet shows all the existing accounts along with their balances. If the \"new\" argument is specified, the wallet creates a new account with its label initialized by the provided label text (which can be empty). If the \"switch\" argument is specified, the wallet switches to the account specified by <index>. If the \"label\" argument is specified, the wallet sets the label of the account specified by <index> to the provided label text.")); + m_cmd_binder.set_handler("address", boost::bind(&simple_wallet::print_address, this, _1), tr("address [ new <label text with white spaces allowed> | all | <index_min> [<index_max>] | label <index> <label text with white spaces allowed> ] - If no arguments are specified or <index> is specified, the wallet shows the default or specified address. If \"all\" is specified, the walllet shows all the existing addresses in the currently selected account. If \"new \" is specified, the wallet creates a new address with the provided label text (which can be empty). If \"label\" is specified, the wallet sets the label of the address specified by <index> to the provided label text.")); m_cmd_binder.set_handler("integrated_address", boost::bind(&simple_wallet::print_integrated_address, this, _1), tr("integrated_address [PID] - Encode a payment ID into an integrated address for the current wallet public address (no argument uses a random payment ID), or decode an integrated address to standard address and payment ID")); m_cmd_binder.set_handler("address_book", boost::bind(&simple_wallet::address_book, this, _1), tr("address_book [(add (<address> [pid <long or short payment id>])|<integrated address> [<description possibly with whitespaces>])|(delete <index>)] - Print all entries in the address book, optionally adding/deleting an entry to/from it")); m_cmd_binder.set_handler("save", boost::bind(&simple_wallet::save, this, _1), tr("Save wallet data")); @@ -756,18 +819,20 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("viewkey", boost::bind(&simple_wallet::viewkey, this, _1), tr("Display private view key")); m_cmd_binder.set_handler("spendkey", boost::bind(&simple_wallet::spendkey, this, _1), tr("Display private spend key")); m_cmd_binder.set_handler("seed", boost::bind(&simple_wallet::seed, this, _1), tr("Display Electrum-style mnemonic seed")); - m_cmd_binder.set_handler("set", boost::bind(&simple_wallet::set_variable, this, _1), tr("Available options: seed language - set wallet seed language; always-confirm-transfers <1|0> - whether to confirm unsplit txes; print-ring-members <1|0> - whether to print detailed information about ring members during confirmation; store-tx-info <1|0> - whether to store outgoing tx info (destination address, payment ID, tx secret key) for future reference; default-ring-size <n> - set default ring size (default is 5); auto-refresh <1|0> - whether to automatically sync new blocks from the daemon; refresh-type <full|optimize-coinbase|no-coinbase|default> - set wallet refresh behaviour; priority [0|1|2|3|4] - default/unimportant/normal/elevated/priority fee; confirm-missing-payment-id <1|0>; ask-password <1|0>; unit <monero|millinero|micronero|nanonero|piconero> - set default monero (sub-)unit; min-outputs-count [n] - try to keep at least that many outputs of value at least min-outputs-value; min-outputs-value [n] - try to keep at least min-outputs-count outputs of at least that value; merge-destinations <1|0> - whether to merge multiple payments to the same destination address; confirm-backlog <1|0> - whether to warn if there is transaction backlog")); + m_cmd_binder.set_handler("set", boost::bind(&simple_wallet::set_variable, this, _1), tr("Available options: seed language - set wallet seed language; always-confirm-transfers <1|0> - whether to confirm unsplit txes; print-ring-members <1|0> - whether to print detailed information about ring members during confirmation; store-tx-info <1|0> - whether to store outgoing tx info (destination address, payment ID, tx secret key) for future reference; default-ring-size <n> - set default ring size (default is 5); auto-refresh <1|0> - whether to automatically sync new blocks from the daemon; refresh-type <full|optimize-coinbase|no-coinbase|default> - set wallet refresh behaviour; priority [0|1|2|3|4] - default/unimportant/normal/elevated/priority fee; confirm-missing-payment-id <1|0>; ask-password <1|0>; unit <monero|millinero|micronero|nanonero|piconero> - set default monero (sub-)unit; min-outputs-count [n] - try to keep at least that many outputs of value at least min-outputs-value; min-outputs-value [n] - try to keep at least min-outputs-count outputs of at least that value; merge-destinations <1|0> - whether to merge multiple payments to the same destination address; confirm-backlog <1|0> - whether to warn if there is transaction backlog; confirm-backlog-threshold [n] - sets a threshold for confirm-backlog to only warn if the transaction backlog is greater than n blocks")); + m_cmd_binder.set_handler("encrypted_seed", boost::bind(&simple_wallet::encrypted_seed, this, _1), tr("Display encrypted Electrum-style mnemonic seed")); m_cmd_binder.set_handler("rescan_spent", boost::bind(&simple_wallet::rescan_spent, this, _1), tr("Rescan blockchain for spent outputs")); m_cmd_binder.set_handler("get_tx_key", boost::bind(&simple_wallet::get_tx_key, this, _1), tr("Get transaction key (r) for a given <txid>")); m_cmd_binder.set_handler("check_tx_key", boost::bind(&simple_wallet::check_tx_key, this, _1), tr("Check amount going to <address> in <txid>")); m_cmd_binder.set_handler("get_tx_proof", boost::bind(&simple_wallet::get_tx_proof, this, _1), tr("Generate a signature to prove payment to <address> in <txid> using the transaction secret key (r) without revealing it")); m_cmd_binder.set_handler("check_tx_proof", boost::bind(&simple_wallet::check_tx_proof, this, _1), tr("Check tx proof for payment going to <address> in <txid>")); - m_cmd_binder.set_handler("show_transfers", boost::bind(&simple_wallet::show_transfers, this, _1), tr("show_transfers [in|out|pending|failed|pool] [<min_height> [<max_height>]] - Show incoming/outgoing transfers within an optional height range")); - m_cmd_binder.set_handler("unspent_outputs", boost::bind(&simple_wallet::unspent_outputs, this, _1), tr("unspent_outputs [<min_amount> <max_amount>] - Show unspent outputs within an optional amount range")); + m_cmd_binder.set_handler("show_transfers", boost::bind(&simple_wallet::show_transfers, this, _1), tr("show_transfers [in|out|pending|failed|pool] [index=<N1>[,<N2>,...]] [<min_height> [<max_height>]] - Show incoming/outgoing transfers within an optional height range")); + m_cmd_binder.set_handler("unspent_outputs", boost::bind(&simple_wallet::unspent_outputs, this, _1), tr("unspent_outputs [index=<N1>[,<N2>,...]] [<min_amount> [<max_amount>]] - Show unspent outputs of a specified address within an optional amount range")); m_cmd_binder.set_handler("rescan_bc", boost::bind(&simple_wallet::rescan_blockchain, this, _1), tr("Rescan blockchain from scratch")); m_cmd_binder.set_handler("set_tx_note", boost::bind(&simple_wallet::set_tx_note, this, _1), tr("Set an arbitrary string note for a txid")); m_cmd_binder.set_handler("get_tx_note", boost::bind(&simple_wallet::get_tx_note, this, _1), tr("Get a string note for a txid")); m_cmd_binder.set_handler("status", boost::bind(&simple_wallet::status, this, _1), tr("Show wallet status information")); + m_cmd_binder.set_handler("wallet_info", boost::bind(&simple_wallet::wallet_info, this, _1), tr("Show wallet information")); m_cmd_binder.set_handler("sign", boost::bind(&simple_wallet::sign, this, _1), tr("Sign the contents of a file")); m_cmd_binder.set_handler("verify", boost::bind(&simple_wallet::verify, this, _1), tr("Verify a signature on the contents of a file")); m_cmd_binder.set_handler("export_key_images", boost::bind(&simple_wallet::export_key_images, this, _1), tr("Export a signed set of key images")); @@ -800,6 +865,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) success_msg_writer() << "min-outputs-value = " << cryptonote::print_money(m_wallet->get_min_output_value()); success_msg_writer() << "merge-destinations = " << m_wallet->merge_destinations(); success_msg_writer() << "confirm-backlog = " << m_wallet->confirm_backlog(); + success_msg_writer() << "confirm-backlog-threshold = " << m_wallet->get_confirm_backlog_threshold(); return true; } else @@ -846,6 +912,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) CHECK_SIMPLE_VARIABLE("min-outputs-value", set_min_output_value, tr("amount")); CHECK_SIMPLE_VARIABLE("merge-destinations", set_merge_destinations, tr("0 or 1")); CHECK_SIMPLE_VARIABLE("confirm-backlog", set_confirm_backlog, tr("0 or 1")); + CHECK_SIMPLE_VARIABLE("confirm-backlog-threshold", set_confirm_backlog_threshold, tr("unsigned integer")); } fail_msg_writer() << tr("set: unrecognized argument(s)"); return true; @@ -854,12 +921,14 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) //---------------------------------------------------------------------------------------------------- bool simple_wallet::set_log(const std::vector<std::string> &args) { - if(args.size() != 1) + if(args.size() > 1) { fail_msg_writer() << tr("usage: set_log <log_level_number_0-4> | <categories>"); return true; } - mlog_set_log(args[0].c_str()); + if (!args.empty()) + mlog_set_log(args[0].c_str()); + success_msg_writer() << "New log categories: " << mlog_get_categories(); return true; } //---------------------------------------------------------------------------------------------------- @@ -1026,6 +1095,13 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) fail_msg_writer() << tr("Electrum-style word list failed verification"); return false; } + + auto pwd_container = tools::password_container::prompt(false, tr("Enter seed encryption passphrase, empty if none")); + if (std::cin.eof() || !pwd_container) + return false; + std::string seed_pass = pwd_container->password(); + if (!seed_pass.empty()) + m_recovery_key = cryptonote::decrypt_key(m_recovery_key, seed_pass); } if (!m_generate_from_view_key.empty()) { @@ -1038,14 +1114,17 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) fail_msg_writer() << tr("No data supplied, cancelled"); return false; } - cryptonote::account_public_address address; - bool has_payment_id; - crypto::hash8 new_payment_id; - if(!get_account_integrated_address_from_str(address, has_payment_id, new_payment_id, tools::wallet2::has_testnet_option(vm), address_string)) + cryptonote::address_parse_info info; + if(!get_account_address_from_str(info, tools::wallet2::has_testnet_option(vm), address_string)) { fail_msg_writer() << tr("failed to parse address"); return false; } + if (info.is_subaddress) + { + fail_msg_writer() << tr("This address is a subaddress which cannot be used here."); + return false; + } // parse view secret key std::string viewkey_string = command_line::input_line("View key: "); @@ -1071,12 +1150,12 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) fail_msg_writer() << tr("failed to verify view key secret key"); return false; } - if (address.m_view_public_key != pkey) { + if (info.address.m_view_public_key != pkey) { fail_msg_writer() << tr("view key does not match standard address"); return false; } - bool r = new_wallet(vm, address, boost::none, viewkey); + bool r = new_wallet(vm, info.address, boost::none, viewkey); CHECK_AND_ASSERT_MES(r, false, tr("account creation failed")); } else if (!m_generate_from_keys.empty()) @@ -1090,14 +1169,17 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) fail_msg_writer() << tr("No data supplied, cancelled"); return false; } - cryptonote::account_public_address address; - bool has_payment_id; - crypto::hash8 new_payment_id; - if(!get_account_integrated_address_from_str(address, has_payment_id, new_payment_id, tools::wallet2::has_testnet_option(vm), address_string)) + cryptonote::address_parse_info info; + if(!get_account_address_from_str(info, tools::wallet2::has_testnet_option(vm), address_string)) { fail_msg_writer() << tr("failed to parse address"); return false; } + if (info.is_subaddress) + { + fail_msg_writer() << tr("This address is a subaddress which cannot be used here."); + return false; + } // parse spend secret key std::string spendkey_string = command_line::input_line("Secret spend key: "); @@ -1139,7 +1221,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) fail_msg_writer() << tr("failed to verify spend key secret key"); return false; } - if (address.m_spend_public_key != pkey) { + if (info.address.m_spend_public_key != pkey) { fail_msg_writer() << tr("spend key does not match standard address"); return false; } @@ -1147,11 +1229,11 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) fail_msg_writer() << tr("failed to verify view key secret key"); return false; } - if (address.m_view_public_key != pkey) { + if (info.address.m_view_public_key != pkey) { fail_msg_writer() << tr("view key does not match standard address"); return false; } - bool r = new_wallet(vm, address, spendkey, viewkey); + bool r = new_wallet(vm, info.address, spendkey, viewkey); CHECK_AND_ASSERT_MES(r, false, tr("account creation failed")); } @@ -1196,10 +1278,8 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) fail_msg_writer() << tr("No data supplied, cancelled"); return false; } - cryptonote::account_public_address address; - bool has_payment_id; - crypto::hash8 new_payment_id; - if(!get_account_integrated_address_from_str(address, has_payment_id, new_payment_id, tools::wallet2::has_testnet_option(vm), address_string)) + cryptonote::address_parse_info info; + if(!get_account_address_from_str(info, tools::wallet2::has_testnet_option(vm), address_string)) { fail_msg_writer() << tr("failed to parse address"); return false; @@ -1229,7 +1309,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) fail_msg_writer() << tr("failed to verify secret view key"); return false; } - if (address.m_view_public_key != pkey) + if (info.address.m_view_public_key != pkey) { fail_msg_writer() << tr("view key does not match standard address"); return false; @@ -1280,14 +1360,14 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) fail_msg_writer() << tr("failed to verify spend key secret key"); return false; } - if (address.m_spend_public_key != pkey) + if (info.address.m_spend_public_key != pkey) { fail_msg_writer() << tr("spend key does not match standard address"); return false; } // create wallet - bool r = new_wallet(vm, address, spendkey, viewkey); + bool r = new_wallet(vm, info.address, spendkey, viewkey); CHECK_AND_ASSERT_MES(r, false, tr("account creation failed")); } @@ -1396,6 +1476,9 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) } catch (const std::exception &e) { } + if (!m_trusted_daemon) + message_writer() << (boost::format(tr("Warning: using an untrusted daemon at %s, privacy will be lessened")) % m_wallet->get_daemon_address()).str(); + m_http_client.set_server(m_wallet->get_daemon_address(), m_wallet->get_daemon_login()); m_wallet->callback(this); @@ -1680,9 +1763,18 @@ bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm) catch (const std::exception& e) { fail_msg_writer() << tr("failed to load wallet: ") << e.what(); - // only suggest removing cache if the password was actually correct - if (m_wallet && m_wallet->verify_password(password)) - fail_msg_writer() << boost::format(tr("You may want to remove the file \"%s\" and try again")) % m_wallet_file; + if (m_wallet) + { + // only suggest removing cache if the password was actually correct + bool password_is_correct = false; + try + { + password_is_correct = m_wallet->verify_password(password); + } + catch (...) { } // guard against I/O errors + if (password_is_correct) + fail_msg_writer() << boost::format(tr("You may want to remove the file \"%s\" and try again")) % m_wallet_file; + } return false; } success_msg_writer() << @@ -1857,29 +1949,31 @@ 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 crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount) +void simple_wallet::on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index) { message_writer(console_color_green, false) << "\r" << tr("Height ") << height << ", " << - tr("transaction ") << txid << ", " << - tr("received ") << print_money(amount); + tr("txid ") << txid << ", " << + print_money(amount) << tr(" XMR, ") << + tr("idx ") << subaddr_index; if (m_auto_refresh_refreshing) m_cmd_binder.print_prompt(); else m_refresh_progress_reporter.update(height, true); } //---------------------------------------------------------------------------------------------------- -void simple_wallet::on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount) +void simple_wallet::on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index) { // Not implemented in CLI wallet } //---------------------------------------------------------------------------------------------------- -void simple_wallet::on_money_spent(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx) +void simple_wallet::on_money_spent(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx, const cryptonote::subaddress_index& subaddr_index) { message_writer(console_color_magenta, false) << "\r" << tr("Height ") << height << ", " << - tr("transaction ") << txid << ", " << - tr("spent ") << print_money(amount); + tr("txid ") << txid << ", " << + tr("spent ") << print_money(amount) << ", " << + tr("idx ") << subaddr_index; if (m_auto_refresh_refreshing) m_cmd_binder.print_prompt(); else @@ -1898,7 +1992,7 @@ void simple_wallet::on_skip_transaction(uint64_t height, const crypto::hash &txi m_refresh_progress_reporter.update(height, true); } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::refresh_main(uint64_t start_height, bool reset) +bool simple_wallet::refresh_main(uint64_t start_height, bool reset, bool is_init) { if (!try_connect_to_daemon()) return true; @@ -1926,6 +2020,8 @@ bool simple_wallet::refresh_main(uint64_t start_height, bool reset) // Clear line "Height xxx of xxx" std::cout << "\r \r"; success_msg_writer(true) << tr("Refresh done, blocks received: ") << fetched_blocks; + if (is_init) + print_accounts(); show_balance_unlocked(); } catch (const tools::error::daemon_busy&) @@ -1986,47 +2082,84 @@ bool simple_wallet::refresh(const std::vector<std::string>& args) return refresh_main(start_height, false); } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::show_balance_unlocked() +bool simple_wallet::show_balance_unlocked(bool detailed) { - success_msg_writer() << tr("Balance: ") << print_money(m_wallet->balance()) << ", " - << tr("unlocked balance: ") << print_money(m_wallet->unlocked_balance()); + success_msg_writer() << tr("Currently selected account: [") << m_current_subaddress_account << tr("] ") << m_wallet->get_subaddress_label({m_current_subaddress_account, 0}); + success_msg_writer() << tr("Balance: ") << print_money(m_wallet->balance(m_current_subaddress_account)) << ", " + << tr("unlocked balance: ") << print_money(m_wallet->unlocked_balance(m_current_subaddress_account)); + std::map<uint32_t, uint64_t> balance_per_subaddress = m_wallet->balance_per_subaddress(m_current_subaddress_account); + std::map<uint32_t, uint64_t> unlocked_balance_per_subaddress = m_wallet->unlocked_balance_per_subaddress(m_current_subaddress_account); + if (!detailed || balance_per_subaddress.empty()) + return true; + success_msg_writer() << tr("Balance per address:"); + success_msg_writer() << boost::format("%15s %21s %21s %7s %21s") % tr("Address") % tr("Balance") % tr("Unlocked balance") % tr("Outputs") % tr("Label"); + std::vector<tools::wallet2::transfer_details> transfers; + m_wallet->get_transfers(transfers); + for (const auto& i : balance_per_subaddress) + { + cryptonote::subaddress_index subaddr_index = {m_current_subaddress_account, i.first}; + std::string address_str = m_wallet->get_subaddress_as_str(subaddr_index).substr(0, 6); + uint64_t num_unspent_outputs = std::count_if(transfers.begin(), transfers.end(), [&subaddr_index](const tools::wallet2::transfer_details& td) { return !td.m_spent && td.m_subaddr_index == subaddr_index; }); + success_msg_writer() << boost::format(tr("%8u %6s %21s %21s %7u %21s")) % i.first % address_str % print_money(i.second) % print_money(unlocked_balance_per_subaddress[i.first]) % num_unspent_outputs % m_wallet->get_subaddress_label(subaddr_index); + } return true; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::show_balance(const std::vector<std::string>& args/* = std::vector<std::string>()*/) { + if (args.size() > 1 || (args.size() == 1 && args[0] != "detail")) + { + fail_msg_writer() << tr("usage: balance [detail]"); + return true; + } LOCK_IDLE_SCOPE(); - show_balance_unlocked(); + show_balance_unlocked(args.size() == 1); return true; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& args) { + if (args.size() > 3) + { + fail_msg_writer() << tr("usage: incoming_transfers [available|unavailable] [verbose] [index=<N>]"); + return true; + } + auto local_args = args; LOCK_IDLE_SCOPE(); bool filter = false; bool available = false; bool verbose = false; - for (const auto &arg: args) + if (local_args.size() > 0) { - if (arg == "available") + if (local_args[0] == "available") { filter = true; available = true; + local_args.erase(local_args.begin()); } - else if (arg == "unavailable") + else if (local_args[0] == "unavailable") { filter = true; available = false; - } - else if (arg == "verbose") - { - verbose = true; + local_args.erase(local_args.begin()); } } + if (local_args.size() > 0 && local_args[0] == "verbose") + { + verbose = true; + local_args.erase(local_args.begin()); + } PAUSE_READLINE(); + std::set<uint32_t> subaddr_indices; + if (local_args.size() > 0 && local_args[0].substr(0, 6) == "index=") + { + if (!parse_subaddress_indices(local_args[0], subaddr_indices)) + return true; + } + tools::wallet2::transfer_container transfers; m_wallet->get_transfers(transfers); @@ -2035,25 +2168,28 @@ bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& args { if (!filter || available != td.m_spent) { + if (m_current_subaddress_account != td.m_subaddr_index.major || (!subaddr_indices.empty() && subaddr_indices.count(td.m_subaddr_index.minor) == 0)) + continue; if (!transfers_found) { std::string verbose_string; if (verbose) verbose_string = (boost::format("%68s%68s") % tr("pubkey") % tr("key image")).str(); - message_writer() << boost::format("%21s%8s%12s%8s%16s%68s%s") % tr("amount") % tr("spent") % tr("unlocked") % tr("ringct") % tr("global index") % tr("tx id") % verbose_string; + message_writer() << boost::format("%21s%8s%12s%8s%16s%68s%16s%s") % tr("amount") % tr("spent") % tr("unlocked") % tr("ringct") % tr("global index") % tr("tx id") % tr("addr index") % verbose_string; transfers_found = true; } std::string verbose_string; if (verbose) verbose_string = (boost::format("%68s%68s") % td.get_public_key() % (td.m_key_image_known ? epee::string_tools::pod_to_hex(td.m_key_image) : std::string('?', 64))).str(); message_writer(td.m_spent ? console_color_magenta : console_color_green, false) << - boost::format("%21s%8s%12s%8s%16u%68s%s") % + boost::format("%21s%8s%12s%8s%16u%68s%16u%s") % 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 % td.m_txid % + td.m_subaddr_index.minor % verbose_string; } } @@ -2089,8 +2225,8 @@ bool simple_wallet::show_payments(const std::vector<std::string> &args) PAUSE_READLINE(); - message_writer() << boost::format("%68s%68s%12s%21s%16s") % - tr("payment") % tr("transaction") % tr("height") % tr("amount") % tr("unlock time"); + message_writer() << boost::format("%68s%68s%12s%21s%16s%16s") % + tr("payment") % tr("transaction") % tr("height") % tr("amount") % tr("unlock time") % tr("addr index"); bool payments_found = false; for(std::string arg : args) @@ -2113,12 +2249,13 @@ bool simple_wallet::show_payments(const std::vector<std::string> &args) payments_found = true; } success_msg_writer(true) << - boost::format("%68s%68s%12s%21s%16s") % + boost::format("%68s%68s%12s%21s%16s%16s") % payment_id % pd.m_tx_hash % pd.m_block_height % print_money(pd.m_amount) % - pd.m_unlock_time; + pd.m_unlock_time % + pd.m_subaddr_index.minor; } } else @@ -2309,6 +2446,7 @@ bool simple_wallet::print_ring_members(const std::vector<tools::wallet2::pending //---------------------------------------------------------------------------------------------------- bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::string> &args_) { +// "transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> <amount> [<payment_id>]" if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } if (!try_connect_to_daemon()) return true; @@ -2317,6 +2455,14 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri std::vector<std::string> local_args = args_; + std::set<uint32_t> subaddr_indices; + if (local_args.size() > 0 && local_args[0].substr(0, 6) == "index=") + { + if (!parse_subaddress_indices(local_args[0], subaddr_indices)) + return true; + local_args.erase(local_args.begin()); + } + int priority = 0; if(local_args.size() > 0) { auto priority_pos = std::find( @@ -2329,7 +2475,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri } } - size_t fake_outs_count; + size_t fake_outs_count = 0; if(local_args.size() > 0) { size_t ring_size; if(!epee::string_tools::get_xtype_from_string(ring_size, local_args[0])) @@ -2411,16 +2557,17 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri vector<cryptonote::tx_destination_entry> dsts; for (size_t i = 0; i < local_args.size(); i += 2) { + cryptonote::address_parse_info info; cryptonote::tx_destination_entry de; - bool has_payment_id; - crypto::hash8 new_payment_id; - if (!cryptonote::get_account_address_from_str_or_url(de.addr, has_payment_id, new_payment_id, m_wallet->testnet(), local_args[i], oa_prompter)) + if (!cryptonote::get_account_address_from_str_or_url(info, m_wallet->testnet(), local_args[i], oa_prompter)) { fail_msg_writer() << tr("failed to parse address"); return true; } + de.addr = info.address; + de.is_subaddress = info.is_subaddress; - if (has_payment_id) + if (info.has_payment_id) { if (payment_id_seen) { @@ -2429,7 +2576,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri } std::string extra_nonce; - set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, new_payment_id); + set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, info.payment_id); bool r = add_extra_nonce_to_tx_extra(extra, extra_nonce); if(!r) { @@ -2480,10 +2627,10 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri return true; } unlock_block = bc_height + locked_blocks; - ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, unlock_block /* unlock_time */, priority, extra, m_trusted_daemon); + ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, unlock_block /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices, m_trusted_daemon); break; case TransferNew: - ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, 0 /* unlock_time */, priority, extra, m_trusted_daemon); + ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, 0 /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices, m_trusted_daemon); break; default: LOG_ERROR("Unknown transfer method, using original"); @@ -2525,7 +2672,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri } else { - if (nblocks[0].first > 0) + if (nblocks[0].first > m_wallet->get_confirm_backlog_threshold()) prompt << (boost::format(tr("There is currently a %u block backlog at that fee level. Is this okay? (Y/Yes/N/No)")) % nblocks[0].first).str(); } } @@ -2570,6 +2717,17 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri } std::stringstream prompt; + for (size_t n = 0; n < ptx_vector.size(); ++n) + { + prompt << tr("\nTransaction ") << (n + 1) << "/" << ptx_vector.size() << ":\n"; + subaddr_indices.clear(); + for (uint32_t i : ptx_vector[n].construction_data.subaddr_indices) + subaddr_indices.insert(i); + for (uint32_t i : subaddr_indices) + prompt << boost::format(tr("Spending from address index %d\n")) % i; + if (subaddr_indices.size() > 1) + prompt << tr("WARNING: Outputs of multiple addresses are being used together, which might potentially compromise your privacy.\n"); + } prompt << boost::format(tr("Sending %s. ")) % print_money(total_sent); if (ptx_vector.size() > 1) { @@ -2900,13 +3058,22 @@ bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_) //---------------------------------------------------------------------------------------------------- bool simple_wallet::sweep_main(uint64_t below, const std::vector<std::string> &args_) { + // sweep_all [index=<N1>[,<N2>,...]] [<ring_size>] <address> [<payment_id>] if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } if (!try_connect_to_daemon()) return true; std::vector<std::string> local_args = args_; - size_t fake_outs_count; + std::set<uint32_t> subaddr_indices; + if (local_args.size() > 0 && local_args[0].substr(0, 6) == "index=") + { + if (!parse_subaddress_indices(local_args[0], subaddr_indices)) + return true; + local_args.erase(local_args.begin()); + } + + size_t fake_outs_count = 0; if(local_args.size() > 0) { size_t ring_size; if(!epee::string_tools::get_xtype_from_string(ring_size, local_args[0])) @@ -2924,10 +3091,9 @@ bool simple_wallet::sweep_main(uint64_t below, const std::vector<std::string> &a std::vector<uint8_t> extra; bool payment_id_seen = false; - if (2 == local_args.size()) + if (2 >= local_args.size()) { std::string payment_id_str = local_args.back(); - local_args.pop_back(); crypto::hash payment_id; bool r = tools::wallet2::parse_long_payment_id(payment_id_str, payment_id); @@ -2936,6 +3102,7 @@ bool simple_wallet::sweep_main(uint64_t below, const std::vector<std::string> &a std::string extra_nonce; set_payment_id_to_tx_extra_nonce(extra_nonce, payment_id); r = add_extra_nonce_to_tx_extra(extra, extra_nonce); + payment_id_seen = true; } else { @@ -2946,15 +3113,17 @@ bool simple_wallet::sweep_main(uint64_t below, const std::vector<std::string> &a std::string extra_nonce; set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, payment_id8); r = add_extra_nonce_to_tx_extra(extra, extra_nonce); + payment_id_seen = true; } } - if(!r) + if(!r && local_args.size() == 3) { fail_msg_writer() << tr("payment id has invalid format, expected 16 or 64 character hex string: ") << payment_id_str; return true; } - payment_id_seen = true; + if (payment_id_seen) + local_args.pop_back(); } if (local_args.size() == 0) @@ -2963,16 +3132,14 @@ bool simple_wallet::sweep_main(uint64_t below, const std::vector<std::string> &a return true; } - bool has_payment_id; - crypto::hash8 new_payment_id; - cryptonote::account_public_address address; - if (!cryptonote::get_account_address_from_str_or_url(address, has_payment_id, new_payment_id, m_wallet->testnet(), local_args[0], oa_prompter)) + cryptonote::address_parse_info info; + if (!cryptonote::get_account_address_from_str_or_url(info, m_wallet->testnet(), local_args[0], oa_prompter)) { fail_msg_writer() << tr("failed to parse address"); return true; } - if (has_payment_id) + if (info.has_payment_id) { if (payment_id_seen) { @@ -2981,7 +3148,7 @@ bool simple_wallet::sweep_main(uint64_t below, const std::vector<std::string> &a } std::string extra_nonce; - set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, new_payment_id); + set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, info.payment_id); bool r = add_extra_nonce_to_tx_extra(extra, extra_nonce); if(!r) { @@ -3010,7 +3177,7 @@ bool simple_wallet::sweep_main(uint64_t below, const std::vector<std::string> &a try { // figure out what tx will be necessary - auto ptx_vector = m_wallet->create_transactions_all(below, address, fake_outs_count, 0 /* unlock_time */, 0 /* unused fee arg*/, extra, m_trusted_daemon); + auto ptx_vector = m_wallet->create_transactions_all(below, info.address, info.is_subaddress, fake_outs_count, 0 /* unlock_time */, 0 /* unused fee arg*/, extra, m_current_subaddress_account, subaddr_indices, m_trusted_daemon); if (ptx_vector.empty()) { @@ -3028,7 +3195,18 @@ bool simple_wallet::sweep_main(uint64_t below, const std::vector<std::string> &a } std::ostringstream prompt; - if (!print_ring_members(ptx_vector, prompt)) + for (size_t n = 0; n < ptx_vector.size(); ++n) + { + prompt << tr("\nTransaction ") << (n + 1) << "/" << ptx_vector.size() << ":\n"; + subaddr_indices.clear(); + for (uint32_t i : ptx_vector[n].construction_data.subaddr_indices) + subaddr_indices.insert(i); + for (uint32_t i : subaddr_indices) + prompt << boost::format(tr("Spending from address index %d\n")) % i; + if (subaddr_indices.size() > 1) + prompt << tr("WARNING: Outputs of multiple addresses are being used together, which might potentially compromise your privacy.\n"); + } + if (m_wallet->print_ring_members() && !print_ring_members(ptx_vector, prompt)) return true; if (ptx_vector.size() > 1) { prompt << boost::format(tr("Sweeping %s in %llu transactions for a total fee of %s. Is this okay? (Y/Yes/N/No): ")) % @@ -3187,17 +3365,16 @@ bool simple_wallet::sweep_below(const std::vector<std::string> &args_) bool simple_wallet::donate(const std::vector<std::string> &args_) { std::vector<std::string> local_args = args_; - if(local_args.empty() || local_args.size() > 3) + if(local_args.empty() || local_args.size() > 5) { - fail_msg_writer() << tr("wrong number of arguments"); + fail_msg_writer() << tr("usage: donate [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <amount> [<payment_id>]"); return true; } - std::string ring_size_str; // Hardcode Monero's donation address (see #1447) const std::string address_str = "44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A"; std::string amount_str; std::string payment_id_str; - // check payment id + // get payment id and pop crypto::hash payment_id; crypto::hash8 payment_id8; if (tools::wallet2::parse_long_payment_id (local_args.back(), payment_id ) || @@ -3206,17 +3383,10 @@ bool simple_wallet::donate(const std::vector<std::string> &args_) payment_id_str = local_args.back(); local_args.pop_back(); } - // check ring size - if (local_args.size() > 1) - { - ring_size_str = local_args[0]; - local_args.erase(local_args.begin()); - } - amount_str = local_args[0]; - // refill args as necessary - local_args.clear(); - if (!ring_size_str.empty()) - local_args.push_back(ring_size_str); + // get amount and pop + amount_str = local_args.back(); + local_args.pop_back(); + // push back address, amount, payment id local_args.push_back(address_str); local_args.push_back(amount_str); if (!payment_id_str.empty()) @@ -3231,8 +3401,7 @@ bool simple_wallet::accept_loaded_tx(const std::function<size_t()> get_num_txes, // gather info to ask the user uint64_t amount = 0, amount_to_dests = 0, change = 0; size_t min_ring_size = ~0; - std::unordered_map<std::string, std::pair<std::string, uint64_t>> dests; - const std::string wallet_address = m_wallet->get_account().get_public_address_str(m_wallet->testnet()); + std::unordered_map<cryptonote::account_public_address, std::pair<std::string, uint64_t>> dests; int first_known_non_zero_change_index = -1; std::string payment_id_string = ""; for (size_t n = 0; n < get_num_txes(); ++n) @@ -3241,7 +3410,7 @@ bool simple_wallet::accept_loaded_tx(const std::function<size_t()> get_num_txes, std::vector<tx_extra_field> tx_extra_fields; bool has_encrypted_payment_id = false; - crypto::hash8 payment_id8 = cryptonote::null_hash8; + crypto::hash8 payment_id8 = crypto::null_hash8; if (cryptonote::parse_tx_extra(cd.extra, tx_extra_fields)) { tx_extra_nonce extra_nonce; @@ -3274,24 +3443,24 @@ bool simple_wallet::accept_loaded_tx(const std::function<size_t()> get_num_txes, for (size_t d = 0; d < cd.splitted_dsts.size(); ++d) { const tx_destination_entry &entry = cd.splitted_dsts[d]; - std::string address, standard_address = get_account_address_as_str(m_wallet->testnet(), entry.addr); - if (has_encrypted_payment_id) + std::string address, standard_address = get_account_address_as_str(m_wallet->testnet(), entry.is_subaddress, entry.addr); + if (has_encrypted_payment_id && !entry.is_subaddress) { address = get_account_integrated_address_as_str(m_wallet->testnet(), entry.addr, payment_id8); address += std::string(" (" + standard_address + " with encrypted payment id " + epee::string_tools::pod_to_hex(payment_id8) + ")"); } else address = standard_address; - std::unordered_map<std::string,std::pair<std::string,uint64_t>>::iterator i = dests.find(standard_address); + auto i = dests.find(entry.addr); if (i == dests.end()) - dests.insert(std::make_pair(standard_address, std::make_pair(address, entry.amount))); + dests.insert(std::make_pair(entry.addr, std::make_pair(address, entry.amount))); else i->second.second += entry.amount; amount_to_dests += entry.amount; } if (cd.change_dts.amount > 0) { - std::unordered_map<std::string, std::pair<std::string, uint64_t>>::iterator it = dests.find(get_account_address_as_str(m_wallet->testnet(), cd.change_dts.addr)); + auto it = dests.find(cd.change_dts.addr); if (it == dests.end()) { fail_msg_writer() << tr("Claimed change does not go to a paid address"); @@ -3315,7 +3484,7 @@ bool simple_wallet::accept_loaded_tx(const std::function<size_t()> get_num_txes, change += cd.change_dts.amount; it->second.second -= cd.change_dts.amount; if (it->second.second == 0) - dests.erase(get_account_address_as_str(m_wallet->testnet(), cd.change_dts.addr)); + dests.erase(cd.change_dts.addr); } } @@ -3323,7 +3492,7 @@ bool simple_wallet::accept_loaded_tx(const std::function<size_t()> get_num_txes, payment_id_string = "no payment ID"; std::string dest_string; - for (std::unordered_map<std::string, std::pair<std::string, uint64_t>>::const_iterator i = dests.begin(); i != dests.end(); ) + for (auto i = dests.begin(); i != dests.end(); ) { dest_string += (boost::format(tr("sending %s to %s")) % print_money(i->second.second) % i->second.first).str(); ++i; @@ -3336,7 +3505,7 @@ bool simple_wallet::accept_loaded_tx(const std::function<size_t()> get_num_txes, std::string change_string; if (change > 0) { - std::string address = get_account_address_as_str(m_wallet->testnet(), get_tx(0).change_dts.addr); + std::string address = get_account_address_as_str(m_wallet->testnet(), get_tx(0).subaddr_account > 0, get_tx(0).change_dts.addr); change_string += (boost::format(tr("%s change to %s")) % print_money(change) % address).str(); } else @@ -3474,6 +3643,9 @@ bool simple_wallet::submit_transfer(const std::vector<std::string> &args_) catch (const tools::error::tx_rejected& e) { fail_msg_writer() << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status(); + std::string reason = e.reason(); + if (!reason.empty()) + fail_msg_writer() << tr("Reason: ") << reason; } catch (const tools::error::tx_sum_overflow& e) { @@ -3532,8 +3704,9 @@ bool simple_wallet::get_tx_key(const std::vector<std::string> &args_) LOCK_IDLE_SCOPE(); crypto::secret_key tx_key; + std::vector<crypto::secret_key> additional_tx_keys; std::vector<crypto::secret_key> amount_keys; - if (m_wallet->get_tx_key(txid, tx_key)) + if (m_wallet->get_tx_key(txid, tx_key, additional_tx_keys)) { success_msg_writer() << tr("Tx key: ") << epee::string_tools::pod_to_hex(tx_key); return true; @@ -3547,8 +3720,8 @@ bool simple_wallet::get_tx_key(const std::vector<std::string> &args_) //---------------------------------------------------------------------------------------------------- bool simple_wallet::get_tx_proof(const std::vector<std::string> &args) { - if(args.size() != 2 && args.size() != 3) { - fail_msg_writer() << tr("usage: get_tx_proof <txid> <dest_address> [<tx_key>]"); + if(args.size() != 2) { + fail_msg_writer() << tr("usage: get_tx_proof <txid> <dest_address>"); return true; } if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } @@ -3561,10 +3734,8 @@ bool simple_wallet::get_tx_proof(const std::vector<std::string> &args) } crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); - cryptonote::account_public_address address; - bool has_payment_id; - crypto::hash8 payment_id; - if(!cryptonote::get_account_address_from_str_or_url(address, has_payment_id, payment_id, m_wallet->testnet(), args[1], oa_prompter)) + cryptonote::address_parse_info info; + if(!cryptonote::get_account_address_from_str_or_url(info, m_wallet->testnet(), args[1], oa_prompter)) { fail_msg_writer() << tr("failed to parse address"); return true; @@ -3572,43 +3743,126 @@ bool simple_wallet::get_tx_proof(const std::vector<std::string> &args) LOCK_IDLE_SCOPE(); - crypto::secret_key tx_key, tx_key2; - bool r = m_wallet->get_tx_key(txid, tx_key); - cryptonote::blobdata tx_key_data; - if (args.size() == 3) + crypto::secret_key tx_key; + std::vector<crypto::secret_key> additional_tx_keys; + if (!m_wallet->get_tx_key(txid, tx_key, additional_tx_keys)) { - if(!epee::string_tools::parse_hexstr_to_binbuff(args[2], tx_key_data) || tx_key_data.size() != sizeof(crypto::secret_key)) + fail_msg_writer() << tr("Tx secret key wasn't found in the wallet file."); + return true; + } + + // fetch tx prefix either from the daemon or from the wallet cache if it's still pending + cryptonote::transaction_prefix tx; + // first, look up the wallet cache + std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>> unconfirmed_payments_out; + m_wallet->get_unconfirmed_payments_out(unconfirmed_payments_out, m_current_subaddress_account); + auto found = std::find_if(unconfirmed_payments_out.begin(), unconfirmed_payments_out.end(), [&](const std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>& p) + { + return p.first == txid; + }); + // if not found, query the daemon + if (found == unconfirmed_payments_out.end()) + { + COMMAND_RPC_GET_TRANSACTIONS::request req; + COMMAND_RPC_GET_TRANSACTIONS::response res; + req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); + if (!net_utils::invoke_http_json("/gettransactions", req, res, m_http_client) || + (res.txs.size() != 1 && res.txs_as_hex.size() != 1)) { - fail_msg_writer() << tr("failed to parse tx_key"); + fail_msg_writer() << tr("failed to get transaction from daemon"); return true; } - tx_key2 = *reinterpret_cast<const crypto::secret_key*>(tx_key_data.data()); - } - if (r) - { - if (args.size() == 3 && tx_key != rct::sk2rct(tx_key2)) + cryptonote::blobdata tx_data; + bool ok; + 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); + if (!ok) + { + fail_msg_writer() << tr("failed to parse transaction from daemon"); + return true; + } + crypto::hash tx_hash, tx_prefix_hash; + cryptonote::transaction tx_full; + if (!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx_full, tx_hash, tx_prefix_hash)) { - fail_msg_writer() << tr("Tx secret key was found for the given txid, but you've also provided another tx secret key which doesn't match the found one."); + fail_msg_writer() << tr("failed to validate transaction from daemon"); return true; } + if (tx_hash != txid) + { + fail_msg_writer() << tr("failed to get the right transaction from daemon"); + return true; + } + tx = tx_full; } else { - if (tx_key_data.empty()) + tx = found->second.m_tx; + } + + // fetch tx pubkey + crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); + if (tx_pub_key == null_pkey) + { + fail_msg_writer() << tr("Tx pubkey was not found"); + return true; + } + const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(tx); + if (additional_tx_keys.size() != additional_tx_pub_keys.size()) + { + fail_msg_writer() << tr("The set of additional tx secret/public keys doesn't match"); + return true; + } + + // find the correct R: + // R = r*G for standard addresses + // R = r*B for subaddresses (where B is the recipient's spend pubkey) + crypto::public_key R; + crypto::secret_key r; + if (info.is_subaddress) + { + auto i = additional_tx_keys.begin(); + auto j = additional_tx_pub_keys.begin(); + for (; ; ++i, ++j) { + if (i == additional_tx_keys.end()) + { + r = tx_key; + R = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(info.address.m_spend_public_key), rct::sk2rct(r))); + if (R == tx_pub_key) + break; + fail_msg_writer() << tr("The matching tx secret/pubkey pair wasn't found for this subaddress"); + return true; + } + else + { + r = *i; + R = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(info.address.m_spend_public_key), rct::sk2rct(r))); + if (R == *j) + break; + } + } + } + else + { + r = tx_key; + crypto::secret_key_to_public_key(r, R); + if (R != tx_pub_key) { - fail_msg_writer() << tr("Tx secret key wasn't found in the wallet file. Provide it as the optional third parameter if you have it elsewhere."); + fail_msg_writer() << tr("The destinations of this tx don't include a standard address"); return true; } - tx_key = tx_key2; } - crypto::public_key R; - crypto::secret_key_to_public_key(tx_key, R); - crypto::public_key rA = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(address.m_view_public_key), rct::sk2rct(tx_key))); + crypto::public_key rA = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(info.address.m_view_public_key), rct::sk2rct(r))); crypto::signature sig; try { - crypto::generate_tx_proof(txid, R, address.m_view_public_key, rA, tx_key, sig); + if (info.is_subaddress) + crypto::generate_tx_proof(txid, R, info.address.m_view_public_key, info.address.m_spend_public_key, rA, r, sig); + else + crypto::generate_tx_proof(txid, R, info.address.m_view_public_key, boost::none, rA, r, sig); } catch (const std::runtime_error &e) { @@ -3663,26 +3917,24 @@ bool simple_wallet::check_tx_key(const std::vector<std::string> &args_) } tx_key = *reinterpret_cast<const crypto::secret_key*>(tx_key_data.data()); - cryptonote::account_public_address address; - bool has_payment_id; - crypto::hash8 payment_id; - if(!cryptonote::get_account_address_from_str_or_url(address, has_payment_id, payment_id, m_wallet->testnet(), local_args[2], oa_prompter)) + cryptonote::address_parse_info info; + if(!cryptonote::get_account_address_from_str_or_url(info, m_wallet->testnet(), local_args[2], oa_prompter)) { fail_msg_writer() << tr("failed to parse address"); return true; } crypto::key_derivation derivation; - if (!crypto::generate_key_derivation(address.m_view_public_key, tx_key, derivation)) + if (!crypto::generate_key_derivation(info.address.m_view_public_key, tx_key, derivation)) { fail_msg_writer() << tr("failed to generate key derivation from supplied parameters"); return true; } - return check_tx_key_helper(txid, address, derivation); + return check_tx_key_helper(txid, info.address, info.is_subaddress, derivation); } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::check_tx_key_helper(const crypto::hash &txid, const cryptonote::account_public_address &address, const crypto::key_derivation &derivation) +bool simple_wallet::check_tx_key_helper(const crypto::hash &txid, const cryptonote::account_public_address &address, bool is_subaddress, const crypto::key_derivation &derivation) { COMMAND_RPC_GET_TRANSACTIONS::request req; COMMAND_RPC_GET_TRANSACTIONS::response res; @@ -3765,13 +4017,14 @@ bool simple_wallet::check_tx_key_helper(const crypto::hash &txid, const cryptono return true; } + std::string address_str = get_account_address_as_str(m_wallet->testnet(), is_subaddress, address); if (received > 0) { - success_msg_writer() << get_account_address_as_str(m_wallet->testnet(), address) << " " << tr("received") << " " << print_money(received) << " " << tr("in txid") << " " << txid; + success_msg_writer() << address_str << " " << tr("received") << " " << print_money(received) << " " << tr("in txid") << " " << txid; } else { - fail_msg_writer() << get_account_address_as_str(m_wallet->testnet(), address) << " " << tr("received nothing in txid") << " " << txid; + fail_msg_writer() << address_str << " " << tr("received nothing in txid") << " " << txid; } if (res.txs.front().in_pool) { @@ -3815,10 +4068,8 @@ bool simple_wallet::check_tx_proof(const std::vector<std::string> &args) crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); // parse address - cryptonote::account_public_address address; - bool has_payment_id; - crypto::hash8 payment_id; - if(!cryptonote::get_account_address_from_str_or_url(address, has_payment_id, payment_id, m_wallet->testnet(), args[1], oa_prompter)) + cryptonote::address_parse_info info; + if(!cryptonote::get_account_address_from_str_or_url(info, m_wallet->testnet(), args[1], oa_prompter)) { fail_msg_writer() << tr("failed to parse address"); return true; @@ -3889,15 +4140,34 @@ bool simple_wallet::check_tx_proof(const std::vector<std::string> &args) fail_msg_writer() << tr("failed to get the right transaction from daemon"); return true; } - crypto::public_key R = get_tx_pub_key_from_extra(tx); - if (R == null_pkey) + crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); + if (tx_pub_key == null_pkey) { fail_msg_writer() << tr("Tx pubkey was not found"); return true; } + const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(tx); // check signature - if (crypto::check_tx_proof(txid, R, address.m_view_public_key, rA, sig)) + bool good_signature = false; + if (info.is_subaddress) + { + good_signature = crypto::check_tx_proof(txid, tx_pub_key, info.address.m_view_public_key, info.address.m_spend_public_key, rA, sig); + if (!good_signature) + { + for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i) + { + good_signature = crypto::check_tx_proof(txid, additional_tx_pub_keys[i], info.address.m_view_public_key, info.address.m_spend_public_key, rA, sig); + if (good_signature) + break; + } + } + } + else + { + good_signature = crypto::check_tx_proof(txid, tx_pub_key, info.address.m_view_public_key, boost::none, rA, sig); + } + if (good_signature) { success_msg_writer() << tr("Good signature"); } @@ -3915,7 +4185,7 @@ bool simple_wallet::check_tx_proof(const std::vector<std::string> &args) return true; } - return check_tx_key_helper(txid, address, derivation); + return check_tx_key_helper(txid, info.address, info.is_subaddress, derivation); } //---------------------------------------------------------------------------------------------------- static std::string get_human_readable_timestamp(uint64_t ts) @@ -3965,9 +4235,10 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_) bool pool = true; uint64_t min_height = 0; uint64_t max_height = (uint64_t)-1; + boost::optional<uint32_t> subaddr_index; - if(local_args.size() > 3) { - fail_msg_writer() << tr("usage: show_transfers [in|out|all|pending|failed] [<min_height> [<max_height>]]"); + if(local_args.size() > 4) { + fail_msg_writer() << tr("usage: show_transfers [in|out|all|pending|failed] [index=<N1>[,<N2>,...]] [<min_height> [<max_height>]]"); return true; } @@ -4000,6 +4271,15 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_) } } + // subaddr_index + std::set<uint32_t> subaddr_indices; + if (local_args.size() > 0 && local_args[0].substr(0, 6) == "index=") + { + if (!parse_subaddress_indices(local_args[0], subaddr_indices)) + return true; + local_args.erase(local_args.begin()); + } + // min height if (local_args.size() > 0) { try { @@ -4030,20 +4310,32 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_) if (in) { std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> payments; - m_wallet->get_payments(payments, min_height, max_height); + m_wallet->get_payments(payments, min_height, max_height, m_current_subaddress_account, subaddr_indices); for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) { const tools::wallet2::payment_details &pd = i->second; std::string payment_id = string_tools::pod_to_hex(i->first); if (payment_id.substr(16).find_first_not_of('0') == std::string::npos) payment_id = payment_id.substr(0,16); std::string note = m_wallet->get_tx_note(pd.m_tx_hash); - output.insert(std::make_pair(pd.m_block_height, std::make_pair(true, (boost::format("%16.16s %20.20s %s %s %s %s") % get_human_readable_timestamp(pd.m_timestamp) % print_money(pd.m_amount) % string_tools::pod_to_hex(pd.m_tx_hash) % payment_id % "-" % note).str()))); + output.insert(std::make_pair(pd.m_block_height, std::make_pair(true, (boost::format("%16.16s %20.20s %s %s %d %s %s") % get_human_readable_timestamp(pd.m_timestamp) % print_money(pd.m_amount) % string_tools::pod_to_hex(pd.m_tx_hash) % payment_id % pd.m_subaddr_index.minor % "-" % note).str()))); } } + auto print_subaddr_indices = [](const std::set<uint32_t>& indices) + { + stringstream ss; + bool first = true; + for (uint32_t i : indices) + { + ss << (first ? "" : ",") << i; + first = false; + } + return ss.str(); + }; + if (out) { std::list<std::pair<crypto::hash, tools::wallet2::confirmed_transfer_details>> payments; - m_wallet->get_payments_out(payments, min_height, max_height); + m_wallet->get_payments_out(payments, min_height, max_height, m_current_subaddress_account, subaddr_indices); for (std::list<std::pair<crypto::hash, tools::wallet2::confirmed_transfer_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) { const tools::wallet2::confirmed_transfer_details &pd = i->second; uint64_t change = pd.m_change == (uint64_t)-1 ? 0 : pd.m_change; // change may not be known @@ -4052,13 +4344,13 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_) for (const auto &d: pd.m_dests) { if (!dests.empty()) dests += ", "; - dests += get_account_address_as_str(m_wallet->testnet(), d.addr) + ": " + print_money(d.amount); + dests += get_account_address_as_str(m_wallet->testnet(), d.is_subaddress, d.addr) + ": " + print_money(d.amount); } 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); std::string note = m_wallet->get_tx_note(i->first); - output.insert(std::make_pair(pd.m_block_height, std::make_pair(false, (boost::format("%16.16s %20.20s %s %s %14.14s %s %s") % get_human_readable_timestamp(pd.m_timestamp) % print_money(pd.m_amount_in - change - fee) % string_tools::pod_to_hex(i->first) % payment_id % print_money(fee) % dests % note).str()))); + output.insert(std::make_pair(pd.m_block_height, std::make_pair(false, (boost::format("%16.16s %20.20s %s %s %14.14s %s %s - %s") % get_human_readable_timestamp(pd.m_timestamp) % print_money(pd.m_amount_in - change - fee) % string_tools::pod_to_hex(i->first) % payment_id % print_money(fee) % dests % print_subaddr_indices(pd.m_subaddr_indices) % note).str()))); } } @@ -4074,26 +4366,26 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_) { m_wallet->update_pool_state(); std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> payments; - m_wallet->get_unconfirmed_payments(payments); + m_wallet->get_unconfirmed_payments(payments, m_current_subaddress_account, subaddr_indices); for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) { const tools::wallet2::payment_details &pd = i->second; std::string payment_id = string_tools::pod_to_hex(i->first); if (payment_id.substr(16).find_first_not_of('0') == std::string::npos) payment_id = payment_id.substr(0,16); std::string note = m_wallet->get_tx_note(pd.m_tx_hash); - message_writer() << (boost::format("%8.8s %6.6s %16.16s %20.20s %s %s %s %s") % "pool" % "in" % get_human_readable_timestamp(pd.m_timestamp) % print_money(pd.m_amount) % string_tools::pod_to_hex(pd.m_tx_hash) % payment_id % "-" % note).str(); + message_writer() << (boost::format("%8.8s %6.6s %16.16s %20.20s %s %s %d %s %s") % "pool" % "in" % get_human_readable_timestamp(pd.m_timestamp) % print_money(pd.m_amount) % string_tools::pod_to_hex(pd.m_tx_hash) % payment_id % pd.m_subaddr_index.minor % "-" % note).str(); } } - catch (...) + catch (const std::exception& e) { - fail_msg_writer() << "Failed to get pool state"; + fail_msg_writer() << "Failed to get pool state:" << e.what(); } } // print unconfirmed last if (pending || failed) { std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>> upayments; - m_wallet->get_unconfirmed_payments_out(upayments); + m_wallet->get_unconfirmed_payments_out(upayments, m_current_subaddress_account, subaddr_indices); 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 = pd.m_amount_in; @@ -4104,7 +4396,7 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_) std::string note = m_wallet->get_tx_note(i->first); bool is_failed = pd.m_state == tools::wallet2::unconfirmed_transfer_details::failed; if ((failed && is_failed) || (!is_failed && pending)) { - message_writer() << (boost::format("%8.8s %6.6s %16.16s %20.20s %s %s %14.14s %s") % (is_failed ? tr("failed") : tr("pending")) % tr("out") % get_human_readable_timestamp(pd.m_timestamp) % print_money(amount - pd.m_change - fee) % string_tools::pod_to_hex(i->first) % payment_id % print_money(fee) % note).str(); + message_writer() << (boost::format("%8.8s %6.6s %16.16s %20.20s %s %s %14.14s %s - %s") % (is_failed ? tr("failed") : tr("pending")) % tr("out") % get_human_readable_timestamp(pd.m_timestamp) % print_money(amount - pd.m_change - fee) % string_tools::pod_to_hex(i->first) % payment_id % print_money(fee) % print_subaddr_indices(pd.m_subaddr_indices) % note).str(); } } } @@ -4114,20 +4406,40 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_) //---------------------------------------------------------------------------------------------------- bool simple_wallet::unspent_outputs(const std::vector<std::string> &args_) { - if(!args_.empty() && args_.size() != 2) { - fail_msg_writer() << tr("usage: unspent_outputs [<min_amount> <max_amount>]"); + if(args_.size() > 3) + { + fail_msg_writer() << tr("usage: unspent_outputs [index=<N1>[,<N2>,...]] [<min_amount> [<max_amount>]]"); return true; } + auto local_args = args_; + + std::set<uint32_t> subaddr_indices; + if (local_args.size() > 0 && local_args[0].substr(0, 6) != "index=") + { + if (!parse_subaddress_indices(local_args[0], subaddr_indices)) + return true; + local_args.erase(local_args.begin()); + } + uint64_t min_amount = 0; uint64_t max_amount = std::numeric_limits<uint64_t>::max(); - if (args_.size() == 2) + if (local_args.size() > 0) { - if (!cryptonote::parse_amount(min_amount, args_[0]) || !cryptonote::parse_amount(max_amount, args_[1])) + if (!cryptonote::parse_amount(min_amount, local_args[0])) { - fail_msg_writer() << tr("amount is wrong: ") << args_[0] << ' ' << args_[1] << - ", " << tr("expected number from 0 to ") << print_money(std::numeric_limits<uint64_t>::max()); + fail_msg_writer() << tr("amount is wrong: ") << local_args[0]; return true; } + local_args.erase(local_args.begin()); + if (local_args.size() > 0) + { + if (!cryptonote::parse_amount(max_amount, local_args[0])) + { + fail_msg_writer() << tr("amount is wrong: ") << local_args[0]; + return true; + } + local_args.erase(local_args.begin()); + } if (min_amount > max_amount) { fail_msg_writer() << tr("<min_amount> should be smaller than <max_amount>"); @@ -4136,11 +4448,6 @@ bool simple_wallet::unspent_outputs(const std::vector<std::string> &args_) } tools::wallet2::transfer_container transfers; m_wallet->get_transfers(transfers); - if (transfers.empty()) - { - success_msg_writer() << "There is no unspent output in this wallet."; - return true; - } std::map<uint64_t, tools::wallet2::transfer_container> amount_to_tds; uint64_t min_height = std::numeric_limits<uint64_t>::max(); uint64_t max_height = 0; @@ -4150,7 +4457,7 @@ bool simple_wallet::unspent_outputs(const std::vector<std::string> &args_) for (const auto& td : transfers) { uint64_t amount = td.amount(); - if (td.m_spent || amount < min_amount || amount > max_amount) + if (td.m_spent || amount < min_amount || amount > max_amount || td.m_subaddr_index.major != m_current_subaddress_account || subaddr_indices.count(td.m_subaddr_index.minor) == 0) continue; amount_to_tds[amount].push_back(td); if (min_height > td.m_block_height) min_height = td.m_block_height; @@ -4159,6 +4466,11 @@ bool simple_wallet::unspent_outputs(const std::vector<std::string> &args_) if (found_max_amount < amount) found_max_amount = amount; ++count; } + if (amount_to_tds.empty()) + { + success_msg_writer() << tr("There is no unspent output in the specified address"); + return true; + } for (const auto& amount_tds : amount_to_tds) { auto& tds = amount_tds.second; @@ -4265,7 +4577,7 @@ void simple_wallet::wallet_idle_thread() //---------------------------------------------------------------------------------------------------- std::string simple_wallet::get_prompt() const { - std::string addr_start = m_wallet->get_account().get_public_address_str(m_wallet->testnet()).substr(0, 6); + std::string addr_start = m_wallet->get_subaddress_as_str({m_current_subaddress_account, 0}).substr(0, 6); std::string prompt = std::string("[") + tr("wallet") + " " + addr_start; uint32_t version; if (!m_wallet->check_connection(&version)) @@ -4281,7 +4593,7 @@ bool simple_wallet::run() // check and display warning, but go on anyway try_connect_to_daemon(); - refresh_main(0, false); + refresh_main(0, false, true); m_auto_refresh_enabled = m_wallet->auto_refresh(); m_idle_thread = boost::thread([&]{wallet_idle_thread();}); @@ -4301,9 +4613,197 @@ void simple_wallet::stop() m_idle_cond.notify_one(); } //---------------------------------------------------------------------------------------------------- +bool simple_wallet::account(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +{ + // Usage: + // account + // account new <label text with white spaces allowed> + // account switch <index> + // account label <index> <label text with white spaces allowed> + + if (args.empty()) + { + // print all the existing accounts + LOCK_IDLE_SCOPE(); + print_accounts(); + return true; + } + + std::vector<std::string> local_args = args; + std::string command = local_args[0]; + local_args.erase(local_args.begin()); + if (command == "new") + { + // create a new account and switch to it + std::string label = boost::join(local_args, " "); + if (label.empty()) + label = tr("(Untitled account)"); + m_wallet->add_subaddress_account(label); + m_current_subaddress_account = m_wallet->get_num_subaddress_accounts() - 1; + // update_prompt(); + LOCK_IDLE_SCOPE(); + print_accounts(); + } + else if (command == "switch" && local_args.size() == 1) + { + // switch to the specified account + uint32_t index_major; + if (!epee::string_tools::get_xtype_from_string(index_major, local_args[0])) + { + fail_msg_writer() << tr("failed to parse index: ") << local_args[0]; + return true; + } + if (index_major >= m_wallet->get_num_subaddress_accounts()) + { + fail_msg_writer() << tr("specify an index between 0 and ") << (m_wallet->get_num_subaddress_accounts() - 1); + return true; + } + m_current_subaddress_account = index_major; + // update_prompt(); + show_balance(); + } + else if (command == "label" && local_args.size() >= 1) + { + // set label of the specified account + uint32_t index_major; + if (!epee::string_tools::get_xtype_from_string(index_major, local_args[0])) + { + fail_msg_writer() << tr("failed to parse index: ") << local_args[0]; + return true; + } + local_args.erase(local_args.begin()); + std::string label = boost::join(local_args, " "); + try + { + m_wallet->set_subaddress_label({index_major, 0}, label); + LOCK_IDLE_SCOPE(); + print_accounts(); + } + catch (const std::exception& e) + { + fail_msg_writer() << e.what(); + } + } + else + { + fail_msg_writer() << tr("usage: account [new <label text with white spaces allowed> | switch <index> | label <index> <label text with white spaces allowed>]"); + } + return true; +} +//---------------------------------------------------------------------------------------------------- +void simple_wallet::print_accounts() +{ + success_msg_writer() << boost::format("%15s %21s %21s %21s") % tr("Account") % tr("Balance") % tr("Unlocked balance") % tr("Label"); + for (uint32_t account_index = 0; account_index < m_wallet->get_num_subaddress_accounts(); ++account_index) + { + success_msg_writer() << boost::format(tr("%8u %6s %21s %21s %21s")) + % account_index + % m_wallet->get_subaddress_as_str({account_index, 0}).substr(0, 6) + % print_money(m_wallet->balance(account_index)) + % print_money(m_wallet->unlocked_balance(account_index)) + % m_wallet->get_subaddress_label({account_index, 0}); + } + success_msg_writer() << tr("----------------------------------------------------------------------------------"); + success_msg_writer() << boost::format(tr("%15s %21s %21s")) % "Total" % print_money(m_wallet->balance_all()) % print_money(m_wallet->unlocked_balance_all()); +} +//---------------------------------------------------------------------------------------------------- bool simple_wallet::print_address(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { - success_msg_writer() << m_wallet->get_account().get_public_address_str(m_wallet->testnet()); + // Usage: + // address + // address new <label text with white spaces allowed> + // address all + // address <index_min> [<index_max>] + // address label <index> <label text with white spaces allowed> + + std::vector<std::string> local_args = args; + tools::wallet2::transfer_container transfers; + m_wallet->get_transfers(transfers); + + auto print_address_sub = [this, &transfers](uint32_t index) + { + bool used = std::find_if( + transfers.begin(), transfers.end(), + [this, &index](const tools::wallet2::transfer_details& td) { + return td.m_subaddr_index == cryptonote::subaddress_index{ m_current_subaddress_account, index }; + }) != transfers.end(); + success_msg_writer() << index << " " << m_wallet->get_subaddress_as_str({m_current_subaddress_account, index}) << " " << (index == 0 ? tr("Primary address") : m_wallet->get_subaddress_label({m_current_subaddress_account, index})) << " " << (used ? tr("(used)") : ""); + }; + + uint32_t index = 0; + if (local_args.empty()) + { + print_address_sub(index); + } + else if (local_args.size() == 1 && local_args[0] == "all") + { + local_args.erase(local_args.begin()); + for (; index < m_wallet->get_num_subaddresses(m_current_subaddress_account); ++index) + print_address_sub(index); + } + else if (local_args[0] == "new") + { + local_args.erase(local_args.begin()); + std::string label; + if (local_args.size() > 0) + label = boost::join(local_args, " "); + if (label.empty()) + label = tr("(Untitled address)"); + m_wallet->add_subaddress(m_current_subaddress_account, label); + print_address_sub(m_wallet->get_num_subaddresses(m_current_subaddress_account) - 1); + } + else if (local_args.size() >= 2 && local_args[0] == "label") + { + if (!epee::string_tools::get_xtype_from_string(index, local_args[1])) + { + fail_msg_writer() << tr("failed to parse index: ") << local_args[1]; + return true; + } + if (index >= m_wallet->get_num_subaddresses(m_current_subaddress_account)) + { + fail_msg_writer() << tr("specify an index between 0 and ") << (m_wallet->get_num_subaddresses(m_current_subaddress_account) - 1); + return true; + } + local_args.erase(local_args.begin()); + local_args.erase(local_args.begin()); + std::string label = boost::join(local_args, " "); + m_wallet->set_subaddress_label({m_current_subaddress_account, index}, label); + print_address_sub(index); + } + else if (local_args.size() <= 2 && epee::string_tools::get_xtype_from_string(index, local_args[0])) + { + local_args.erase(local_args.begin()); + uint32_t index_min = index; + uint32_t index_max = index_min; + if (local_args.size() > 0) + { + if (!epee::string_tools::get_xtype_from_string(index_max, local_args[0])) + { + fail_msg_writer() << tr("failed to parse index: ") << local_args[0]; + return true; + } + local_args.erase(local_args.begin()); + } + if (index_max < index_min) + std::swap(index_min, index_max); + if (index_min >= m_wallet->get_num_subaddresses(m_current_subaddress_account)) + { + fail_msg_writer() << tr("<index_min> is already out of bound"); + return true; + } + if (index_max >= m_wallet->get_num_subaddresses(m_current_subaddress_account)) + { + message_writer() << tr("<index_max> exceeds the bound"); + index_max = m_wallet->get_num_subaddresses(m_current_subaddress_account) - 1; + } + for (index = index_min; index <= index_max; ++index) + print_address_sub(index); + } + else + { + fail_msg_writer() << tr("usage: address [ new <label text with white spaces allowed> | all | <index_min> [<index_max>] | label <index> <label text with white spaces allowed> ]"); + } + return true; } //---------------------------------------------------------------------------------------------------- @@ -4328,19 +4828,17 @@ bool simple_wallet::print_integrated_address(const std::vector<std::string> &arg return true; } else { - bool has_payment_id; - crypto::hash8 payment_id; - account_public_address addr; - if(get_account_integrated_address_from_str(addr, has_payment_id, payment_id, m_wallet->testnet(), args.back())) + address_parse_info info; + if(get_account_address_from_str(info, m_wallet->testnet(), args.back())) { - if (has_payment_id) + if (info.has_payment_id) { - success_msg_writer() << boost::format(tr("Integrated address: account %s, payment ID %s")) % - get_account_address_as_str(m_wallet->testnet(),addr) % epee::string_tools::pod_to_hex(payment_id); + success_msg_writer() << boost::format(tr("Integrated address: %s, payment ID: %s")) % + get_account_address_as_str(m_wallet->testnet(), false, info.address) % epee::string_tools::pod_to_hex(info.payment_id); } else { - success_msg_writer() << tr("Standard address: ") << get_account_address_as_str(m_wallet->testnet(),addr); + success_msg_writer() << (info.is_subaddress ? tr("Subaddress: ") : tr("Standard address: ")) << get_account_address_as_str(m_wallet->testnet(), info.is_subaddress, info.address); } return true; } @@ -4361,29 +4859,27 @@ bool simple_wallet::address_book(const std::vector<std::string> &args/* = std::v } else if (args[0] == "add") { - cryptonote::account_public_address address; - bool has_payment_id; - crypto::hash8 payment_id8; - if(!cryptonote::get_account_address_from_str_or_url(address, has_payment_id, payment_id8, m_wallet->testnet(), args[1], oa_prompter)) + cryptonote::address_parse_info info; + if(!cryptonote::get_account_address_from_str_or_url(info, m_wallet->testnet(), args[1], oa_prompter)) { fail_msg_writer() << tr("failed to parse address"); return true; } crypto::hash payment_id = null_hash; size_t description_start = 2; - if (has_payment_id) + if (info.has_payment_id) { - memcpy(payment_id.data, payment_id8.data, 8); + memcpy(payment_id.data, info.payment_id.data, 8); } - else if (!has_payment_id && args.size() >= 4 && args[2] == "pid") + else if (!info.has_payment_id && args.size() >= 4 && args[2] == "pid") { if (tools::wallet2::parse_long_payment_id(args[3], payment_id)) { description_start += 2; } - else if (tools::wallet2::parse_short_payment_id(args[3], payment_id8)) + else if (tools::wallet2::parse_short_payment_id(args[3], info.payment_id)) { - memcpy(payment_id.data, payment_id8.data, 8); + memcpy(payment_id.data, info.payment_id.data, 8); description_start += 2; } else @@ -4399,7 +4895,7 @@ bool simple_wallet::address_book(const std::vector<std::string> &args/* = std::v description += " "; description += args[i]; } - m_wallet->add_address_book_row(address, payment_id, description); + m_wallet->add_address_book_row(info.address, payment_id, description, info.is_subaddress); } else { @@ -4421,7 +4917,7 @@ bool simple_wallet::address_book(const std::vector<std::string> &args/* = std::v for (size_t i = 0; i < address_book.size(); ++i) { auto& row = address_book[i]; success_msg_writer() << tr("Index: ") << i; - success_msg_writer() << tr("Address: ") << get_account_address_as_str(m_wallet->testnet(), row.m_address); + success_msg_writer() << tr("Address: ") << get_account_address_as_str(m_wallet->testnet(), row.m_is_subaddress, row.m_address); success_msg_writer() << tr("Payment ID: ") << row.m_payment_id; success_msg_writer() << tr("Description: ") << row.m_description << "\n"; } @@ -4507,6 +5003,15 @@ bool simple_wallet::status(const std::vector<std::string> &args) return true; } //---------------------------------------------------------------------------------------------------- +bool simple_wallet::wallet_info(const std::vector<std::string> &args) +{ + message_writer() << tr("Filename: ") << m_wallet->get_wallet_file(); + message_writer() << tr("Address: ") << m_wallet->get_account().get_public_address_str(m_wallet->testnet()); + message_writer() << tr("Watch only: ") << (m_wallet->watch_only() ? tr("Yes") : tr("No")); + message_writer() << tr("Testnet: ") << (m_wallet->testnet() ? tr("Yes") : tr("No")); + return true; +} +//---------------------------------------------------------------------------------------------------- bool simple_wallet::sign(const std::vector<std::string> &args) { if (args.size() != 1) @@ -4552,16 +5057,14 @@ bool simple_wallet::verify(const std::vector<std::string> &args) return true; } - cryptonote::account_public_address address; - bool has_payment_id; - crypto::hash8 payment_id; - if(!cryptonote::get_account_address_from_str_or_url(address, has_payment_id, payment_id, m_wallet->testnet(), address_string, oa_prompter)) + cryptonote::address_parse_info info; + if(!cryptonote::get_account_address_from_str_or_url(info, m_wallet->testnet(), address_string, oa_prompter)) { fail_msg_writer() << tr("failed to parse address"); return true; } - r = m_wallet->verify(data, address, signature); + r = m_wallet->verify(data, info.address, signature); if (!r) { fail_msg_writer() << tr("Bad signature from ") << address_string; @@ -4782,7 +5285,7 @@ bool simple_wallet::show_transfer(const std::vector<std::string> &args) const uint64_t last_block_height = m_wallet->get_blockchain_current_height(); std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> payments; - m_wallet->get_payments(payments, 0); + m_wallet->get_payments(payments, 0, m_current_subaddress_account); for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) { const tools::wallet2::payment_details &pd = i->second; if (pd.m_tx_hash == txid) { @@ -4812,13 +5315,14 @@ bool simple_wallet::show_transfer(const std::vector<std::string> &args) else success_msg_writer() << "locked for " << get_human_readable_timespan(std::chrono::seconds(pd.m_unlock_time - threshold)); } + success_msg_writer() << "Address index: " << pd.m_subaddr_index.minor; success_msg_writer() << "Note: " << m_wallet->get_tx_note(txid); return true; } } std::list<std::pair<crypto::hash, tools::wallet2::confirmed_transfer_details>> payments_out; - m_wallet->get_payments_out(payments_out, 0); + m_wallet->get_payments_out(payments_out, 0, (uint64_t)-1, m_current_subaddress_account); for (std::list<std::pair<crypto::hash, tools::wallet2::confirmed_transfer_details>>::const_iterator i = payments_out.begin(); i != payments_out.end(); ++i) { if (i->first == txid) { @@ -4829,7 +5333,7 @@ bool simple_wallet::show_transfer(const std::vector<std::string> &args) for (const auto &d: pd.m_dests) { if (!dests.empty()) dests += ", "; - dests += get_account_address_as_str(m_wallet->testnet(), d.addr) + ": " + print_money(d.amount); + dests += get_account_address_as_str(m_wallet->testnet(), d.is_subaddress, d.addr) + ": " + print_money(d.amount); } 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) @@ -4865,6 +5369,7 @@ bool simple_wallet::show_transfer(const std::vector<std::string> &args) success_msg_writer() << "Timestamp: " << get_human_readable_timestamp(pd.m_timestamp); success_msg_writer() << "Amount: " << print_money(pd.m_amount); success_msg_writer() << "Payment ID: " << payment_id; + success_msg_writer() << "Address index: " << pd.m_subaddr_index.minor; success_msg_writer() << "Note: " << m_wallet->get_tx_note(txid); return true; } diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 079fae9f5..3525e82bc 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -97,6 +97,7 @@ namespace cryptonote bool viewkey(const std::vector<std::string> &args = std::vector<std::string>()); bool spendkey(const std::vector<std::string> &args = std::vector<std::string>()); bool seed(const std::vector<std::string> &args = std::vector<std::string>()); + bool encrypted_seed(const std::vector<std::string> &args = std::vector<std::string>()); /*! * \brief Sets seed language. @@ -121,12 +122,13 @@ namespace cryptonote bool set_min_output_value(const std::vector<std::string> &args = std::vector<std::string>()); bool set_merge_destinations(const std::vector<std::string> &args = std::vector<std::string>()); bool set_confirm_backlog(const std::vector<std::string> &args = std::vector<std::string>()); + bool set_confirm_backlog_threshold(const std::vector<std::string> &args = std::vector<std::string>()); bool help(const std::vector<std::string> &args = std::vector<std::string>()); bool start_mining(const std::vector<std::string> &args); bool stop_mining(const std::vector<std::string> &args); bool save_bc(const std::vector<std::string>& args); bool refresh(const std::vector<std::string> &args); - bool show_balance_unlocked(); + bool show_balance_unlocked(bool detailed = false); bool show_balance(const std::vector<std::string> &args = std::vector<std::string>()); bool show_incoming_transfers(const std::vector<std::string> &args); bool show_payments(const std::vector<std::string> &args); @@ -145,6 +147,8 @@ namespace cryptonote std::vector<std::vector<cryptonote::tx_destination_entry>> split_amounts( std::vector<cryptonote::tx_destination_entry> dsts, size_t num_splits ); + bool account(const std::vector<std::string> &args = std::vector<std::string>()); + void print_accounts(); bool print_address(const std::vector<std::string> &args = std::vector<std::string>()); bool print_integrated_address(const std::vector<std::string> &args = std::vector<std::string>()); bool address_book(const std::vector<std::string> &args = std::vector<std::string>()); @@ -155,16 +159,17 @@ namespace cryptonote bool set_log(const std::vector<std::string> &args); bool get_tx_key(const std::vector<std::string> &args); bool check_tx_key(const std::vector<std::string> &args); - bool check_tx_key_helper(const crypto::hash &txid, const cryptonote::account_public_address &address, const crypto::key_derivation &derivation); + bool check_tx_key_helper(const crypto::hash &txid, const cryptonote::account_public_address &address, bool is_subaddress, const crypto::key_derivation &derivation); bool get_tx_proof(const std::vector<std::string> &args); bool check_tx_proof(const std::vector<std::string> &args); bool show_transfers(const std::vector<std::string> &args); bool unspent_outputs(const std::vector<std::string> &args); bool rescan_blockchain(const std::vector<std::string> &args); - bool refresh_main(uint64_t start_height, bool reset = false); + bool refresh_main(uint64_t start_height, bool reset = false, bool is_init = false); bool set_tx_note(const std::vector<std::string> &args); bool get_tx_note(const std::vector<std::string> &args); bool status(const std::vector<std::string> &args); + bool wallet_info(const std::vector<std::string> &args); bool set_default_priority(const std::vector<std::string> &args); bool sign(const std::vector<std::string> &args); bool verify(const std::vector<std::string> &args); @@ -185,6 +190,7 @@ namespace cryptonote bool accept_loaded_tx(const tools::wallet2::signed_tx_set &txs); bool print_ring_members(const std::vector<tools::wallet2::pending_tx>& ptx_vector, std::ostream& ostr); std::string get_prompt() const; + bool print_seed(bool encrypted); /*! * \brief Prints the seed with a nice message @@ -203,9 +209,9 @@ 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 crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount); - virtual void on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount); - virtual void on_money_spent(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx); + virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index); + virtual void on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index); + virtual void on_money_spent(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx, const cryptonote::subaddress_index& subaddr_index); virtual void on_skip_transaction(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx); //---------------------------------------------------------- @@ -296,5 +302,6 @@ namespace cryptonote std::atomic<bool> m_auto_refresh_enabled; bool m_auto_refresh_refreshing; std::atomic<bool> m_in_manual_refresh; + uint32_t m_current_subaddress_account; }; } diff --git a/src/version.cmake b/src/version.cmake index 623d3cf1f..45a97cd20 100644 --- a/src/version.cmake +++ b/src/version.cmake @@ -36,7 +36,7 @@ if(RET) message(WARNING "Cannot determine current commit. Make sure that you are building either from a Git working tree or from a source archive.") set(VERSIONTAG "unknown") - configure_file("src/version.h.in" "${TO}") + configure_file("src/version.cpp.in" "${TO}") else() message(STATUS "You are currently on commit ${COMMIT}") @@ -59,5 +59,5 @@ else() endif() endif() - configure_file("src/version.h.in" "${TO}") + configure_file("src/version.cpp.in" "${TO}") endif() diff --git a/src/version.cpp.in b/src/version.cpp.in new file mode 100644 index 000000000..d1444f867 --- /dev/null +++ b/src/version.cpp.in @@ -0,0 +1,11 @@ +#define DEF_MONERO_VERSION_TAG "@VERSIONTAG@" +#define DEF_MONERO_VERSION "0.11.0.0" +#define DEF_MONERO_RELEASE_NAME "Helium Hydra" +#define DEF_MONERO_VERSION_FULL DEF_MONERO_VERSION "-" DEF_MONERO_VERSION_TAG + +#include "version.h" + +const char* const MONERO_VERSION_TAG = DEF_MONERO_VERSION_TAG; +const char* const MONERO_VERSION = DEF_MONERO_VERSION; +const char* const MONERO_RELEASE_NAME = DEF_MONERO_RELEASE_NAME; +const char* const MONERO_VERSION_FULL = DEF_MONERO_VERSION_FULL; diff --git a/src/version.h b/src/version.h new file mode 100644 index 000000000..d1d06c790 --- /dev/null +++ b/src/version.h @@ -0,0 +1,6 @@ +#pragma once + +extern const char* const MONERO_VERSION_TAG; +extern const char* const MONERO_VERSION; +extern const char* const MONERO_RELEASE_NAME; +extern const char* const MONERO_VERSION_FULL; diff --git a/src/version.h.in b/src/version.h.in deleted file mode 100644 index 281b52db4..000000000 --- a/src/version.h.in +++ /dev/null @@ -1,4 +0,0 @@ -#define MONERO_VERSION_TAG "@VERSIONTAG@" -#define MONERO_VERSION "0.11.0.0" -#define MONERO_RELEASE_NAME "Helium Hydra" -#define MONERO_VERSION_FULL MONERO_VERSION "-" MONERO_VERSION_TAG diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt index 639080051..24399790c 100644 --- a/src/wallet/CMakeLists.txt +++ b/src/wallet/CMakeLists.txt @@ -41,6 +41,8 @@ set(wallet_sources api/pending_transaction.cpp api/utils.cpp api/address_book.cpp + api/subaddress.cpp + api/subaddress_account.cpp api/unsigned_transaction.cpp) set(wallet_api_headers @@ -62,6 +64,8 @@ set(wallet_private_headers api/pending_transaction.h api/common_defines.h api/address_book.h + api/subaddress.h + api/subaddress_account.h api/unsigned_transaction.h) monero_private_headers(wallet @@ -84,7 +88,6 @@ target_link_libraries(wallet ${Boost_REGEX_LIBRARY} PRIVATE ${EXTRA_LIBRARIES}) -add_dependencies(wallet version) if (NOT BUILD_GUI_DEPS) set(wallet_rpc_sources @@ -110,13 +113,13 @@ if (NOT BUILD_GUI_DEPS) cryptonote_core cncrypto common + version ${Boost_CHRONO_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_THREAD_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ${EXTRA_LIBRARIES}) - add_dependencies(wallet_rpc_server version) set_property(TARGET wallet_rpc_server PROPERTY OUTPUT_NAME "monero-wallet-rpc") @@ -126,7 +129,16 @@ endif() # build and install libwallet_merged only if we building for GUI if (BUILD_GUI_DEPS) - set(libs_to_merge wallet cryptonote_core cryptonote_basic mnemonics common cncrypto ringct) + set(libs_to_merge + wallet + cryptonote_core + cryptonote_basic + mnemonics + common + cncrypto + ringct + checkpoints + version) foreach(lib ${libs_to_merge}) list(APPEND objlibs $<TARGET_OBJECTS:obj_${lib}>) # matches naming convention in src/CMakeLists.txt diff --git a/src/wallet/api/address_book.cpp b/src/wallet/api/address_book.cpp index 28f835ebd..da412cd4b 100644 --- a/src/wallet/api/address_book.cpp +++ b/src/wallet/api/address_book.cpp @@ -42,22 +42,20 @@ namespace Monero { AddressBook::~AddressBook() {} AddressBookImpl::AddressBookImpl(WalletImpl *wallet) - : m_wallet(wallet) {} + : m_wallet(wallet), m_errorCode(Status_Ok) {} bool AddressBookImpl::addRow(const std::string &dst_addr , const std::string &payment_id_str, const std::string &description) { clearStatus(); - cryptonote::account_public_address addr; - bool has_short_pid; - crypto::hash8 payment_id_short; - if(!cryptonote::get_account_integrated_address_from_str(addr, has_short_pid, payment_id_short, m_wallet->m_wallet->testnet(), dst_addr)) { + cryptonote::address_parse_info info; + if(!cryptonote::get_account_address_from_str(info, m_wallet->m_wallet->testnet(), dst_addr)) { m_errorString = tr("Invalid destination address"); m_errorCode = Invalid_Address; return false; } - crypto::hash payment_id = cryptonote::null_hash; + crypto::hash payment_id = crypto::null_hash; bool has_long_pid = (payment_id_str.empty())? false : tools::wallet2::parse_long_payment_id(payment_id_str, payment_id); // Short payment id provided @@ -75,19 +73,19 @@ bool AddressBookImpl::addRow(const std::string &dst_addr , const std::string &pa } // integrated + long payment id provided - if(has_long_pid && has_short_pid) { + if(has_long_pid && info.has_payment_id) { m_errorString = tr("Integrated address and long payment id can't be used at the same time"); m_errorCode = Invalid_Payment_Id; return false; } // Pad short pid with zeros - if (has_short_pid) + if (info.has_payment_id) { - memcpy(payment_id.data, payment_id_short.data, 8); + memcpy(payment_id.data, info.payment_id.data, 8); } - bool r = m_wallet->m_wallet->add_address_book_row(addr,payment_id,description); + bool r = m_wallet->m_wallet->add_address_book_row(info.address,payment_id,description,info.is_subaddress); if (r) refresh(); else @@ -106,10 +104,10 @@ void AddressBookImpl::refresh() for (size_t i = 0; i < rows.size(); ++i) { tools::wallet2::address_book_row * row = &rows.at(i); - std::string payment_id = (row->m_payment_id == cryptonote::null_hash)? "" : epee::string_tools::pod_to_hex(row->m_payment_id); - std::string address = cryptonote::get_account_address_as_str(m_wallet->m_wallet->testnet(),row->m_address); + std::string payment_id = (row->m_payment_id == crypto::null_hash)? "" : epee::string_tools::pod_to_hex(row->m_payment_id); + std::string address = cryptonote::get_account_address_as_str(m_wallet->m_wallet->testnet(), row->m_is_subaddress, row->m_address); // convert the zero padded short payment id to integrated address - if (payment_id.length() > 16 && payment_id.substr(16).find_first_not_of('0') == std::string::npos) { + if (!row->m_is_subaddress && payment_id.length() > 16 && payment_id.substr(16).find_first_not_of('0') == std::string::npos) { payment_id = payment_id.substr(0,16); crypto::hash8 payment_id_short; if(tools::wallet2::parse_short_payment_id(payment_id, payment_id_short)) { diff --git a/src/wallet/api/pending_transaction.cpp b/src/wallet/api/pending_transaction.cpp index c98a599e7..e17931de1 100644 --- a/src/wallet/api/pending_transaction.cpp +++ b/src/wallet/api/pending_transaction.cpp @@ -172,6 +172,22 @@ uint64_t PendingTransactionImpl::txCount() const return m_pending_tx.size(); } +std::vector<uint32_t> PendingTransactionImpl::subaddrAccount() const +{ + std::vector<uint32_t> result; + for (const auto& ptx : m_pending_tx) + result.push_back(ptx.construction_data.subaddr_account); + return result; +} + +std::vector<std::set<uint32_t>> PendingTransactionImpl::subaddrIndices() const +{ + std::vector<std::set<uint32_t>> result; + for (const auto& ptx : m_pending_tx) + result.push_back(ptx.construction_data.subaddr_indices); + return result; +} + } namespace Bitmonero = Monero; diff --git a/src/wallet/api/pending_transaction.h b/src/wallet/api/pending_transaction.h index 0c3e95a85..e5b33ac01 100644 --- a/src/wallet/api/pending_transaction.h +++ b/src/wallet/api/pending_transaction.h @@ -51,6 +51,8 @@ public: uint64_t fee() const; std::vector<std::string> txid() const; uint64_t txCount() const; + std::vector<uint32_t> subaddrAccount() const; + std::vector<std::set<uint32_t>> subaddrIndices() const; // TODO: continue with interface; private: diff --git a/src/wallet/api/subaddress.cpp b/src/wallet/api/subaddress.cpp new file mode 100644 index 000000000..ceda9a9da --- /dev/null +++ b/src/wallet/api/subaddress.cpp @@ -0,0 +1,91 @@ +// Copyright (c) 2017, 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 "subaddress.h" +#include "wallet.h" +#include "crypto/hash.h" +#include "wallet/wallet2.h" +#include "common_defines.h" + +#include <vector> + +namespace Monero { + +Subaddress::~Subaddress() {} + +SubaddressImpl::SubaddressImpl(WalletImpl *wallet) + : m_wallet(wallet) {} + +void SubaddressImpl::addRow(uint32_t accountIndex, const std::string &label) +{ + m_wallet->m_wallet->add_subaddress(accountIndex, label); + refresh(accountIndex); +} + +void SubaddressImpl::setLabel(uint32_t accountIndex, uint32_t addressIndex, const std::string &label) +{ + try + { + m_wallet->m_wallet->set_subaddress_label({accountIndex, addressIndex}, label); + refresh(accountIndex); + } + catch (const std::exception& e) + { + LOG_ERROR("setLabel: " << e.what()); + } +} + +void SubaddressImpl::refresh(uint32_t accountIndex) +{ + LOG_PRINT_L2("Refreshing subaddress"); + + clearRows(); + for (size_t i = 0; i < m_wallet->m_wallet->get_num_subaddresses(accountIndex); ++i) + { + m_rows.push_back(new SubaddressRow(i, m_wallet->m_wallet->get_subaddress_as_str({accountIndex, (uint32_t)i}), m_wallet->m_wallet->get_subaddress_label({accountIndex, (uint32_t)i}))); + } +} + +void SubaddressImpl::clearRows() { + for (auto r : m_rows) { + delete r; + } + m_rows.clear(); +} + +std::vector<SubaddressRow*> SubaddressImpl::getAll() const +{ + return m_rows; +} + +SubaddressImpl::~SubaddressImpl() +{ + clearRows(); +} + +} // namespace diff --git a/src/wallet/api/subaddress.h b/src/wallet/api/subaddress.h new file mode 100644 index 000000000..e3e28eba1 --- /dev/null +++ b/src/wallet/api/subaddress.h @@ -0,0 +1,56 @@ +// Copyright (c) 2017, 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 "wallet/wallet2_api.h" +#include "wallet/wallet2.h" + +namespace Monero { + +class WalletImpl; + +class SubaddressImpl : public Subaddress +{ +public: + SubaddressImpl(WalletImpl * wallet); + ~SubaddressImpl(); + + // Fetches addresses from Wallet2 + void refresh(uint32_t accountIndex); + std::vector<SubaddressRow*> getAll() const; + void addRow(uint32_t accountIndex, const std::string &label); + void setLabel(uint32_t accountIndex, uint32_t addressIndex, const std::string &label); + +private: + void clearRows(); + +private: + WalletImpl *m_wallet; + std::vector<SubaddressRow*> m_rows; +}; + +} diff --git a/src/wallet/api/subaddress_account.cpp b/src/wallet/api/subaddress_account.cpp new file mode 100644 index 000000000..736ef874e --- /dev/null +++ b/src/wallet/api/subaddress_account.cpp @@ -0,0 +1,90 @@ +// Copyright (c) 2017, 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 "subaddress_account.h" +#include "wallet.h" +#include "crypto/hash.h" +#include "wallet/wallet2.h" +#include "common_defines.h" + +#include <vector> + +namespace Monero { + +SubaddressAccount::~SubaddressAccount() {} + +SubaddressAccountImpl::SubaddressAccountImpl(WalletImpl *wallet) + : m_wallet(wallet) {} + +void SubaddressAccountImpl::addRow(const std::string &label) +{ + m_wallet->m_wallet->add_subaddress_account(label); + refresh(); +} + +void SubaddressAccountImpl::setLabel(uint32_t accountIndex, const std::string &label) +{ + m_wallet->m_wallet->set_subaddress_label({accountIndex, 0}, label); + refresh(); +} + +void SubaddressAccountImpl::refresh() +{ + LOG_PRINT_L2("Refreshing subaddress account"); + + clearRows(); + for (uint32_t i = 0; i < m_wallet->m_wallet->get_num_subaddress_accounts(); ++i) + { + m_rows.push_back(new SubaddressAccountRow( + i, + m_wallet->m_wallet->get_subaddress_as_str({i,0}).substr(0,6), + m_wallet->m_wallet->get_subaddress_label({i,0}), + cryptonote::print_money(m_wallet->m_wallet->balance(i)), + cryptonote::print_money(m_wallet->m_wallet->unlocked_balance(i)) + )); + } +} + +void SubaddressAccountImpl::clearRows() { + for (auto r : m_rows) { + delete r; + } + m_rows.clear(); +} + +std::vector<SubaddressAccountRow*> SubaddressAccountImpl::getAll() const +{ + return m_rows; +} + +SubaddressAccountImpl::~SubaddressAccountImpl() +{ + clearRows(); +} + +} // namespace diff --git a/src/wallet/api/subaddress_account.h b/src/wallet/api/subaddress_account.h new file mode 100644 index 000000000..107d7f87f --- /dev/null +++ b/src/wallet/api/subaddress_account.h @@ -0,0 +1,56 @@ +// Copyright (c) 2017, 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 "wallet/wallet2_api.h" +#include "wallet/wallet2.h" + +namespace Monero { + +class WalletImpl; + +class SubaddressAccountImpl : public SubaddressAccount +{ +public: + SubaddressAccountImpl(WalletImpl * wallet); + ~SubaddressAccountImpl(); + + // Fetches addresses from Wallet2 + void refresh(); + std::vector<SubaddressAccountRow*> getAll() const; + void addRow(const std::string &label); + void setLabel(uint32_t accountIndex, const std::string &label); + +private: + void clearRows(); + +private: + WalletImpl *m_wallet; + std::vector<SubaddressAccountRow*> m_rows; +}; + +} diff --git a/src/wallet/api/transaction_history.cpp b/src/wallet/api/transaction_history.cpp index 23d3905b2..b6ba8c359 100644 --- a/src/wallet/api/transaction_history.cpp +++ b/src/wallet/api/transaction_history.cpp @@ -130,6 +130,9 @@ void TransactionHistoryImpl::refresh() ti->m_direction = TransactionInfo::Direction_In; ti->m_hash = string_tools::pod_to_hex(pd.m_tx_hash); ti->m_blockheight = pd.m_block_height; + ti->m_subaddrIndex = { pd.m_subaddr_index.minor }; + ti->m_subaddrAccount = pd.m_subaddr_index.major; + ti->m_label = m_wallet->m_wallet->get_subaddress_label(pd.m_subaddr_index); ti->m_timestamp = pd.m_timestamp; ti->m_confirmations = wallet_height - pd.m_block_height; ti->m_unlock_time = pd.m_unlock_time; @@ -174,12 +177,15 @@ void TransactionHistoryImpl::refresh() ti->m_direction = TransactionInfo::Direction_Out; ti->m_hash = string_tools::pod_to_hex(hash); ti->m_blockheight = pd.m_block_height; + ti->m_subaddrIndex = pd.m_subaddr_indices; + ti->m_subaddrAccount = pd.m_subaddr_account; + ti->m_label = pd.m_subaddr_indices.size() == 1 ? m_wallet->m_wallet->get_subaddress_label({pd.m_subaddr_account, *pd.m_subaddr_indices.begin()}) : ""; ti->m_timestamp = pd.m_timestamp; ti->m_confirmations = wallet_height - pd.m_block_height; // single output transaction might contain multiple transfers for (const auto &d: pd.m_dests) { - ti->m_transfers.push_back({d.amount, get_account_address_as_str(m_wallet->m_wallet->testnet(), d.addr)}); + ti->m_transfers.push_back({d.amount, get_account_address_as_str(m_wallet->m_wallet->testnet(), d.is_subaddress, d.addr)}); } m_history.push_back(ti); } @@ -199,12 +205,15 @@ void TransactionHistoryImpl::refresh() TransactionInfoImpl * ti = new TransactionInfoImpl(); ti->m_paymentid = payment_id; - ti->m_amount = amount - pd.m_change; + ti->m_amount = amount - pd.m_change - fee; ti->m_fee = fee; ti->m_direction = TransactionInfo::Direction_Out; ti->m_failed = is_failed; ti->m_pending = true; ti->m_hash = string_tools::pod_to_hex(hash); + ti->m_subaddrIndex = pd.m_subaddr_indices; + ti->m_subaddrAccount = pd.m_subaddr_account; + ti->m_label = pd.m_subaddr_indices.size() == 1 ? m_wallet->m_wallet->get_subaddress_label({pd.m_subaddr_account, *pd.m_subaddr_indices.begin()}) : ""; ti->m_timestamp = pd.m_timestamp; ti->m_confirmations = 0; m_history.push_back(ti); @@ -226,6 +235,9 @@ void TransactionHistoryImpl::refresh() ti->m_hash = string_tools::pod_to_hex(pd.m_tx_hash); ti->m_blockheight = pd.m_block_height; ti->m_pending = true; + ti->m_subaddrIndex = { pd.m_subaddr_index.minor }; + ti->m_subaddrAccount = pd.m_subaddr_index.major; + ti->m_label = m_wallet->m_wallet->get_subaddress_label(pd.m_subaddr_index); ti->m_timestamp = pd.m_timestamp; ti->m_confirmations = 0; m_history.push_back(ti); diff --git a/src/wallet/api/transaction_info.cpp b/src/wallet/api/transaction_info.cpp index 171272265..1a5df454c 100644 --- a/src/wallet/api/transaction_info.cpp +++ b/src/wallet/api/transaction_info.cpp @@ -48,6 +48,7 @@ TransactionInfoImpl::TransactionInfoImpl() , m_amount(0) , m_fee(0) , m_blockheight(0) + , m_subaddrAccount(0) , m_timestamp(0) , m_confirmations(0) , m_unlock_time(0) @@ -91,6 +92,22 @@ uint64_t TransactionInfoImpl::blockHeight() const return m_blockheight; } +std::set<uint32_t> TransactionInfoImpl::subaddrIndex() const +{ + return m_subaddrIndex; +} + +uint32_t TransactionInfoImpl::subaddrAccount() const +{ + return m_subaddrAccount; +} + +string TransactionInfoImpl::label() const +{ + return m_label; +} + + string TransactionInfoImpl::hash() const { return m_hash; diff --git a/src/wallet/api/transaction_info.h b/src/wallet/api/transaction_info.h index ee56b859f..d19ef8899 100644 --- a/src/wallet/api/transaction_info.h +++ b/src/wallet/api/transaction_info.h @@ -50,6 +50,9 @@ public: //! always 0 for incoming txes virtual uint64_t fee() const; virtual uint64_t blockHeight() const; + virtual std::set<uint32_t> subaddrIndex() const; + virtual uint32_t subaddrAccount() const; + virtual std::string label() const; virtual std::string hash() const; virtual std::time_t timestamp() const; @@ -65,6 +68,9 @@ private: uint64_t m_amount; uint64_t m_fee; uint64_t m_blockheight; + std::set<uint32_t> m_subaddrIndex; // always unique index for incoming transfers; can be multiple indices for outgoing transfers + uint32_t m_subaddrAccount; + std::string m_label; std::string m_hash; std::time_t m_timestamp; std::string m_paymentid; diff --git a/src/wallet/api/unsigned_transaction.cpp b/src/wallet/api/unsigned_transaction.cpp index 961bd772a..4c8c5ade2 100644 --- a/src/wallet/api/unsigned_transaction.cpp +++ b/src/wallet/api/unsigned_transaction.cpp @@ -102,12 +102,38 @@ bool UnsignedTransactionImpl::checkLoadedTx(const std::function<size_t()> get_nu // gather info to ask the user uint64_t amount = 0, amount_to_dests = 0, change = 0; size_t min_ring_size = ~0; - std::unordered_map<std::string, uint64_t> dests; - const std::string wallet_address = m_wallet.m_wallet->get_account().get_public_address_str(m_wallet.m_wallet->testnet()); + std::unordered_map<cryptonote::account_public_address, std::pair<std::string, uint64_t>> dests; int first_known_non_zero_change_index = -1; + std::string payment_id_string = ""; for (size_t n = 0; n < get_num_txes(); ++n) { const tools::wallet2::tx_construction_data &cd = get_tx(n); + + std::vector<cryptonote::tx_extra_field> tx_extra_fields; + bool has_encrypted_payment_id = false; + crypto::hash8 payment_id8 = crypto::null_hash8; + if (cryptonote::parse_tx_extra(cd.extra, tx_extra_fields)) + { + cryptonote::tx_extra_nonce extra_nonce; + if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) + { + crypto::hash payment_id; + if(cryptonote::get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8)) + { + if (!payment_id_string.empty()) + payment_id_string += ", "; + payment_id_string = std::string("encrypted payment ID ") + epee::string_tools::pod_to_hex(payment_id8); + has_encrypted_payment_id = true; + } + else if (cryptonote::get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id)) + { + if (!payment_id_string.empty()) + payment_id_string += ", "; + payment_id_string = std::string("unencrypted payment ID ") + epee::string_tools::pod_to_hex(payment_id); + } + } + } + for (size_t s = 0; s < cd.sources.size(); ++s) { amount += cd.sources[s].amount; @@ -118,24 +144,31 @@ bool UnsignedTransactionImpl::checkLoadedTx(const std::function<size_t()> get_nu for (size_t d = 0; d < cd.splitted_dsts.size(); ++d) { const cryptonote::tx_destination_entry &entry = cd.splitted_dsts[d]; - std::string address = get_account_address_as_str(m_wallet.m_wallet->testnet(), entry.addr); - std::unordered_map<std::string,uint64_t>::iterator i = dests.find(address); + std::string address, standard_address = get_account_address_as_str(m_wallet.testnet(), entry.is_subaddress, entry.addr); + if (has_encrypted_payment_id && !entry.is_subaddress) + { + address = get_account_integrated_address_as_str(m_wallet.testnet(), entry.addr, payment_id8); + address += std::string(" (" + standard_address + " with encrypted payment id " + epee::string_tools::pod_to_hex(payment_id8) + ")"); + } + else + address = standard_address; + auto i = dests.find(entry.addr); if (i == dests.end()) - dests.insert(std::make_pair(address, entry.amount)); + dests.insert(std::make_pair(entry.addr, std::make_pair(address, entry.amount))); else - i->second += entry.amount; + i->second.second += entry.amount; amount_to_dests += entry.amount; } if (cd.change_dts.amount > 0) { - std::unordered_map<std::string, uint64_t>::iterator it = dests.find(get_account_address_as_str(m_wallet.m_wallet->testnet(), cd.change_dts.addr)); + auto it = dests.find(cd.change_dts.addr); if (it == dests.end()) { m_status = Status_Error; m_errorString = tr("Claimed change does not go to a paid address"); return false; } - if (it->second < cd.change_dts.amount) + if (it->second.second < cd.change_dts.amount) { m_status = Status_Error; m_errorString = tr("Claimed change is larger than payment to the change address"); @@ -153,15 +186,15 @@ bool UnsignedTransactionImpl::checkLoadedTx(const std::function<size_t()> get_nu } } change += cd.change_dts.amount; - it->second -= cd.change_dts.amount; - if (it->second == 0) - dests.erase(get_account_address_as_str(m_wallet.m_wallet->testnet(), cd.change_dts.addr)); + it->second.second -= cd.change_dts.amount; + if (it->second.second == 0) + dests.erase(cd.change_dts.addr); } } std::string dest_string; - for (std::unordered_map<std::string, uint64_t>::const_iterator i = dests.begin(); i != dests.end(); ) + for (auto i = dests.begin(); i != dests.end(); ) { - dest_string += (boost::format(tr("sending %s to %s")) % cryptonote::print_money(i->second) % i->first).str(); + dest_string += (boost::format(tr("sending %s to %s")) % cryptonote::print_money(i->second.second) % i->second.first).str(); ++i; if (i != dests.end()) dest_string += ", "; @@ -172,7 +205,7 @@ bool UnsignedTransactionImpl::checkLoadedTx(const std::function<size_t()> get_nu std::string change_string; if (change > 0) { - std::string address = get_account_address_as_str(m_wallet.m_wallet->testnet(), get_tx(0).change_dts.addr); + std::string address = get_account_address_as_str(m_wallet.m_wallet->testnet(), get_tx(0).subaddr_account > 0, get_tx(0).change_dts.addr); change_string += (boost::format(tr("%s change to %s")) % cryptonote::print_money(change) % address).str(); } else @@ -230,13 +263,13 @@ std::vector<std::string> UnsignedTransactionImpl::paymentId() const { std::vector<string> result; for (const auto &utx: m_unsigned_tx_set.txes) { - crypto::hash payment_id = cryptonote::null_hash; + crypto::hash payment_id = crypto::null_hash; cryptonote::tx_extra_nonce extra_nonce; std::vector<cryptonote::tx_extra_field> tx_extra_fields; cryptonote::parse_tx_extra(utx.extra, tx_extra_fields); if (cryptonote::find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) { - crypto::hash8 payment_id8 = cryptonote::null_hash8; + crypto::hash8 payment_id8 = crypto::null_hash8; if(cryptonote::get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8)) { // We can't decrypt short pid without recipient key. @@ -244,10 +277,10 @@ std::vector<std::string> UnsignedTransactionImpl::paymentId() const } else if (!cryptonote::get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id)) { - payment_id = cryptonote::null_hash; + payment_id = crypto::null_hash; } } - if(payment_id != cryptonote::null_hash) + if(payment_id != crypto::null_hash) result.push_back(epee::string_tools::pod_to_hex(payment_id)); else result.push_back(""); @@ -260,7 +293,7 @@ std::vector<std::string> UnsignedTransactionImpl::recipientAddress() const // TODO: return integrated address if short payment ID exists std::vector<string> result; for (const auto &utx: m_unsigned_tx_set.txes) { - result.push_back(cryptonote::get_account_address_as_str(m_wallet.m_wallet->testnet(), utx.dests[0].addr)); + result.push_back(cryptonote::get_account_address_as_str(m_wallet.m_wallet->testnet(), utx.dests[0].is_subaddress, utx.dests[0].addr)); } return result; } diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 7afc1f449..a932d9d6f 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -34,6 +34,8 @@ #include "unsigned_transaction.h" #include "transaction_history.h" #include "address_book.h" +#include "subaddress.h" +#include "subaddress_account.h" #include "common_defines.h" #include "common/util.h" @@ -100,14 +102,15 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback } } - virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount) + virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index) { std::string tx_hash = epee::string_tools::pod_to_hex(txid); LOG_PRINT_L3(__FUNCTION__ << ": money received. height: " << height << ", tx: " << tx_hash - << ", amount: " << print_money(amount)); + << ", amount: " << print_money(amount) + << ", idx: " << subaddr_index); // do not signal on received tx if wallet is not syncronized completely if (m_listener && m_wallet->synchronized()) { m_listener->moneyReceived(tx_hash, amount); @@ -115,14 +118,15 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback } } - virtual void on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount) + virtual void on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index) { std::string tx_hash = epee::string_tools::pod_to_hex(txid); LOG_PRINT_L3(__FUNCTION__ << ": unconfirmed money received. height: " << height << ", tx: " << tx_hash - << ", amount: " << print_money(amount)); + << ", amount: " << print_money(amount) + << ", idx: " << subaddr_index); // do not signal on received tx if wallet is not syncronized completely if (m_listener && m_wallet->synchronized()) { m_listener->unconfirmedMoneyReceived(tx_hash, amount); @@ -131,13 +135,14 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback } virtual void on_money_spent(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& in_tx, - uint64_t amount, const cryptonote::transaction& spend_tx) + uint64_t amount, const cryptonote::transaction& spend_tx, const cryptonote::subaddress_index& subaddr_index) { // TODO; std::string tx_hash = epee::string_tools::pod_to_hex(txid); LOG_PRINT_L3(__FUNCTION__ << ": money spent. height: " << height << ", tx: " << tx_hash - << ", amount: " << print_money(amount)); + << ", amount: " << print_money(amount) + << ", idx: " << subaddr_index); // do not signal on sent tx if wallet is not syncronized completely if (m_listener && m_wallet->synchronized()) { m_listener->moneySpent(tx_hash, amount); @@ -198,18 +203,14 @@ bool Wallet::paymentIdValid(const string &paiment_id) bool Wallet::addressValid(const std::string &str, bool testnet) { - bool has_payment_id; - cryptonote::account_public_address address; - crypto::hash8 pid; - return get_account_integrated_address_from_str(address, has_payment_id, pid, testnet, str); + cryptonote::address_parse_info info; + return get_account_address_from_str(info, testnet, str); } bool Wallet::keyValid(const std::string &secret_key_string, const std::string &address_string, bool isViewKey, bool testnet, std::string &error) { - bool has_payment_id; - cryptonote::account_public_address address; - crypto::hash8 pid; - if(!get_account_integrated_address_from_str(address, has_payment_id, pid, testnet, address_string)) { + cryptonote::address_parse_info info; + if(!get_account_address_from_str(info, testnet, address_string)) { error = tr("Failed to parse address"); return false; } @@ -230,9 +231,9 @@ bool Wallet::keyValid(const std::string &secret_key_string, const std::string &a } bool matchAddress = false; if(isViewKey) - matchAddress = address.m_view_public_key == pkey; + matchAddress = info.address.m_view_public_key == pkey; else - matchAddress = address.m_spend_public_key == pkey; + matchAddress = info.address.m_spend_public_key == pkey; if(!matchAddress) { error = tr("key does not match address"); @@ -244,14 +245,12 @@ bool Wallet::keyValid(const std::string &secret_key_string, const std::string &a std::string Wallet::paymentIdFromAddress(const std::string &str, bool testnet) { - bool has_payment_id; - cryptonote::account_public_address address; - crypto::hash8 pid; - if (!get_account_integrated_address_from_str(address, has_payment_id, pid, testnet, str)) + cryptonote::address_parse_info info; + if (!get_account_address_from_str(info, testnet, str)) return ""; - if (!has_payment_id) + if (!info.has_payment_id) return ""; - return epee::string_tools::pod_to_hex(pid); + return epee::string_tools::pod_to_hex(info.payment_id); } uint64_t Wallet::maximumAllowedAmount() @@ -286,6 +285,8 @@ WalletImpl::WalletImpl(bool testnet) m_refreshThreadDone = false; m_refreshEnabled = false; m_addressBook = new AddressBookImpl(this); + m_subaddress = new SubaddressImpl(this); + m_subaddressAccount = new SubaddressAccountImpl(this); m_refreshIntervalMillis = DEFAULT_REFRESH_INTERVAL_MILLIS; @@ -303,12 +304,14 @@ WalletImpl::~WalletImpl() // Pause refresh thread - prevents refresh from starting again pauseRefresh(); // Close wallet - stores cache and stops ongoing refresh operation - close(); + close(false); // do not store wallet as part of the closing activities // Stop refresh thread stopRefresh(); delete m_wallet2Callback; delete m_history; delete m_addressBook; + delete m_subaddress; + delete m_subaddressAccount; delete m_wallet; LOG_PRINT_L1(__FUNCTION__ << " finished"); } @@ -423,10 +426,8 @@ bool WalletImpl::recoverFromKeys(const std::string &path, const std::string &viewkey_string, const std::string &spendkey_string) { - cryptonote::account_public_address address; - bool has_payment_id; - crypto::hash8 new_payment_id; - if(!get_account_integrated_address_from_str(address, has_payment_id, new_payment_id, m_wallet->testnet(), address_string)) + cryptonote::address_parse_info info; + if(!get_account_address_from_str(info, m_wallet->testnet(), address_string)) { m_errorString = tr("failed to parse address"); m_status = Status_Error; @@ -471,7 +472,7 @@ bool WalletImpl::recoverFromKeys(const std::string &path, m_status = Status_Error; return false; } - if (address.m_spend_public_key != pkey) { + if (info.address.m_spend_public_key != pkey) { m_errorString = tr("spend key does not match address"); m_status = Status_Error; return false; @@ -482,7 +483,7 @@ bool WalletImpl::recoverFromKeys(const std::string &path, m_status = Status_Error; return false; } - if (address.m_view_public_key != pkey) { + if (info.address.m_view_public_key != pkey) { m_errorString = tr("view key does not match address"); m_status = Status_Error; return false; @@ -491,12 +492,12 @@ bool WalletImpl::recoverFromKeys(const std::string &path, try { if (has_spendkey) { - m_wallet->generate(path, "", address, spendkey, viewkey); + m_wallet->generate(path, "", info.address, spendkey, viewkey); setSeedLanguage(language); LOG_PRINT_L1("Generated new wallet from keys with seed language: " + language); } else { - m_wallet->generate(path, "", address, viewkey); + m_wallet->generate(path, "", info.address, viewkey); LOG_PRINT_L1("Generated new view only wallet from keys"); } @@ -566,19 +567,21 @@ bool WalletImpl::recover(const std::string &path, const std::string &seed) return m_status == Status_Ok; } -bool WalletImpl::close() +bool WalletImpl::close(bool store) { bool result = false; LOG_PRINT_L1("closing wallet..."); try { - // Do not store wallet with invalid status - // Status Critical refers to errors on opening or creating wallets. - if (status() != Status_Critical) - m_wallet->store(); - else - LOG_ERROR("Status_Critical - not storing wallet"); - LOG_PRINT_L1("wallet::store done"); + if (store) { + // Do not store wallet with invalid status + // Status Critical refers to errors on opening or creating wallets. + if (status() != Status_Critical) + m_wallet->store(); + else + LOG_ERROR("Status_Critical - not storing wallet"); + LOG_PRINT_L1("wallet::store done"); + } LOG_PRINT_L1("Calling wallet::stop..."); m_wallet->stop(); LOG_PRINT_L1("wallet::stop done"); @@ -633,9 +636,9 @@ bool WalletImpl::setPassword(const std::string &password) return m_status == Status_Ok; } -std::string WalletImpl::address() const +std::string WalletImpl::address(uint32_t accountIndex, uint32_t addressIndex) const { - return m_wallet->get_account().get_public_address_str(m_wallet->testnet()); + return m_wallet->get_subaddress_as_str({accountIndex, addressIndex}); } std::string WalletImpl::integratedAddress(const std::string &payment_id) const @@ -644,7 +647,7 @@ std::string WalletImpl::integratedAddress(const std::string &payment_id) const if (!tools::wallet2::parse_short_payment_id(payment_id, pid)) { return ""; } - return m_wallet->get_account().get_public_integrated_address_str(pid, m_wallet->testnet()); + return m_wallet->get_integrated_address_as_str(pid); } std::string WalletImpl::secretViewKey() const @@ -718,14 +721,14 @@ void WalletImpl::setRecoveringFromSeed(bool recoveringFromSeed) m_recoveringFromSeed = recoveringFromSeed; } -uint64_t WalletImpl::balance() const +uint64_t WalletImpl::balance(uint32_t accountIndex) const { - return m_wallet->balance(); + return m_wallet->balance(accountIndex); } -uint64_t WalletImpl::unlockedBalance() const +uint64_t WalletImpl::unlockedBalance(uint32_t accountIndex) const { - return m_wallet->unlocked_balance(); + return m_wallet->unlocked_balance(accountIndex); } uint64_t WalletImpl::blockChainHeight() const @@ -912,6 +915,50 @@ bool WalletImpl::importKeyImages(const string &filename) return true; } +void WalletImpl::addSubaddressAccount(const std::string& label) +{ + m_wallet->add_subaddress_account(label); +} +size_t WalletImpl::numSubaddressAccounts() const +{ + return m_wallet->get_num_subaddress_accounts(); +} +size_t WalletImpl::numSubaddresses(uint32_t accountIndex) const +{ + return m_wallet->get_num_subaddresses(accountIndex); +} +void WalletImpl::addSubaddress(uint32_t accountIndex, const std::string& label) +{ + m_wallet->add_subaddress(accountIndex, label); +} +std::string WalletImpl::getSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex) const +{ + try + { + return m_wallet->get_subaddress_label({accountIndex, addressIndex}); + } + catch (const std::exception &e) + { + LOG_ERROR("Error getting subaddress label: ") << e.what(); + m_errorString = string(tr("Failed to get subaddress label: ")) + e.what(); + m_status = Status_Error; + return ""; + } +} +void WalletImpl::setSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex, const std::string &label) +{ + try + { + return m_wallet->set_subaddress_label({accountIndex, addressIndex}, label); + } + catch (const std::exception &e) + { + LOG_ERROR("Error setting subaddress label: ") << e.what(); + m_errorString = string(tr("Failed to set subaddress label: ")) + e.what(); + m_status = Status_Error; + } +} + // TODO: // 1 - properly handle payment id (add another menthod with explicit 'payment_id' param) // 2 - check / design how "Transaction" can be single interface @@ -923,6 +970,7 @@ bool WalletImpl::importKeyImages(const string &filename) // - confirmed_transfer_details) PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const string &payment_id, optional<uint64_t> amount, uint32_t mixin_count, + uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, PendingTransaction::Priority priority) { @@ -930,11 +978,9 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const // Pause refresh thread while creating transaction pauseRefresh(); - cryptonote::account_public_address addr; + cryptonote::address_parse_info info; // indicates if dst_addr is integrated address (address + payment_id) - bool has_payment_id; - crypto::hash8 payment_id_short; // TODO: (https://bitcointalk.org/index.php?topic=753252.msg9985441#msg9985441) size_t fake_outs_count = mixin_count > 0 ? mixin_count : m_wallet->default_mixin(); if (fake_outs_count == 0) @@ -943,7 +989,7 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const PendingTransactionImpl * transaction = new PendingTransactionImpl(*this); do { - if(!cryptonote::get_account_integrated_address_from_str(addr, has_payment_id, payment_id_short, m_wallet->testnet(), dst_addr)) { + if(!cryptonote::get_account_address_from_str(info, m_wallet->testnet(), dst_addr)) { // TODO: copy-paste 'if treating as an address fails, try as url' from simplewallet.cpp:1982 m_status = Status_Error; m_errorString = "Invalid destination address"; @@ -953,7 +999,7 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const std::vector<uint8_t> extra; // if dst_addr is not an integrated address, parse payment_id - if (!has_payment_id && !payment_id.empty()) { + if (!info.has_payment_id && !payment_id.empty()) { // copy-pasted from simplewallet.cpp:2212 crypto::hash payment_id_long; bool r = tools::wallet2::parse_long_payment_id(payment_id, payment_id_long); @@ -962,10 +1008,10 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const cryptonote::set_payment_id_to_tx_extra_nonce(extra_nonce, payment_id_long); r = add_extra_nonce_to_tx_extra(extra, extra_nonce); } else { - r = tools::wallet2::parse_short_payment_id(payment_id, payment_id_short); + r = tools::wallet2::parse_short_payment_id(payment_id, info.payment_id); if (r) { std::string extra_nonce; - set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, payment_id_short); + set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, info.payment_id); r = add_extra_nonce_to_tx_extra(extra, extra_nonce); } } @@ -976,13 +1022,13 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const break; } } - else if (has_payment_id) { + else if (info.has_payment_id) { std::string extra_nonce; - set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, payment_id_short); + set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, info.payment_id); bool r = add_extra_nonce_to_tx_extra(extra, extra_nonce); if (!r) { m_status = Status_Error; - m_errorString = tr("Failed to add short payment id: ") + epee::string_tools::pod_to_hex(payment_id_short); + m_errorString = tr("Failed to add short payment id: ") + epee::string_tools::pod_to_hex(info.payment_id); break; } } @@ -994,16 +1040,23 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const if (amount) { vector<cryptonote::tx_destination_entry> dsts; cryptonote::tx_destination_entry de; - de.addr = addr; + de.addr = info.address; de.amount = *amount; + de.is_subaddress = info.is_subaddress; dsts.push_back(de); transaction->m_pending_tx = m_wallet->create_transactions_2(dsts, fake_outs_count, 0 /* unlock_time */, static_cast<uint32_t>(priority), - extra, m_trustedDaemon); + extra, subaddr_account, subaddr_indices, m_trustedDaemon); } else { - transaction->m_pending_tx = m_wallet->create_transactions_all(0, addr, fake_outs_count, 0 /* unlock_time */, + // for the GUI, sweep_all (i.e. amount set as "(all)") will always sweep all the funds in all the addresses + if (subaddr_indices.empty()) + { + for (uint32_t index = 0; index < m_wallet->get_num_subaddresses(subaddr_account); ++index) + subaddr_indices.insert(index); + } + transaction->m_pending_tx = m_wallet->create_transactions_all(0, info.address, info.is_subaddress, fake_outs_count, 0 /* unlock_time */, static_cast<uint32_t>(priority), - extra, m_trustedDaemon); + extra, subaddr_account, subaddr_indices, m_trustedDaemon); } } catch (const tools::error::daemon_busy&) { @@ -1184,16 +1237,26 @@ void WalletImpl::disposeTransaction(PendingTransaction *t) delete t; } -TransactionHistory *WalletImpl::history() const +TransactionHistory *WalletImpl::history() { return m_history; } -AddressBook *WalletImpl::addressBook() const +AddressBook *WalletImpl::addressBook() { return m_addressBook; } +Subaddress *WalletImpl::subaddress() +{ + return m_subaddress; +} + +SubaddressAccount *WalletImpl::subaddressAccount() +{ + return m_subaddressAccount; +} + void WalletImpl::setListener(WalletListener *l) { // TODO thread synchronization; @@ -1241,7 +1304,8 @@ std::string WalletImpl::getTxKey(const std::string &txid) const const crypto::hash htxid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); crypto::secret_key tx_key; - if (m_wallet->get_tx_key(htxid, tx_key)) + std::vector<crypto::secret_key> additional_tx_keys; + if (m_wallet->get_tx_key(htxid, tx_key, additional_tx_keys)) { return epee::string_tools::pod_to_hex(tx_key); } @@ -1258,14 +1322,12 @@ std::string WalletImpl::signMessage(const std::string &message) bool WalletImpl::verifySignedMessage(const std::string &message, const std::string &address, const std::string &signature) const { - cryptonote::account_public_address addr; - bool has_payment_id; - crypto::hash8 payment_id; + cryptonote::address_parse_info info; - if (!cryptonote::get_account_integrated_address_from_str(addr, has_payment_id, payment_id, m_wallet->testnet(), address)) + if (!cryptonote::get_account_address_from_str(info, m_wallet->testnet(), address)) return false; - return m_wallet->verify(message, addr, signature); + return m_wallet->verify(message, info.address, signature); } bool WalletImpl::connectToDaemon() diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index 8190c7873..020c5e46a 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -45,6 +45,8 @@ class TransactionHistoryImpl; class PendingTransactionImpl; class UnsignedTransactionImpl; class AddressBookImpl; +class SubaddressImpl; +class SubaddressAccountImpl; struct Wallet2CallbackImpl; class WalletImpl : public Wallet @@ -63,7 +65,7 @@ public: const std::string &address_string, const std::string &viewkey_string, const std::string &spendkey_string = ""); - bool close(); + bool close(bool store = true); std::string seed() const; std::string getSeedLanguage() const; void setSeedLanguage(const std::string &arg); @@ -71,7 +73,7 @@ public: int status() const; std::string errorString() const; bool setPassword(const std::string &password); - std::string address() const; + std::string address(uint32_t accountIndex, uint32_t addressIndex) const; std::string integratedAddress(const std::string &payment_id) const; std::string secretViewKey() const; std::string publicViewKey() const; @@ -86,8 +88,8 @@ public: ConnectionStatus connected() const; void setTrustedDaemon(bool arg); bool trustedDaemon() const; - uint64_t balance() const; - uint64_t unlockedBalance() const; + uint64_t balance(uint32_t accountIndex) const; + uint64_t unlockedBalance(uint32_t accountIndex) const; uint64_t blockChainHeight() const; uint64_t approximateBlockChainHeight() const; uint64_t daemonBlockChainHeight() const; @@ -106,8 +108,17 @@ public: void hardForkInfo(uint8_t &version, uint64_t &earliest_height) const; bool useForkRules(uint8_t version, int64_t early_blocks) const; + void addSubaddressAccount(const std::string& label); + size_t numSubaddressAccounts() const; + size_t numSubaddresses(uint32_t accountIndex) const; + void addSubaddress(uint32_t accountIndex, const std::string& label); + std::string getSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex) const; + void setSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex, const std::string &label); + PendingTransaction * createTransaction(const std::string &dst_addr, const std::string &payment_id, optional<uint64_t> amount, uint32_t mixin_count, + uint32_t subaddr_account, + std::set<uint32_t> subaddr_indices, PendingTransaction::Priority priority = PendingTransaction::Priority_Low); virtual PendingTransaction * createSweepUnmixableTransaction(); bool submitTransaction(const std::string &fileName); @@ -116,8 +127,10 @@ public: bool importKeyImages(const std::string &filename); virtual void disposeTransaction(PendingTransaction * t); - virtual TransactionHistory * history() const; - virtual AddressBook * addressBook() const; + virtual TransactionHistory * history(); + virtual AddressBook * addressBook(); + virtual Subaddress * subaddress(); + virtual SubaddressAccount * subaddressAccount(); virtual void setListener(WalletListener * l); virtual uint32_t defaultMixin() const; virtual void setDefaultMixin(uint32_t arg); @@ -146,6 +159,8 @@ private: friend class TransactionHistoryImpl; friend struct Wallet2CallbackImpl; friend class AddressBookImpl; + friend class SubaddressImpl; + friend class SubaddressAccountImpl; tools::wallet2 * m_wallet; mutable std::atomic<int> m_status; @@ -153,9 +168,10 @@ private: std::string m_password; TransactionHistoryImpl * m_history; bool m_trustedDaemon; - WalletListener * m_walletListener; Wallet2CallbackImpl * m_wallet2Callback; AddressBookImpl * m_addressBook; + SubaddressImpl * m_subaddress; + SubaddressAccountImpl * m_subaddressAccount; // multi-threaded refresh stuff std::atomic<bool> m_refreshEnabled; diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp index a23533530..a64766c84 100644 --- a/src/wallet/api/wallet_manager.cpp +++ b/src/wallet/api/wallet_manager.cpp @@ -102,10 +102,12 @@ Wallet *WalletManagerImpl::createWalletFromKeys(const std::string &path, return wallet; } -bool WalletManagerImpl::closeWallet(Wallet *wallet) +bool WalletManagerImpl::closeWallet(Wallet *wallet, bool store) { WalletImpl * wallet_ = dynamic_cast<WalletImpl*>(wallet); - bool result = wallet_->close(); + if (!wallet_) + return false; + bool result = wallet_->close(store); if (!result) { m_errorString = wallet_->errorString(); } else { @@ -213,10 +215,8 @@ bool WalletManagerImpl::checkPayment(const std::string &address_text, const std: tx_key = *reinterpret_cast<const crypto::secret_key*>(tx_key_data.data()); bool testnet = address_text[0] != '4'; - cryptonote::account_public_address address; - bool has_payment_id; - crypto::hash8 payment_id; - if(!cryptonote::get_account_integrated_address_from_str(address, has_payment_id, payment_id, testnet, address_text)) + cryptonote::address_parse_info info; + if(!cryptonote::get_account_address_from_str(info, testnet, address_text)) { error = tr("failed to parse address"); return false; @@ -256,7 +256,7 @@ bool WalletManagerImpl::checkPayment(const std::string &address_text, const std: } crypto::key_derivation derivation; - if (!crypto::generate_key_derivation(address.m_view_public_key, tx_key, derivation)) + if (!crypto::generate_key_derivation(info.address.m_view_public_key, tx_key, derivation)) { error = tr("failed to generate key derivation from supplied parameters"); return false; @@ -270,7 +270,7 @@ bool WalletManagerImpl::checkPayment(const std::string &address_text, const std: continue; const cryptonote::txout_to_key tx_out_to_key = boost::get<cryptonote::txout_to_key>(tx.vout[n].target); crypto::public_key pubkey; - derive_public_key(derivation, n, address.m_spend_public_key, pubkey); + derive_public_key(derivation, n, info.address.m_spend_public_key, pubkey); if (pubkey == tx_out_to_key.key) { uint64_t amount; @@ -285,7 +285,7 @@ bool WalletManagerImpl::checkPayment(const std::string &address_text, const std: 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); + bool r = crypto::generate_key_derivation(info.address.m_view_public_key, tx_key, derivation); if (!r) { LOG_ERROR("Failed to generate key derivation to decode rct output " << n); @@ -320,11 +320,11 @@ bool WalletManagerImpl::checkPayment(const std::string &address_text, const std: if (received > 0) { - LOG_PRINT_L1(get_account_address_as_str(testnet, address) << " " << tr("received") << " " << cryptonote::print_money(received) << " " << tr("in txid") << " " << txid); + LOG_PRINT_L1(get_account_address_as_str(testnet, info.is_subaddress, info.address) << " " << tr("received") << " " << cryptonote::print_money(received) << " " << tr("in txid") << " " << txid); } else { - LOG_PRINT_L1(get_account_address_as_str(testnet, address) << " " << tr("received nothing in txid") << " " << txid); + LOG_PRINT_L1(get_account_address_as_str(testnet, info.is_subaddress, info.address) << " " << tr("received nothing in txid") << " " << txid); } if (res.txs.front().in_pool) { diff --git a/src/wallet/api/wallet_manager.h b/src/wallet/api/wallet_manager.h index aa6ea439e..8455f0f16 100644 --- a/src/wallet/api/wallet_manager.h +++ b/src/wallet/api/wallet_manager.h @@ -48,7 +48,7 @@ public: const std::string &addressString, const std::string &viewKeyString, const std::string &spendKeyString = ""); - virtual bool closeWallet(Wallet *wallet); + virtual bool closeWallet(Wallet *wallet, bool store = true); bool walletExists(const std::string &path); bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool watch_only) const; std::vector<std::string> findWallets(const std::string &path); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 323a3a7fe..d451baa09 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -33,6 +33,7 @@ #include <boost/format.hpp> #include <boost/optional/optional.hpp> #include <boost/utility/value_init.hpp> +#include <boost/algorithm/string/join.hpp> #include "include_base_utils.h" using namespace epee; @@ -45,6 +46,7 @@ using namespace epee; #include "cryptonote_basic/cryptonote_basic_impl.h" #include "common/boost_serialization_helper.h" #include "common/command_line.h" +#include "common/threadpool.h" #include "profile_tools.h" #include "crypto/crypto.h" #include "serialization/binary_utils.h" @@ -89,13 +91,8 @@ using namespace cryptonote; #define SECOND_OUTPUT_RELATEDNESS_THRESHOLD 0.0f -#define KILL_IOSERVICE() \ - do { \ - work.reset(); \ - while (!ioservice.stopped()) ioservice.poll(); \ - threadpool.join_all(); \ - ioservice.stop(); \ - } while(0) +#define SUBADDRESS_LOOKAHEAD_MAJOR 50 +#define SUBADDRESS_LOOKAHEAD_MINOR 200 #define KEY_IMAGE_EXPORT_FILE_MAGIC "Monero key image export\002" @@ -296,6 +293,13 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, return false; } restore_deterministic_wallet = true; + + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, seed_passphrase, std::string, String, false, std::string()); + if (field_seed_passphrase_found) + { + if (!field_seed_passphrase.empty()) + recovery_key = cryptonote::decrypt_key(recovery_key, field_seed_passphrase); + } } GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, address, std::string, String, false, std::string()); @@ -316,10 +320,8 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, // public key if it was not given if (field_address_found) { - cryptonote::account_public_address address; - bool has_payment_id; - crypto::hash8 new_payment_id; - if(!get_account_integrated_address_from_str(address, has_payment_id, new_payment_id, testnet, field_address)) + cryptonote::address_parse_info info; + if(!get_account_address_from_str(info, testnet, field_address)) { tools::fail_msg_writer() << tools::wallet2::tr("invalid address"); return false; @@ -331,7 +333,7 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, tools::fail_msg_writer() << tools::wallet2::tr("failed to verify view key secret key"); return false; } - if (address.m_view_public_key != pkey) { + if (info.address.m_view_public_key != pkey) { tools::fail_msg_writer() << tools::wallet2::tr("view key does not match standard address"); return false; } @@ -343,7 +345,7 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, tools::fail_msg_writer() << tools::wallet2::tr("failed to verify spend key secret key"); return false; } - if (address.m_spend_public_key != pkey) { + if (info.address.m_spend_public_key != pkey) { tools::fail_msg_writer() << tools::wallet2::tr("spend key does not match standard address"); return false; } @@ -380,11 +382,13 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, // from the address if (field_address_found) { - cryptonote::account_public_address address2; - bool has_payment_id; - crypto::hash8 new_payment_id; - get_account_integrated_address_from_str(address2, has_payment_id, new_payment_id, testnet, field_address); - address.m_spend_public_key = address2.m_spend_public_key; + cryptonote::address_parse_info info; + if(!get_account_address_from_str(info, testnet, field_address)) + { + tools::fail_msg_writer() << tools::wallet2::tr("failed to parse address: ") << field_address; + return false; + } + address.m_spend_public_key = info.address.m_spend_public_key; } wallet->generate(field_filename, field_password, address, viewkey); } @@ -426,6 +430,20 @@ static void throw_on_rpc_response_error(const boost::optional<std::string> &stat THROW_WALLET_EXCEPTION_IF(*status != CORE_RPC_STATUS_OK, tools::error::wallet_generic_rpc_error, method, *status); } +std::string strjoin(const std::vector<size_t> &V, const char *sep) +{ + std::stringstream ss; + bool first = true; + for (const auto &v: V) + { + if (!first) + ss << sep; + ss << std::to_string(v); + first = false; + } + return ss.str(); +} + } //namespace namespace tools @@ -509,6 +527,7 @@ std::unique_ptr<wallet2> wallet2::make_dummy(const boost::program_options::varia //---------------------------------------------------------------------------------------------------- bool wallet2::init(std::string daemon_address, boost::optional<epee::net_utils::http::login> daemon_login, uint64_t upper_transaction_size_limit) { + m_checkpoints.init_default_checkpoints(m_testnet); if(m_http_client.is_connected()) m_http_client.disconnect(); m_is_initialized = true; @@ -527,7 +546,7 @@ bool wallet2::is_deterministic() const return keys_deterministic; } //---------------------------------------------------------------------------------------------------- -bool wallet2::get_seed(std::string& electrum_words) const +bool wallet2::get_seed(std::string& electrum_words, const std::string &passphrase) const { bool keys_deterministic = is_deterministic(); if (!keys_deterministic) @@ -541,7 +560,10 @@ bool wallet2::get_seed(std::string& electrum_words) const return false; } - crypto::ElectrumWords::bytes_to_words(get_account().get_keys().m_spend_secret_key, electrum_words, seed_language); + crypto::secret_key key = get_account().get_keys().m_spend_secret_key; + if (!passphrase.empty()) + key = cryptonote::encrypt_key(key, passphrase); + crypto::ElectrumWords::bytes_to_words(key, electrum_words, seed_language); return true; } @@ -560,6 +582,129 @@ void wallet2::set_seed_language(const std::string &language) { seed_language = language; } +//---------------------------------------------------------------------------------------------------- +cryptonote::account_public_address wallet2::get_subaddress(const cryptonote::subaddress_index& index) const +{ + const cryptonote::account_keys& keys = m_account.get_keys(); + if (index.is_zero()) + return keys.m_account_address; + + crypto::public_key D = get_subaddress_spend_public_key(index); + + // C = a*D + crypto::public_key C = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(D), rct::sk2rct(keys.m_view_secret_key))); // could have defined secret_key_mult_public_key() under src/crypto + + // result: (C, D) + cryptonote::account_public_address address; + address.m_view_public_key = C; + address.m_spend_public_key = D; + return address; +} +//---------------------------------------------------------------------------------------------------- +crypto::public_key wallet2::get_subaddress_spend_public_key(const cryptonote::subaddress_index& index) const +{ + const cryptonote::account_keys& keys = m_account.get_keys(); + if (index.is_zero()) + return keys.m_account_address.m_spend_public_key; + + // m = Hs(a || index_major || index_minor) + crypto::secret_key m = cryptonote::get_subaddress_secret_key(keys.m_view_secret_key, index); + + // M = m*G + crypto::public_key M; + crypto::secret_key_to_public_key(m, M); + + // D = B + M + rct::key D_rct; + rct::addKeys(D_rct, rct::pk2rct(keys.m_account_address.m_spend_public_key), rct::pk2rct(M)); // could have defined add_public_key() under src/crypto + crypto::public_key D = rct::rct2pk(D_rct); + + return D; +} +//---------------------------------------------------------------------------------------------------- +std::string wallet2::get_subaddress_as_str(const cryptonote::subaddress_index& index) const +{ + cryptonote::account_public_address address = get_subaddress(index); + return cryptonote::get_account_address_as_str(m_testnet, !index.is_zero(), address); +} +//---------------------------------------------------------------------------------------------------- +std::string wallet2::get_integrated_address_as_str(const crypto::hash8& payment_id) const +{ + return cryptonote::get_account_integrated_address_as_str(m_testnet, get_address(), payment_id); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::add_subaddress_account(const std::string& label) +{ + uint32_t index_major = (uint32_t)get_num_subaddress_accounts(); + expand_subaddresses({index_major, 0}); + m_subaddress_labels[index_major][0] = label; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::add_subaddress(uint32_t index_major, const std::string& label) +{ + if (index_major >= m_subaddress_labels.size()) + throw std::runtime_error("index_major is out of bound"); + uint32_t index_minor = (uint32_t)get_num_subaddresses(index_major); + expand_subaddresses({index_major, index_minor}); + m_subaddress_labels[index_major][index_minor] = label; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::expand_subaddresses(const cryptonote::subaddress_index& index) +{ + if (m_subaddress_labels.size() <= index.major) + { + // add new accounts + cryptonote::subaddress_index index2; + for (index2.major = m_subaddress_labels.size(); index2.major < index.major + SUBADDRESS_LOOKAHEAD_MAJOR; ++index2.major) + { + for (index2.minor = 0; index2.minor < (index2.major == index.major ? index.minor : 0) + SUBADDRESS_LOOKAHEAD_MINOR; ++index2.minor) + { + if (m_subaddresses_inv.count(index2) == 0) + { + crypto::public_key D = get_subaddress_spend_public_key(index2); + m_subaddresses[D] = index2; + m_subaddresses_inv[index2] = D; + } + } + } + m_subaddress_labels.resize(index.major + 1, {"Untitled account"}); + m_subaddress_labels[index.major].resize(index.minor + 1); + } + else if (m_subaddress_labels[index.major].size() <= index.minor) + { + // add new subaddresses + cryptonote::subaddress_index index2 = index; + for (index2.minor = m_subaddress_labels[index.major].size(); index2.minor < index.minor + SUBADDRESS_LOOKAHEAD_MINOR; ++index2.minor) + { + if (m_subaddresses_inv.count(index2) == 0) + { + crypto::public_key D = get_subaddress_spend_public_key(index2); + m_subaddresses[D] = index2; + m_subaddresses_inv[index2] = D; + } + } + m_subaddress_labels[index.major].resize(index.minor + 1); + } +} +//---------------------------------------------------------------------------------------------------- +std::string wallet2::get_subaddress_label(const cryptonote::subaddress_index& index) const +{ + if (index.major >= m_subaddress_labels.size()) + throw std::runtime_error("index.major is out of bound"); + if (index.minor >= m_subaddress_labels[index.major].size()) + throw std::runtime_error("index.minor is out of bound"); + return m_subaddress_labels[index.major][index.minor]; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::set_subaddress_label(const cryptonote::subaddress_index& index, const std::string &label) +{ + if (index.major >= m_subaddress_labels.size()) + throw std::runtime_error("index.major is out of bound"); + if (index.minor >= m_subaddress_labels[index.major].size()) + throw std::runtime_error("index.minor is out of bound"); + m_subaddress_labels[index.major][index.minor] = label; +} +//---------------------------------------------------------------------------------------------------- /*! * \brief Tells if the wallet file is deprecated. */ @@ -584,35 +729,28 @@ void wallet2::set_unspent(size_t idx) td.m_spent_height = 0; } //---------------------------------------------------------------------------------------------------- -void wallet2::check_acc_out_precomp(const crypto::public_key &spend_public_key, const tx_out &o, const crypto::key_derivation &derivation, size_t i, bool &received, uint64_t &money_transfered, bool &error) const +void wallet2::check_acc_out_precomp(const tx_out &o, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, size_t i, tx_scan_info_t &tx_scan_info) const { if (o.target.type() != typeid(txout_to_key)) { - error = true; + tx_scan_info.error = true; LOG_ERROR("wrong type id in transaction out"); return; } - received = is_out_to_acc_precomp(spend_public_key, boost::get<txout_to_key>(o.target), derivation, i); - if(received) + tx_scan_info.received = is_out_to_acc_precomp(m_subaddresses, boost::get<txout_to_key>(o.target).key, derivation, additional_derivations, i); + if(tx_scan_info.received) { - money_transfered = o.amount; // may be 0 for ringct outputs + tx_scan_info.money_transfered = o.amount; // may be 0 for ringct outputs } else { - money_transfered = 0; + tx_scan_info.money_transfered = 0; } - error = false; + tx_scan_info.error = false; } //---------------------------------------------------------------------------------------------------- -static uint64_t decodeRct(const rct::rctSig & rv, const crypto::public_key &pub, const crypto::secret_key &sec, unsigned int i, rct::key & mask) +static uint64_t decodeRct(const rct::rctSig & rv, const crypto::key_derivation &derivation, 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 @@ -635,11 +773,21 @@ static uint64_t decodeRct(const rct::rctSig & rv, const crypto::public_key &pub, } } //---------------------------------------------------------------------------------------------------- -bool wallet2::wallet_generate_key_image_helper(const cryptonote::account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, cryptonote::keypair& in_ephemeral, crypto::key_image& ki) +void wallet2::scan_output(const cryptonote::account_keys &keys, const cryptonote::transaction &tx, const crypto::public_key &tx_pub_key, size_t i, tx_scan_info_t &tx_scan_info, int &num_vouts_received, std::unordered_map<cryptonote::subaddress_index, uint64_t> &tx_money_got_in_outs, std::vector<size_t> &outs) { - if (!cryptonote::generate_key_image_helper(ack, tx_public_key, real_output_index, in_ephemeral, ki)) - return false; - return true; + bool r = cryptonote::generate_key_image_helper_precomp(keys, boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key, tx_scan_info.received->derivation, i, tx_scan_info.received->index, tx_scan_info.in_ephemeral, tx_scan_info.ki); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); + THROW_WALLET_EXCEPTION_IF(tx_scan_info.in_ephemeral.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 (tx_scan_info.money_transfered == 0) + { + tx_scan_info.money_transfered = tools::decodeRct(tx.rct_signatures, tx_scan_info.received->derivation, i, tx_scan_info.mask); + } + tx_money_got_in_outs[tx_scan_info.received->index] = tx_scan_info.money_transfered; + tx_scan_info.amount = tx_scan_info.money_transfered; + ++num_vouts_received; } //---------------------------------------------------------------------------------------------------- void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint64_t ts, bool miner_tx, bool pool) @@ -647,10 +795,10 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote // In this function, tx (probably) only contains the base information // (that is, the prunable stuff may or may not be included) - if (!miner_tx) + if (!miner_tx && !pool) process_unconfirmed(txid, tx, height); std::vector<size_t> outs; - uint64_t tx_money_got_in_outs = 0; + std::unordered_map<cryptonote::subaddress_index, uint64_t> tx_money_got_in_outs; // per receiving subaddress index crypto::public_key tx_pub_key = null_pkey; std::vector<tx_extra_field> tx_extra_fields; @@ -662,6 +810,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote // Don't try to extract tx public key if tx has no ouputs size_t pk_index = 0; + std::vector<tx_scan_info_t> tx_scan_info(tx.vout.size()); while (!tx.vout.empty()) { // if tx.vout is not empty, we loop through all tx pubkeys @@ -680,131 +829,84 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote 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(); + tools::threadpool& tpool = tools::threadpool::getInstance(); + tools::threadpool::waiter waiter; const cryptonote::account_keys& keys = m_account.get_keys(); crypto::key_derivation derivation; generate_key_derivation(tx_pub_key, keys.m_view_secret_key, derivation); + + // additional tx pubkeys and derivations for multi-destination transfers involving one or more subaddresses + std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(tx); + std::vector<crypto::key_derivation> additional_derivations; + for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i) + { + additional_derivations.push_back({}); + generate_key_derivation(additional_tx_pub_keys[i], keys.m_view_secret_key, additional_derivations.back()); + } + if (miner_tx && m_refresh_type == RefreshNoCoinbase) { // assume coinbase isn't for us } else if (miner_tx && m_refresh_type == RefreshOptimizeCoinbase) { - uint64_t money_transfered = 0; - bool error = false, received = false; - check_acc_out_precomp(keys.m_account_address.m_spend_public_key, tx.vout[0], derivation, 0, received, money_transfered, error); - if (error) + check_acc_out_precomp(tx.vout[0], derivation, additional_derivations, 0, tx_scan_info[0]); + if (tx_scan_info[0].error) { r = false; } else { // this assumes that the miner tx pays a single address - if (received) + if (tx_scan_info[0].received) { - wallet_generate_key_image_helper(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) - { - 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; + scan_output(keys, tx, tx_pub_key, 0, tx_scan_info[0], num_vouts_received, tx_money_got_in_outs, outs); // process the other outs from that tx - boost::asio::io_service ioservice; - boost::thread_group threadpool; - std::unique_ptr < boost::asio::io_service::work > work(new boost::asio::io_service::work(ioservice)); - for (int i = 0; i < threads; i++) - { - threadpool.create_thread(boost::bind(&boost::asio::io_service::run, &ioservice)); - } - - 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_precomp, this, std::cref(keys.m_account_address.m_spend_public_key), std::cref(tx.vout[i]), std::cref(derivation), i, - std::ref(received[i]), std::ref(money_transfered[i]), std::ref(error[i]))); + tpool.submit(&waiter, boost::bind(&wallet2::check_acc_out_precomp, this, std::cref(tx.vout[i]), std::cref(derivation), std::cref(additional_derivations), i, + std::ref(tx_scan_info[i]))); } - KILL_IOSERVICE(); + waiter.wait(); + for (size_t i = 1; i < tx.vout.size(); ++i) { - if (error[i]) + if (tx_scan_info[i].error) { r = false; break; } - if (received[i]) + if (tx_scan_info[i].received) { - wallet_generate_key_image_helper(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) - { - 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; + scan_output(keys, tx, tx_pub_key, i, tx_scan_info[i], num_vouts_received, tx_money_got_in_outs, outs); } } } } } - else if (tx.vout.size() > 1 && threads > 1) + else if (tx.vout.size() > 1 && tools::threadpool::getInstance().get_max_concurrency() > 1) { - boost::asio::io_service ioservice; - boost::thread_group threadpool; - std::unique_ptr < boost::asio::io_service::work > work(new boost::asio::io_service::work(ioservice)); - for (int i = 0; i < threads; i++) - { - threadpool.create_thread(boost::bind(&boost::asio::io_service::run, &ioservice)); - } + tools::threadpool& tpool = tools::threadpool::getInstance(); + tools::threadpool::waiter waiter; - 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_precomp, this, std::cref(keys.m_account_address.m_spend_public_key), std::cref(tx.vout[i]), std::cref(derivation), i, - std::ref(received[i]), std::ref(money_transfered[i]), std::ref(error[i]))); + tpool.submit(&waiter, boost::bind(&wallet2::check_acc_out_precomp, this, std::cref(tx.vout[i]), std::cref(derivation), std::cref(additional_derivations), i, + std::ref(tx_scan_info[i]))); } - KILL_IOSERVICE(); - tx_money_got_in_outs = 0; + waiter.wait(); for (size_t i = 0; i < tx.vout.size(); ++i) { - if (error[i]) + if (tx_scan_info[i].error) { r = false; break; } - if (received[i]) + if (tx_scan_info[i].received) { - wallet_generate_key_image_helper(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) - { - 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; + scan_output(keys, tx, tx_pub_key, i, tx_scan_info[i], num_vouts_received, tx_money_got_in_outs, outs); } } } @@ -812,31 +914,15 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote { for (size_t i = 0; i < tx.vout.size(); ++i) { - uint64_t money_transfered = 0; - bool error = false, received = false; - check_acc_out_precomp(keys.m_account_address.m_spend_public_key, tx.vout[i], derivation, i, received, money_transfered, error); - if (error) + check_acc_out_precomp(tx.vout[i], derivation, additional_derivations, i, tx_scan_info[i]); + if (tx_scan_info[i].error) { r = false; break; } - else + if (tx_scan_info[i].received) { - if (received) - { - wallet_generate_key_image_helper(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) - { - 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; - } + scan_output(keys, tx, tx_pub_key, i, tx_scan_info[i], num_vouts_received, tx_money_got_in_outs, outs); } } } @@ -858,7 +944,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote 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())); - auto kit = m_pub_keys.find(in_ephemeral[o].pub); + auto kit = m_pub_keys.find(tx_scan_info[o].in_ephemeral.pub); THROW_WALLET_EXCEPTION_IF(kit != m_pub_keys.end() && kit->second >= m_transfers.size(), error::wallet_internal_error, std::string("Unexpected transfer index from public key: ") + "got " + (kit == m_pub_keys.end() ? "<none>" : boost::lexical_cast<std::string>(kit->second)) @@ -874,14 +960,16 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote 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_key_image = tx_scan_info[o].ki; td.m_key_image_known = !m_watch_only; td.m_amount = tx.vout[o].amount; td.m_pk_index = pk_index - 1; + td.m_subaddr_index = tx_scan_info[o].received->index; + expand_subaddresses(tx_scan_info[o].received->index); if (td.m_amount == 0) { - td.m_mask = mask[o]; - td.m_amount = amount[o]; + td.m_mask = tx_scan_info[o].mask; + td.m_amount = tx_scan_info[o].amount; td.m_rct = true; } else if (miner_tx && tx.version == 2) @@ -896,10 +984,10 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote } set_unspent(m_transfers.size()-1); m_key_images[td.m_key_image] = m_transfers.size()-1; - m_pub_keys[in_ephemeral[o].pub] = m_transfers.size()-1; + m_pub_keys[tx_scan_info[o].in_ephemeral.pub] = m_transfers.size()-1; LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << txid); if (0 != m_callback) - m_callback->on_money_received(height, txid, tx, td.m_amount); + m_callback->on_money_received(height, txid, tx, td.m_amount, td.m_subaddr_index); } } else if (m_transfers[kit->second].m_spent || m_transfers[kit->second].amount() >= tx.vout[o].amount) @@ -915,7 +1003,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote << " from received " << print_money(tx.vout[o].amount) << " output already exists with " << print_money(m_transfers[kit->second].amount()) << ", replacing with new output"); // The new larger output replaced a previous smaller one - tx_money_got_in_outs -= tx.vout[o].amount; + tx_money_got_in_outs[tx_scan_info[o].received->index] -= tx.vout[o].amount; if (!pool) { @@ -927,10 +1015,12 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote td.m_txid = txid; td.m_amount = tx.vout[o].amount; td.m_pk_index = pk_index - 1; + td.m_subaddr_index = tx_scan_info[o].received->index; + expand_subaddresses(tx_scan_info[o].received->index); if (td.m_amount == 0) { - td.m_mask = mask[o]; - td.m_amount = amount[o]; + td.m_mask = tx_scan_info[o].mask; + td.m_amount = tx_scan_info[o].amount; td.m_rct = true; } else if (miner_tx && tx.version == 2) @@ -943,12 +1033,12 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote td.m_mask = rct::identity(); td.m_rct = false; } - THROW_WALLET_EXCEPTION_IF(td.get_public_key() != in_ephemeral[o].pub, error::wallet_internal_error, "Inconsistent public keys"); + THROW_WALLET_EXCEPTION_IF(td.get_public_key() != tx_scan_info[o].in_ephemeral.pub, error::wallet_internal_error, "Inconsistent public keys"); 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: " << txid); if (0 != m_callback) - m_callback->on_money_received(height, txid, tx, td.m_amount); + m_callback->on_money_received(height, txid, tx, td.m_amount, td.m_subaddr_index); } } } @@ -956,6 +1046,8 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote } uint64_t tx_money_spent_in_ins = 0; + boost::optional<uint32_t> subaddr_account; + std::set<uint32_t> subaddr_indices; // check all outputs for spending (compare key images) for(auto& in: tx.vin) { @@ -974,23 +1066,50 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote } amount = td.amount(); tx_money_spent_in_ins += amount; + if (subaddr_account && *subaddr_account != td.m_subaddr_index.major) + LOG_ERROR("spent funds are from different subaddress accounts; count of incoming/outgoing payments will be incorrect"); + subaddr_account = td.m_subaddr_index.major; + subaddr_indices.insert(td.m_subaddr_index.minor); if (!pool) { LOG_PRINT_L0("Spent money: " << print_money(amount) << ", with tx: " << txid); set_spent(it->second, height); if (0 != m_callback) - m_callback->on_money_spent(height, txid, tx, amount, tx); + m_callback->on_money_spent(height, txid, tx, amount, tx, td.m_subaddr_index); } } } - if (tx_money_spent_in_ins > 0) + if (tx_money_spent_in_ins > 0 && !pool) + { + uint64_t self_received = std::accumulate<decltype(tx_money_got_in_outs.begin()), uint64_t>(tx_money_got_in_outs.begin(), tx_money_got_in_outs.end(), 0, + [&subaddr_account] (uint64_t acc, const std::pair<cryptonote::subaddress_index, uint64_t>& p) + { + return acc + (p.first.major == *subaddr_account ? p.second : 0); + }); + process_outgoing(txid, tx, height, ts, tx_money_spent_in_ins, self_received, *subaddr_account, subaddr_indices); + // if sending to yourself at the same subaddress account, set the outgoing payment amount to 0 so that it's less confusing + uint64_t fee = tx.version == 1 ? tx_money_spent_in_ins - get_outs_money_amount(tx) : tx.rct_signatures.txnFee; + if (tx_money_spent_in_ins == self_received + fee) + { + auto i = m_confirmed_txs.find(txid); + THROW_WALLET_EXCEPTION_IF(i == m_confirmed_txs.end(), error::wallet_internal_error, + "confirmed tx wasn't found: " + string_tools::pod_to_hex(txid)); + i->second.m_change = self_received; + } + } + + // remove change sent to the spending subaddress account from the list of received funds + for (auto i = tx_money_got_in_outs.begin(); i != tx_money_got_in_outs.end();) { - process_outgoing(txid, tx, height, ts, tx_money_spent_in_ins, tx_money_got_in_outs); + if (subaddr_account && i->first.major == *subaddr_account) + i = tx_money_got_in_outs.erase(i); + else + ++i; } - uint64_t received = (tx_money_spent_in_ins < tx_money_got_in_outs) ? tx_money_got_in_outs - tx_money_spent_in_ins : 0; - if (0 < received) + // create payment_details for each incoming transfer to a subaddress index + if (tx_money_got_in_outs.size() > 0) { tx_extra_nonce extra_nonce; crypto::hash payment_id = null_hash; @@ -1031,20 +1150,24 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote LOG_PRINT_L2("Found unencrypted payment ID: " << payment_id); } - payment_details payment; - payment.m_tx_hash = txid; - payment.m_amount = received; - payment.m_block_height = height; - payment.m_unlock_time = tx.unlock_time; - payment.m_timestamp = ts; - if (pool) { - m_unconfirmed_payments.emplace(payment_id, payment); - if (0 != m_callback) - m_callback->on_unconfirmed_money_received(height, txid, tx, payment.m_amount); + for (const auto& i : tx_money_got_in_outs) + { + payment_details payment; + payment.m_tx_hash = txid; + payment.m_amount = i.second; + payment.m_block_height = height; + payment.m_unlock_time = tx.unlock_time; + payment.m_timestamp = ts; + payment.m_subaddr_index = i.first; + if (pool) { + m_unconfirmed_payments.emplace(payment_id, payment); + if (0 != m_callback) + m_callback->on_unconfirmed_money_received(height, txid, tx, payment.m_amount, payment.m_subaddr_index); + } + else + m_payments.emplace(payment_id, payment); + LOG_PRINT_L2("Payment found in " << (pool ? "pool" : "block") << ": " << payment_id << " / " << payment.m_tx_hash << " / " << payment.m_amount); } - else - m_payments.emplace(payment_id, payment); - LOG_PRINT_L2("Payment found in " << (pool ? "pool" : "block") << ": " << payment_id << " / " << payment.m_tx_hash << " / " << payment.m_amount); } } //---------------------------------------------------------------------------------------------------- @@ -1068,7 +1191,7 @@ void wallet2::process_unconfirmed(const crypto::hash &txid, const cryptonote::tr } } //---------------------------------------------------------------------------------------------------- -void wallet2::process_outgoing(const crypto::hash &txid, const cryptonote::transaction &tx, uint64_t height, uint64_t ts, uint64_t spent, uint64_t received) +void wallet2::process_outgoing(const crypto::hash &txid, const cryptonote::transaction &tx, uint64_t height, uint64_t ts, uint64_t spent, uint64_t received, uint32_t subaddr_account, const std::set<uint32_t>& subaddr_indices) { 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 @@ -1094,6 +1217,8 @@ void wallet2::process_outgoing(const crypto::hash &txid, const cryptonote::trans get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, entry.first->second.m_payment_id); } } + entry.first->second.m_subaddr_account = subaddr_account; + entry.first->second.m_subaddr_indices = subaddr_indices; } entry.first->second.m_block_height = height; entry.first->second.m_timestamp = ts; @@ -1145,16 +1270,19 @@ void wallet2::get_short_chain_history(std::list<crypto::hash>& ids) const { size_t i = 0; size_t current_multiplier = 1; - size_t sz = m_blockchain.size(); + size_t sz = m_blockchain.size() - m_blockchain.offset(); if(!sz) + { + ids.push_back(m_blockchain.genesis()); return; + } size_t current_back_offset = 1; - bool genesis_included = false; + bool base_included = false; while(current_back_offset < sz) { - ids.push_back(m_blockchain[sz-current_back_offset]); + ids.push_back(m_blockchain[m_blockchain.offset() + sz-current_back_offset]); if(sz-current_back_offset == 0) - genesis_included = true; + base_included = true; if(i < 10) { ++current_back_offset; @@ -1164,8 +1292,10 @@ void wallet2::get_short_chain_history(std::list<crypto::hash>& ids) const } ++i; } - if(!genesis_included) - ids.push_back(m_blockchain[0]); + if(!base_included) + ids.push_back(m_blockchain[m_blockchain.offset()]); + if(m_blockchain.offset()) + ids.push_back(m_blockchain.genesis()); } //---------------------------------------------------------------------------------------------------- void wallet2::parse_block_round(const cryptonote::blobdata &blob, cryptonote::block &bl, crypto::hash &bl_id, bool &error) const @@ -1250,8 +1380,10 @@ void wallet2::process_blocks(uint64_t start_height, const std::list<cryptonote:: size_t tx_o_indices_idx = 0; THROW_WALLET_EXCEPTION_IF(blocks.size() != o_indices.size(), error::wallet_internal_error, "size mismatch"); + THROW_WALLET_EXCEPTION_IF(!m_blockchain.is_in_bounds(current_index), error::wallet_internal_error, "Index out of bounds of hashchain"); - int threads = tools::get_max_concurrency(); + tools::threadpool& tpool = tools::threadpool::getInstance(); + int threads = tpool.get_max_concurrency(); if (threads > 1) { std::vector<crypto::hash> round_block_hashes(threads); @@ -1262,23 +1394,16 @@ void wallet2::process_blocks(uint64_t start_height, const std::list<cryptonote:: for (size_t b = 0; b < blocks_size; b += threads) { size_t round_size = std::min((size_t)threads, blocks_size - b); - - boost::asio::io_service ioservice; - boost::thread_group threadpool; - std::unique_ptr < boost::asio::io_service::work > work(new boost::asio::io_service::work(ioservice)); - for (size_t i = 0; i < round_size; i++) - { - threadpool.create_thread(boost::bind(&boost::asio::io_service::run, &ioservice)); - } + tools::threadpool::waiter waiter; std::list<block_complete_entry>::const_iterator tmpblocki = blocki; for (size_t i = 0; i < round_size; ++i) { - ioservice.dispatch(boost::bind(&wallet2::parse_block_round, this, std::cref(tmpblocki->block), + tpool.submit(&waiter, boost::bind(&wallet2::parse_block_round, this, std::cref(tmpblocki->block), std::ref(round_blocks[i]), std::ref(round_block_hashes[i]), std::ref(error[i]))); ++tmpblocki; } - KILL_IOSERVICE(); + waiter.wait(); tmpblocki = blocki; for (size_t i = 0; i < round_size; ++i) { @@ -1516,6 +1641,18 @@ void wallet2::update_pool_state(bool refreshed) if (i.first == txid) { found = true; + // if this is a payment to yourself at a different subaddress account, don't skip it + // so that you can see the incoming pool tx with 'show_transfers' on that receiving subaddress account + const unconfirmed_transfer_details& utd = i.second; + for (const auto& dst : utd.m_dests) + { + auto subaddr_index = m_subaddresses.find(dst.addr.m_spend_public_key); + if (subaddr_index != m_subaddresses.end() && subaddr_index->second.major != utd.m_subaddr_account) + { + found = false; + break; + } + } break; } } @@ -1552,23 +1689,22 @@ void wallet2::update_pool_state(bool refreshed) { if (res.txs.size() == txids.size()) { - size_t n = 0; - for (const auto &txid: txids) + for (const auto &tx_entry: res.txs) { - // might have just been put in a block - if (res.txs[n].in_pool) + if (tx_entry.in_pool) { cryptonote::transaction tx; cryptonote::blobdata bd; crypto::hash tx_hash, tx_prefix_hash; - if (epee::string_tools::parse_hexstr_to_binbuff(res.txs[n].as_hex, bd)) + if (epee::string_tools::parse_hexstr_to_binbuff(tx_entry.as_hex, bd)) { if (cryptonote::parse_and_validate_tx_from_blob(bd, tx, tx_hash, tx_prefix_hash)) { - if (tx_hash == txid) + const std::vector<crypto::hash>::const_iterator i = std::find(txids.begin(), txids.end(), tx_hash); + if (i != txids.end()) { - process_new_transaction(txid, tx, std::vector<uint64_t>(), 0, time(NULL), false, true); - m_scanned_pool_txs[0].insert(txid); + process_new_transaction(tx_hash, tx, std::vector<uint64_t>(), 0, time(NULL), false, true); + m_scanned_pool_txs[0].insert(tx_hash); if (m_scanned_pool_txs[0].size() > 5000) { std::swap(m_scanned_pool_txs[0], m_scanned_pool_txs[1]); @@ -1577,7 +1713,7 @@ void wallet2::update_pool_state(bool refreshed) } else { - LOG_PRINT_L0("Mismatched txids when processing unconfimed txes from pool"); + MERROR("Got txid " << tx_hash << " which we did not ask for"); } } else @@ -1587,14 +1723,13 @@ void wallet2::update_pool_state(bool refreshed) } else { - LOG_PRINT_L0("Failed to parse tx " << txid); + LOG_PRINT_L0("Failed to parse transaction from daemon"); } } else { - LOG_PRINT_L1("Tx " << txid << " was in pool, but is no more"); + LOG_PRINT_L1("Transaction from daemon was in pool, but is no more"); } - ++n; } } else @@ -1666,12 +1801,13 @@ void wallet2::fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, } -bool wallet2::add_address_book_row(const cryptonote::account_public_address &address, const crypto::hash &payment_id, const std::string &description) +bool wallet2::add_address_book_row(const cryptonote::account_public_address &address, const crypto::hash &payment_id, const std::string &description, bool is_subaddress) { wallet2::address_book_row a; a.m_address = address; a.m_payment_id = payment_id; a.m_description = description; + a.m_is_subaddress = is_subaddress; auto old_size = m_address_book.size(); m_address_book.push_back(a); @@ -1698,7 +1834,8 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& re size_t try_count = 0; 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; + tools::threadpool& tpool = tools::threadpool::getInstance(); + tools::threadpool::waiter waiter; uint64_t blocks_start_height; std::list<cryptonote::block_complete_entry> blocks; std::vector<COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> o_indices; @@ -1736,11 +1873,11 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& re 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, next_o_indices, error);}); + tpool.submit(&waiter, [&]{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, o_indices, added_blocks); blocks_fetched += added_blocks; - pull_thread.join(); + waiter.wait(); if(blocks_start_height == next_blocks_start_height) { m_node_rpc_proxy.set_height(m_blockchain.size()); @@ -1762,8 +1899,7 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& re catch (const std::exception&) { blocks_fetched += added_blocks; - if (pull_thread.joinable()) - pull_thread.join(); + waiter.wait(); if(try_count < 3) { LOG_PRINT_L1("Another try pull_blocks (try_count=" << try_count << ")..."); @@ -1790,7 +1926,7 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& re LOG_PRINT_L1("Failed to check pending transactions"); } - LOG_PRINT_L1("Refresh done, blocks received: " << blocks_fetched << ", balance: " << print_money(balance()) << ", unlocked: " << print_money(unlocked_balance())); + LOG_PRINT_L1("Refresh done, blocks received: " << blocks_fetched << ", balance (all accounts): " << print_money(balance_all()) << ", unlocked: " << print_money(unlocked_balance_all())); } //---------------------------------------------------------------------------------------------------- bool wallet2::refresh(uint64_t & blocks_fetched, bool& received_money, bool& ok) @@ -1810,6 +1946,13 @@ bool wallet2::refresh(uint64_t & blocks_fetched, bool& received_money, bool& ok) void wallet2::detach_blockchain(uint64_t height) { LOG_PRINT_L0("Detaching blockchain on height " << height); + + // size 1 2 3 4 5 6 7 8 9 + // block 0 1 2 3 4 5 6 7 8 + // C + THROW_WALLET_EXCEPTION_IF(height <= m_checkpoints.get_max_height() && m_blockchain.size() > m_checkpoints.get_max_height(), + error::wallet_internal_error, "Daemon claims reorg below last checkpoint"); + size_t transfers_detached = 0; for (size_t i = 0; i < m_transfers.size(); ++i) @@ -1840,8 +1983,8 @@ void wallet2::detach_blockchain(uint64_t height) } m_transfers.erase(it, m_transfers.end()); - size_t blocks_detached = m_blockchain.end() - (m_blockchain.begin()+height); - m_blockchain.erase(m_blockchain.begin()+height, m_blockchain.end()); + size_t blocks_detached = m_blockchain.size() - height; + m_blockchain.crop(height); m_local_bc_height -= blocks_detached; for (auto it = m_payments.begin(); it != m_payments.end(); ) @@ -1878,12 +2021,16 @@ bool wallet2::clear() m_unconfirmed_txs.clear(); m_payments.clear(); m_tx_keys.clear(); + m_additional_tx_keys.clear(); m_confirmed_txs.clear(); m_unconfirmed_payments.clear(); m_scanned_pool_txs[0].clear(); m_scanned_pool_txs[1].clear(); m_address_book.clear(); m_local_bc_height = 1; + m_subaddresses.clear(); + m_subaddresses_inv.clear(); + m_subaddress_labels.clear(); return true; } @@ -1966,6 +2113,9 @@ bool wallet2::store_keys(const std::string& keys_file_name, const std::string& p value2.SetInt(m_confirm_backlog ? 1 :0); json.AddMember("confirm_backlog", value2, json.GetAllocator()); + value2.SetUint(m_confirm_backlog_threshold); + json.AddMember("confirm_backlog_threshold", value2, json.GetAllocator()); + value2.SetInt(m_testnet ? 1 :0); json.AddMember("testnet", value2, json.GetAllocator()); @@ -2041,6 +2191,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const std::string& pa m_min_output_value = 0; m_merge_destinations = false; m_confirm_backlog = true; + m_confirm_backlog_threshold = 0; } else { @@ -2113,6 +2264,8 @@ bool wallet2::load_keys(const std::string& keys_file_name, const std::string& pa m_merge_destinations = field_merge_destinations; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, confirm_backlog, int, Int, false, true); m_confirm_backlog = field_confirm_backlog; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, confirm_backlog_threshold, uint32_t, Uint, false, 0); + m_confirm_backlog_threshold = field_confirm_backlog_threshold; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, testnet, int, Int, false, m_testnet); // Wallet is being opened with testnet flag, but is saved as a mainnet wallet THROW_WALLET_EXCEPTION_IF(m_testnet && !field_testnet, error::wallet_internal_error, "Mainnet wallet can not be opened as testnet wallet"); @@ -2255,7 +2408,7 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const std::stri // Set blockchain height calculated from current date/time uint64_t approx_blockchain_height = get_approximate_blockchain_height(); if(approx_blockchain_height > 0) { - m_refresh_from_block_height = approx_blockchain_height - blocks_per_month; + m_refresh_from_block_height = approx_blockchain_height >= blocks_per_month ? approx_blockchain_height - blocks_per_month : 0; } } bool r = store_keys(m_keys_file, password, false); @@ -2267,6 +2420,7 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const std::stri cryptonote::block b; generate_genesis(b); m_blockchain.push_back(get_block_hash(b)); + add_subaddress_account(tr("Primary account")); store(); return retval; @@ -2302,6 +2456,7 @@ void wallet2::generate(const std::string& wallet_, const std::string& password, cryptonote::block b; generate_genesis(b); m_blockchain.push_back(get_block_hash(b)); + add_subaddress_account(tr("Primary account")); store(); } @@ -2337,6 +2492,7 @@ void wallet2::generate(const std::string& wallet_, const std::string& password, cryptonote::block b; generate_genesis(b); m_blockchain.push_back(get_block_hash(b)); + add_subaddress_account(tr("Primary account")); store(); } @@ -2585,13 +2741,52 @@ void wallet2::load(const std::string& wallet_, const std::string& password) check_genesis(genesis_hash); } + trim_hashchain(); + + if (get_num_subaddress_accounts() == 0) + add_subaddress_account(tr("Primary account")); + m_local_bc_height = m_blockchain.size(); } //---------------------------------------------------------------------------------------------------- +void wallet2::trim_hashchain() +{ + uint64_t height = m_checkpoints.get_max_height(); + if (!m_blockchain.empty() && m_blockchain.size() == m_blockchain.offset()) + { + MINFO("Fixing empty hashchain"); + epee::json_rpc::request<cryptonote::COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::request> req = AUTO_VAL_INIT(req); + epee::json_rpc::response<cryptonote::COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::response, std::string> res = AUTO_VAL_INIT(res); + m_daemon_rpc_mutex.lock(); + req.jsonrpc = "2.0"; + req.id = epee::serialization::storage_entry(0); + req.method = "getblockheaderbyheight"; + req.params.height = m_blockchain.size() - 1; + bool r = net_utils::invoke_http_json("/json_rpc", req, res, m_http_client, rpc_timeout); + m_daemon_rpc_mutex.unlock(); + if (r && res.result.status == CORE_RPC_STATUS_OK) + { + crypto::hash hash; + epee::string_tools::hex_to_pod(res.result.block_header.hash, hash); + m_blockchain.refill(hash); + } + else + { + MERROR("Failed to request block header from daemon, hash chain may be unable to sync till the wallet is loaded with a usable daemon"); + } + } + if (height > 0 && m_blockchain.size() > height) + { + --height; + MDEBUG("trimming to " << height << ", offset " << m_blockchain.offset()); + m_blockchain.trim(height); + } +} +//---------------------------------------------------------------------------------------------------- void wallet2::check_genesis(const crypto::hash& genesis_hash) const { std::string what("Genesis block mismatch. You probably use wallet without testnet flag with blockchain from test network or vice versa"); - THROW_WALLET_EXCEPTION_IF(genesis_hash != m_blockchain[0], error::wallet_internal_error, what); + THROW_WALLET_EXCEPTION_IF(genesis_hash != m_blockchain.genesis(), error::wallet_internal_error, what); } //---------------------------------------------------------------------------------------------------- std::string wallet2::path() const @@ -2606,6 +2801,8 @@ void wallet2::store() //---------------------------------------------------------------------------------------------------- void wallet2::store_to(const std::string &path, const std::string &password) { + trim_hashchain(); + // if file is the same, we do: // 1. save wallet to the *.new file // 2. remove old wallet file @@ -2668,10 +2865,11 @@ void wallet2::store_to(const std::string &path, const std::string &password) // if we here, main wallet file is saved and we only need to save keys and address files if (!same_file) { prepare_file_names(path); - store_keys(m_keys_file, password, false); + bool r = store_keys(m_keys_file, password, false); + THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); // save address to the new file const std::string address_file = m_wallet_file + ".address.txt"; - bool r = file_io_utils::save_string_to_file(address_file, m_account.get_public_address_str(m_testnet)); + r = file_io_utils::save_string_to_file(address_file, m_account.get_public_address_str(m_testnet)); THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_wallet_file); // remove old wallet file r = boost::filesystem::remove(old_file); @@ -2695,52 +2893,109 @@ void wallet2::store_to(const std::string &path, const std::string &password) } } //---------------------------------------------------------------------------------------------------- -uint64_t wallet2::unlocked_balance() const +uint64_t wallet2::balance(uint32_t index_major) const { uint64_t amount = 0; - for(const transfer_details& td: m_transfers) - if(!td.m_spent && is_transfer_unlocked(td)) - amount += td.amount(); - + for (const auto& i : balance_per_subaddress(index_major)) + amount += i.second; return amount; } //---------------------------------------------------------------------------------------------------- -uint64_t wallet2::balance() const +uint64_t wallet2::unlocked_balance(uint32_t index_major) const { uint64_t amount = 0; - for(auto& td: m_transfers) - if(!td.m_spent) - amount += td.amount(); - - - for(auto& utx: m_unconfirmed_txs) - if (utx.second.m_state != wallet2::unconfirmed_transfer_details::failed) - amount+= utx.second.m_change; - + for (const auto& i : unlocked_balance_per_subaddress(index_major)) + amount += i.second; return amount; } //---------------------------------------------------------------------------------------------------- +std::map<uint32_t, uint64_t> wallet2::balance_per_subaddress(uint32_t index_major) const +{ + std::map<uint32_t, uint64_t> amount_per_subaddr; + for (const auto& td: m_transfers) + { + if (td.m_subaddr_index.major == index_major && !td.m_spent) + { + auto found = amount_per_subaddr.find(td.m_subaddr_index.minor); + if (found == amount_per_subaddr.end()) + amount_per_subaddr[td.m_subaddr_index.minor] = td.amount(); + else + found->second += td.amount(); + } + } + for (const auto& utx: m_unconfirmed_txs) + { + if (utx.second.m_subaddr_account == index_major && utx.second.m_state != wallet2::unconfirmed_transfer_details::failed) + { + // all changes go to 0-th subaddress (in the current subaddress account) + auto found = amount_per_subaddr.find(0); + if (found == amount_per_subaddr.end()) + amount_per_subaddr[0] = utx.second.m_change; + else + found->second += utx.second.m_change; + } + } + return amount_per_subaddr; +} +//---------------------------------------------------------------------------------------------------- +std::map<uint32_t, uint64_t> wallet2::unlocked_balance_per_subaddress(uint32_t index_major) const +{ + std::map<uint32_t, uint64_t> amount_per_subaddr; + for(const transfer_details& td: m_transfers) + { + if(td.m_subaddr_index.major == index_major && !td.m_spent && is_transfer_unlocked(td)) + { + auto found = amount_per_subaddr.find(td.m_subaddr_index.minor); + if (found == amount_per_subaddr.end()) + amount_per_subaddr[td.m_subaddr_index.minor] = td.amount(); + else + found->second += td.amount(); + } + } + return amount_per_subaddr; +} +//---------------------------------------------------------------------------------------------------- +uint64_t wallet2::balance_all() const +{ + uint64_t r = 0; + for (uint32_t index_major = 0; index_major < get_num_subaddress_accounts(); ++index_major) + r += balance(index_major); + return r; +} +//---------------------------------------------------------------------------------------------------- +uint64_t wallet2::unlocked_balance_all() const +{ + uint64_t r = 0; + for (uint32_t index_major = 0; index_major < get_num_subaddress_accounts(); ++index_major) + r += unlocked_balance(index_major); + return r; +} +//---------------------------------------------------------------------------------------------------- void wallet2::get_transfers(wallet2::transfer_container& incoming_transfers) const { incoming_transfers = m_transfers; } //---------------------------------------------------------------------------------------------------- -void wallet2::get_payments(const crypto::hash& payment_id, std::list<wallet2::payment_details>& payments, uint64_t min_height) const +void wallet2::get_payments(const crypto::hash& payment_id, std::list<wallet2::payment_details>& payments, uint64_t min_height, const boost::optional<uint32_t>& subaddr_account, const std::set<uint32_t>& subaddr_indices) const { auto range = m_payments.equal_range(payment_id); - std::for_each(range.first, range.second, [&payments, &min_height](const payment_container::value_type& x) { - if (min_height < x.second.m_block_height) + std::for_each(range.first, range.second, [&payments, &min_height, &subaddr_account, &subaddr_indices](const payment_container::value_type& x) { + if (min_height < x.second.m_block_height && + (!subaddr_account || *subaddr_account == x.second.m_subaddr_index.major) && + (subaddr_indices.empty() || subaddr_indices.count(x.second.m_subaddr_index.minor) == 1)) { payments.push_back(x.second); } }); } //---------------------------------------------------------------------------------------------------- -void wallet2::get_payments(std::list<std::pair<crypto::hash,wallet2::payment_details>>& payments, uint64_t min_height, uint64_t max_height) const +void wallet2::get_payments(std::list<std::pair<crypto::hash,wallet2::payment_details>>& payments, uint64_t min_height, uint64_t max_height, const boost::optional<uint32_t>& subaddr_account, const std::set<uint32_t>& subaddr_indices) const { auto range = std::make_pair(m_payments.begin(), m_payments.end()); - std::for_each(range.first, range.second, [&payments, &min_height, &max_height](const payment_container::value_type& x) { - if (min_height < x.second.m_block_height && max_height >= x.second.m_block_height) + std::for_each(range.first, range.second, [&payments, &min_height, &max_height, &subaddr_account, &subaddr_indices](const payment_container::value_type& x) { + if (min_height < x.second.m_block_height && max_height >= x.second.m_block_height && + (!subaddr_account || *subaddr_account == x.second.m_subaddr_index.major) && + (subaddr_indices.empty() || subaddr_indices.count(x.second.m_subaddr_index.minor) == 1)) { payments.push_back(x); } @@ -2748,25 +3003,35 @@ void wallet2::get_payments(std::list<std::pair<crypto::hash,wallet2::payment_det } //---------------------------------------------------------------------------------------------------- void wallet2::get_payments_out(std::list<std::pair<crypto::hash,wallet2::confirmed_transfer_details>>& confirmed_payments, - uint64_t min_height, uint64_t max_height) const + uint64_t min_height, uint64_t max_height, const boost::optional<uint32_t>& subaddr_account, const std::set<uint32_t>& subaddr_indices) const { for (auto i = m_confirmed_txs.begin(); i != m_confirmed_txs.end(); ++i) { - if (i->second.m_block_height > min_height && i->second.m_block_height <= max_height) { - confirmed_payments.push_back(*i); - } + if (i->second.m_block_height <= min_height || i->second.m_block_height > max_height) + continue; + if (subaddr_account && *subaddr_account != i->second.m_subaddr_account) + continue; + if (!subaddr_indices.empty() && std::count_if(i->second.m_subaddr_indices.begin(), i->second.m_subaddr_indices.end(), [&subaddr_indices](uint32_t index) { return subaddr_indices.count(index) == 1; }) == 0) + continue; + confirmed_payments.push_back(*i); } } //---------------------------------------------------------------------------------------------------- -void wallet2::get_unconfirmed_payments_out(std::list<std::pair<crypto::hash,wallet2::unconfirmed_transfer_details>>& unconfirmed_payments) const +void wallet2::get_unconfirmed_payments_out(std::list<std::pair<crypto::hash,wallet2::unconfirmed_transfer_details>>& unconfirmed_payments, const boost::optional<uint32_t>& subaddr_account, const std::set<uint32_t>& subaddr_indices) const { for (auto i = m_unconfirmed_txs.begin(); i != m_unconfirmed_txs.end(); ++i) { + if (subaddr_account && *subaddr_account != i->second.m_subaddr_account) + continue; + if (!subaddr_indices.empty() && std::count_if(i->second.m_subaddr_indices.begin(), i->second.m_subaddr_indices.end(), [&subaddr_indices](uint32_t index) { return subaddr_indices.count(index) == 1; }) == 0) + continue; unconfirmed_payments.push_back(*i); } } //---------------------------------------------------------------------------------------------------- -void wallet2::get_unconfirmed_payments(std::list<std::pair<crypto::hash,wallet2::payment_details>>& unconfirmed_payments) const +void wallet2::get_unconfirmed_payments(std::list<std::pair<crypto::hash,wallet2::payment_details>>& unconfirmed_payments, const boost::optional<uint32_t>& subaddr_account, const std::set<uint32_t>& subaddr_indices) const { for (auto i = m_unconfirmed_payments.begin(); i != m_unconfirmed_payments.end(); ++i) { + if ((!subaddr_account || *subaddr_account == i->second.m_subaddr_index.major) && + (subaddr_indices.empty() || subaddr_indices.count(i->second.m_subaddr_index.minor) == 1)) unconfirmed_payments.push_back(*i); } } @@ -2831,6 +3096,7 @@ void wallet2::rescan_blockchain(bool refresh) generate_genesis(genesis); crypto::hash genesis_hash = get_block_hash(genesis); m_blockchain.push_back(genesis_hash); + add_subaddress_account(tr("Primary account")); m_local_bc_height = 1; if (refresh) @@ -3028,7 +3294,7 @@ 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, uint64_t amount_in, 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, uint32_t subaddr_account, const std::set<uint32_t>& subaddr_indices) { unconfirmed_transfer_details& utd = m_unconfirmed_txs[cryptonote::get_transaction_hash(tx)]; utd.m_amount_in = amount_in; @@ -3043,6 +3309,8 @@ void wallet2::add_unconfirmed_tx(const cryptonote::transaction& tx, uint64_t amo utd.m_payment_id = payment_id; utd.m_state = wallet2::unconfirmed_transfer_details::pending; utd.m_timestamp = time(NULL); + utd.m_subaddr_account = subaddr_account; + utd.m_subaddr_indices = subaddr_indices; } //---------------------------------------------------------------------------------------------------- @@ -3113,7 +3381,7 @@ crypto::hash wallet2::get_payment_id(const pending_tx &ptx) const { std::vector<tx_extra_field> tx_extra_fields; if(!parse_tx_extra(ptx.tx.extra, tx_extra_fields)) - return cryptonote::null_hash; + return crypto::null_hash; tx_extra_nonce extra_nonce; crypto::hash payment_id = null_hash; if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) @@ -3128,7 +3396,7 @@ crypto::hash wallet2::get_payment_id(const pending_tx &ptx) const } else if (!get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id)) { - payment_id = cryptonote::null_hash; + payment_id = crypto::null_hash; } } return payment_id; @@ -3177,7 +3445,7 @@ void wallet2::commit_tx(pending_tx& ptx) } txid = get_transaction_hash(ptx.tx); - crypto::hash payment_id = cryptonote::null_hash; + crypto::hash payment_id = crypto::null_hash; std::vector<cryptonote::tx_destination_entry> dests; uint64_t amount_in = 0; if (store_tx_info()) @@ -3187,10 +3455,11 @@ void wallet2::commit_tx(pending_tx& ptx) for(size_t idx: ptx.selected_transfers) amount_in += m_transfers[idx].amount(); } - add_unconfirmed_tx(ptx.tx, amount_in, dests, payment_id, ptx.change_dts.amount); + add_unconfirmed_tx(ptx.tx, amount_in, dests, payment_id, ptx.change_dts.amount, ptx.construction_data.subaddr_account, ptx.construction_data.subaddr_indices); if (store_tx_info()) { m_tx_keys.insert(std::make_pair(txid, ptx.tx_key)); + m_additional_tx_keys.insert(std::make_pair(txid, ptx.additional_tx_keys)); } LOG_PRINT_L2("transaction " << txid << " generated ok and sent to daemon, key_images: [" << ptx.key_images << "]"); @@ -3203,8 +3472,8 @@ void wallet2::commit_tx(pending_tx& ptx) //fee includes dust if dust policy specified it. LOG_PRINT_L1("Transaction successfully sent. <" << txid << ">" << ENDL << "Commission: " << print_money(ptx.fee) << " (dust sent to dust addr: " << print_money((ptx.dust_added_to_fee ? 0 : ptx.dust)) << ")" << ENDL - << "Balance: " << print_money(balance()) << ENDL - << "Unlocked: " << print_money(unlocked_balance()) << ENDL + << "Balance: " << print_money(balance(ptx.construction_data.subaddr_account)) << ENDL + << "Unlocked: " << print_money(unlocked_balance(ptx.construction_data.subaddr_account)) << ENDL << "Please, wait for confirmation for your balance to be unlocked."); } @@ -3322,12 +3591,13 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_f signed_tx_set signed_txes; for (size_t n = 0; n < exported_txs.txes.size(); ++n) { - const tools::wallet2::tx_construction_data &sd = exported_txs.txes[n]; + tools::wallet2::tx_construction_data &sd = exported_txs.txes[n]; LOG_PRINT_L1(" " << (n+1) << ": " << sd.sources.size() << " inputs, ring size " << sd.sources[0].outputs.size()); signed_txes.ptx.push_back(pending_tx()); tools::wallet2::pending_tx &ptx = signed_txes.ptx.back(); crypto::secret_key tx_key; - bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), sd.sources, sd.splitted_dsts, sd.extra, ptx.tx, sd.unlock_time, tx_key, sd.use_rct); + std::vector<crypto::secret_key> additional_tx_keys; + bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sd.sources, sd.splitted_dsts, sd.change_dts.addr, sd.extra, ptx.tx, sd.unlock_time, tx_key, additional_tx_keys, sd.use_rct); THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sd.sources, sd.splitted_dsts, sd.unlock_time, m_testnet); // we don't test tx size, because we don't know the current limit, due to not having a blockchain, // and it's a bit pointless to fail there anyway, since it'd be a (good) guess only. We sign anyway, @@ -3341,6 +3611,7 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_f { const crypto::hash txid = get_transaction_hash(ptx.tx); m_tx_keys.insert(std::make_pair(txid, tx_key)); + m_additional_tx_keys.insert(std::make_pair(txid, additional_tx_keys)); } std::string key_images; @@ -3527,7 +3798,7 @@ int wallet2::get_fee_algorithm() // // this function will make multiple calls to wallet2::transfer if multiple // transactions will be required -std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon) +std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon) { const std::vector<size_t> unused_transfers_indices = select_available_outputs_from_histogram(fake_outs_count + 1, true, true, true, trusted_daemon); @@ -3867,7 +4138,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> } template<typename T> -void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::list<size_t> selected_transfers, size_t fake_outputs_count, +void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::list<size_t>& selected_transfers, size_t fake_outputs_count, std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, 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) { @@ -3898,6 +4169,10 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent 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); + uint32_t subaddr_account = m_transfers[*selected_transfers.begin()].m_subaddr_index.major; + for (auto i = ++selected_transfers.begin(); i != selected_transfers.end(); ++i) + THROW_WALLET_EXCEPTION_IF(subaddr_account != m_transfers[*i].m_subaddr_index.major, error::wallet_internal_error, "the tx uses funds from multiple accounts"); + if (outs.empty()) get_outs(outs, selected_transfers, fake_outputs_count); // may throw @@ -3940,6 +4215,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent 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, td.m_pk_index); + src.real_out_additional_tx_keys = get_additional_tx_pub_keys_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; detail::print_source_entry(src); @@ -3950,7 +4226,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent 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.addr = get_subaddress({subaddr_account, 0}); change_dts.amount = found_money - needed_money; } @@ -3963,13 +4239,14 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent } for(auto& d: dust_dsts) { if (!dust_policy.add_to_fee) - splitted_dsts.push_back(cryptonote::tx_destination_entry(d.amount, dust_policy.addr_for_dust)); + splitted_dsts.push_back(cryptonote::tx_destination_entry(d.amount, dust_policy.addr_for_dust, d.is_subaddress)); dust += d.amount; } crypto::secret_key tx_key; + std::vector<crypto::secret_key> additional_tx_keys; LOG_PRINT_L2("constructing tx"); - bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), sources, splitted_dsts, extra, tx, unlock_time, tx_key); + bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys); LOG_PRINT_L2("constructed tx, r="<<r); THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_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); @@ -3997,6 +4274,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent ptx.change_dts = change_dts; ptx.selected_transfers = selected_transfers; ptx.tx_key = tx_key; + ptx.additional_tx_keys = additional_tx_keys; ptx.dests = dsts; ptx.construction_data.sources = sources; ptx.construction_data.change_dts = change_dts; @@ -4006,10 +4284,15 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent ptx.construction_data.unlock_time = unlock_time; ptx.construction_data.use_rct = false; ptx.construction_data.dests = dsts; + // record which subaddress indices are being used as inputs + ptx.construction_data.subaddr_account = subaddr_account; + ptx.construction_data.subaddr_indices.clear(); + for (size_t idx: selected_transfers) + ptx.construction_data.subaddr_indices.insert(m_transfers[idx].m_subaddr_index.minor); LOG_PRINT_L2("transfer_selected done"); } -void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::list<size_t> selected_transfers, size_t fake_outputs_count, +void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::list<size_t>& selected_transfers, size_t fake_outputs_count, std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx) { @@ -4043,6 +4326,10 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry 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); + uint32_t subaddr_account = m_transfers[*selected_transfers.begin()].m_subaddr_index.major; + for (auto i = ++selected_transfers.begin(); i != selected_transfers.end(); ++i) + THROW_WALLET_EXCEPTION_IF(subaddr_account != m_transfers[*i].m_subaddr_index.major, error::wallet_internal_error, "the tx uses funds from multiple accounts"); + if (outs.empty()) get_outs(outs, selected_transfers, fake_outputs_count); // may throw @@ -4084,6 +4371,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry 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, td.m_pk_index); + src.real_out_additional_tx_keys = get_additional_tx_pub_keys_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; @@ -4110,13 +4398,14 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry } else { - change_dts.addr = m_account.get_keys().m_account_address; + change_dts.addr = get_subaddress({subaddr_account, 0}); } splitted_dsts.push_back(change_dts); crypto::secret_key tx_key; + std::vector<crypto::secret_key> additional_tx_keys; LOG_PRINT_L2("constructing tx"); - bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), sources, splitted_dsts, extra, tx, unlock_time, tx_key, true); + bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys, true); LOG_PRINT_L2("constructed tx, r="<<r); 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); @@ -4140,6 +4429,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry ptx.change_dts = change_dts; ptx.selected_transfers = selected_transfers; ptx.tx_key = tx_key; + ptx.additional_tx_keys = additional_tx_keys; ptx.dests = dsts; ptx.construction_data.sources = sources; ptx.construction_data.change_dts = change_dts; @@ -4149,6 +4439,11 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry ptx.construction_data.unlock_time = unlock_time; ptx.construction_data.use_rct = true; ptx.construction_data.dests = dsts; + // record which subaddress indices are being used as inputs + ptx.construction_data.subaddr_account = subaddr_account; + ptx.construction_data.subaddr_indices.clear(); + for (size_t idx: selected_transfers) + ptx.construction_data.subaddr_indices.insert(m_transfers[idx].m_subaddr_index.minor); LOG_PRINT_L2("transfer_selected_rct done"); } @@ -4205,7 +4500,7 @@ static size_t estimate_tx_size(bool use_rct, int n_inputs, int mixin, int n_outp return n_inputs * (mixin+1) * APPROXIMATE_INPUT_BYTES; } -std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money) const +std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money, uint32_t subaddr_account, const std::set<uint32_t> &subaddr_indices) const { std::vector<size_t> picks; float current_output_relatdness = 1.0f; @@ -4216,7 +4511,7 @@ std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money) co 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)) + if (!td.m_spent && td.is_rct() && td.amount() >= needed_money && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) { LOG_PRINT_L2("We can use " << i << " alone: " << print_money(td.amount())); picks.push_back(i); @@ -4231,13 +4526,13 @@ std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money) co 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)) + if (!td.m_spent && td.is_rct() && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) { 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)) + if (!td2.m_spent && td2.is_rct() && td.amount() + td2.amount() >= needed_money && is_transfer_unlocked(td2) && td2.m_subaddr_index == td.m_subaddr_index) { // update our picks if those outputs are less related than any we // already found. If the same, don't update, and oldest suitable outputs @@ -4334,10 +4629,10 @@ static uint32_t get_count_above(const std::vector<wallet2::transfer_details> &tr // This system allows for sending (almost) the entire balance, since it does // not generate spurious change in all txes, thus decreasing the instantaneous // usable balance. -std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon) +std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, bool trusted_daemon) { - std::vector<size_t> unused_transfers_indices; - std::vector<size_t> unused_dust_indices; + std::vector<std::pair<uint32_t, std::vector<size_t>>> unused_transfers_indices_per_subaddr; + std::vector<std::pair<uint32_t, std::vector<size_t>>> unused_dust_indices_per_subaddr; uint64_t needed_money; uint64_t accumulated_fee, accumulated_outputs, accumulated_change; struct TX { @@ -4347,23 +4642,24 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp pending_tx ptx; size_t bytes; - void add(const account_public_address &addr, uint64_t amount, unsigned int original_output_index, bool merge_destinations) { + void add(const account_public_address &addr, bool is_subaddress, uint64_t amount, unsigned int original_output_index, bool merge_destinations) { if (merge_destinations) { std::vector<cryptonote::tx_destination_entry>::iterator i; i = std::find_if(dsts.begin(), dsts.end(), [&](const cryptonote::tx_destination_entry &d) { return !memcmp (&d.addr, &addr, sizeof(addr)); }); if (i == dsts.end()) { - dsts.push_back(tx_destination_entry(0,addr)); + dsts.push_back(tx_destination_entry(0,addr,is_subaddress)); i = dsts.end() - 1; } i->amount += amount; } else { - THROW_WALLET_EXCEPTION_IF(original_output_index > dsts.size(), error::wallet_internal_error, "original_output_index too large"); + THROW_WALLET_EXCEPTION_IF(original_output_index > dsts.size(), error::wallet_internal_error, + std::string("original_output_index too large: ") + std::to_string(original_output_index) + " > " + std::to_string(dsts.size())); if (original_output_index == dsts.size()) - dsts.push_back(tx_destination_entry(0,addr)); + dsts.push_back(tx_destination_entry(0,addr,is_subaddress)); THROW_WALLET_EXCEPTION_IF(memcmp(&dsts[original_output_index].addr, &addr, sizeof(addr)), error::wallet_internal_error, "Mismatched destination address"); dsts[original_output_index].amount += amount; } @@ -4395,29 +4691,90 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp // throw if attempting a transaction with no money THROW_WALLET_EXCEPTION_IF(needed_money == 0, error::zero_destination); - // gather all our dust and non dust outputs + std::map<uint32_t, uint64_t> balance_per_subaddr = unlocked_balance_per_subaddress(subaddr_account); + + if (subaddr_indices.empty()) // "index=<N1>[,<N2>,...]" wasn't specified -> use all the indices with non-zero unlocked bakance + { + for (const auto& i : balance_per_subaddr) + subaddr_indices.insert(i.first); + } + + // early out if we know we can't make it anyway + // we could also check for being within FEE_PER_KB, but if the fee calculation + // ever changes, this might be missed, so let this go through + uint64_t balance_subtotal = 0; + for (uint32_t index_minor : subaddr_indices) + balance_subtotal += balance_per_subaddr[index_minor]; + THROW_WALLET_EXCEPTION_IF(needed_money > balance_subtotal, error::not_enough_money, + balance_subtotal, needed_money, 0); + + for (uint32_t i : subaddr_indices) + LOG_PRINT_L2("Candidate subaddress index for spending: " << i); + + // gather all dust and non-dust outputs belonging to specified subaddresses + size_t num_nondust_outputs = 0; + size_t num_dust_outputs = 0; for (size_t i = 0; i < m_transfers.size(); ++i) { const transfer_details& td = m_transfers[i]; - if (!td.m_spent && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td)) + if (!td.m_spent && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) { + const uint32_t index_minor = td.m_subaddr_index.minor; + auto find_predicate = [&index_minor](const std::pair<uint32_t, std::vector<size_t>>& x) { return x.first == index_minor; }; if ((td.is_rct()) || is_valid_decomposed_amount(td.amount())) - unused_transfers_indices.push_back(i); + { + auto found = std::find_if(unused_transfers_indices_per_subaddr.begin(), unused_transfers_indices_per_subaddr.end(), find_predicate); + if (found == unused_transfers_indices_per_subaddr.end()) + { + unused_transfers_indices_per_subaddr.push_back({index_minor, {i}}); + } + else + { + found->second.push_back(i); + } + ++num_nondust_outputs; + } else - unused_dust_indices.push_back(i); + { + auto found = std::find_if(unused_dust_indices_per_subaddr.begin(), unused_dust_indices_per_subaddr.end(), find_predicate); + if (found == unused_dust_indices_per_subaddr.end()) + { + unused_dust_indices_per_subaddr.push_back({index_minor, {i}}); + } + else + { + found->second.push_back(i); + } + ++num_dust_outputs; + } } } - LOG_PRINT_L2("Starting with " << unused_transfers_indices.size() << " non-dust outputs and " << unused_dust_indices.size() << " dust outputs"); - // early out if we know we can't make it anyway - // we could also check for being within FEE_PER_KB, but if the fee calculation - // ever changes, this might be missed, so let this go through - THROW_WALLET_EXCEPTION_IF(needed_money > unlocked_balance(), error::not_enough_money, - unlocked_balance(), needed_money, 0); + // shuffle & sort output indices + { + std::random_device rd; + std::mt19937 g(rd()); + std::shuffle(unused_transfers_indices_per_subaddr.begin(), unused_transfers_indices_per_subaddr.end(), g); + std::shuffle(unused_dust_indices_per_subaddr.begin(), unused_dust_indices_per_subaddr.end(), g); + auto sort_predicate = [&balance_per_subaddr] (const std::pair<uint32_t, std::vector<size_t>>& x, const std::pair<uint32_t, std::vector<size_t>>& y) + { + return balance_per_subaddr[x.first] > balance_per_subaddr[y.first]; + }; + std::sort(unused_transfers_indices_per_subaddr.begin(), unused_transfers_indices_per_subaddr.end(), sort_predicate); + std::sort(unused_dust_indices_per_subaddr.begin(), unused_dust_indices_per_subaddr.end(), sort_predicate); + } - if (unused_dust_indices.empty() && unused_transfers_indices.empty()) + LOG_PRINT_L2("Starting with " << num_nondust_outputs << " non-dust outputs and " << num_dust_outputs << " dust outputs"); + + if (unused_dust_indices_per_subaddr.empty() && unused_transfers_indices_per_subaddr.empty()) return std::vector<wallet2::pending_tx>(); + // if empty, put dummy entry so that the front can be referenced later in the loop + if (unused_dust_indices_per_subaddr.empty()) + unused_dust_indices_per_subaddr.push_back({}); + if (unused_transfers_indices_per_subaddr.empty()) + unused_transfers_indices_per_subaddr.push_back({}); + // start with an empty tx txes.push_back(TX()); accumulated_fee = 0; @@ -4441,12 +4798,31 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp // 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(fee_per_kb, estimate_rct_tx_size(2, fake_outs_count + 1, 2), fee_multiplier); - preferred_inputs = pick_preferred_rct_inputs(needed_money + estimated_fee); + preferred_inputs = pick_preferred_rct_inputs(needed_money + estimated_fee, subaddr_account, subaddr_indices); if (!preferred_inputs.empty()) { string s; for (auto i: preferred_inputs) s += boost::lexical_cast<std::string>(i) + "(" + print_money(m_transfers[i].amount()) + ") "; - LOG_PRINT_L1("Found preferred rct inputs for rct tx: " << s); + LOG_PRINT_L1("Found prefered rct inputs for rct tx: " << s); + + // bring the list of available outputs stored by the same subaddress index to the front of the list + uint32_t index_minor = m_transfers[preferred_inputs[0]].m_subaddr_index.minor; + for (size_t i = 1; i < unused_transfers_indices_per_subaddr.size(); ++i) + { + if (unused_transfers_indices_per_subaddr[i].first == index_minor) + { + std::swap(unused_transfers_indices_per_subaddr[0], unused_transfers_indices_per_subaddr[i]); + break; + } + } + for (size_t i = 1; i < unused_dust_indices_per_subaddr.size(); ++i) + { + if (unused_dust_indices_per_subaddr[i].first == index_minor) + { + std::swap(unused_dust_indices_per_subaddr[0], unused_dust_indices_per_subaddr[i]); + break; + } + } } } LOG_PRINT_L2("done checking preferred"); @@ -4456,23 +4832,22 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp // - or we need to gather more fee // - or we have just one input in that tx, which is rct (to try and make all/most rct txes 2/2) unsigned int original_output_index = 0; - while ((!dsts.empty() && dsts[0].amount > 0) || adding_fee || should_pick_a_second_output(use_rct, txes.back().selected_transfers.size(), unused_transfers_indices, unused_dust_indices)) { + std::vector<size_t>* unused_transfers_indices = &unused_transfers_indices_per_subaddr[0].second; + std::vector<size_t>* unused_dust_indices = &unused_dust_indices_per_subaddr[0].second; + while ((!dsts.empty() && dsts[0].amount > 0) || adding_fee || should_pick_a_second_output(use_rct, txes.back().selected_transfers.size(), *unused_transfers_indices, *unused_dust_indices)) { TX &tx = txes.back(); - LOG_PRINT_L2("Start of loop with " << unused_transfers_indices.size() << " " << unused_dust_indices.size()); - LOG_PRINT_L2("unused_transfers_indices:"); - for (auto t: unused_transfers_indices) - LOG_PRINT_L2(" " << t); - LOG_PRINT_L2("unused_dust_indices:"); - for (auto t: unused_dust_indices) - LOG_PRINT_L2(" " << t); + LOG_PRINT_L2("Start of loop with " << unused_transfers_indices->size() << " " << unused_dust_indices->size()); + LOG_PRINT_L2("Start of loop with " << unused_transfers_indices->size() << " " << unused_dust_indices->size()); + LOG_PRINT_L2("unused_transfers_indices: " << strjoin(*unused_transfers_indices, " ")); + LOG_PRINT_L2("unused_dust_indices:" << strjoin(*unused_dust_indices, " ")); LOG_PRINT_L2("dsts size " << dsts.size() << ", first " << (dsts.empty() ? -1 : dsts[0].amount)); LOG_PRINT_L2("adding_fee " << adding_fee << ", use_rct " << use_rct); // if we need to spend money and don't have any left, we fail - if (unused_dust_indices.empty() && unused_transfers_indices.empty()) { + if (unused_dust_indices->empty() && unused_transfers_indices->empty()) { LOG_PRINT_L2("No more outputs to choose from"); - THROW_WALLET_EXCEPTION_IF(1, error::tx_not_possible, unlocked_balance(), needed_money, accumulated_fee + needed_fee); + THROW_WALLET_EXCEPTION_IF(1, error::tx_not_possible, unlocked_balance(subaddr_account), needed_money, accumulated_fee + needed_fee); } // get a random unspent output and use it to pay part (or all) of the current destination (and maybe next one, etc) @@ -4480,12 +4855,12 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp size_t idx; if ((dsts.empty() || dsts[0].amount == 0) && !adding_fee) { // the "make rct txes 2/2" case - we pick a small value output to "clean up" the wallet too - std::vector<size_t> indices = get_only_rct(unused_dust_indices, unused_transfers_indices); + std::vector<size_t> indices = get_only_rct(*unused_dust_indices, *unused_transfers_indices); idx = pop_best_value(indices, tx.selected_transfers, true); // we might not want to add it if it's a large output and we don't have many left if (m_transfers[idx].amount() >= m_min_output_value) { - if (get_count_above(m_transfers, unused_transfers_indices, m_min_output_value) < m_min_output_count) { + if (get_count_above(m_transfers, *unused_transfers_indices, m_min_output_value) < m_min_output_count) { LOG_PRINT_L2("Second output was not strictly needed, and we're running out of outputs above " << print_money(m_min_output_value) << ", not adding"); break; } @@ -4499,14 +4874,14 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp LOG_PRINT_L2("Second output was not strictly needed, and relatedness " << relatedness << ", not adding"); break; } - pop_if_present(unused_transfers_indices, idx); - pop_if_present(unused_dust_indices, idx); + pop_if_present(*unused_transfers_indices, idx); + pop_if_present(*unused_dust_indices, idx); } else if (!preferred_inputs.empty()) { idx = pop_back(preferred_inputs); - pop_if_present(unused_transfers_indices, idx); - pop_if_present(unused_dust_indices, idx); + pop_if_present(*unused_transfers_indices, idx); + pop_if_present(*unused_dust_indices, idx); } else - idx = pop_best_value(unused_transfers_indices.empty() ? unused_dust_indices : unused_transfers_indices, tx.selected_transfers); + idx = pop_best_value(unused_transfers_indices->empty() ? *unused_dust_indices : *unused_transfers_indices, tx.selected_transfers); const transfer_details &td = m_transfers[idx]; LOG_PRINT_L2("Picking output " << idx << ", amount " << print_money(td.amount()) << ", ki " << td.m_key_image); @@ -4529,9 +4904,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp while (!dsts.empty() && dsts[0].amount <= available_amount && estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()) < TX_SIZE_TARGET(upper_transaction_size_limit)) { // we can fully pay that destination - LOG_PRINT_L2("We can fully pay " << get_account_address_as_str(m_testnet, dsts[0].addr) << + LOG_PRINT_L2("We can fully pay " << get_account_address_as_str(m_testnet, dsts[0].is_subaddress, dsts[0].addr) << " for " << print_money(dsts[0].amount)); - tx.add(dsts[0].addr, dsts[0].amount, original_output_index, m_merge_destinations); + tx.add(dsts[0].addr, dsts[0].is_subaddress, dsts[0].amount, original_output_index, m_merge_destinations); available_amount -= dsts[0].amount; dsts[0].amount = 0; pop_index(dsts, 0); @@ -4540,9 +4915,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp if (available_amount > 0 && !dsts.empty() && estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()) < TX_SIZE_TARGET(upper_transaction_size_limit)) { // we can partially fill that destination - LOG_PRINT_L2("We can partially pay " << get_account_address_as_str(m_testnet, dsts[0].addr) << + LOG_PRINT_L2("We can partially pay " << get_account_address_as_str(m_testnet, dsts[0].is_subaddress, dsts[0].addr) << " for " << print_money(available_amount) << "/" << print_money(dsts[0].amount)); - tx.add(dsts[0].addr, available_amount, original_output_index, m_merge_destinations); + tx.add(dsts[0].addr, dsts[0].is_subaddress, available_amount, original_output_index, m_merge_destinations); dsts[0].amount -= available_amount; available_amount = 0; } @@ -4594,7 +4969,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp if (i->amount > needed_fee) { uint64_t new_paid_amount = i->amount /*+ test_ptx.fee*/ - needed_fee; - LOG_PRINT_L2("Adjusting amount paid to " << get_account_address_as_str(m_testnet, i->addr) << " from " << + LOG_PRINT_L2("Adjusting amount paid to " << get_account_address_as_str(m_testnet, i->is_subaddress, i->addr) << " from " << print_money(i->amount) << " to " << print_money(new_paid_amount) << " to accommodate " << print_money(needed_fee) << " fee"); dsts[0].amount += i->amount - new_paid_amount; @@ -4639,15 +5014,32 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp { LOG_PRINT_L2("We have more to pay, starting another tx"); txes.push_back(TX()); + original_output_index = 0; } } } + + // if unused_*_indices is empty while unused_*_indices_per_subaddr has multiple elements, and if we still have something to pay, + // pop front of unused_*_indices_per_subaddr and have unused_*_indices point to the front of unused_*_indices_per_subaddr + if ((!dsts.empty() && dsts[0].amount > 0) || adding_fee) + { + if (unused_transfers_indices->empty() && unused_transfers_indices_per_subaddr.size() > 1) + { + unused_transfers_indices_per_subaddr.erase(unused_transfers_indices_per_subaddr.begin()); + unused_transfers_indices = &unused_transfers_indices_per_subaddr[0].second; + } + if (unused_dust_indices->empty() && unused_dust_indices_per_subaddr.size() > 1) + { + unused_dust_indices_per_subaddr.erase(unused_dust_indices_per_subaddr.begin()); + unused_dust_indices = &unused_dust_indices_per_subaddr[0].second; + } + } } if (adding_fee) { LOG_PRINT_L1("We ran out of outputs while trying to gather final fee"); - THROW_WALLET_EXCEPTION_IF(1, error::tx_not_possible, unlocked_balance(), needed_money, accumulated_fee + needed_fee); + THROW_WALLET_EXCEPTION_IF(1, error::tx_not_possible, unlocked_balance(subaddr_account), needed_money, accumulated_fee + needed_fee); } LOG_PRINT_L1("Done creating " << txes.size() << " transactions, " << print_money(accumulated_fee) << @@ -4671,21 +5063,37 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp return ptx_vector; } -std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon) +std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, bool trusted_daemon) { std::vector<size_t> unused_transfers_indices; std::vector<size_t> unused_dust_indices; const bool use_rct = use_fork_rules(4, 0); - // gather all our dust and non dust outputs + THROW_WALLET_EXCEPTION_IF(unlocked_balance(subaddr_account) == 0, error::wallet_internal_error, "No unclocked balance in the entire wallet"); + + std::map<uint32_t, uint64_t> balance_per_subaddr = unlocked_balance_per_subaddress(subaddr_account); + + if (subaddr_indices.empty()) + { + // in case subaddress index wasn't specified, choose non-empty subaddress randomly (with index=0 being chosen last) + if (balance_per_subaddr.count(0) == 1 && balance_per_subaddr.size() > 1) + balance_per_subaddr.erase(0); + auto i = balance_per_subaddr.begin(); + std::advance(i, crypto::rand<size_t>() % balance_per_subaddr.size()); + subaddr_indices.insert(i->first); + } + for (uint32_t i : subaddr_indices) + LOG_PRINT_L2("Spending from subaddress index " << i); + + // gather all dust and non-dust outputs of specified subaddress for (size_t i = 0; i < m_transfers.size(); ++i) { const transfer_details& td = m_transfers[i]; - if (!td.m_spent && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td)) + if (!td.m_spent && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) { if (below == 0 || td.amount() < below) { - if (td.is_rct() || 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); @@ -4693,10 +5101,12 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below } } - return create_transactions_from(address, unused_transfers_indices, unused_dust_indices, fake_outs_count, unlock_time, priority, extra, trusted_daemon); + THROW_WALLET_EXCEPTION_IF(unused_transfers_indices.empty() && unused_dust_indices.empty(), error::not_enough_money, 0, 0, 0); // not sure if a new error class (something like 'cant_sweep_empty'?) should be introduced + + return create_transactions_from(address, is_subaddress, unused_transfers_indices, unused_dust_indices, fake_outs_count, unlock_time, priority, extra, trusted_daemon); } -std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const cryptonote::account_public_address &address, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon) +std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon) { uint64_t accumulated_fee, accumulated_outputs, accumulated_change; struct TX { @@ -4759,7 +5169,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton needed_fee = 0; - tx.dsts.push_back(tx_destination_entry(1, address)); + tx.dsts.push_back(tx_destination_entry(1, address, is_subaddress)); LOG_PRINT_L2("Trying to create a tx now, with " << tx.dsts.size() << " destinations and " << tx.selected_transfers.size() << " outputs"); @@ -4781,7 +5191,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton LOG_PRINT_L2("We made a tx, adjusting fee and saving it"); tx.dsts[0].amount = available_for_fee - needed_fee; if (use_rct) - transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra, + transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra, test_tx, test_ptx); else transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra, @@ -4828,21 +5238,6 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton // if we made it this far, we're OK to actually send the transactions return ptx_vector; } - -uint64_t wallet2::unlocked_dust_balance(const tx_dust_policy &dust_policy) const -{ - uint64_t money = 0; - std::list<transfer_container::iterator> selected_transfers; - for (transfer_container::const_iterator i = m_transfers.begin(); i != m_transfers.end(); ++i) - { - const transfer_details& td = *i; - if (!td.m_spent && td.amount() < dust_policy.dust_threshold && is_transfer_unlocked(td)) - { - money += td.amount(); - } - } - return money; -} //---------------------------------------------------------------------------------------------------- void wallet2::get_hard_fork_info(uint8_t version, uint64_t &earliest_height) { @@ -5017,15 +5412,19 @@ std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions(bo unmixable_transfer_outputs.push_back(n); } - return create_transactions_from(m_account_public_address, unmixable_transfer_outputs, unmixable_dust_outputs, 0 /*fake_outs_count */, 0 /* unlock_time */, 1 /*priority */, std::vector<uint8_t>(), trusted_daemon); + return create_transactions_from(m_account_public_address, false, unmixable_transfer_outputs, unmixable_dust_outputs, 0 /*fake_outs_count */, 0 /* unlock_time */, 1 /*priority */, std::vector<uint8_t>(), trusted_daemon); } -bool wallet2::get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key) const +bool wallet2::get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys) const { + additional_tx_keys.clear(); const std::unordered_map<crypto::hash, crypto::secret_key>::const_iterator i = m_tx_keys.find(txid); if (i == m_tx_keys.end()) return false; tx_key = i->second; + const auto j = m_additional_tx_keys.find(txid); + if (j != m_additional_tx_keys.end()) + additional_tx_keys = j->second; return true; } @@ -5180,6 +5579,15 @@ crypto::public_key wallet2::get_tx_pub_key_from_received_outs(const tools::walle // more than one, loop and search const cryptonote::account_keys& keys = m_account.get_keys(); size_t pk_index = 0; + + const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(td.m_tx); + std::vector<crypto::key_derivation> additional_derivations; + for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i) + { + additional_derivations.push_back({}); + generate_key_derivation(additional_tx_pub_keys[i], keys.m_view_secret_key, additional_derivations.back()); + } + while (find_tx_extra_field_by_type(tx_extra_fields, pub_key_field, pk_index++)) { const crypto::public_key tx_pub_key = pub_key_field.pub_key; crypto::key_derivation derivation; @@ -5187,10 +5595,9 @@ crypto::public_key wallet2::get_tx_pub_key_from_received_outs(const tools::walle for (size_t i = 0; i < td.m_tx.vout.size(); ++i) { - uint64_t money_transfered = 0; - bool error = false, received = false; - check_acc_out_precomp(keys.m_account_address.m_spend_public_key, td.m_tx.vout[i], derivation, i, received, money_transfered, error); - if (!error && received) + tx_scan_info_t tx_scan_info; + check_acc_out_precomp(td.m_tx.vout[i], derivation, additional_derivations, i, tx_scan_info); + if (!tx_scan_info.error && tx_scan_info.received) return tx_pub_key; } } @@ -5198,10 +5605,10 @@ crypto::public_key wallet2::get_tx_pub_key_from_received_outs(const tools::walle // we found no key yielding an output THROW_WALLET_EXCEPTION_IF(true, error::wallet_internal_error, "Public key yielding at least one output wasn't found in the transaction extra"); - return cryptonote::null_pkey; + return crypto::null_pkey; } -bool wallet2::export_key_images(const std::string filename) +bool wallet2::export_key_images(const std::string &filename) { std::vector<std::pair<crypto::key_image, crypto::signature>> ski = export_key_images(); std::string magic(KEY_IMAGE_EXPORT_FILE_MAGIC, strlen(KEY_IMAGE_EXPORT_FILE_MAGIC)); @@ -5249,11 +5656,13 @@ std::vector<std::pair<crypto::key_image, crypto::signature>> wallet2::export_key } crypto::public_key tx_pub_key = get_tx_pub_key_from_received_outs(td); + const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(td.m_tx); // generate ephemeral secret key crypto::key_image ki; cryptonote::keypair in_ephemeral; - cryptonote::generate_key_image_helper(m_account.get_keys(), tx_pub_key, td.m_internal_output_index, in_ephemeral, ki); + bool r = cryptonote::generate_key_image_helper(m_account.get_keys(), m_subaddresses, pkey, tx_pub_key, additional_tx_pub_keys, td.m_internal_output_index, in_ephemeral, ki); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); THROW_WALLET_EXCEPTION_IF(td.m_key_image_known && ki != td.m_key_image, error::wallet_internal_error, "key_image generated not matched with cached key image"); @@ -5404,6 +5813,9 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag } spent = 0; unspent = 0; + std::unordered_set<crypto::hash> spent_txids; // For each spent key image, search for a tx in m_transfers that uses it as input. + std::vector<size_t> swept_transfers; // If such a spending tx wasn't found in m_transfers, this means the spending tx + // was created by sweep_all, so we can't know the spent height and other detailed info. for(size_t i = 0; i < m_transfers.size(); ++i) { transfer_details &td = m_transfers[i]; @@ -5414,8 +5826,158 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag unspent += amount; LOG_PRINT_L2("Transfer " << i << ": " << print_money(amount) << " (" << td.m_global_output_index << "): " << (td.m_spent ? "spent" : "unspent") << " (key image " << req.key_images[i] << ")"); + + if (i < daemon_resp.spent_status.size() && daemon_resp.spent_status[i] == COMMAND_RPC_IS_KEY_IMAGE_SPENT::SPENT_IN_BLOCKCHAIN) + { + bool is_spent_tx_found = false; + for (auto it = m_transfers.rbegin(); &(*it) != &td; ++it) + { + bool is_spent_tx = false; + for(const cryptonote::txin_v& in : it->m_tx.vin) + { + if(in.type() == typeid(cryptonote::txin_to_key) && td.m_key_image == boost::get<cryptonote::txin_to_key>(in).k_image) + { + is_spent_tx = true; + break; + } + } + if (is_spent_tx) + { + is_spent_tx_found = true; + spent_txids.insert(it->m_txid); + break; + } + } + + if (!is_spent_tx_found) + swept_transfers.push_back(i); + } } MDEBUG("Total: " << print_money(spent) << " spent, " << print_money(unspent) << " unspent"); + + if (check_spent) + { + // query outgoing txes + COMMAND_RPC_GET_TRANSACTIONS::request gettxs_req; + COMMAND_RPC_GET_TRANSACTIONS::response gettxs_res; + gettxs_req.decode_as_json = false; + for (const crypto::hash& spent_txid : spent_txids) + gettxs_req.txs_hashes.push_back(epee::string_tools::pod_to_hex(spent_txid)); + m_daemon_rpc_mutex.lock(); + bool r = epee::net_utils::invoke_http_json("/gettransactions", gettxs_req, gettxs_res, m_http_client, rpc_timeout); + m_daemon_rpc_mutex.unlock(); + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "gettransactions"); + THROW_WALLET_EXCEPTION_IF(gettxs_res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "gettransactions"); + THROW_WALLET_EXCEPTION_IF(gettxs_res.txs.size() != spent_txids.size(), error::wallet_internal_error, + "daemon returned wrong response for gettransactions, wrong count = " + std::to_string(gettxs_res.txs.size()) + ", expected " + std::to_string(spent_txids.size())); + + // process each outgoing tx + auto spent_txid = spent_txids.begin(); + for (const COMMAND_RPC_GET_TRANSACTIONS::entry& e : gettxs_res.txs) + { + THROW_WALLET_EXCEPTION_IF(e.in_pool, error::wallet_internal_error, "spent tx isn't supposed to be in txpool"); + + // parse tx + cryptonote::blobdata bd; + THROW_WALLET_EXCEPTION_IF(!epee::string_tools::parse_hexstr_to_binbuff(e.as_hex, bd), error::wallet_internal_error, "parse_hexstr_to_binbuff failed"); + cryptonote::transaction spent_tx; + crypto::hash spnet_txid_parsed, spent_txid_prefix; + THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(bd, spent_tx, spnet_txid_parsed, spent_txid_prefix), error::wallet_internal_error, "parse_and_validate_tx_from_blob failed"); + THROW_WALLET_EXCEPTION_IF(*spent_txid != spnet_txid_parsed, error::wallet_internal_error, "parsed txid mismatch"); + + // get received (change) amount + uint64_t tx_money_got_in_outs = 0; + const cryptonote::account_keys& keys = m_account.get_keys(); + const crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(spent_tx); + crypto::key_derivation derivation; + generate_key_derivation(tx_pub_key, keys.m_view_secret_key, derivation); + const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(spent_tx); + std::vector<crypto::key_derivation> additional_derivations; + for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i) + { + additional_derivations.push_back({}); + generate_key_derivation(additional_tx_pub_keys[i], keys.m_view_secret_key, additional_derivations.back()); + } + size_t output_index = 0; + for (const cryptonote::tx_out& out : spent_tx.vout) + { + tx_scan_info_t tx_scan_info; + check_acc_out_precomp(out, derivation, additional_derivations, output_index, tx_scan_info); + THROW_WALLET_EXCEPTION_IF(tx_scan_info.error, error::wallet_internal_error, "check_acc_out_precomp failed"); + if (tx_scan_info.received) + { + if (tx_scan_info.money_transfered == 0) + { + rct::key mask; + tx_scan_info.money_transfered = tools::decodeRct(spent_tx.rct_signatures, tx_scan_info.received->derivation, output_index, mask); + } + tx_money_got_in_outs += tx_scan_info.money_transfered; + } + ++output_index; + } + + // get spent amount + uint64_t tx_money_spent_in_ins = 0; + uint32_t subaddr_account = (uint32_t)-1; + std::set<uint32_t> subaddr_indices; + for (const cryptonote::txin_v& in : spent_tx.vin) + { + if (in.type() != typeid(cryptonote::txin_to_key)) + continue; + auto it = m_key_images.find(boost::get<cryptonote::txin_to_key>(in).k_image); + if (it != m_key_images.end()) + { + const transfer_details& td = m_transfers[it->second]; + 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(); + tx_money_spent_in_ins += amount; + + LOG_PRINT_L0("Spent money: " << print_money(amount) << ", with tx: " << *spent_txid); + set_spent(it->second, e.block_height); + if (m_callback) + m_callback->on_money_spent(e.block_height, *spent_txid, spent_tx, amount, spent_tx, td.m_subaddr_index); + if (subaddr_account != (uint32_t)-1 && subaddr_account != td.m_subaddr_index.major) + LOG_PRINT_L0("WARNING: This tx spends outputs received by different subaddress accounts, which isn't supposed to happen"); + subaddr_account = td.m_subaddr_index.major; + subaddr_indices.insert(td.m_subaddr_index.minor); + } + } + + // create outgoing payment + process_outgoing(*spent_txid, spent_tx, e.block_height, e.block_timestamp, tx_money_spent_in_ins, tx_money_got_in_outs, subaddr_account, subaddr_indices); + + // erase corresponding incoming payment + for (auto j = m_payments.begin(); j != m_payments.end(); ++j) + { + if (j->second.m_tx_hash == *spent_txid) + { + m_payments.erase(j); + break; + } + } + + ++spent_txid; + } + + for (size_t n : swept_transfers) + { + const transfer_details& td = m_transfers[n]; + confirmed_transfer_details pd; + pd.m_change = (uint64_t)-1; // cahnge is unknown + pd.m_amount_in = pd.m_amount_out = td.amount(); // fee is unknown + std::string err; + pd.m_block_height = get_daemon_blockchain_height(err); // spent block height is unknown, so hypothetically set to the highest + crypto::hash spent_txid = crypto::rand<crypto::hash>(); // spent txid is unknown, so hypothetically set to random + m_confirmed_txs.insert(std::make_pair(spent_txid, pd)); + } + } + return m_transfers[signed_key_images.size() - 1].m_block_height; } wallet2::payment_container wallet2::export_payments() const @@ -5444,20 +6006,28 @@ void wallet2::import_payments_out(const std::list<std::pair<crypto::hash,wallet2 } } -std::vector<crypto::hash> wallet2::export_blockchain() const +std::tuple<size_t,crypto::hash,std::vector<crypto::hash>> wallet2::export_blockchain() const { - std::vector<crypto::hash> bc; - for (auto const &b : m_blockchain) + std::tuple<size_t, crypto::hash, std::vector<crypto::hash>> bc; + std::get<0>(bc) = m_blockchain.offset(); + std::get<1>(bc) = m_blockchain.empty() ? crypto::null_hash: m_blockchain.genesis(); + for (size_t n = m_blockchain.offset(); n < m_blockchain.size(); ++n) { - bc.push_back(b); + std::get<2>(bc).push_back(m_blockchain[n]); } return bc; } -void wallet2::import_blockchain(const std::vector<crypto::hash> &bc) +void wallet2::import_blockchain(const std::tuple<size_t, crypto::hash, std::vector<crypto::hash>> &bc) { m_blockchain.clear(); - for (auto const &b : bc) + if (std::get<0>(bc)) + { + for (size_t n = std::get<0>(bc); n > 0; ++n) + m_blockchain.push_back(std::get<1>(bc)); + m_blockchain.trim(std::get<0>(bc)); + } + for (auto const &b : std::get<2>(bc)) { m_blockchain.push_back(b); } @@ -5494,14 +6064,17 @@ size_t wallet2::import_outputs(const std::vector<tools::wallet2::transfer_detail // the hot wallet wouldn't have known about key images (except if we already exported them) cryptonote::keypair in_ephemeral; std::vector<tx_extra_field> tx_extra_fields; - tx_extra_pub_key pub_key_field; THROW_WALLET_EXCEPTION_IF(td.m_tx.vout.empty(), error::wallet_internal_error, "tx with no outputs at index " + boost::lexical_cast<std::string>(i)); THROW_WALLET_EXCEPTION_IF(!parse_tx_extra(td.m_tx.extra, tx_extra_fields), error::wallet_internal_error, "Transaction extra has unsupported format at index " + boost::lexical_cast<std::string>(i)); crypto::public_key tx_pub_key = get_tx_pub_key_from_received_outs(td); + const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(td.m_tx); - cryptonote::generate_key_image_helper(m_account.get_keys(), tx_pub_key, td.m_internal_output_index, in_ephemeral, td.m_key_image); + const crypto::public_key& out_key = boost::get<cryptonote::txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key; + bool r = cryptonote::generate_key_image_helper(m_account.get_keys(), m_subaddresses, out_key, tx_pub_key, additional_tx_pub_keys, td.m_internal_output_index, in_ephemeral, td.m_key_image); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); + expand_subaddresses(td.m_subaddr_index); td.m_key_image_known = true; THROW_WALLET_EXCEPTION_IF(in_ephemeral.pub != boost::get<cryptonote::txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key, error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key at index " + boost::lexical_cast<std::string>(i)); @@ -5559,7 +6132,7 @@ std::string wallet2::decrypt(const std::string &ciphertext, const crypto::secret crypto::secret_key_to_public_key(skey, pkey); const crypto::signature &signature = *(const crypto::signature*)&ciphertext[ciphertext.size() - sizeof(crypto::signature)]; THROW_WALLET_EXCEPTION_IF(!crypto::check_signature(hash, pkey, signature), - error::wallet_internal_error, "Failed to authenticate criphertext"); + error::wallet_internal_error, "Failed to authenticate ciphertext"); } crypto::chacha8(ciphertext.data() + sizeof(iv), ciphertext.size() - prefix_size, key, iv, &plaintext[0]); return plaintext; @@ -5572,17 +6145,15 @@ std::string wallet2::decrypt_with_view_secret_key(const std::string &ciphertext, //---------------------------------------------------------------------------------------------------- std::string wallet2::make_uri(const std::string &address, const std::string &payment_id, uint64_t amount, const std::string &tx_description, const std::string &recipient_name, std::string &error) { - cryptonote::account_public_address tmp_address; - bool has_payment_id; - crypto::hash8 new_payment_id; - if(!get_account_integrated_address_from_str(tmp_address, has_payment_id, new_payment_id, testnet(), address)) + cryptonote::address_parse_info info; + if(!get_account_address_from_str(info, testnet(), address)) { error = std::string("wrong address: ") + address; return std::string(); } // we want only one payment id - if (has_payment_id && !payment_id.empty()) + if (info.has_payment_id && !payment_id.empty()) { error = "A single payment id is allowed"; return std::string(); @@ -5638,10 +6209,8 @@ bool wallet2::parse_uri(const std::string &uri, std::string &address, std::strin const char *ptr = strchr(remainder.c_str(), '?'); address = ptr ? remainder.substr(0, ptr-remainder.c_str()) : remainder; - cryptonote::account_public_address addr; - bool has_payment_id; - crypto::hash8 new_payment_id; - if(!get_account_integrated_address_from_str(addr, has_payment_id, new_payment_id, testnet(), address)) + cryptonote::address_parse_info info; + if(!get_account_address_from_str(info, testnet(), address)) { error = std::string("URI has wrong address: ") + address; return false; @@ -5682,7 +6251,7 @@ bool wallet2::parse_uri(const std::string &uri, std::string &address, std::strin } else if (kv[0] == "tx_payment_id") { - if (has_payment_id) + if (info.has_payment_id) { error = "Separate payment id given with an integrated address"; return false; diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index adf03abcc..ff049552e 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -36,6 +36,7 @@ #include <boost/program_options/variables_map.hpp> #include <boost/serialization/list.hpp> #include <boost/serialization/vector.hpp> +#include <boost/serialization/deque.hpp> #include <atomic> #include "include_base_utils.h" @@ -52,6 +53,7 @@ #include "crypto/hash.h" #include "ringct/rctTypes.h" #include "ringct/rctOps.h" +#include "checkpoints/checkpoints.h" #include "wallet_errors.h" #include "common/password.h" @@ -70,9 +72,9 @@ namespace tools { public: virtual void on_new_block(uint64_t height, const cryptonote::block& block) {} - virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount) {} - virtual void on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount) {} - virtual void on_money_spent(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx) {} + virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index) {} + virtual void on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index) {} + virtual void on_money_spent(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx, const cryptonote::subaddress_index& subaddr_index) {} virtual void on_skip_transaction(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx) {} virtual ~i_wallet2_callback() {} }; @@ -91,6 +93,38 @@ namespace tools } }; + class hashchain + { + public: + hashchain(): m_genesis(crypto::null_hash), m_offset(0) {} + + size_t size() const { return m_blockchain.size() + m_offset; } + size_t offset() const { return m_offset; } + const crypto::hash &genesis() const { return m_genesis; } + void push_back(const crypto::hash &hash) { if (m_offset == 0 && m_blockchain.empty()) m_genesis = hash; m_blockchain.push_back(hash); } + bool is_in_bounds(size_t idx) const { return idx >= m_offset && idx < size(); } + const crypto::hash &operator[](size_t idx) const { return m_blockchain[idx - m_offset]; } + crypto::hash &operator[](size_t idx) { return m_blockchain[idx - m_offset]; } + void crop(size_t height) { m_blockchain.resize(height - m_offset); } + void clear() { m_offset = 0; m_blockchain.clear(); } + bool empty() const { return m_blockchain.empty() && m_offset == 0; } + void trim(size_t height) { while (height > m_offset+1 && m_blockchain.size() > 1) { m_blockchain.pop_front(); ++m_offset; } m_blockchain.shrink_to_fit(); } + void refill(const crypto::hash &hash) { m_blockchain.push_back(hash); --m_offset; } + + template <class t_archive> + inline void serialize(t_archive &a, const unsigned int ver) + { + a & m_offset; + a & m_genesis; + a & m_blockchain; + } + + private: + size_t m_offset; + crypto::hash m_genesis; + std::deque<crypto::hash> m_blockchain; + }; + class wallet2 { friend class ::Serialization_portability_wallet_Test; @@ -133,6 +167,19 @@ namespace tools wallet2(bool testnet = false, bool restricted = false) : m_run(true), m_callback(0), m_testnet(testnet), m_always_confirm_transfers(true), m_print_ring_members(false), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshOptimizeCoinbase), m_auto_refresh(true), m_refresh_from_block_height(0), m_confirm_missing_payment_id(true), m_ask_password(true), m_min_output_count(0), m_min_output_value(0), m_merge_destinations(false), m_confirm_backlog(true), m_is_initialized(false), m_restricted(restricted), is_old_file_format(false), m_node_rpc_proxy(m_http_client, m_daemon_rpc_mutex) {} + struct tx_scan_info_t + { + cryptonote::keypair in_ephemeral; + crypto::key_image ki; + rct::key mask; + uint64_t amount; + uint64_t money_transfered; + bool error; + boost::optional<cryptonote::subaddress_receive_info> received; + + tx_scan_info_t(): money_transfered(0), error(true) {} + }; + struct transfer_details { uint64_t m_block_height; @@ -148,6 +195,7 @@ namespace tools bool m_rct; bool m_key_image_known; size_t m_pk_index; + cryptonote::subaddress_index m_subaddr_index; bool is_rct() const { return m_rct; } uint64_t amount() const { return m_amount; } @@ -167,6 +215,7 @@ namespace tools FIELD(m_rct) FIELD(m_key_image_known) FIELD(m_pk_index) + FIELD(m_subaddr_index) END_SERIALIZE() }; @@ -177,6 +226,7 @@ namespace tools uint64_t m_block_height; uint64_t m_unlock_time; uint64_t m_timestamp; + cryptonote::subaddress_index m_subaddr_index; }; struct unconfirmed_transfer_details @@ -190,6 +240,8 @@ namespace tools crypto::hash m_payment_id; enum { pending, pending_not_in_pool, failed } m_state; uint64_t m_timestamp; + uint32_t m_subaddr_account; // subaddress account of your wallet to be used in this transfer + std::set<uint32_t> m_subaddr_indices; // set of address indices used as inputs in this transfer }; struct confirmed_transfer_details @@ -202,10 +254,12 @@ namespace tools crypto::hash m_payment_id; uint64_t m_timestamp; uint64_t m_unlock_time; + uint32_t m_subaddr_account; // subaddress account of your wallet to be used in this transfer + std::set<uint32_t> m_subaddr_indices; // set of address indices used as inputs in this transfer - 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), m_timestamp(0), m_unlock_time(0) {} + confirmed_transfer_details(): m_amount_in(0), m_amount_out(0), m_change((uint64_t)-1), m_block_height(0), m_payment_id(crypto::null_hash), m_timestamp(0), m_unlock_time(0), m_subaddr_account((uint32_t)-1) {} confirmed_transfer_details(const unconfirmed_transfer_details &utd, uint64_t height): - 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), m_unlock_time(utd.m_tx.unlock_time) {} + 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), m_unlock_time(utd.m_tx.unlock_time), m_subaddr_account(utd.m_subaddr_account), m_subaddr_indices(utd.m_subaddr_indices) {} }; struct tx_construction_data @@ -218,6 +272,8 @@ namespace tools uint64_t unlock_time; bool use_rct; std::vector<cryptonote::tx_destination_entry> dests; // original setup, does not include change + uint32_t subaddr_account; // subaddress account of your wallet to be used in this transfer + std::set<uint32_t> subaddr_indices; // set of address indices used as inputs in this transfer }; typedef std::vector<transfer_details> transfer_container; @@ -235,6 +291,7 @@ namespace tools std::list<size_t> selected_transfers; std::string key_images; crypto::secret_key tx_key; + std::vector<crypto::secret_key> additional_tx_keys; std::vector<cryptonote::tx_destination_entry> dests; tx_construction_data construction_data; @@ -282,6 +339,7 @@ namespace tools cryptonote::account_public_address m_address; crypto::hash m_payment_id; std::string m_description; + bool m_is_subaddress; }; typedef std::tuple<uint64_t, crypto::public_key, rct::key> get_outs_entry; @@ -363,7 +421,7 @@ namespace tools * \brief Checks if deterministic wallet */ bool is_deterministic() const; - bool get_seed(std::string& electrum_words) const; + bool get_seed(std::string& electrum_words, const std::string &passphrase = std::string()) const; /*! * \brief Gets the seed language */ @@ -372,6 +430,21 @@ namespace tools * \brief Sets the seed language */ void set_seed_language(const std::string &language); + + // Subaddress scheme + cryptonote::account_public_address get_subaddress(const cryptonote::subaddress_index& index) const; + cryptonote::account_public_address get_address() const { return get_subaddress({0,0}); } + crypto::public_key get_subaddress_spend_public_key(const cryptonote::subaddress_index& index) const; + std::string get_subaddress_as_str(const cryptonote::subaddress_index& index) const; + std::string get_address_as_str() const { return get_subaddress_as_str({0, 0}); } + std::string get_integrated_address_as_str(const crypto::hash8& payment_id) const; + void add_subaddress_account(const std::string& label); + size_t get_num_subaddress_accounts() const { return m_subaddress_labels.size(); } + size_t get_num_subaddresses(uint32_t index_major) const { return index_major < m_subaddress_labels.size() ? m_subaddress_labels[index_major].size() : 0; } + void add_subaddress(uint32_t index_major, const std::string& label); // throws when index is out of bound + void expand_subaddresses(const cryptonote::subaddress_index& index); + std::string get_subaddress_label(const cryptonote::subaddress_index& index) const; // throws when index is out of bound + void set_subaddress_label(const cryptonote::subaddress_index &index, const std::string &label); // throws when index is out of bound /*! * \brief Tells if the wallet file is deprecated. */ @@ -388,9 +461,15 @@ namespace tools bool restricted() const { return m_restricted; } bool watch_only() const { return m_watch_only; } - uint64_t balance() const; - uint64_t unlocked_balance() const; - uint64_t unlocked_dust_balance(const tx_dust_policy &dust_policy) const; + // locked & unlocked balance of given or current subaddress account + uint64_t balance(uint32_t subaddr_index_major) const; + uint64_t unlocked_balance(uint32_t subaddr_index_major) const; + // locked & unlocked balance per subaddress of given or current subaddress account + std::map<uint32_t, uint64_t> balance_per_subaddress(uint32_t subaddr_index_major) const; + std::map<uint32_t, uint64_t> unlocked_balance_per_subaddress(uint32_t subaddr_index_major) const; + // all locked & unlocked balances of all subaddress accounts + uint64_t balance_all() const; + uint64_t unlocked_balance_all() const; template<typename T> void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outputs_count, const std::vector<size_t> &unused_transfers_indices, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, bool trusted_daemon); template<typename T> @@ -398,10 +477,10 @@ namespace tools void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outputs_count, const std::vector<size_t> &unused_transfers_indices, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, bool trusted_daemon); void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outputs_count, const std::vector<size_t> &unused_transfers_indices, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx& ptx, bool trusted_daemon); template<typename T> - void transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::list<size_t> selected_transfers, size_t fake_outputs_count, + void transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::list<size_t>& selected_transfers, size_t fake_outputs_count, std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, 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<size_t> selected_transfers, size_t fake_outputs_count, + void transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::list<size_t>& selected_transfers, size_t fake_outputs_count, std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx); @@ -415,19 +494,19 @@ namespace tools // load unsigned_tx_set from file. bool load_unsigned_tx(const std::string &unsigned_filename, unsigned_tx_set &exported_txs); bool load_tx(const std::string &signed_filename, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set&)> accept_func = NULL); - std::vector<pending_tx> create_transactions(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon); - std::vector<wallet2::pending_tx> create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon); - std::vector<wallet2::pending_tx> create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon); - std::vector<wallet2::pending_tx> create_transactions_from(const cryptonote::account_public_address &address, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon); + std::vector<pending_tx> create_transactions(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon); + std::vector<wallet2::pending_tx> create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, bool trusted_daemon); // pass subaddr_indices by value on purpose + std::vector<wallet2::pending_tx> create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, bool trusted_daemon); + std::vector<wallet2::pending_tx> create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon); std::vector<pending_tx> create_unmixable_sweep_transactions(bool trusted_daemon); bool check_connection(uint32_t *version = NULL, uint32_t timeout = 200000); void get_transfers(wallet2::transfer_container& incoming_transfers) const; - void get_payments(const crypto::hash& payment_id, std::list<wallet2::payment_details>& payments, uint64_t min_height = 0) const; - void get_payments(std::list<std::pair<crypto::hash,wallet2::payment_details>>& payments, uint64_t min_height, uint64_t max_height = (uint64_t)-1) const; + void get_payments(const crypto::hash& payment_id, std::list<wallet2::payment_details>& payments, uint64_t min_height = 0, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const; + void get_payments(std::list<std::pair<crypto::hash,wallet2::payment_details>>& payments, uint64_t min_height, uint64_t max_height = (uint64_t)-1, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const; void get_payments_out(std::list<std::pair<crypto::hash,wallet2::confirmed_transfer_details>>& confirmed_payments, - uint64_t min_height, uint64_t max_height = (uint64_t)-1) const; - void get_unconfirmed_payments_out(std::list<std::pair<crypto::hash,wallet2::unconfirmed_transfer_details>>& unconfirmed_payments) const; - void get_unconfirmed_payments(std::list<std::pair<crypto::hash,wallet2::payment_details>>& unconfirmed_payments) const; + uint64_t min_height, uint64_t max_height = (uint64_t)-1, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const; + void get_unconfirmed_payments_out(std::list<std::pair<crypto::hash,wallet2::unconfirmed_transfer_details>>& unconfirmed_payments, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const; + void get_unconfirmed_payments(std::list<std::pair<crypto::hash,wallet2::payment_details>>& unconfirmed_payments, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const; uint64_t get_blockchain_current_height() const { return m_local_bc_height; } void rescan_spent(); @@ -439,7 +518,19 @@ namespace tools uint64_t dummy_refresh_height = 0; // moved to keys file if(ver < 5) return; - a & m_blockchain; + if (ver < 19) + { + std::vector<crypto::hash> blockchain; + a & blockchain; + for (const auto &b: blockchain) + { + m_blockchain.push_back(b); + } + } + else + { + a & m_blockchain; + } a & m_transfers; a & m_account_public_address; a & m_key_images; @@ -496,6 +587,12 @@ namespace tools return; a & m_scanned_pool_txs[0]; a & m_scanned_pool_txs[1]; + if (ver < 20) + return; + a & m_subaddresses; + a & m_subaddresses_inv; + a & m_subaddress_labels; + a & m_additional_tx_keys; } /*! @@ -541,14 +638,16 @@ namespace tools bool merge_destinations() const { return m_merge_destinations; } bool confirm_backlog() const { return m_confirm_backlog; } void confirm_backlog(bool always) { m_confirm_backlog = always; } + void set_confirm_backlog_threshold(uint32_t threshold) { m_confirm_backlog_threshold = threshold; }; + uint32_t get_confirm_backlog_threshold() const { return m_confirm_backlog_threshold; }; - bool get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key) const; + bool get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys) const; /*! * \brief GUI Address book get/store */ std::vector<address_book_row> get_address_book() const { return m_address_book; } - bool add_address_book_row(const cryptonote::account_public_address &address, const crypto::hash &payment_id, const std::string &description); + bool add_address_book_row(const cryptonote::account_public_address &address, const crypto::hash &payment_id, const std::string &description, bool is_subaddress); bool delete_address_book_row(std::size_t row_id); uint64_t get_num_rct_outputs(); @@ -588,9 +687,9 @@ namespace tools payment_container export_payments() const; void import_payments(const payment_container &payments); void import_payments_out(const std::list<std::pair<crypto::hash,wallet2::confirmed_transfer_details>> &confirmed_payments); - std::vector<crypto::hash> export_blockchain() const; - void import_blockchain(const std::vector<crypto::hash> &bc); - bool export_key_images(const std::string filename); + std::tuple<size_t, crypto::hash, std::vector<crypto::hash>> export_blockchain() const; + void import_blockchain(const std::tuple<size_t, crypto::hash, std::vector<crypto::hash>> &bc); + bool export_key_images(const std::string &filename); std::vector<std::pair<crypto::key_image, crypto::signature>> export_key_images() const; uint64_t import_key_images(const std::vector<std::pair<crypto::key_image, crypto::signature>> &signed_key_images, uint64_t &spent, uint64_t &unspent, bool check_spent = true); uint64_t import_key_images(const std::string &filename, uint64_t &spent, uint64_t &unspent); @@ -643,27 +742,28 @@ namespace tools uint64_t select_transfers(uint64_t needed_money, std::vector<size_t> unused_transfers_indices, std::list<size_t>& selected_transfers, bool trusted_daemon); bool prepare_file_names(const std::string& file_path); void process_unconfirmed(const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t height); - void process_outgoing(const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t height, uint64_t ts, uint64_t spent, uint64_t received); - 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 process_outgoing(const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t height, uint64_t ts, uint64_t spent, uint64_t received, uint32_t subaddr_account, const std::set<uint32_t>& subaddr_indices); + 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, uint32_t subaddr_account, const std::set<uint32_t>& subaddr_indices); 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; crypto::hash8 get_short_payment_id(const pending_tx &ptx) const; - void check_acc_out_precomp(const crypto::public_key &spend_public_key, const cryptonote::tx_out &o, const crypto::key_derivation &derivation, size_t i, bool &received, uint64_t &money_transfered, bool &error) const; + void check_acc_out_precomp(const cryptonote::tx_out &o, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, size_t i, tx_scan_info_t &tx_scan_info) const; void parse_block_round(const cryptonote::blobdata &blob, cryptonote::block &bl, crypto::hash &bl_id, bool &error) const; uint64_t get_upper_transaction_size_limit(); std::vector<uint64_t> get_unspent_amounts_vector(); uint64_t get_dynamic_per_kb_fee_estimate(); float get_output_relatedness(const transfer_details &td0, const transfer_details &td1) const; - std::vector<size_t> pick_preferred_rct_inputs(uint64_t needed_money) const; + std::vector<size_t> pick_preferred_rct_inputs(uint64_t needed_money, uint32_t subaddr_account, const std::set<uint32_t> &subaddr_indices) const; void set_spent(size_t idx, uint64_t height); void set_unspent(size_t idx); void get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::list<size_t> &selected_transfers, size_t fake_outputs_count); - bool wallet_generate_key_image_helper(const cryptonote::account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, cryptonote::keypair& in_ephemeral, crypto::key_image& ki); crypto::public_key get_tx_pub_key_from_received_outs(const tools::wallet2::transfer_details &td) const; bool should_pick_a_second_output(bool use_rct, size_t n_transfers, const std::vector<size_t> &unused_transfers_indices, const std::vector<size_t> &unused_dust_indices) const; std::vector<size_t> get_only_rct(const std::vector<size_t> &unused_dust_indices, const std::vector<size_t> &unused_transfers_indices) const; + void scan_output(const cryptonote::account_keys &keys, const cryptonote::transaction &tx, const crypto::public_key &tx_pub_key, size_t i, tx_scan_info_t &tx_scan_info, int &num_vouts_received, std::unordered_map<cryptonote::subaddress_index, uint64_t> &tx_money_got_in_outs, std::vector<size_t> &outs); + void trim_hashchain(); cryptonote::account_base m_account; boost::optional<epee::net_utils::http::login> m_daemon_login; @@ -671,18 +771,23 @@ namespace tools std::string m_wallet_file; std::string m_keys_file; epee::net_utils::http::http_simple_client m_http_client; - std::vector<crypto::hash> m_blockchain; + hashchain m_blockchain; std::atomic<uint64_t> m_local_bc_height; //temporary workaround std::unordered_map<crypto::hash, unconfirmed_transfer_details> m_unconfirmed_txs; std::unordered_map<crypto::hash, confirmed_transfer_details> m_confirmed_txs; std::unordered_multimap<crypto::hash, payment_details> m_unconfirmed_payments; std::unordered_map<crypto::hash, crypto::secret_key> m_tx_keys; + cryptonote::checkpoints m_checkpoints; + std::unordered_map<crypto::hash, std::vector<crypto::secret_key>> m_additional_tx_keys; transfer_container m_transfers; payment_container m_payments; std::unordered_map<crypto::key_image, size_t> m_key_images; std::unordered_map<crypto::public_key, size_t> m_pub_keys; cryptonote::account_public_address m_account_public_address; + std::unordered_map<crypto::public_key, cryptonote::subaddress_index> m_subaddresses; + std::unordered_map<cryptonote::subaddress_index, crypto::public_key> m_subaddresses_inv; + std::vector<std::vector<std::string>> m_subaddress_labels; std::unordered_map<crypto::hash, std::string> m_tx_notes; std::vector<tools::wallet2::address_book_row> m_address_book; uint64_t m_upper_transaction_size_limit; //TODO: auto-calc this value or request from daemon, now use some fixed value @@ -711,21 +816,22 @@ namespace tools uint64_t m_min_output_value; bool m_merge_destinations; bool m_confirm_backlog; + uint32_t m_confirm_backlog_threshold; bool m_is_initialized; NodeRPCProxy m_node_rpc_proxy; std::unordered_set<crypto::hash> m_scanned_pool_txs[2]; }; } -BOOST_CLASS_VERSION(tools::wallet2, 18) -BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 7) -BOOST_CLASS_VERSION(tools::wallet2::payment_details, 1) -BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 6) -BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 4) -BOOST_CLASS_VERSION(tools::wallet2::address_book_row, 16) +BOOST_CLASS_VERSION(tools::wallet2, 20) +BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 8) +BOOST_CLASS_VERSION(tools::wallet2::payment_details, 2) +BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 7) +BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 5) +BOOST_CLASS_VERSION(tools::wallet2::address_book_row, 17) BOOST_CLASS_VERSION(tools::wallet2::unsigned_tx_set, 0) BOOST_CLASS_VERSION(tools::wallet2::signed_tx_set, 0) -BOOST_CLASS_VERSION(tools::wallet2::tx_construction_data, 0) -BOOST_CLASS_VERSION(tools::wallet2::pending_tx, 0) +BOOST_CLASS_VERSION(tools::wallet2::tx_construction_data, 1) +BOOST_CLASS_VERSION(tools::wallet2::pending_tx, 1) namespace boost { @@ -759,6 +865,10 @@ namespace boost { x.m_pk_index = 0; } + if (ver < 8) + { + x.m_subaddr_index = {}; + } } template <class Archive> @@ -826,6 +936,12 @@ namespace boost return; } a & x.m_pk_index; + if (ver < 8) + { + initialize_transfer_details(a, x, ver); + return; + } + a & x.m_subaddr_index; } template <class Archive> @@ -865,6 +981,10 @@ namespace boost if (!typename Archive::is_saving() && x.m_change != (uint64_t)-1) x.m_amount_out += x.m_change; } + if (ver < 7) + return; + a & x.m_subaddr_account; + a & x.m_subaddr_indices; } template <class Archive> @@ -902,6 +1022,10 @@ namespace boost return; } a & x.m_unlock_time; + if (ver < 5) + return; + a & x.m_subaddr_account; + a & x.m_subaddr_indices; } template <class Archive> @@ -914,13 +1038,9 @@ namespace boost if (ver < 1) return; a & x.m_timestamp; - } - - template <class Archive> - inline void serialize(Archive& a, cryptonote::tx_destination_entry& x, const boost::serialization::version_type ver) - { - a & x.amount; - a & x.addr; + if (ver < 2) + return; + a & x.m_subaddr_index; } template <class Archive> @@ -929,6 +1049,9 @@ namespace boost a & x.m_address; a & x.m_payment_id; a & x.m_description; + if (ver < 17) + return; + a & x.m_is_subaddress; } template <class Archive> @@ -956,6 +1079,10 @@ namespace boost a & x.unlock_time; a & x.use_rct; a & x.dests; + if (ver < 1) + return; + a & x.subaddr_account; + a & x.subaddr_indices; } template <class Archive> @@ -971,6 +1098,9 @@ namespace boost a & x.tx_key; a & x.dests; a & x.construction_data; + if (ver < 1) + return; + a & x.additional_tx_keys; } } } @@ -991,18 +1121,18 @@ namespace tools for(auto& de: dsts) { cryptonote::decompose_amount_into_digits(de.amount, 0, - [&](uint64_t chunk) { splitted_dsts.push_back(cryptonote::tx_destination_entry(chunk, de.addr)); }, - [&](uint64_t a_dust) { splitted_dsts.push_back(cryptonote::tx_destination_entry(a_dust, de.addr)); } ); + [&](uint64_t chunk) { splitted_dsts.push_back(cryptonote::tx_destination_entry(chunk, de.addr, de.is_subaddress)); }, + [&](uint64_t a_dust) { splitted_dsts.push_back(cryptonote::tx_destination_entry(a_dust, de.addr, de.is_subaddress)); } ); } cryptonote::decompose_amount_into_digits(change_dst.amount, 0, [&](uint64_t chunk) { if (chunk <= dust_threshold) - dust_dsts.push_back(cryptonote::tx_destination_entry(chunk, change_dst.addr)); + dust_dsts.push_back(cryptonote::tx_destination_entry(chunk, change_dst.addr, false)); else - splitted_dsts.push_back(cryptonote::tx_destination_entry(chunk, change_dst.addr)); + splitted_dsts.push_back(cryptonote::tx_destination_entry(chunk, change_dst.addr, false)); }, - [&](uint64_t a_dust) { dust_dsts.push_back(cryptonote::tx_destination_entry(a_dust, change_dst.addr)); } ); + [&](uint64_t a_dust) { dust_dsts.push_back(cryptonote::tx_destination_entry(a_dust, change_dst.addr, false)); } ); } //---------------------------------------------------------------------------------------------------- inline void null_split_strategy(const std::vector<cryptonote::tx_destination_entry>& dsts, @@ -1016,7 +1146,7 @@ namespace tools if (0 != change) { - splitted_dsts.push_back(cryptonote::tx_destination_entry(change, change_dst.addr)); + splitted_dsts.push_back(cryptonote::tx_destination_entry(change, change_dst.addr, false)); } } //---------------------------------------------------------------------------------------------------- @@ -1039,7 +1169,7 @@ namespace tools } template<typename T> - void wallet2::transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, size_t fake_outputs_count, const std::vector<size_t> &unused_transfers_indices, + void wallet2::transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outputs_count, const std::vector<size_t> &unused_transfers_indices, 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, bool trusted_daemon) { using namespace cryptonote; @@ -1064,6 +1194,10 @@ namespace tools uint64_t found_money = select_transfers(needed_money, unused_transfers_indices, selected_transfers, trusted_daemon); THROW_WALLET_EXCEPTION_IF(found_money < needed_money, error::not_enough_money, found_money, needed_money - fee, fee); + uint32_t subaddr_account = m_transfers[*selected_transfers.begin()].m_subaddr_index.major; + for (auto i = ++selected_transfers.begin(); i != selected_transfers.end(); ++i) + THROW_WALLET_EXCEPTION_IF(subaddr_account != *i, error::wallet_internal_error, "the tx uses funds from multiple accounts"); + typedef COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry out_entry; typedef cryptonote::tx_source_entry::output_entry tx_output_entry; @@ -1151,7 +1285,7 @@ namespace tools 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.addr = get_subaddress({subaddr_account, 0}); change_dts.amount = found_money - needed_money; } @@ -1164,12 +1298,13 @@ namespace tools } for(auto& d: dust_dsts) { if (!dust_policy.add_to_fee) - splitted_dsts.push_back(cryptonote::tx_destination_entry(d.amount, dust_policy.addr_for_dust)); + splitted_dsts.push_back(cryptonote::tx_destination_entry(d.amount, dust_policy.addr_for_dust, d.is_subaddress)); dust += d.amount; } crypto::secret_key tx_key; - bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), sources, splitted_dsts, extra, tx, unlock_time, tx_key); + std::vector<crypto::secret_key> additional_tx_keys; + bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys); THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_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); @@ -1195,6 +1330,7 @@ namespace tools ptx.change_dts = change_dts; ptx.selected_transfers = selected_transfers; ptx.tx_key = tx_key; + ptx.additional_tx_keys = additional_tx_keys; ptx.dests = dsts; ptx.construction_data.sources = sources; ptx.construction_data.change_dts = change_dts; @@ -1204,6 +1340,11 @@ namespace tools ptx.construction_data.unlock_time = unlock_time; ptx.construction_data.use_rct = false; ptx.construction_data.dests = dsts; + // record which subaddress indices are being used as inputs + ptx.construction_data.subaddr_account = subaddr_account; + ptx.construction_data.subaddr_indices.clear(); + for (size_t idx: selected_transfers) + ptx.construction_data.subaddr_indices.insert(m_transfers[idx].m_subaddr_index.minor); } diff --git a/src/wallet/wallet2_api.h b/src/wallet/wallet2_api.h index 8da8c62eb..6f612bbb3 100644 --- a/src/wallet/wallet2_api.h +++ b/src/wallet/wallet2_api.h @@ -33,6 +33,7 @@ #include <string> #include <vector> +#include <set> #include <ctime> #include <iostream> @@ -88,6 +89,8 @@ struct PendingTransaction * \return */ virtual uint64_t txCount() const = 0; + virtual std::vector<uint32_t> subaddrAccount() const = 0; + virtual std::vector<std::set<uint32_t>> subaddrIndices() const = 0; }; /** @@ -155,6 +158,9 @@ struct TransactionInfo virtual uint64_t amount() const = 0; virtual uint64_t fee() const = 0; virtual uint64_t blockHeight() const = 0; + virtual std::set<uint32_t> subaddrIndex() const = 0; + virtual uint32_t subaddrAccount() const = 0; + virtual std::string label() const = 0; virtual uint64_t confirmations() const = 0; virtual uint64_t unlockTime() const = 0; //! transaction_id @@ -223,6 +229,66 @@ struct AddressBook virtual int lookupPaymentID(const std::string &payment_id) const = 0; }; +struct SubaddressRow { +public: + SubaddressRow(std::size_t _rowId, const std::string &_address, const std::string &_label): + m_rowId(_rowId), + m_address(_address), + m_label(_label) {} + +private: + std::size_t m_rowId; + std::string m_address; + std::string m_label; +public: + std::string extra; + std::string getAddress() const {return m_address;} + std::string getLabel() const {return m_label;} + std::size_t getRowId() const {return m_rowId;} +}; + +struct Subaddress +{ + virtual ~Subaddress() = 0; + virtual std::vector<SubaddressRow*> getAll() const = 0; + virtual void addRow(uint32_t accountIndex, const std::string &label) = 0; + virtual void setLabel(uint32_t accountIndex, uint32_t addressIndex, const std::string &label) = 0; + virtual void refresh(uint32_t accountIndex) = 0; +}; + +struct SubaddressAccountRow { +public: + SubaddressAccountRow(std::size_t _rowId, const std::string &_address, const std::string &_label, const std::string &_balance, const std::string &_unlockedBalance): + m_rowId(_rowId), + m_address(_address), + m_label(_label), + m_balance(_balance), + m_unlockedBalance(_unlockedBalance) {} + +private: + std::size_t m_rowId; + std::string m_address; + std::string m_label; + std::string m_balance; + std::string m_unlockedBalance; +public: + std::string extra; + std::string getAddress() const {return m_address;} + std::string getLabel() const {return m_label;} + std::string getBalance() const {return m_balance;} + std::string getUnlockedBalance() const {return m_unlockedBalance;} + std::size_t getRowId() const {return m_rowId;} +}; + +struct SubaddressAccount +{ + virtual ~SubaddressAccount() = 0; + virtual std::vector<SubaddressAccountRow*> getAll() const = 0; + virtual void addRow(const std::string &label) = 0; + virtual void setLabel(uint32_t accountIndex, const std::string &label) = 0; + virtual void refresh() = 0; +}; + struct WalletListener { virtual ~WalletListener() = 0; @@ -294,7 +360,8 @@ struct Wallet //! in case error status, returns error string virtual std::string errorString() const = 0; virtual bool setPassword(const std::string &password) = 0; - virtual std::string address() const = 0; + virtual std::string address(uint32_t accountIndex, uint32_t addressIndex) const = 0; + std::string mainAddress() const { return address(0, 0); } virtual std::string path() const = 0; virtual bool testnet() const = 0; //! returns current hard fork info @@ -406,8 +473,20 @@ struct Wallet virtual ConnectionStatus connected() const = 0; virtual void setTrustedDaemon(bool arg) = 0; virtual bool trustedDaemon() const = 0; - virtual uint64_t balance() const = 0; - virtual uint64_t unlockedBalance() const = 0; + virtual uint64_t balance(uint32_t accountIndex) const = 0; + uint64_t balanceAll() const { + uint64_t result = 0; + for (uint32_t i = 0; i < numSubaddressAccounts(); ++i) + result += balance(i); + return result; + } + virtual uint64_t unlockedBalance(uint32_t accountIndex) const = 0; + uint64_t unlockedBalanceAll() const { + uint64_t result = 0; + for (uint32_t i = 0; i < numSubaddressAccounts(); ++i) + result += unlockedBalance(i); + return result; + } /** * @brief watchOnly - checks if wallet is watch only @@ -492,6 +571,39 @@ struct Wallet */ virtual int autoRefreshInterval() const = 0; + /** + * @brief addSubaddressAccount - appends a new subaddress account at the end of the last major index of existing subaddress accounts + * @param label - the label for the new account (which is the as the label of the primary address (accountIndex,0)) + */ + virtual void addSubaddressAccount(const std::string& label) = 0; + /** + * @brief numSubaddressAccounts - returns the number of existing subaddress accounts + */ + virtual size_t numSubaddressAccounts() const = 0; + /** + * @brief numSubaddresses - returns the number of existing subaddresses associated with the specified subaddress account + * @param accountIndex - the major index specifying the subaddress account + */ + virtual size_t numSubaddresses(uint32_t accountIndex) const = 0; + /** + * @brief addSubaddress - appends a new subaddress at the end of the last minor index of the specified subaddress account + * @param accountIndex - the major index specifying the subaddress account + * @param label - the label for the new subaddress + */ + virtual void addSubaddress(uint32_t accountIndex, const std::string& label) = 0; + /** + * @brief getSubaddressLabel - gets the label of the specified subaddress + * @param accountIndex - the major index specifying the subaddress account + * @param addressIndex - the minor index specifying the subaddress + */ + virtual std::string getSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex) const = 0; + /** + * @brief setSubaddressLabel - sets the label of the specified subaddress + * @param accountIndex - the major index specifying the subaddress account + * @param addressIndex - the minor index specifying the subaddress + * @param label - the new label for the specified subaddress + */ + virtual void setSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex, const std::string &label) = 0; /*! * \brief createTransaction creates transaction. if dst_addr is an integrated address, payment_id is ignored @@ -499,6 +611,8 @@ struct Wallet * \param payment_id optional payment_id, can be empty string * \param amount amount * \param mixin_count mixin count. if 0 passed, wallet will use default value + * \param subaddr_account subaddress account from which the input funds are taken + * \param subaddr_indices set of subaddress indices to use for transfer or sweeping. if set empty, all are chosen when sweeping, and one or more are automatically chosen when transferring. after execution, returns the set of actually used indices * \param priority * \return PendingTransaction object. caller is responsible to check PendingTransaction::status() * after object returned @@ -506,6 +620,8 @@ struct Wallet virtual PendingTransaction * createTransaction(const std::string &dst_addr, const std::string &payment_id, optional<uint64_t> amount, uint32_t mixin_count, + uint32_t subaddr_account, + std::set<uint32_t> subaddr_indices, PendingTransaction::Priority = PendingTransaction::Priority_Low) = 0; /*! @@ -551,8 +667,10 @@ struct Wallet virtual bool importKeyImages(const std::string &filename) = 0; - virtual TransactionHistory * history() const = 0; - virtual AddressBook * addressBook() const = 0; + virtual TransactionHistory * history() = 0; + virtual AddressBook * addressBook() = 0; + virtual Subaddress * subaddress() = 0; + virtual SubaddressAccount * subaddressAccount() = 0; virtual void setListener(WalletListener *) = 0; /*! * \brief defaultMixin - returns number of mixins used in transactions @@ -663,7 +781,7 @@ struct WalletManager * \param wallet previously opened / created wallet instance * \return None */ - virtual bool closeWallet(Wallet *wallet) = 0; + virtual bool closeWallet(Wallet *wallet, bool store = true) = 0; /* * ! checks if wallet with the given name already exists diff --git a/src/wallet/wallet_args.cpp b/src/wallet/wallet_args.cpp index 34c5a2a5d..22cfcc269 100644 --- a/src/wallet/wallet_args.cpp +++ b/src/wallet/wallet_args.cpp @@ -84,6 +84,7 @@ namespace wallet_args #endif const command_line::arg_descriptor<std::string> arg_log_level = {"log-level", "0-4 or categories", ""}; + const command_line::arg_descriptor<std::size_t> arg_max_log_file_size = {"max-log-file-size", "Specify maximum log file size [B]", MAX_LOG_FILE_SIZE}; const command_line::arg_descriptor<uint32_t> arg_max_concurrency = {"max-concurrency", wallet_args::tr("Max number of threads to use for a parallel job"), DEFAULT_MAX_CONCURRENCY}; const command_line::arg_descriptor<std::string> arg_log_file = {"log-file", wallet_args::tr("Specify log file"), ""}; const command_line::arg_descriptor<std::string> arg_config_file = {"config-file", wallet_args::tr("Config file"), "", true}; @@ -99,8 +100,9 @@ namespace wallet_args command_line::add_arg(desc_general, command_line::arg_help); command_line::add_arg(desc_general, command_line::arg_version); - command_line::add_arg(desc_params, arg_log_file, ""); + command_line::add_arg(desc_params, arg_log_file); command_line::add_arg(desc_params, arg_log_level); + command_line::add_arg(desc_params, arg_max_log_file_size); command_line::add_arg(desc_params, arg_max_concurrency); command_line::add_arg(desc_params, arg_config_file); @@ -114,6 +116,21 @@ namespace wallet_args auto parser = po::command_line_parser(argc, argv).options(desc_all).positional(positional_options); po::store(parser.run(), vm); + if (command_line::get_arg(vm, command_line::arg_help)) + { + tools::msg_writer() << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")" << ENDL; + tools::msg_writer() << wallet_args::tr("This is the command line monero wallet. It needs to connect to a monero\n" + "daemon to work correctly.") << ENDL; + tools::msg_writer() << wallet_args::tr("Usage:") << ENDL << " " << usage; + tools::msg_writer() << desc_all; + return false; + } + else if (command_line::get_arg(vm, command_line::arg_version)) + { + tools::msg_writer() << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")"; + return false; + } + if(command_line::has_arg(vm, arg_config_file)) { std::string config = command_line::get_arg(vm, arg_config_file); @@ -141,27 +158,12 @@ namespace wallet_args log_path = command_line::get_arg(vm, arg_log_file); else log_path = mlog_get_default_log_path(default_log_name); - mlog_configure(log_path, log_to_console); + mlog_configure(log_path, log_to_console, command_line::get_arg(vm, arg_max_log_file_size)); if (!vm["log-level"].defaulted()) { mlog_set_log(command_line::get_arg(vm, arg_log_level).c_str()); } - if (command_line::get_arg(vm, command_line::arg_help)) - { - tools::msg_writer() << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")" << ENDL; - tools::msg_writer() << wallet_args::tr("This is the command line monero wallet. It needs to connect to a monero\n" - "daemon to work correctly.") << ENDL; - tools::msg_writer() << wallet_args::tr("Usage:") << ENDL << " " << usage; - tools::msg_writer() << desc_all; - return boost::none; - } - else if (command_line::get_arg(vm, command_line::arg_version)) - { - tools::msg_writer() << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")"; - return boost::none; - } - if(command_line::has_arg(vm, arg_max_concurrency)) tools::set_max_concurrency(command_line::get_arg(vm, arg_max_concurrency)); diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index 2e5acc8cb..d1f4a796d 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -492,7 +492,7 @@ namespace tools for (size_t i = 0; i < m_destinations.size(); ++i) { const cryptonote::tx_destination_entry& dst = m_destinations[i]; - ss << "\n " << i << ": " << cryptonote::get_account_address_as_str(m_testnet, dst.addr) << " " << + ss << "\n " << i << ": " << cryptonote::get_account_address_as_str(m_testnet, dst.is_subaddress, dst.addr) << " " << cryptonote::print_money(dst.amount); } @@ -567,7 +567,7 @@ namespace tools ", destinations:"; for (const auto& dst : m_destinations) { - ss << '\n' << cryptonote::print_money(dst.amount) << " -> " << cryptonote::get_account_address_as_str(m_testnet, dst.addr); + ss << '\n' << cryptonote::print_money(dst.amount) << " -> " << cryptonote::get_account_address_as_str(m_testnet, dst.is_subaddress, dst.addr); } return ss.str(); } diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 773d12775..25f780134 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -27,6 +27,7 @@ // 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 <boost/format.hpp> #include <boost/asio/ip/address.hpp> #include <boost/filesystem/operations.hpp> #include <cstdint> @@ -37,7 +38,6 @@ using namespace epee; #include "wallet/wallet_args.h" #include "common/command_line.h" #include "common/i18n.h" -#include "common/util.h" #include "cryptonote_basic/cryptonote_format_utils.h" #include "cryptonote_basic/account.h" #include "wallet_rpc_server_commands_defs.h" @@ -70,18 +70,12 @@ namespace tools } //------------------------------------------------------------------------------------------------------------------------------ - wallet_rpc_server::wallet_rpc_server():m_wallet(NULL), rpc_login_filename(), m_stop(false), m_trusted_daemon(false) + wallet_rpc_server::wallet_rpc_server():m_wallet(NULL), rpc_login_file(), m_stop(false), m_trusted_daemon(false), m_vm(NULL) { } //------------------------------------------------------------------------------------------------------------------------------ wallet_rpc_server::~wallet_rpc_server() { - try - { - boost::system::error_code ec{}; - boost::filesystem::remove(rpc_login_filename, ec); - } - catch (...) {} } //------------------------------------------------------------------------------------------------------------------------------ void wallet_rpc_server::set_wallet(wallet2 *cr) @@ -160,7 +154,15 @@ namespace tools #else #define MKDIR(path, mode) mkdir(path, mode) #endif - MKDIR(m_wallet_dir.c_str(), 0700); + if (MKDIR(m_wallet_dir.c_str(), 0700) < 0) + { +#ifdef _WIN32 + LOG_ERROR(tr("Failed to create directory ") + m_wallet_dir); +#else + LOG_ERROR((boost::format(tr("Failed to create directory %s: %s")) % m_wallet_dir % strerror(errno)).str()); +#endif + return false; + } } if (disable_auth) @@ -182,34 +184,32 @@ namespace tools default_rpc_username, string_encoding::base64_encode(rand_128bit.data(), rand_128bit.size()) ); + + std::string temp = "monero-wallet-rpc." + bind_port + ".login"; + rpc_login_file = tools::private_file::create(temp); + if (!rpc_login_file.handle()) + { + LOG_ERROR(tr("Failed to create file ") << temp << tr(". Check permissions or remove file")); + return false; + } + std::fputs(http_login->username.c_str(), rpc_login_file.handle()); + std::fputc(':', rpc_login_file.handle()); + std::fputs(http_login->password.c_str(), rpc_login_file.handle()); + std::fflush(rpc_login_file.handle()); + if (std::ferror(rpc_login_file.handle())) + { + LOG_ERROR(tr("Error writing to file ") << temp); + return false; + } + LOG_PRINT_L0(tr("RPC username/password is stored in file ") << temp); } - else + else // chosen user/pass { http_login.emplace( std::move(rpc_config->login->username), std::move(rpc_config->login->password).password() ); } assert(bool(http_login)); - - std::string temp = "monero-wallet-rpc." + bind_port + ".login"; - const auto cookie = tools::create_private_file(temp); - if (!cookie) - { - LOG_ERROR(tr("Failed to create file ") << temp << tr(". Check permissions or remove file")); - return false; - } - rpc_login_filename.swap(temp); // nothrow guarantee destructor cleanup - temp = rpc_login_filename; - std::fputs(http_login->username.c_str(), cookie.get()); - std::fputc(':', cookie.get()); - std::fputs(http_login->password.c_str(), cookie.get()); - std::fflush(cookie.get()); - if (std::ferror(cookie.get())) - { - LOG_ERROR(tr("Error writing to file ") << temp); - return false; - } - LOG_PRINT_L0(tr("RPC username/password is stored in file ") << temp); } // end auth enabled m_http_client.set_server(walvars->get_daemon_address(), walvars->get_daemon_login()); @@ -253,6 +253,7 @@ namespace tools entry.fee = 0; // TODO entry.note = m_wallet->get_tx_note(pd.m_tx_hash); entry.type = "in"; + entry.subaddr_index = pd.m_subaddr_index; } //------------------------------------------------------------------------------------------------------------------------------ void wallet_rpc_server::fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const tools::wallet2::confirmed_transfer_details &pd) @@ -273,10 +274,11 @@ namespace tools entry.destinations.push_back(wallet_rpc::transfer_destination()); wallet_rpc::transfer_destination &td = entry.destinations.back(); td.amount = d.amount; - td.address = get_account_address_as_str(m_wallet->testnet(), d.addr); + td.address = get_account_address_as_str(m_wallet->testnet(), d.is_subaddress, d.addr); } entry.type = "out"; + entry.subaddr_index = { pd.m_subaddr_account, 0 }; } //------------------------------------------------------------------------------------------------------------------------------ void wallet_rpc_server::fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const tools::wallet2::unconfirmed_transfer_details &pd) @@ -294,6 +296,7 @@ namespace tools entry.unlock_time = pd.m_tx.unlock_time; entry.note = m_wallet->get_tx_note(txid); entry.type = is_failed ? "failed" : "pending"; + entry.subaddr_index = { pd.m_subaddr_account, 0 }; } //------------------------------------------------------------------------------------------------------------------------------ void wallet_rpc_server::fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &payment_id, const tools::wallet2::payment_details &pd) @@ -309,6 +312,7 @@ namespace tools entry.fee = 0; // TODO entry.note = m_wallet->get_tx_note(pd.m_tx_hash); entry.type = "pool"; + entry.subaddr_index = pd.m_subaddr_index; } //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_getbalance(const wallet_rpc::COMMAND_RPC_GET_BALANCE::request& req, wallet_rpc::COMMAND_RPC_GET_BALANCE::response& res, epee::json_rpc::error& er) @@ -316,8 +320,24 @@ namespace tools if (!m_wallet) return not_open(er); try { - res.balance = m_wallet->balance(); - res.unlocked_balance = m_wallet->unlocked_balance(); + res.balance = m_wallet->balance(req.account_index); + res.unlocked_balance = m_wallet->unlocked_balance(req.account_index); + std::map<uint32_t, uint64_t> balance_per_subaddress = m_wallet->balance_per_subaddress(req.account_index); + std::map<uint32_t, uint64_t> unlocked_balance_per_subaddress = m_wallet->unlocked_balance_per_subaddress(req.account_index); + std::vector<tools::wallet2::transfer_details> transfers; + m_wallet->get_transfers(transfers); + for (const auto& i : balance_per_subaddress) + { + wallet_rpc::COMMAND_RPC_GET_BALANCE::per_subaddress_info info; + info.address_index = i.first; + cryptonote::subaddress_index index = {req.account_index, info.address_index}; + info.address = m_wallet->get_subaddress_as_str(index); + info.balance = i.second; + info.unlocked_balance = unlocked_balance_per_subaddress[i.first]; + info.label = m_wallet->get_subaddress_label(index); + info.num_unspent_outputs = std::count_if(transfers.begin(), transfers.end(), [&](const tools::wallet2::transfer_details& td) { return !td.m_spent && td.m_subaddr_index == index; }); + res.per_subaddress.push_back(info); + } } catch (const std::exception& e) { @@ -333,7 +353,126 @@ namespace tools if (!m_wallet) return not_open(er); try { - res.address = m_wallet->get_account().get_public_address_str(m_wallet->testnet()); + res.addresses.resize(m_wallet->get_num_subaddresses(req.account_index)); + tools::wallet2::transfer_container transfers; + m_wallet->get_transfers(transfers); + cryptonote::subaddress_index index = {req.account_index, 0}; + for (; index.minor < m_wallet->get_num_subaddresses(req.account_index); ++index.minor) + { + auto& info = res.addresses[index.minor]; + info.address = m_wallet->get_subaddress_as_str(index); + info.label = m_wallet->get_subaddress_label(index); + info.address_index = index.minor; + info.used = std::find_if(transfers.begin(), transfers.end(), [&](const tools::wallet2::transfer_details& td) { return td.m_subaddr_index == index; }) != transfers.end(); + } + res.address = m_wallet->get_subaddress_as_str({req.account_index, 0}); + } + catch (const std::exception& e) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = e.what(); + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_create_address(const wallet_rpc::COMMAND_RPC_CREATE_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_CREATE_ADDRESS::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + m_wallet->add_subaddress(req.account_index, req.label); + res.address_index = m_wallet->get_num_subaddresses(req.account_index) - 1; + res.address = m_wallet->get_subaddress_as_str({req.account_index, res.address_index}); + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_label_address(const wallet_rpc::COMMAND_RPC_LABEL_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_LABEL_ADDRESS::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + if (req.index.major >= m_wallet->get_num_subaddress_accounts()) + { + er.code = WALLET_RPC_ERROR_CODE_ACCOUNT_INDEX_OUTOFBOUND; + er.message = "Account index is out of bound"; + return false; + } + if (req.index.minor >= m_wallet->get_num_subaddresses(req.index.major)) + { + er.code = WALLET_RPC_ERROR_CODE_ADDRESS_INDEX_OUTOFBOUND; + er.message = "Address index is out of bound"; + return false; + } + try + { + m_wallet->set_subaddress_label(req.index, req.label); + } + catch (const std::exception& e) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = e.what(); + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_get_accounts(const wallet_rpc::COMMAND_RPC_GET_ACCOUNTS::request& req, wallet_rpc::COMMAND_RPC_GET_ACCOUNTS::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + try + { + res.total_balance = 0; + res.total_unlocked_balance = 0; + cryptonote::subaddress_index subaddr_index = {0,0}; + for (; subaddr_index.major < m_wallet->get_num_subaddress_accounts(); ++subaddr_index.major) + { + wallet_rpc::COMMAND_RPC_GET_ACCOUNTS::subaddress_account_info info; + info.account_index = subaddr_index.major; + info.base_address = m_wallet->get_subaddress_as_str(subaddr_index); + info.balance = m_wallet->balance(subaddr_index.major); + info.unlocked_balance = m_wallet->unlocked_balance(subaddr_index.major); + info.label = m_wallet->get_subaddress_label(subaddr_index); + res.subaddress_accounts.push_back(info); + res.total_balance += info.balance; + res.total_unlocked_balance += info.unlocked_balance; + } + } + catch (const std::exception& e) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = e.what(); + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_create_account(const wallet_rpc::COMMAND_RPC_CREATE_ACCOUNT::request& req, wallet_rpc::COMMAND_RPC_CREATE_ACCOUNT::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + try + { + m_wallet->add_subaddress_account(req.label); + res.account_index = m_wallet->get_num_subaddress_accounts() - 1; + res.address = m_wallet->get_subaddress_as_str({res.account_index, 0}); + } + catch (const std::exception& e) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = e.what(); + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_label_account(const wallet_rpc::COMMAND_RPC_LABEL_ACCOUNT::request& req, wallet_rpc::COMMAND_RPC_LABEL_ACCOUNT::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + if (req.account_index >= m_wallet->get_num_subaddress_accounts()) + { + er.code = WALLET_RPC_ERROR_CODE_ACCOUNT_INDEX_OUTOFBOUND; + er.message = "Account index is out of bound"; + return false; + } + try + { + m_wallet->set_subaddress_label({req.account_index, 0}, req.label); } catch (const std::exception& e) { @@ -360,17 +499,16 @@ namespace tools return true; } //------------------------------------------------------------------------------------------------------------------------------ - bool wallet_rpc_server::validate_transfer(const std::list<wallet_rpc::transfer_destination> destinations, std::string payment_id, std::vector<cryptonote::tx_destination_entry>& dsts, std::vector<uint8_t>& extra, epee::json_rpc::error& er) + bool wallet_rpc_server::validate_transfer(const std::list<wallet_rpc::transfer_destination>& destinations, const std::string& payment_id, std::vector<cryptonote::tx_destination_entry>& dsts, std::vector<uint8_t>& extra, epee::json_rpc::error& er) { - crypto::hash8 integrated_payment_id = cryptonote::null_hash8; + crypto::hash8 integrated_payment_id = crypto::null_hash8; std::string extra_nonce; for (auto it = destinations.begin(); it != destinations.end(); it++) { + cryptonote::address_parse_info info; cryptonote::tx_destination_entry de; - bool has_payment_id; - crypto::hash8 new_payment_id; er.message = ""; - if(!get_account_address_from_str_or_url(de.addr, has_payment_id, new_payment_id, m_wallet->testnet(), it->address, + if(!get_account_address_from_str_or_url(info, m_wallet->testnet(), it->address, [&er](const std::string &url, const std::vector<std::string> &addresses, bool dnssec_valid)->std::string { if (!dnssec_valid) { @@ -390,18 +528,21 @@ namespace tools er.message = std::string("WALLET_RPC_ERROR_CODE_WRONG_ADDRESS: ") + it->address; return false; } + + de.addr = info.address; + de.is_subaddress = info.is_subaddress; de.amount = it->amount; dsts.push_back(de); - if (has_payment_id) + if (info.has_payment_id) { - if (!payment_id.empty() || integrated_payment_id != cryptonote::null_hash8) + if (!payment_id.empty() || integrated_payment_id != crypto::null_hash8) { er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; er.message = "A single payment id is allowed per transaction"; return false; } - integrated_payment_id = new_payment_id; + integrated_payment_id = info.payment_id; cryptonote::set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, integrated_payment_id); /* Append Payment ID data into extra */ @@ -472,7 +613,7 @@ namespace tools try { uint64_t mixin = adjust_mixin(req.mixin); - std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, req.priority, extra, m_trusted_daemon); + std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, req.priority, extra, req.account_index, req.subaddr_indices, m_trusted_daemon); // reject proposed transactions if there are more than one. see on_transfer_split below. if (ptx_vector.size() != 1) @@ -548,7 +689,7 @@ namespace tools uint64_t ptx_amount; std::vector<wallet2::pending_tx> ptx_vector; LOG_PRINT_L2("on_transfer_split calling create_transactions_2"); - ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, req.priority, extra, m_trusted_daemon); + ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, req.priority, extra, req.account_index, req.subaddr_indices, m_trusted_daemon); LOG_PRINT_L2("on_transfer_split called create_transactions_2"); if (!req.do_not_relay) @@ -688,7 +829,7 @@ namespace tools try { uint64_t mixin = adjust_mixin(req.mixin); - std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_all(req.below_amount, dsts[0].addr, mixin, req.unlock_time, req.priority, extra, m_trusted_daemon); + std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_all(req.below_amount, dsts[0].addr, dsts[0].is_subaddress, mixin, req.unlock_time, req.priority, extra, req.account_index, req.subaddr_indices, m_trusted_daemon); if (!req.do_not_relay) m_wallet->commit_tx(ptx_vector); @@ -752,7 +893,7 @@ namespace tools } } - res.integrated_address = m_wallet->get_account().get_public_integrated_address_str(payment_id, m_wallet->testnet()); + res.integrated_address = m_wallet->get_integrated_address_as_str(payment_id); res.payment_id = epee::string_tools::pod_to_hex(payment_id); return true; } @@ -770,24 +911,22 @@ namespace tools if (!m_wallet) return not_open(er); try { - cryptonote::account_public_address address; - crypto::hash8 payment_id; - bool has_payment_id; + cryptonote::address_parse_info info; - if(!get_account_integrated_address_from_str(address, has_payment_id, payment_id, m_wallet->testnet(), req.integrated_address)) + if(!get_account_address_from_str(info, m_wallet->testnet(), req.integrated_address)) { er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS; er.message = "Invalid address"; return false; } - if(!has_payment_id) + if(!info.has_payment_id) { er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS; er.message = "Address is not an integrated address"; return false; } - res.standard_address = get_account_address_as_str(m_wallet->testnet(),address); - res.payment_id = epee::string_tools::pod_to_hex(payment_id); + res.standard_address = get_account_address_as_str(m_wallet->testnet(), info.is_subaddress, info.address); + res.payment_id = epee::string_tools::pod_to_hex(info.payment_id); return true; } catch (const std::exception &e) @@ -863,6 +1002,7 @@ namespace tools rpc_payment.amount = payment.m_amount; rpc_payment.block_height = payment.m_block_height; rpc_payment.unlock_time = payment.m_unlock_time; + rpc_payment.subaddr_index = payment.m_subaddr_index; res.payments.push_back(rpc_payment); } @@ -888,6 +1028,7 @@ namespace tools rpc_payment.amount = payment.second.m_amount; rpc_payment.block_height = payment.second.m_block_height; rpc_payment.unlock_time = payment.second.m_unlock_time; + rpc_payment.subaddr_index = payment.second.m_subaddr_index; res.payments.push_back(std::move(rpc_payment)); } @@ -937,6 +1078,7 @@ namespace tools rpc_payment.amount = payment.m_amount; rpc_payment.block_height = payment.m_block_height; rpc_payment.unlock_time = payment.m_unlock_time; + rpc_payment.subaddr_index = payment.m_subaddr_index; res.payments.push_back(std::move(rpc_payment)); } } @@ -975,6 +1117,8 @@ namespace tools { if (!filter || available != td.m_spent) { + if (req.account_index != td.m_subaddr_index.major || (!req.subaddr_indices.empty() && req.subaddr_indices.count(td.m_subaddr_index.minor) == 0)) + continue; if (!transfers_found) { transfers_found = true; @@ -986,6 +1130,7 @@ namespace tools rpc_transfers.global_index = td.m_global_output_index; rpc_transfers.tx_hash = epee::string_tools::pod_to_hex(td.m_txid); rpc_transfers.tx_size = txBlob.size(); + rpc_transfers.subaddr_index = td.m_subaddr_index.minor; res.transfers.push_back(rpc_transfers); } } @@ -1071,11 +1216,9 @@ namespace tools return false; } - cryptonote::account_public_address address; - bool has_payment_id; - crypto::hash8 payment_id; + cryptonote::address_parse_info info; er.message = ""; - if(!get_account_address_from_str_or_url(address, has_payment_id, payment_id, m_wallet->testnet(), req.address, + if(!get_account_address_from_str_or_url(info, m_wallet->testnet(), req.address, [&er](const std::string &url, const std::vector<std::string> &addresses, bool dnssec_valid)->std::string { if (!dnssec_valid) { @@ -1094,7 +1237,7 @@ namespace tools return false; } - res.good = m_wallet->verify(req.data, address, req.signature); + res.good = m_wallet->verify(req.data, info.address, req.signature); return true; } //------------------------------------------------------------------------------------------------------------------------------ @@ -1214,7 +1357,7 @@ namespace tools if (req.in) { std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> payments; - m_wallet->get_payments(payments, min_height, max_height); + m_wallet->get_payments(payments, min_height, max_height, req.account_index, req.subaddr_indices); for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) { res.in.push_back(wallet_rpc::transfer_entry()); fill_transfer_entry(res.in.back(), i->second.m_tx_hash, i->first, i->second); @@ -1224,7 +1367,7 @@ namespace tools if (req.out) { std::list<std::pair<crypto::hash, tools::wallet2::confirmed_transfer_details>> payments; - m_wallet->get_payments_out(payments, min_height, max_height); + m_wallet->get_payments_out(payments, min_height, max_height, req.account_index, req.subaddr_indices); for (std::list<std::pair<crypto::hash, tools::wallet2::confirmed_transfer_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) { res.out.push_back(wallet_rpc::transfer_entry()); fill_transfer_entry(res.out.back(), i->first, i->second); @@ -1233,7 +1376,7 @@ namespace tools if (req.pending || req.failed) { std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>> upayments; - m_wallet->get_unconfirmed_payments_out(upayments); + m_wallet->get_unconfirmed_payments_out(upayments, req.account_index, req.subaddr_indices); 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; bool is_failed = pd.m_state == tools::wallet2::unconfirmed_transfer_details::failed; @@ -1250,7 +1393,7 @@ namespace tools m_wallet->update_pool_state(); std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> payments; - m_wallet->get_unconfirmed_payments(payments); + m_wallet->get_unconfirmed_payments(payments, req.account_index, req.subaddr_indices); for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) { res.pool.push_back(wallet_rpc::transfer_entry()); fill_transfer_entry(res.pool.back(), i->first, i->second); @@ -1453,7 +1596,7 @@ namespace tools { uint64_t idx = 0; for (const auto &entry: ab) - res.entries.push_back(wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY::entry{idx++, get_account_address_as_str(m_wallet->testnet(), entry.m_address), epee::string_tools::pod_to_hex(entry.m_payment_id), entry.m_description}); + res.entries.push_back(wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY::entry{idx++, get_account_address_as_str(m_wallet->testnet(), entry.m_is_subaddress, entry.m_address), epee::string_tools::pod_to_hex(entry.m_payment_id), entry.m_description}); } else { @@ -1466,7 +1609,7 @@ namespace tools return false; } const auto &entry = ab[idx]; - res.entries.push_back(wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY::entry{idx, get_account_address_as_str(m_wallet->testnet(), entry.m_address), epee::string_tools::pod_to_hex(entry.m_payment_id), entry.m_description}); + res.entries.push_back(wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY::entry{idx, get_account_address_as_str(m_wallet->testnet(), entry.m_is_subaddress, entry.m_address), epee::string_tools::pod_to_hex(entry.m_payment_id), entry.m_description}); } } return true; @@ -1482,12 +1625,10 @@ namespace tools return false; } - cryptonote::account_public_address address; - bool has_payment_id; - crypto::hash8 payment_id8; - crypto::hash payment_id = cryptonote::null_hash; + cryptonote::address_parse_info info; + crypto::hash payment_id = crypto::null_hash; er.message = ""; - if(!get_account_address_from_str_or_url(address, has_payment_id, payment_id8, m_wallet->testnet(), req.address, + if(!get_account_address_from_str_or_url(info, m_wallet->testnet(), req.address, [&er](const std::string &url, const std::vector<std::string> &addresses, bool dnssec_valid)->std::string { if (!dnssec_valid) { @@ -1507,14 +1648,14 @@ namespace tools er.message = std::string("WALLET_RPC_ERROR_CODE_WRONG_ADDRESS: ") + req.address; return false; } - if (has_payment_id) + if (info.has_payment_id) { - memcpy(payment_id.data, payment_id8.data, 8); + memcpy(payment_id.data, info.payment_id.data, 8); memset(payment_id.data + 8, 0, 24); } if (!req.payment_id.empty()) { - if (has_payment_id) + if (info.has_payment_id) { er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; er.message = "Separate payment ID given with integrated address"; @@ -1526,7 +1667,7 @@ namespace tools if (!wallet2::parse_long_payment_id(req.payment_id, payment_id)) { - if (!wallet2::parse_short_payment_id(req.payment_id, payment_id8)) + if (!wallet2::parse_short_payment_id(req.payment_id, info.payment_id)) { er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; er.message = "Payment id has invalid format: \"" + req.payment_id + "\", expected 16 or 64 character string"; @@ -1534,18 +1675,18 @@ namespace tools } else { - memcpy(payment_id.data, payment_id8.data, 8); + memcpy(payment_id.data, info.payment_id.data, 8); memset(payment_id.data + 8, 0, 24); } } } - if (!m_wallet->add_address_book_row(address, payment_id, req.description)) + if (!m_wallet->add_address_book_row(info.address, payment_id, req.description, info.is_subaddress)) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; er.message = "Failed to add address book entry"; return false; } - res.index = m_wallet->get_address_book().size(); + res.index = m_wallet->get_address_book().size() - 1; return true; } //------------------------------------------------------------------------------------------------------------------------------ @@ -1896,7 +2037,15 @@ just_dir: wrpc.send_stop_signal(); }); LOG_PRINT_L0(tools::wallet_rpc_server::tr("Starting wallet rpc server")); - wrpc.run(); + try + { + wrpc.run(); + } + catch (const std::exception &e) + { + LOG_ERROR(tools::wallet_rpc_server::tr("Failed to run wallet: ") << e.what()); + return 1; + } LOG_PRINT_L0(tools::wallet_rpc_server::tr("Stopped wallet rpc server")); try { diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index dd54222b0..7f4c412e4 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -33,6 +33,7 @@ #include <boost/program_options/options_description.hpp> #include <boost/program_options/variables_map.hpp> #include <string> +#include "common/util.h" #include "net/http_server_impl_base.h" #include "wallet_rpc_server_commands_defs.h" #include "wallet2.h" @@ -68,6 +69,11 @@ namespace tools BEGIN_JSON_RPC_MAP("/json_rpc") MAP_JON_RPC_WE("getbalance", on_getbalance, wallet_rpc::COMMAND_RPC_GET_BALANCE) MAP_JON_RPC_WE("getaddress", on_getaddress, wallet_rpc::COMMAND_RPC_GET_ADDRESS) + MAP_JON_RPC_WE("create_address", on_create_address, wallet_rpc::COMMAND_RPC_CREATE_ADDRESS) + MAP_JON_RPC_WE("label_address", on_label_address, wallet_rpc::COMMAND_RPC_LABEL_ADDRESS) + MAP_JON_RPC_WE("get_accounts", on_get_accounts, wallet_rpc::COMMAND_RPC_GET_ACCOUNTS) + MAP_JON_RPC_WE("create_account", on_create_account, wallet_rpc::COMMAND_RPC_CREATE_ACCOUNT) + MAP_JON_RPC_WE("label_account", on_label_account, wallet_rpc::COMMAND_RPC_LABEL_ACCOUNT) MAP_JON_RPC_WE("getheight", on_getheight, wallet_rpc::COMMAND_RPC_GET_HEIGHT) MAP_JON_RPC_WE("transfer", on_transfer, wallet_rpc::COMMAND_RPC_TRANSFER) MAP_JON_RPC_WE("transfer_split", on_transfer_split, wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT) @@ -107,8 +113,13 @@ namespace tools //json_rpc bool on_getbalance(const wallet_rpc::COMMAND_RPC_GET_BALANCE::request& req, wallet_rpc::COMMAND_RPC_GET_BALANCE::response& res, epee::json_rpc::error& er); bool on_getaddress(const wallet_rpc::COMMAND_RPC_GET_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_GET_ADDRESS::response& res, epee::json_rpc::error& er); + bool on_create_address(const wallet_rpc::COMMAND_RPC_CREATE_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_CREATE_ADDRESS::response& res, epee::json_rpc::error& er); + bool on_label_address(const wallet_rpc::COMMAND_RPC_LABEL_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_LABEL_ADDRESS::response& res, epee::json_rpc::error& er); + bool on_get_accounts(const wallet_rpc::COMMAND_RPC_GET_ACCOUNTS::request& req, wallet_rpc::COMMAND_RPC_GET_ACCOUNTS::response& res, epee::json_rpc::error& er); + bool on_create_account(const wallet_rpc::COMMAND_RPC_CREATE_ACCOUNT::request& req, wallet_rpc::COMMAND_RPC_CREATE_ACCOUNT::response& res, epee::json_rpc::error& er); + bool on_label_account(const wallet_rpc::COMMAND_RPC_LABEL_ACCOUNT::request& req, wallet_rpc::COMMAND_RPC_LABEL_ACCOUNT::response& res, epee::json_rpc::error& er); bool on_getheight(const wallet_rpc::COMMAND_RPC_GET_HEIGHT::request& req, wallet_rpc::COMMAND_RPC_GET_HEIGHT::response& res, epee::json_rpc::error& er); - bool validate_transfer(const std::list<wallet_rpc::transfer_destination> destinations, const std::string payment_id, std::vector<cryptonote::tx_destination_entry>& dsts, std::vector<uint8_t>& extra, epee::json_rpc::error& er); + bool validate_transfer(const std::list<wallet_rpc::transfer_destination>& destinations, const std::string& payment_id, std::vector<cryptonote::tx_destination_entry>& dsts, std::vector<uint8_t>& extra, epee::json_rpc::error& er); bool on_transfer(const wallet_rpc::COMMAND_RPC_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_TRANSFER::response& res, epee::json_rpc::error& er); bool on_transfer_split(const wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT::request& req, wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT::response& res, epee::json_rpc::error& er); bool on_sweep_dust(const wallet_rpc::COMMAND_RPC_SWEEP_DUST::request& req, wallet_rpc::COMMAND_RPC_SWEEP_DUST::response& res, epee::json_rpc::error& er); @@ -154,7 +165,7 @@ namespace tools wallet2 *m_wallet; std::string m_wallet_dir; - std::string rpc_login_filename; + tools::private_file rpc_login_file; std::atomic<bool> m_stop; bool m_trusted_daemon; epee::net_utils::http::http_simple_client m_http_client; diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index fa5c154de..f652fa7ff 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -31,6 +31,7 @@ #pragma once #include "cryptonote_protocol/cryptonote_protocol_defs.h" #include "cryptonote_basic/cryptonote_basic.h" +#include "cryptonote_basic/subaddress_index.h" #include "crypto/hash.h" #include "wallet_rpc_server_error_codes.h" @@ -48,7 +49,28 @@ namespace wallet_rpc { struct request { + uint32_t account_index; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(account_index) + END_KV_SERIALIZE_MAP() + }; + + struct per_subaddress_info + { + uint32_t address_index; + std::string address; + uint64_t balance; + uint64_t unlocked_balance; + std::string label; + uint64_t num_unspent_outputs; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(address_index) + KV_SERIALIZE(address) + KV_SERIALIZE(balance) + KV_SERIALIZE(unlocked_balance) + KV_SERIALIZE(label) + KV_SERIALIZE(num_unspent_outputs) END_KV_SERIALIZE_MAP() }; @@ -56,10 +78,12 @@ namespace wallet_rpc { uint64_t balance; uint64_t unlocked_balance; + std::vector<per_subaddress_info> per_subaddress; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(balance) KV_SERIALIZE(unlocked_balance) + KV_SERIALIZE(per_subaddress) END_KV_SERIALIZE_MAP() }; }; @@ -68,20 +92,164 @@ namespace wallet_rpc { struct request { + uint32_t account_index; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(account_index) + END_KV_SERIALIZE_MAP() + }; + + struct address_info + { + std::string address; + std::string label; + uint32_t address_index; + bool used; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(address) + KV_SERIALIZE(label) + KV_SERIALIZE(address_index) + KV_SERIALIZE(used) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string address; // to remain compatible with older RPC format + std::vector<address_info> addresses; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(address) + KV_SERIALIZE(addresses) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_CREATE_ADDRESS + { + struct request + { + uint32_t account_index; + std::string label; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(account_index) + KV_SERIALIZE(label) END_KV_SERIALIZE_MAP() }; struct response { std::string address; + uint32_t address_index; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(address) + KV_SERIALIZE(address_index) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_LABEL_ADDRESS + { + struct request + { + cryptonote::subaddress_index index; + std::string label; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(index) + KV_SERIALIZE(label) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_GET_ACCOUNTS + { + struct request + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + + struct subaddress_account_info + { + uint32_t account_index; + std::string base_address; + uint64_t balance; + uint64_t unlocked_balance; + std::string label; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(account_index) + KV_SERIALIZE(base_address) + KV_SERIALIZE(balance) + KV_SERIALIZE(unlocked_balance) + KV_SERIALIZE(label) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + uint64_t total_balance; + uint64_t total_unlocked_balance; + std::vector<subaddress_account_info> subaddress_accounts; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(total_balance) + KV_SERIALIZE(total_unlocked_balance) + KV_SERIALIZE(subaddress_accounts) + END_KV_SERIALIZE_MAP() + }; + }; + struct COMMAND_RPC_CREATE_ACCOUNT + { + struct request + { + std::string label; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(label) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + uint32_t account_index; + std::string address; // the 0-th address for convenience + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(account_index) KV_SERIALIZE(address) END_KV_SERIALIZE_MAP() }; }; + struct COMMAND_RPC_LABEL_ACCOUNT + { + struct request + { + uint32_t account_index; + std::string label; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(account_index) + KV_SERIALIZE(label) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + }; + struct COMMAND_RPC_GET_HEIGHT { struct request @@ -114,6 +282,8 @@ namespace wallet_rpc struct request { std::list<transfer_destination> destinations; + uint32_t account_index; + std::set<uint32_t> subaddr_indices; uint32_t priority; uint64_t mixin; uint64_t unlock_time; @@ -124,6 +294,8 @@ namespace wallet_rpc BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(destinations) + KV_SERIALIZE(account_index) + KV_SERIALIZE(subaddr_indices) KV_SERIALIZE(priority) KV_SERIALIZE(mixin) KV_SERIALIZE(unlock_time) @@ -157,6 +329,8 @@ namespace wallet_rpc struct request { std::list<transfer_destination> destinations; + uint32_t account_index; + std::set<uint32_t> subaddr_indices; uint32_t priority; uint64_t mixin; uint64_t unlock_time; @@ -167,6 +341,8 @@ namespace wallet_rpc BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(destinations) + KV_SERIALIZE(account_index) + KV_SERIALIZE(subaddr_indices) KV_SERIALIZE(priority) KV_SERIALIZE(mixin) KV_SERIALIZE(unlock_time) @@ -249,6 +425,8 @@ namespace wallet_rpc struct request { std::string address; + uint32_t account_index; + std::set<uint32_t> subaddr_indices; uint32_t priority; uint64_t mixin; uint64_t unlock_time; @@ -260,6 +438,8 @@ namespace wallet_rpc BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(address) + KV_SERIALIZE(account_index) + KV_SERIALIZE(subaddr_indices) KV_SERIALIZE(priority) KV_SERIALIZE(mixin) KV_SERIALIZE(unlock_time) @@ -318,6 +498,7 @@ namespace wallet_rpc uint64_t amount; uint64_t block_height; uint64_t unlock_time; + cryptonote::subaddress_index subaddr_index; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(payment_id) @@ -325,6 +506,7 @@ namespace wallet_rpc KV_SERIALIZE(amount) KV_SERIALIZE(block_height) KV_SERIALIZE(unlock_time) + KV_SERIALIZE(subaddr_index) END_KV_SERIALIZE_MAP() }; @@ -379,6 +561,7 @@ namespace wallet_rpc uint64_t global_index; std::string tx_hash; uint64_t tx_size; + uint32_t subaddr_index; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(amount) @@ -386,6 +569,7 @@ namespace wallet_rpc KV_SERIALIZE(global_index) KV_SERIALIZE(tx_hash) KV_SERIALIZE(tx_size) + KV_SERIALIZE(subaddr_index) END_KV_SERIALIZE_MAP() }; @@ -394,9 +578,13 @@ namespace wallet_rpc struct request { std::string transfer_type; + uint32_t account_index; + std::set<uint32_t> subaddr_indices; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(transfer_type) + KV_SERIALIZE(account_index) + KV_SERIALIZE(subaddr_indices) END_KV_SERIALIZE_MAP() }; @@ -470,10 +658,12 @@ namespace wallet_rpc { std::string standard_address; std::string payment_id; + bool is_subaddress; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(standard_address) KV_SERIALIZE(payment_id) + KV_SERIALIZE(is_subaddress) END_KV_SERIALIZE_MAP() }; }; @@ -561,6 +751,7 @@ namespace wallet_rpc std::list<transfer_destination> destinations; std::string type; uint64_t unlock_time; + cryptonote::subaddress_index subaddr_index; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(txid); @@ -573,6 +764,7 @@ namespace wallet_rpc KV_SERIALIZE(destinations); KV_SERIALIZE(type); KV_SERIALIZE(unlock_time) + KV_SERIALIZE(subaddr_index); END_KV_SERIALIZE_MAP() }; @@ -589,6 +781,8 @@ namespace wallet_rpc bool filter_by_height; uint64_t min_height; uint64_t max_height; + uint32_t account_index; + std::set<uint32_t> subaddr_indices; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(in); @@ -599,6 +793,8 @@ namespace wallet_rpc KV_SERIALIZE(filter_by_height); KV_SERIALIZE(min_height); KV_SERIALIZE(max_height); + KV_SERIALIZE(account_index); + KV_SERIALIZE(subaddr_indices); END_KV_SERIALIZE_MAP() }; diff --git a/src/wallet/wallet_rpc_server_error_codes.h b/src/wallet/wallet_rpc_server_error_codes.h index 3c79c0ac3..cc9fd3856 100644 --- a/src/wallet/wallet_rpc_server_error_codes.h +++ b/src/wallet/wallet_rpc_server_error_codes.h @@ -44,3 +44,5 @@ #define WALLET_RPC_ERROR_CODE_WRONG_URI -11 #define WALLET_RPC_ERROR_CODE_WRONG_INDEX -12 #define WALLET_RPC_ERROR_CODE_NOT_OPEN -13 +#define WALLET_RPC_ERROR_CODE_ACCOUNT_INDEX_OUTOFBOUND -14 +#define WALLET_RPC_ERROR_CODE_ADDRESS_INDEX_OUTOFBOUND -15 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 85763f8b5..a5f5335db 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -30,6 +30,7 @@ # The docs say this only affects grouping in IDEs set(folder "tests") +set(TEST_DATA_DIR "${CMAKE_CURRENT_LIST_DIR}/data") if (WIN32 AND STATIC) add_definitions(-DSTATICLIB) diff --git a/tests/core_proxy/CMakeLists.txt b/tests/core_proxy/CMakeLists.txt index d22fecc9c..d2dc93cf0 100644 --- a/tests/core_proxy/CMakeLists.txt +++ b/tests/core_proxy/CMakeLists.txt @@ -40,6 +40,7 @@ target_link_libraries(core_proxy cryptonote_core cryptonote_protocol p2p + version epee ${CMAKE_THREAD_LIBS_INIT} ${EXTRA_LIBRARIES}) diff --git a/tests/core_proxy/core_proxy.cpp b/tests/core_proxy/core_proxy.cpp index 366937e1d..a0be3db96 100644 --- a/tests/core_proxy/core_proxy.cpp +++ b/tests/core_proxy/core_proxy.cpp @@ -229,10 +229,9 @@ bool tests::proxy_core::get_short_chain_history(std::list<crypto::hash>& ids) { return true; } -bool tests::proxy_core::get_blockchain_top(uint64_t& height, crypto::hash& top_id) { +void tests::proxy_core::get_blockchain_top(uint64_t& height, crypto::hash& top_id) { height = 0; top_id = get_block_hash(m_genesis); - return true; } bool tests::proxy_core::init(const boost::program_options::variables_map& /*vm*/) { @@ -256,7 +255,7 @@ void tests::proxy_core::build_short_history(std::list<crypto::hash> &m_history, m_history.push_front(cit->first); size_t n = 1 << m_history.size(); - while (m_hash2blkidx.end() != cit && cryptonote::null_hash != cit->second.blk.prev_id && n > 0) { + while (m_hash2blkidx.end() != cit && crypto::null_hash != cit->second.blk.prev_id && n > 0) { n--; cit = m_hash2blkidx.find(cit->second.blk.prev_id); } @@ -266,7 +265,7 @@ void tests::proxy_core::build_short_history(std::list<crypto::hash> &m_history, bool tests::proxy_core::add_block(const crypto::hash &_id, const crypto::hash &_longhash, const cryptonote::block &_blk, const cryptonote::blobdata &_blob) { size_t height = 0; - if (cryptonote::null_hash != _blk.prev_id) { + if (crypto::null_hash != _blk.prev_id) { std::unordered_map<crypto::hash, tests::block_index>::const_iterator cit = m_hash2blkidx.find(_blk.prev_id); if (m_hash2blkidx.end() == cit) { cerr << "ERROR: can't find previous block with id \"" << _blk.prev_id << "\"" << endl; diff --git a/tests/core_proxy/core_proxy.h b/tests/core_proxy/core_proxy.h index c4fb462e3..51f0f892c 100644 --- a/tests/core_proxy/core_proxy.h +++ b/tests/core_proxy/core_proxy.h @@ -46,7 +46,7 @@ namespace tests cryptonote::blobdata blob; std::list<cryptonote::transaction> txes; - block_index() : height(0), id(cryptonote::null_hash), longhash(cryptonote::null_hash) { } + block_index() : height(0), id(crypto::null_hash), longhash(crypto::null_hash) { } block_index(size_t _height, const crypto::hash &_id, const crypto::hash &_longhash, const cryptonote::block &_blk, const cryptonote::blobdata &_blob, const std::list<cryptonote::transaction> &_txes) : height(_height), id(_id), longhash(_longhash), blk(_blk), blob(_blob), txes(_txes) { } }; @@ -74,7 +74,7 @@ namespace tests bool get_short_chain_history(std::list<crypto::hash>& ids); bool get_stat_info(cryptonote::core_stat_info& st_inf){return true;} bool have_block(const crypto::hash& id); - bool get_blockchain_top(uint64_t& height, crypto::hash& top_id); + void get_blockchain_top(uint64_t& height, crypto::hash& top_id); bool handle_incoming_tx(const cryptonote::blobdata& tx_blob, cryptonote::tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay); bool handle_incoming_txs(const std::list<cryptonote::blobdata>& tx_blobs, std::vector<cryptonote::tx_verification_context>& tvc, bool keeped_by_block, bool relayed, bool do_not_relay); bool handle_incoming_block(const cryptonote::blobdata& block_blob, cryptonote::block_verification_context& bvc, bool update_miner_blocktemplate = true); @@ -100,5 +100,7 @@ namespace tests uint8_t get_ideal_hard_fork_version(uint64_t height) const { return 0; } uint8_t get_hard_fork_version(uint64_t height) const { return 0; } cryptonote::difficulty_type get_block_cumulative_difficulty(uint64_t height) const { return 0; } + bool fluffy_blocks_enabled() const { return false; } + uint64_t prevalidate_block_hashes(uint64_t height, const std::list<crypto::hash> &hashes) { return 0; } }; } diff --git a/tests/core_tests/CMakeLists.txt b/tests/core_tests/CMakeLists.txt index c1d3161bc..a24bd4fce 100644 --- a/tests/core_tests/CMakeLists.txt +++ b/tests/core_tests/CMakeLists.txt @@ -65,6 +65,7 @@ target_link_libraries(coretests PRIVATE cryptonote_core p2p + version epee ${CMAKE_THREAD_LIBS_INIT} ${EXTRA_LIBRARIES}) diff --git a/tests/core_tests/chain_switch_1.cpp b/tests/core_tests/chain_switch_1.cpp index a48105f49..b04d05219 100644 --- a/tests/core_tests/chain_switch_1.cpp +++ b/tests/core_tests/chain_switch_1.cpp @@ -152,7 +152,7 @@ bool gen_chain_switch_1::check_split_not_switched(cryptonote::core& c, size_t ev std::vector<size_t> tx_outs; uint64_t transfered; - lookup_acc_outs(m_recipient_account_4.get_keys(), tx_pool.front(), get_tx_pub_key_from_extra(tx_pool.front()), tx_outs, transfered); + lookup_acc_outs(m_recipient_account_4.get_keys(), tx_pool.front(), get_tx_pub_key_from_extra(tx_pool.front()), get_additional_tx_pub_keys_from_extra(tx_pool.front()), tx_outs, transfered); CHECK_EQ(MK_COINS(13), transfered); m_chain_1.swap(blocks); diff --git a/tests/core_tests/chaingen.cpp b/tests/core_tests/chaingen.cpp index b15487a24..f23aa8ecb 100644 --- a/tests/core_tests/chaingen.cpp +++ b/tests/core_tests/chaingen.cpp @@ -344,7 +344,7 @@ bool init_output_indices(map_output_idx_t& outs, std::map<uint64_t, std::vector< size_t tx_global_idx = outs[out.amount].size() - 1; outs[out.amount][tx_global_idx].idx = tx_global_idx; // Is out to me? - if (is_out_to_acc(from.get_keys(), boost::get<txout_to_key>(out.target), get_tx_pub_key_from_extra(tx), j)) { + if (is_out_to_acc(from.get_keys(), boost::get<txout_to_key>(out.target), get_tx_pub_key_from_extra(tx), get_additional_tx_pub_keys_from_extra(tx), j)) { outs_mine[out.amount].push_back(tx_global_idx); } } @@ -364,7 +364,10 @@ bool init_spent_output_indices(map_output_idx_t& outs, map_output_t& outs_mine, // construct key image for this output crypto::key_image img; keypair in_ephemeral; - generate_key_image_helper(from.get_keys(), get_tx_pub_key_from_extra(*oi.p_tx), oi.out_no, in_ephemeral, img); + crypto::public_key out_key = boost::get<txout_to_key>(oi.out).key; + std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses; + subaddresses[from.get_keys().m_account_address.m_spend_public_key] = {0,0}; + generate_key_image_helper(from.get_keys(), subaddresses, out_key, get_tx_pub_key_from_extra(*oi.p_tx), get_additional_tx_pub_keys_from_extra(*oi.p_tx), oi.out_no, in_ephemeral, img); // lookup for this key image in the events vector BOOST_FOREACH(auto& tx_pair, mtx) { diff --git a/tests/core_tests/integer_overflow.cpp b/tests/core_tests/integer_overflow.cpp index 75f374434..3ac55c073 100644 --- a/tests/core_tests/integer_overflow.cpp +++ b/tests/core_tests/integer_overflow.cpp @@ -169,10 +169,10 @@ bool gen_uint_overflow_2::generate(std::vector<test_event_entry>& events) const std::vector<cryptonote::tx_destination_entry> destinations; const account_public_address& bob_addr = bob_account.get_keys().m_account_address; - destinations.push_back(tx_destination_entry(MONEY_SUPPLY, bob_addr)); - destinations.push_back(tx_destination_entry(MONEY_SUPPLY - 1, bob_addr)); + destinations.push_back(tx_destination_entry(MONEY_SUPPLY, bob_addr, false)); + destinations.push_back(tx_destination_entry(MONEY_SUPPLY - 1, bob_addr, false)); // sources.front().amount = destinations[0].amount + destinations[2].amount + destinations[3].amount + TESTS_DEFAULT_FEE - destinations.push_back(tx_destination_entry(sources.front().amount - MONEY_SUPPLY - MONEY_SUPPLY + 1 - TESTS_DEFAULT_FEE, bob_addr)); + destinations.push_back(tx_destination_entry(sources.front().amount - MONEY_SUPPLY - MONEY_SUPPLY + 1 - TESTS_DEFAULT_FEE, bob_addr, false)); cryptonote::transaction tx_1; if (!construct_tx(miner_account.get_keys(), sources, destinations, std::vector<uint8_t>(), tx_1, 0)) diff --git a/tests/core_tests/rct.cpp b/tests/core_tests/rct.cpp index 21638354d..8a38cbc22 100644 --- a/tests/core_tests/rct.cpp +++ b/tests/core_tests/rct.cpp @@ -117,7 +117,10 @@ bool gen_rct_tx_validation_base::generate_with(std::vector<test_event_entry>& ev 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); + std::vector<crypto::secret_key> additional_tx_keys; + std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses; + subaddresses[miner_accounts[n].get_keys().m_account_address.m_spend_public_key] = {0,0}; + bool r = construct_tx_and_get_tx_key(miner_accounts[n].get_keys(), subaddresses, sources, destinations, cryptonote::account_public_address{}, std::vector<uint8_t>(), rct_txes[n], 0, tx_key, additional_tx_keys, 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])); @@ -215,7 +218,10 @@ bool gen_rct_tx_validation_base::generate_with(std::vector<test_event_entry>& ev 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); + std::vector<crypto::secret_key> additional_tx_keys; + std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses; + subaddresses[miner_accounts[0].get_keys().m_account_address.m_spend_public_key] = {0,0}; + bool r = construct_tx_and_get_tx_key(miner_accounts[0].get_keys(), subaddresses, sources, destinations, cryptonote::account_public_address{}, std::vector<uint8_t>(), tx, 0, tx_key, additional_tx_keys, true); CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction"); if (post_tx) @@ -484,8 +490,9 @@ bool gen_rct_tx_pre_rct_altered_extra::generate(std::vector<test_event_entry>& e const int mixin = 2; const int out_idx[] = {0, -1}; const uint64_t amount_paid = 10000; + bool failed = false; 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);}); + NULL, [&failed](transaction &tx) {std::string extra_nonce; crypto::hash pid = crypto::null_hash; set_payment_id_to_tx_extra_nonce(extra_nonce, pid); if (!add_extra_nonce_to_tx_extra(tx.extra, extra_nonce)) failed = true; }) && !failed; } bool gen_rct_tx_rct_altered_extra::generate(std::vector<test_event_entry>& events) const @@ -493,7 +500,8 @@ bool gen_rct_tx_rct_altered_extra::generate(std::vector<test_event_entry>& event const int mixin = 2; const int out_idx[] = {1, -1}; const uint64_t amount_paid = 10000; + bool failed = false; 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);}); + NULL, [&failed](transaction &tx) {std::string extra_nonce; crypto::hash pid = crypto::null_hash; set_payment_id_to_tx_extra_nonce(extra_nonce, pid); if (!add_extra_nonce_to_tx_extra(tx.extra, extra_nonce)) failed = true; }) && !failed; } diff --git a/tests/core_tests/ring_signature_1.cpp b/tests/core_tests/ring_signature_1.cpp index 997f3090d..f9ec68e45 100644 --- a/tests/core_tests/ring_signature_1.cpp +++ b/tests/core_tests/ring_signature_1.cpp @@ -337,7 +337,8 @@ bool gen_ring_signature_big::check_balances_2(cryptonote::core& c, size_t ev_ind std::vector<size_t> tx_outs; uint64_t transfered; - lookup_acc_outs(m_alice_account.get_keys(), boost::get<transaction>(events[events.size() - 3]), get_tx_pub_key_from_extra(boost::get<transaction>(events[events.size() - 3])), tx_outs, transfered); + const transaction& tx = boost::get<transaction>(events[events.size() - 3]); + lookup_acc_outs(m_alice_account.get_keys(), boost::get<transaction>(events[events.size() - 3]), get_tx_pub_key_from_extra(tx), get_additional_tx_pub_keys_from_extra(tx), tx_outs, transfered); CHECK_EQ(m_tx_amount, transfered); return true; diff --git a/tests/core_tests/transaction_tests.cpp b/tests/core_tests/transaction_tests.cpp index 8c0d92a8f..dcba36e80 100644 --- a/tests/core_tests/transaction_tests.cpp +++ b/tests/core_tests/transaction_tests.cpp @@ -125,11 +125,11 @@ bool test_transaction_generation_and_ring_signature() std::vector<size_t> outs; uint64_t money = 0; - r = lookup_acc_outs(rv_acc.get_keys(), tx_rc1, get_tx_pub_key_from_extra(tx_rc1), outs, money); + r = lookup_acc_outs(rv_acc.get_keys(), tx_rc1, get_tx_pub_key_from_extra(tx_rc1), get_additional_tx_pub_keys_from_extra(tx_rc1), outs, money); CHECK_AND_ASSERT_MES(r, false, "failed to lookup_acc_outs"); CHECK_AND_ASSERT_MES(td.amount == money, false, "wrong money amount in new transaction"); money = 0; - r = lookup_acc_outs(rv_acc2.get_keys(), tx_rc1, get_tx_pub_key_from_extra(tx_rc1), outs, money); + r = lookup_acc_outs(rv_acc2.get_keys(), tx_rc1, get_tx_pub_key_from_extra(tx_rc1), get_additional_tx_pub_keys_from_extra(tx_rc1), outs, money); CHECK_AND_ASSERT_MES(r, false, "failed to lookup_acc_outs"); CHECK_AND_ASSERT_MES(0 == money, false, "wrong money amount in new transaction"); return true; @@ -139,11 +139,11 @@ bool test_block_creation() { uint64_t vszs[] = {80,476,476,475,475,474,475,474,474,475,472,476,476,475,475,474,475,474,474,475,472,476,476,475,475,474,475,474,474,475,9391,476,476,475,475,474,475,8819,8301,475,472,4302,5316,14347,16620,19583,19403,19728,19442,19852,19015,19000,19016,19795,19749,18087,19787,19704,19750,19267,19006,19050,19445,19407,19522,19546,19788,19369,19486,19329,19370,18853,19600,19110,19320,19746,19474,19474,19743,19494,19755,19715,19769,19620,19368,19839,19532,23424,28287,30707}; std::vector<uint64_t> szs(&vszs[0], &vszs[90]); - account_public_address adr; - bool r = get_account_address_from_str(adr, false, "0099be99c70ef10fd534c43c88e9d13d1c8853213df7e362afbec0e4ee6fec4948d0c190b58f4b356cd7feaf8d9d0a76e7c7e5a9a0a497a6b1faf7a765882dd08ac2"); + address_parse_info info; + bool r = get_account_address_from_str(info, false, "0099be99c70ef10fd534c43c88e9d13d1c8853213df7e362afbec0e4ee6fec4948d0c190b58f4b356cd7feaf8d9d0a76e7c7e5a9a0a497a6b1faf7a765882dd08ac2"); CHECK_AND_ASSERT_MES(r, false, "failed to import"); block b; - r = construct_miner_tx(90, epee::misc_utils::median(szs), 3553616528562147, 33094, 10000000, adr, b.miner_tx, blobdata(), 11); + r = construct_miner_tx(90, epee::misc_utils::median(szs), 3553616528562147, 33094, 10000000, info.address, b.miner_tx, blobdata(), 11); return r; } diff --git a/tests/core_tests/tx_validation.cpp b/tests/core_tests/tx_validation.cpp index 66460e876..0e4b2e71a 100644 --- a/tests/core_tests/tx_validation.cpp +++ b/tests/core_tests/tx_validation.cpp @@ -59,7 +59,10 @@ namespace m_in_contexts.push_back(keypair()); keypair& in_ephemeral = m_in_contexts.back(); crypto::key_image img; - generate_key_image_helper(sender_account_keys, src_entr.real_out_tx_key, src_entr.real_output_in_tx_index, in_ephemeral, img); + std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses; + subaddresses[sender_account_keys.m_account_address.m_spend_public_key] = {0,0}; + auto& out_key = reinterpret_cast<const crypto::public_key&>(src_entr.outputs[src_entr.real_output].second.dest); + generate_key_image_helper(sender_account_keys, subaddresses, out_key, src_entr.real_out_tx_key, src_entr.real_out_additional_tx_keys, src_entr.real_output_in_tx_index, in_ephemeral, img); // put key image into tx input txin_to_key input_to_key; diff --git a/tests/crypto/main.cpp b/tests/crypto/main.cpp index 4264409b5..0fc8aba59 100644 --- a/tests/crypto/main.cpp +++ b/tests/crypto/main.cpp @@ -52,10 +52,6 @@ bool operator !=(const ec_point &a, const ec_point &b) { return 0 != memcmp(&a, &b, sizeof(ec_point)); } -bool operator !=(const secret_key &a, const secret_key &b) { - return 0 != memcmp(&a, &b, sizeof(secret_key)); -} - bool operator !=(const key_derivation &a, const key_derivation &b) { return 0 != memcmp(&a, &b, sizeof(key_derivation)); } @@ -99,7 +95,7 @@ int main(int argc, char *argv[]) { vector<char> data; ec_scalar expected, actual; get(input, data, expected); - hash_to_scalar(data.data(), data.size(), actual); + crypto::hash_to_scalar(data.data(), data.size(), actual); if (expected != actual) { goto error; } diff --git a/tests/functional_tests/transactions_flow_test.cpp b/tests/functional_tests/transactions_flow_test.cpp index 48e7b5ab6..d39b1b2e8 100644 --- a/tests/functional_tests/transactions_flow_test.cpp +++ b/tests/functional_tests/transactions_flow_test.cpp @@ -172,7 +172,7 @@ bool transactions_flow_test(std::string& working_folder, //wait for money, until balance will have enough money w1.refresh(blocks_fetched, received_money, ok); - while(w1.unlocked_balance() < amount_to_transfer) + while(w1.unlocked_balance(0) < amount_to_transfer) { misc_utils::sleep_no_w(1000); w1.refresh(blocks_fetched, received_money, ok); @@ -185,7 +185,7 @@ bool transactions_flow_test(std::string& working_folder, { tools::wallet2::transfer_container incoming_transfers; w1.get_transfers(incoming_transfers); - if(incoming_transfers.size() > FIRST_N_TRANSFERS && get_money_in_first_transfers(incoming_transfers, FIRST_N_TRANSFERS) < w1.unlocked_balance() ) + if(incoming_transfers.size() > FIRST_N_TRANSFERS && get_money_in_first_transfers(incoming_transfers, FIRST_N_TRANSFERS) < w1.unlocked_balance(0) ) { //lets go! size_t count = 0; @@ -220,7 +220,7 @@ bool transactions_flow_test(std::string& working_folder, for(i = 0; i != transactions_count; i++) { uint64_t amount_to_tx = (amount_to_transfer - transfered_money) > transfer_size ? transfer_size: (amount_to_transfer - transfered_money); - while(w1.unlocked_balance() < amount_to_tx + TEST_FEE) + while(w1.unlocked_balance(0) < amount_to_tx + TEST_FEE) { misc_utils::sleep_no_w(1000); LOG_PRINT_L0("not enough money, waiting for cashback or mining"); @@ -269,7 +269,7 @@ bool transactions_flow_test(std::string& working_folder, misc_utils::sleep_no_w(DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN*1000);//wait two blocks before sync on another wallet on another daemon } - uint64_t money_2 = w2.balance(); + uint64_t money_2 = w2.balance(0); if(money_2 == transfered_money) { MGINFO_GREEN("-----------------------FINISHING TRANSACTIONS FLOW TEST OK-----------------------"); diff --git a/tests/fuzz/signature.cpp b/tests/fuzz/signature.cpp index 69d0ad25b..071297ba8 100644 --- a/tests/fuzz/signature.cpp +++ b/tests/fuzz/signature.cpp @@ -66,9 +66,8 @@ int SignatureFuzzer::init() boost::filesystem::remove("/tmp/signature-test.address.txt"); boost::filesystem::remove("/tmp/signature-test"); - bool has_payment_id; - crypto::hash8 new_payment_id; - if (!cryptonote::get_account_address_from_str_or_url(address, has_payment_id, new_payment_id, true, "9uVsvEryzpN8WH2t1WWhFFCG5tS8cBNdmJYNRuckLENFimfauV5pZKeS1P2CbxGkSDTUPHXWwiYE5ZGSXDAGbaZgDxobqDN")) + cryptonote::address_parse_info info; + if (!cryptonote::get_account_address_from_str_or_url(info, true, "9uVsvEryzpN8WH2t1WWhFFCG5tS8cBNdmJYNRuckLENFimfauV5pZKeS1P2CbxGkSDTUPHXWwiYE5ZGSXDAGbaZgDxobqDN")) { std::cerr << "failed to parse address" << std::endl; return 1; diff --git a/tests/libwallet_api_tests/CMakeLists.txt b/tests/libwallet_api_tests/CMakeLists.txt index 51375440b..4c5542d91 100644 --- a/tests/libwallet_api_tests/CMakeLists.txt +++ b/tests/libwallet_api_tests/CMakeLists.txt @@ -40,6 +40,7 @@ add_executable(libwallet_api_tests target_link_libraries(libwallet_api_tests PRIVATE wallet + version epee ${Boost_CHRONO_LIBRARY} ${Boost_SERIALIZATION_LIBRARY} diff --git a/tests/libwallet_api_tests/main.cpp b/tests/libwallet_api_tests/main.cpp index bf0483b0f..a3cf227de 100644 --- a/tests/libwallet_api_tests/main.cpp +++ b/tests/libwallet_api_tests/main.cpp @@ -130,7 +130,7 @@ struct Utils { Monero::WalletManager *wmgr = Monero::WalletManagerFactory::getWalletManager(); Monero::Wallet * w = wmgr->openWallet(filename, password, true); - std::string result = w->address(); + std::string result = w->mainAddress(); wmgr->closeWallet(w); return result; } @@ -215,8 +215,8 @@ TEST_F(WalletManagerTest, WalletManagerCreatesWallet) boost::split(words, seed, boost::is_any_of(" "), boost::token_compress_on); ASSERT_TRUE(words.size() == 25); std::cout << "** seed: " << wallet->seed() << std::endl; - ASSERT_FALSE(wallet->address().empty()); - std::cout << "** address: " << wallet->address() << std::endl; + ASSERT_FALSE(wallet->mainAddress().empty()); + std::cout << "** address: " << wallet->mainAddress() << std::endl; ASSERT_TRUE(wmgr->closeWallet(wallet)); } @@ -261,7 +261,7 @@ void open_wallet_helper(Monero::WalletManager *wmgr, Monero::Wallet **wallet, co mutex->lock(); LOG_PRINT_L3("opening wallet in thread: " << boost::this_thread::get_id()); *wallet = wmgr->openWallet(WALLET_NAME, pass, true); - LOG_PRINT_L3("wallet address: " << (*wallet)->address()); + LOG_PRINT_L3("wallet address: " << (*wallet)->mainAddress()); LOG_PRINT_L3("wallet status: " << (*wallet)->status()); LOG_PRINT_L3("closing wallet in thread: " << boost::this_thread::get_id()); if (mutex) @@ -371,14 +371,14 @@ TEST_F(WalletManagerTest, WalletManagerRecoversWallet) { Monero::Wallet * wallet1 = wmgr->createWallet(WALLET_NAME, WALLET_PASS, WALLET_LANG); std::string seed1 = wallet1->seed(); - std::string address1 = wallet1->address(); + std::string address1 = wallet1->mainAddress(); ASSERT_FALSE(address1.empty()); ASSERT_TRUE(wmgr->closeWallet(wallet1)); Utils::deleteWallet(WALLET_NAME); Monero::Wallet * wallet2 = wmgr->recoveryWallet(WALLET_NAME, seed1); ASSERT_TRUE(wallet2->status() == Monero::Wallet::Status_Ok); ASSERT_TRUE(wallet2->seed() == seed1); - ASSERT_TRUE(wallet2->address() == address1); + ASSERT_TRUE(wallet2->mainAddress() == address1); ASSERT_TRUE(wmgr->closeWallet(wallet2)); } @@ -387,7 +387,7 @@ TEST_F(WalletManagerTest, WalletManagerStoresWallet1) { Monero::Wallet * wallet1 = wmgr->createWallet(WALLET_NAME, WALLET_PASS, WALLET_LANG); std::string seed1 = wallet1->seed(); - std::string address1 = wallet1->address(); + std::string address1 = wallet1->mainAddress(); ASSERT_TRUE(wallet1->store("")); ASSERT_TRUE(wallet1->store(WALLET_NAME_COPY)); @@ -395,7 +395,7 @@ TEST_F(WalletManagerTest, WalletManagerStoresWallet1) Monero::Wallet * wallet2 = wmgr->openWallet(WALLET_NAME_COPY, WALLET_PASS); ASSERT_TRUE(wallet2->status() == Monero::Wallet::Status_Ok); ASSERT_TRUE(wallet2->seed() == seed1); - ASSERT_TRUE(wallet2->address() == address1); + ASSERT_TRUE(wallet2->mainAddress() == address1); ASSERT_TRUE(wmgr->closeWallet(wallet2)); } @@ -404,7 +404,7 @@ TEST_F(WalletManagerTest, WalletManagerStoresWallet2) { Monero::Wallet * wallet1 = wmgr->createWallet(WALLET_NAME, WALLET_PASS, WALLET_LANG); std::string seed1 = wallet1->seed(); - std::string address1 = wallet1->address(); + std::string address1 = wallet1->mainAddress(); ASSERT_TRUE(wallet1->store(WALLET_NAME_WITH_DIR)); ASSERT_TRUE(wmgr->closeWallet(wallet1)); @@ -412,7 +412,7 @@ TEST_F(WalletManagerTest, WalletManagerStoresWallet2) wallet1 = wmgr->openWallet(WALLET_NAME_WITH_DIR, WALLET_PASS); ASSERT_TRUE(wallet1->status() == Monero::Wallet::Status_Ok); ASSERT_TRUE(wallet1->seed() == seed1); - ASSERT_TRUE(wallet1->address() == address1); + ASSERT_TRUE(wallet1->mainAddress() == address1); ASSERT_TRUE(wmgr->closeWallet(wallet1)); } @@ -421,7 +421,7 @@ TEST_F(WalletManagerTest, WalletManagerStoresWallet3) { Monero::Wallet * wallet1 = wmgr->createWallet(WALLET_NAME, WALLET_PASS, WALLET_LANG); std::string seed1 = wallet1->seed(); - std::string address1 = wallet1->address(); + std::string address1 = wallet1->mainAddress(); ASSERT_FALSE(wallet1->store(WALLET_NAME_WITH_DIR_NON_WRITABLE)); ASSERT_TRUE(wmgr->closeWallet(wallet1)); @@ -435,7 +435,7 @@ TEST_F(WalletManagerTest, WalletManagerStoresWallet3) wallet1 = wmgr->openWallet(WALLET_NAME, WALLET_PASS); ASSERT_TRUE(wallet1->status() == Monero::Wallet::Status_Ok); ASSERT_TRUE(wallet1->seed() == seed1); - ASSERT_TRUE(wallet1->address() == address1); + ASSERT_TRUE(wallet1->mainAddress() == address1); ASSERT_TRUE(wmgr->closeWallet(wallet1)); } @@ -445,7 +445,7 @@ TEST_F(WalletManagerTest, WalletManagerStoresWallet4) { Monero::Wallet * wallet1 = wmgr->createWallet(WALLET_NAME, WALLET_PASS, WALLET_LANG); std::string seed1 = wallet1->seed(); - std::string address1 = wallet1->address(); + std::string address1 = wallet1->mainAddress(); ASSERT_TRUE(wallet1->store("")); ASSERT_TRUE(wallet1->status() == Monero::Wallet::Status_Ok); @@ -458,7 +458,7 @@ TEST_F(WalletManagerTest, WalletManagerStoresWallet4) wallet1 = wmgr->openWallet(WALLET_NAME, WALLET_PASS); ASSERT_TRUE(wallet1->status() == Monero::Wallet::Status_Ok); ASSERT_TRUE(wallet1->seed() == seed1); - ASSERT_TRUE(wallet1->address() == address1); + ASSERT_TRUE(wallet1->mainAddress() == address1); ASSERT_TRUE(wmgr->closeWallet(wallet1)); } @@ -496,18 +496,18 @@ TEST_F(WalletTest1, WalletGeneratesIntegratedAddress) TEST_F(WalletTest1, WalletShowsBalance) { Monero::Wallet * wallet1 = wmgr->openWallet(CURRENT_SRC_WALLET, TESTNET_WALLET_PASS, true); - ASSERT_TRUE(wallet1->balance() > 0); - ASSERT_TRUE(wallet1->unlockedBalance() > 0); + ASSERT_TRUE(wallet1->balance(0) > 0); + ASSERT_TRUE(wallet1->unlockedBalance(0) > 0); - uint64_t balance1 = wallet1->balance(); - uint64_t unlockedBalance1 = wallet1->unlockedBalance(); + uint64_t balance1 = wallet1->balance(0); + uint64_t unlockedBalance1 = wallet1->unlockedBalance(0); ASSERT_TRUE(wmgr->closeWallet(wallet1)); Monero::Wallet * wallet2 = wmgr->openWallet(CURRENT_SRC_WALLET, TESTNET_WALLET_PASS, true); - ASSERT_TRUE(balance1 == wallet2->balance()); - std::cout << "wallet balance: " << wallet2->balance() << std::endl; - ASSERT_TRUE(unlockedBalance1 == wallet2->unlockedBalance()); - std::cout << "wallet unlocked balance: " << wallet2->unlockedBalance() << std::endl; + ASSERT_TRUE(balance1 == wallet2->balance(0)); + std::cout << "wallet balance: " << wallet2->balance(0) << std::endl; + ASSERT_TRUE(unlockedBalance1 == wallet2->unlockedBalance(0)); + std::cout << "wallet unlocked balance: " << wallet2->unlockedBalance(0) << std::endl; ASSERT_TRUE(wmgr->closeWallet(wallet2)); } @@ -569,7 +569,7 @@ TEST_F(WalletTest1, WalletTransaction) // make sure testnet daemon is running ASSERT_TRUE(wallet1->init(TESTNET_DAEMON_ADDRESS, 0)); ASSERT_TRUE(wallet1->refresh()); - uint64_t balance = wallet1->balance(); + uint64_t balance = wallet1->balance(0); ASSERT_TRUE(wallet1->status() == Monero::PendingTransaction::Status_Ok); std::string recepient_address = Utils::get_wallet_address(CURRENT_DST_WALLET, TESTNET_WALLET_PASS); @@ -580,14 +580,16 @@ TEST_F(WalletTest1, WalletTransaction) PAYMENT_ID_EMPTY, AMOUNT_10XMR, MIXIN_COUNT, + 0, + std::set<uint32_t>{}, Monero::PendingTransaction::Priority_Medium); ASSERT_TRUE(transaction->status() == Monero::PendingTransaction::Status_Ok); wallet1->refresh(); - ASSERT_TRUE(wallet1->balance() == balance); + ASSERT_TRUE(wallet1->balance(0) == balance); ASSERT_TRUE(transaction->amount() == AMOUNT_10XMR); ASSERT_TRUE(transaction->commit()); - ASSERT_FALSE(wallet1->balance() == balance); + ASSERT_FALSE(wallet1->balance(0) == balance); ASSERT_TRUE(wmgr->closeWallet(wallet1)); } @@ -611,14 +613,15 @@ TEST_F(WalletTest1, WalletTransactionWithMixin) // make sure testnet daemon is running ASSERT_TRUE(wallet1->init(TESTNET_DAEMON_ADDRESS, 0)); ASSERT_TRUE(wallet1->refresh()); - uint64_t balance = wallet1->balance(); + uint64_t balance = wallet1->balance(0); ASSERT_TRUE(wallet1->status() == Monero::PendingTransaction::Status_Ok); std::string recepient_address = Utils::get_wallet_address(CURRENT_DST_WALLET, TESTNET_WALLET_PASS); for (auto mixin : mixins) { std::cerr << "Transaction mixin count: " << mixin << std::endl; + Monero::PendingTransaction * transaction = wallet1->createTransaction( - recepient_address, payment_id, AMOUNT_5XMR, mixin); + recepient_address, payment_id, AMOUNT_5XMR, mixin, 0, std::set<uint32_t>{}); std::cerr << "Transaction status: " << transaction->status() << std::endl; std::cerr << "Transaction fee: " << Monero::Wallet::displayAmount(transaction->fee()) << std::endl; @@ -629,7 +632,7 @@ TEST_F(WalletTest1, WalletTransactionWithMixin) wallet1->refresh(); - ASSERT_TRUE(wallet1->balance() == balance); + ASSERT_TRUE(wallet1->balance(0) == balance); ASSERT_TRUE(wmgr->closeWallet(wallet1)); } @@ -643,7 +646,7 @@ TEST_F(WalletTest1, WalletTransactionWithPriority) // make sure testnet daemon is running ASSERT_TRUE(wallet1->init(TESTNET_DAEMON_ADDRESS, 0)); ASSERT_TRUE(wallet1->refresh()); - uint64_t balance = wallet1->balance(); + uint64_t balance = wallet1->balance(0); ASSERT_TRUE(wallet1->status() == Monero::PendingTransaction::Status_Ok); std::string recepient_address = Utils::get_wallet_address(CURRENT_DST_WALLET, TESTNET_WALLET_PASS); @@ -658,8 +661,9 @@ TEST_F(WalletTest1, WalletTransactionWithPriority) for (auto it = priorities.begin(); it != priorities.end(); ++it) { std::cerr << "Transaction priority: " << *it << std::endl; + Monero::PendingTransaction * transaction = wallet1->createTransaction( - recepient_address, payment_id, AMOUNT_5XMR, mixin, *it); + recepient_address, payment_id, AMOUNT_5XMR, mixin, 0, std::set<uint32_t>{}, *it); std::cerr << "Transaction status: " << transaction->status() << std::endl; std::cerr << "Transaction fee: " << Monero::Wallet::displayAmount(transaction->fee()) << std::endl; std::cerr << "Transaction error: " << transaction->errorString() << std::endl; @@ -669,7 +673,7 @@ TEST_F(WalletTest1, WalletTransactionWithPriority) wallet1->disposeTransaction(transaction); } wallet1->refresh(); - ASSERT_TRUE(wallet1->balance() == balance); + ASSERT_TRUE(wallet1->balance(0) == balance); ASSERT_TRUE(wmgr->closeWallet(wallet1)); } @@ -715,7 +719,7 @@ TEST_F(WalletTest1, WalletTransactionAndHistory) Monero::PendingTransaction * tx = wallet_src->createTransaction(wallet4_addr, PAYMENT_ID_EMPTY, - AMOUNT_10XMR * 5, 1); + AMOUNT_10XMR * 5, 1, 0, std::set<uint32_t>{}); ASSERT_TRUE(tx->status() == Monero::PendingTransaction::Status_Ok); ASSERT_TRUE(tx->commit()); @@ -757,7 +761,7 @@ TEST_F(WalletTest1, WalletTransactionWithPaymentId) Monero::PendingTransaction * tx = wallet_src->createTransaction(wallet4_addr, payment_id, - AMOUNT_1XMR, 1); + AMOUNT_1XMR, 1, 0, std::set<uint32_t>{}); ASSERT_TRUE(tx->status() == Monero::PendingTransaction::Status_Ok); ASSERT_TRUE(tx->commit()); @@ -811,12 +815,12 @@ struct MyWalletListener : public Monero::WalletListener void reset() { - send_triggered = receive_triggered = update_triggered = refresh_triggered = false; + send_triggered = receive_triggered = newblock_triggered = update_triggered = refresh_triggered = false; } virtual void moneySpent(const string &txId, uint64_t amount) { - std::cerr << "wallet: " << wallet->address() << "**** just spent money (" + std::cerr << "wallet: " << wallet->mainAddress() << "**** just spent money (" << txId << ", " << wallet->displayAmount(amount) << ")" << std::endl; total_tx += amount; send_triggered = true; @@ -825,7 +829,7 @@ struct MyWalletListener : public Monero::WalletListener virtual void moneyReceived(const string &txId, uint64_t amount) { - std::cout << "wallet: " << wallet->address() << "**** just received money (" + std::cout << "wallet: " << wallet->mainAddress() << "**** just received money (" << txId << ", " << wallet->displayAmount(amount) << ")" << std::endl; total_rx += amount; receive_triggered = true; @@ -834,7 +838,7 @@ struct MyWalletListener : public Monero::WalletListener virtual void unconfirmedMoneyReceived(const string &txId, uint64_t amount) { - std::cout << "wallet: " << wallet->address() << "**** just received unconfirmed money (" + std::cout << "wallet: " << wallet->mainAddress() << "**** just received unconfirmed money (" << txId << ", " << wallet->displayAmount(amount) << ")" << std::endl; // Don't trigger recieve until tx is mined // total_rx += amount; @@ -844,7 +848,7 @@ struct MyWalletListener : public Monero::WalletListener virtual void newBlock(uint64_t height) { -// std::cout << "wallet: " << wallet->address() +// std::cout << "wallet: " << wallet->mainAddress() // <<", new block received, blockHeight: " << height << std::endl; static int bc_height = wallet->daemonBlockChainHeight(); std::cout << height @@ -920,17 +924,17 @@ TEST_F(WalletTest2, WalletCallbackSent) ASSERT_TRUE(wallet_src->init(TESTNET_DAEMON_ADDRESS, 0)); ASSERT_TRUE(wallet_src->refresh()); MyWalletListener * wallet_src_listener = new MyWalletListener(wallet_src); - uint64_t balance = wallet_src->balance(); - std::cout << "** Balance: " << wallet_src->displayAmount(wallet_src->balance()) << std::endl; + uint64_t balance = wallet_src->balance(0); + std::cout << "** Balance: " << wallet_src->displayAmount(wallet_src->balance(0)) << std::endl; Monero::Wallet * wallet_dst = wmgr->openWallet(CURRENT_DST_WALLET, TESTNET_WALLET_PASS, true); uint64_t amount = AMOUNT_1XMR * 5; - std::cout << "** Sending " << Monero::Wallet::displayAmount(amount) << " to " << wallet_dst->address(); + std::cout << "** Sending " << Monero::Wallet::displayAmount(amount) << " to " << wallet_dst->mainAddress(); - Monero::PendingTransaction * tx = wallet_src->createTransaction(wallet_dst->address(), + Monero::PendingTransaction * tx = wallet_src->createTransaction(wallet_dst->mainAddress(), PAYMENT_ID_EMPTY, - amount, 1); + amount, 1, 0, std::set<uint32_t>{}); std::cout << "** Committing transaction: " << Monero::Wallet::displayAmount(tx->amount()) << " with fee: " << Monero::Wallet::displayAmount(tx->fee()); @@ -944,8 +948,8 @@ TEST_F(WalletTest2, WalletCallbackSent) std::cerr << "TEST: send lock acquired...\n"; ASSERT_TRUE(wallet_src_listener->send_triggered); ASSERT_TRUE(wallet_src_listener->update_triggered); - std::cout << "** Balance: " << wallet_src->displayAmount(wallet_src->balance()) << std::endl; - ASSERT_TRUE(wallet_src->balance() < balance); + std::cout << "** Balance: " << wallet_src->displayAmount(wallet_src->balance(0)) << std::endl; + ASSERT_TRUE(wallet_src->balance(0) < balance); wmgr->closeWallet(wallet_src); wmgr->closeWallet(wallet_dst); } @@ -958,20 +962,20 @@ TEST_F(WalletTest2, WalletCallbackReceived) // make sure testnet daemon is running ASSERT_TRUE(wallet_src->init(TESTNET_DAEMON_ADDRESS, 0)); ASSERT_TRUE(wallet_src->refresh()); - std::cout << "** Balance src1: " << wallet_src->displayAmount(wallet_src->balance()) << std::endl; + std::cout << "** Balance src1: " << wallet_src->displayAmount(wallet_src->balance(0)) << std::endl; Monero::Wallet * wallet_dst = wmgr->openWallet(CURRENT_DST_WALLET, TESTNET_WALLET_PASS, true); ASSERT_TRUE(wallet_dst->init(TESTNET_DAEMON_ADDRESS, 0)); ASSERT_TRUE(wallet_dst->refresh()); - uint64_t balance = wallet_dst->balance(); - std::cout << "** Balance dst1: " << wallet_dst->displayAmount(wallet_dst->balance()) << std::endl; + uint64_t balance = wallet_dst->balance(0); + std::cout << "** Balance dst1: " << wallet_dst->displayAmount(wallet_dst->balance(0)) << std::endl; std::unique_ptr<MyWalletListener> wallet_dst_listener (new MyWalletListener(wallet_dst)); uint64_t amount = AMOUNT_1XMR * 5; - std::cout << "** Sending " << Monero::Wallet::displayAmount(amount) << " to " << wallet_dst->address(); - Monero::PendingTransaction * tx = wallet_src->createTransaction(wallet_dst->address(), + std::cout << "** Sending " << Monero::Wallet::displayAmount(amount) << " to " << wallet_dst->mainAddress(); + Monero::PendingTransaction * tx = wallet_src->createTransaction(wallet_dst->mainAddress(), PAYMENT_ID_EMPTY, - amount, 1); + amount, 1, 0, std::set<uint32_t>{}); std::cout << "** Committing transaction: " << Monero::Wallet::displayAmount(tx->amount()) << " with fee: " << Monero::Wallet::displayAmount(tx->fee()); @@ -987,10 +991,10 @@ TEST_F(WalletTest2, WalletCallbackReceived) ASSERT_TRUE(wallet_dst_listener->receive_triggered); ASSERT_TRUE(wallet_dst_listener->update_triggered); - std::cout << "** Balance src2: " << wallet_dst->displayAmount(wallet_src->balance()) << std::endl; - std::cout << "** Balance dst2: " << wallet_dst->displayAmount(wallet_dst->balance()) << std::endl; + std::cout << "** Balance src2: " << wallet_dst->displayAmount(wallet_src->balance(0)) << std::endl; + std::cout << "** Balance dst2: " << wallet_dst->displayAmount(wallet_dst->balance(0)) << std::endl; - ASSERT_TRUE(wallet_dst->balance() > balance); + ASSERT_TRUE(wallet_dst->balance(0) > balance); wmgr->closeWallet(wallet_src); wmgr->closeWallet(wallet_dst); @@ -1099,7 +1103,7 @@ TEST_F(WalletManagerMainnetTest, RecoverAndRefreshWalletMainNetAsync) int SECONDS_TO_REFRESH = 120; Monero::Wallet * wallet = wmgr->createWallet(WALLET_NAME_MAINNET, "", WALLET_LANG); std::string seed = wallet->seed(); - std::string address = wallet->address(); + std::string address = wallet->mainAddress(); wmgr->closeWallet(wallet); // deleting wallet files @@ -1108,7 +1112,7 @@ TEST_F(WalletManagerMainnetTest, RecoverAndRefreshWalletMainNetAsync) wallet = wmgr->recoveryWallet(WALLET_NAME_MAINNET, seed); ASSERT_TRUE(wallet->status() == Monero::Wallet::Status_Ok); - ASSERT_TRUE(wallet->address() == address); + ASSERT_TRUE(wallet->mainAddress() == address); std::unique_ptr<MyWalletListener> wallet_listener (new MyWalletListener(wallet)); boost::chrono::seconds wait_for = boost::chrono::seconds(SECONDS_TO_REFRESH); boost::unique_lock<boost::mutex> lock (wallet_listener->mutex); diff --git a/tests/performance_tests/check_tx_signature.h b/tests/performance_tests/check_tx_signature.h index 1e02bfcaa..02555fae8 100644 --- a/tests/performance_tests/check_tx_signature.h +++ b/tests/performance_tests/check_tx_signature.h @@ -62,10 +62,13 @@ public: m_alice.generate(); std::vector<tx_destination_entry> destinations; - destinations.push_back(tx_destination_entry(this->m_source_amount, m_alice.get_keys().m_account_address)); + destinations.push_back(tx_destination_entry(this->m_source_amount, m_alice.get_keys().m_account_address, false)); crypto::secret_key tx_key; - if (!construct_tx_and_get_tx_key(this->m_miners[this->real_source_idx].get_keys(), this->m_sources, destinations, std::vector<uint8_t>(), m_tx, 0, tx_key, rct)) + std::vector<crypto::secret_key> additional_tx_keys; + std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses; + subaddresses[this->m_miners[this->real_source_idx].get_keys().m_account_address.m_spend_public_key] = {0,0}; + if (!construct_tx_and_get_tx_key(this->m_miners[this->real_source_idx].get_keys(), subaddresses, this->m_sources, destinations, cryptonote::account_public_address{}, std::vector<uint8_t>(), m_tx, 0, tx_key, additional_tx_keys, rct)) return false; get_transaction_prefix_hash(m_tx, m_tx_prefix_hash); diff --git a/tests/performance_tests/construct_tx.h b/tests/performance_tests/construct_tx.h index 0610c86ed..e01ee5cfa 100644 --- a/tests/performance_tests/construct_tx.h +++ b/tests/performance_tests/construct_tx.h @@ -43,7 +43,7 @@ class test_construct_tx : private multi_tx_test_base<a_in_count> static_assert(0 < a_out_count, "out_count must be greater than 0"); public: - static const size_t loop_count = (a_in_count + a_out_count < 100) ? 100 : 10; + static const size_t loop_count = (a_in_count + a_out_count < 10) ? (a_rct ? 10 : 200) : (a_in_count + a_out_count) < 100 ? (a_rct ? 5 : 25) : 5; static const size_t in_count = a_in_count; static const size_t out_count = a_out_count; static const bool rct = a_rct; @@ -61,7 +61,7 @@ public: for (size_t i = 0; i < out_count; ++i) { - m_destinations.push_back(tx_destination_entry(this->m_source_amount / out_count, m_alice.get_keys().m_account_address)); + m_destinations.push_back(tx_destination_entry(this->m_source_amount / out_count, m_alice.get_keys().m_account_address, false)); } return true; @@ -70,7 +70,10 @@ public: bool test() { crypto::secret_key tx_key; - return cryptonote::construct_tx_and_get_tx_key(this->m_miners[this->real_source_idx].get_keys(), this->m_sources, m_destinations, std::vector<uint8_t>(), m_tx, 0, tx_key, rct); + std::vector<crypto::secret_key> additional_tx_keys; + std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses; + subaddresses[this->m_miners[this->real_source_idx].get_keys().m_account_address.m_spend_public_key] = {0,0}; + return cryptonote::construct_tx_and_get_tx_key(this->m_miners[this->real_source_idx].get_keys(), subaddresses, this->m_sources, m_destinations, cryptonote::account_public_address{}, std::vector<uint8_t>(), m_tx, 0, tx_key, additional_tx_keys, rct); } private: diff --git a/tests/performance_tests/derive_secret_key.h b/tests/performance_tests/derive_secret_key.h index 1a2945a77..f97deb974 100644 --- a/tests/performance_tests/derive_secret_key.h +++ b/tests/performance_tests/derive_secret_key.h @@ -38,7 +38,7 @@ class test_derive_secret_key : public single_tx_test_base { public: - static const size_t loop_count = 1000000; + static const size_t loop_count = 10000; bool init() { diff --git a/tests/performance_tests/ge_frombytes_vartime.h b/tests/performance_tests/ge_frombytes_vartime.h index 491cea097..a5e64aeb2 100644 --- a/tests/performance_tests/ge_frombytes_vartime.h +++ b/tests/performance_tests/ge_frombytes_vartime.h @@ -52,7 +52,7 @@ public: m_alice.generate(); std::vector<tx_destination_entry> destinations; - destinations.push_back(tx_destination_entry(1, m_alice.get_keys().m_account_address)); + destinations.push_back(tx_destination_entry(1, m_alice.get_keys().m_account_address, false)); return construct_tx(this->m_miners[this->real_source_idx].get_keys(), this->m_sources, destinations, std::vector<uint8_t>(), m_tx, 0); } diff --git a/tests/performance_tests/generate_key_image_helper.h b/tests/performance_tests/generate_key_image_helper.h index 1332e7f62..bc92303eb 100644 --- a/tests/performance_tests/generate_key_image_helper.h +++ b/tests/performance_tests/generate_key_image_helper.h @@ -45,6 +45,9 @@ public: { cryptonote::keypair in_ephemeral; crypto::key_image ki; - return cryptonote::generate_key_image_helper(m_bob.get_keys(), m_tx_pub_key, 0, in_ephemeral, ki); + std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses; + subaddresses[m_bob.get_keys().m_account_address.m_spend_public_key] = {0,0}; + crypto::public_key out_key = boost::get<cryptonote::txout_to_key>(m_tx.vout[0].target).key; + return cryptonote::generate_key_image_helper(m_bob.get_keys(), subaddresses, out_key, m_tx_pub_key, m_additional_tx_pub_keys, 0, in_ephemeral, ki); } }; diff --git a/tests/performance_tests/is_out_to_acc.h b/tests/performance_tests/is_out_to_acc.h index 656815db9..12ef0f79f 100644 --- a/tests/performance_tests/is_out_to_acc.h +++ b/tests/performance_tests/is_out_to_acc.h @@ -44,6 +44,28 @@ public: bool test() { const cryptonote::txout_to_key& tx_out = boost::get<cryptonote::txout_to_key>(m_tx.vout[0].target); - return cryptonote::is_out_to_acc(m_bob.get_keys(), tx_out, m_tx_pub_key, 0); + return cryptonote::is_out_to_acc(m_bob.get_keys(), tx_out, m_tx_pub_key, m_additional_tx_pub_keys, 0); } }; + +class test_is_out_to_acc_precomp : public single_tx_test_base +{ +public: + static const size_t loop_count = 1000; + + bool init() + { + if (!single_tx_test_base::init()) + return false; + crypto::generate_key_derivation(m_tx_pub_key, m_bob.get_keys().m_view_secret_key, m_derivation); + return true; + } + bool test() + { + const cryptonote::txout_to_key& tx_out = boost::get<cryptonote::txout_to_key>(m_tx.vout[0].target); + return cryptonote::is_out_to_acc_precomp(m_bob.get_keys().m_account_address.m_spend_public_key, tx_out, m_derivation, 0); + } + +private: + crypto::key_derivation m_derivation; +}; diff --git a/tests/performance_tests/main.cpp b/tests/performance_tests/main.cpp index cc9fe86ef..3c0283eca 100644 --- a/tests/performance_tests/main.cpp +++ b/tests/performance_tests/main.cpp @@ -100,6 +100,7 @@ int main(int argc, char** argv) TEST_PERFORMANCE2(test_check_tx_signature, 100, true); TEST_PERFORMANCE0(test_is_out_to_acc); + TEST_PERFORMANCE0(test_is_out_to_acc_precomp); TEST_PERFORMANCE0(test_generate_key_image_helper); TEST_PERFORMANCE0(test_generate_key_derivation); TEST_PERFORMANCE0(test_generate_key_image); diff --git a/tests/performance_tests/single_tx_test_base.h b/tests/performance_tests/single_tx_test_base.h index 9e38d979b..f284b4198 100644 --- a/tests/performance_tests/single_tx_test_base.h +++ b/tests/performance_tests/single_tx_test_base.h @@ -47,6 +47,7 @@ public: return false; m_tx_pub_key = get_tx_pub_key_from_extra(m_tx); + m_additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(m_tx); return true; } @@ -54,4 +55,5 @@ protected: cryptonote::account_base m_bob; cryptonote::transaction m_tx; crypto::public_key m_tx_pub_key; + std::vector<crypto::public_key> m_additional_tx_pub_keys; }; diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt index 2f62dc2aa..1dac92bed 100644 --- a/tests/unit_tests/CMakeLists.txt +++ b/tests/unit_tests/CMakeLists.txt @@ -27,6 +27,7 @@ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. set(unit_tests_sources + apply_permutation.cpp address_from_url.cpp ban.cpp base58.cpp @@ -45,6 +46,7 @@ set(unit_tests_sources epee_utils.cpp fee.cpp get_xtype_from_string.cpp + hashchain.cpp http.cpp main.cpp mnemonics.cpp @@ -55,7 +57,6 @@ set(unit_tests_sources test_tx_utils.cpp test_peerlist.cpp test_protocol_pack.cpp - thread_group.cpp hardfork.cpp unbound.cpp uri.cpp @@ -79,6 +80,7 @@ target_link_libraries(unit_tests rpc wallet p2p + version epee ${Boost_CHRONO_LIBRARY} ${Boost_THREAD_LIBRARY} @@ -98,4 +100,4 @@ endif () add_test( NAME unit_tests - COMMAND unit_tests) + COMMAND unit_tests "${TEST_DATA_DIR}") diff --git a/tests/unit_tests/apply_permutation.cpp b/tests/unit_tests/apply_permutation.cpp new file mode 100644 index 000000000..a008b74ee --- /dev/null +++ b/tests/unit_tests/apply_permutation.cpp @@ -0,0 +1,74 @@ +// Copyright (c) 2017, 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 "gtest/gtest.h" +#include "common/apply_permutation.h" + +TEST(apply_permutation, empty) +{ + std::vector<int> v = {}; + tools::apply_permutation({}, v); + ASSERT_EQ(v, std::vector<int>({})); +} + +TEST(apply_permutation, reorder) +{ + // 0 1 2 3 4 5 6 + std::vector<int> v = {8, 4, 6, 1, 7, 2, 4}; + tools::apply_permutation({3, 5, 6, 1, 2, 4, 0}, v); + ASSERT_EQ(v, std::vector<int>({1, 2, 4, 4, 6, 7, 8})); +} + +TEST(apply_permutation, bad_size) +{ + std::vector<int> v_large = {8, 4, 6, 1, 7, 2, 4, 9}; + std::vector<int> v_small = {8, 4, 6, 1, 7, 2}; + try + { + tools::apply_permutation({3, 5, 6, 1, 2, 4, 0}, v_large); + ASSERT_FALSE(true); + } + catch (const std::exception &e) {} + try + { + tools::apply_permutation({3, 5, 6, 1, 2, 4, 0}, v_small); + ASSERT_FALSE(true); + } + catch (const std::exception &e) {} +} + +TEST(apply_permutation, bad_permutation) +{ + std::vector<int> v = {8, 4, 6, 1, 7, 2, 4}; + try + { + tools::apply_permutation({3, 5, 6, 1, 2, 4, 1}, v); + ASSERT_FALSE(true); + } + catch (const std::exception &e) {} +} diff --git a/tests/unit_tests/ban.cpp b/tests/unit_tests/ban.cpp index bcffc85c9..242e5fe1c 100644 --- a/tests/unit_tests/ban.cpp +++ b/tests/unit_tests/ban.cpp @@ -33,7 +33,7 @@ #include "p2p/net_node.h" #include "cryptonote_protocol/cryptonote_protocol_handler.h" -#define MAKE_IPV4_ADDRESS(a,b,c,d) new epee::net_utils::ipv4_network_address(MAKE_IP(a,b,c,d),0) +#define MAKE_IPV4_ADDRESS(a,b,c,d) epee::net_utils::ipv4_network_address{MAKE_IP(a,b,c,d),0} namespace cryptonote { class blockchain_storage; @@ -51,7 +51,7 @@ public: bool get_short_chain_history(std::list<crypto::hash>& ids) const { return true; } bool get_stat_info(cryptonote::core_stat_info& st_inf) const {return true;} bool have_block(const crypto::hash& id) const {return true;} - bool get_blockchain_top(uint64_t& height, crypto::hash& top_id)const{height=0;top_id=cryptonote::null_hash;return true;} + void get_blockchain_top(uint64_t& height, crypto::hash& top_id)const{height=0;top_id=crypto::null_hash;} bool handle_incoming_tx(const cryptonote::blobdata& tx_blob, cryptonote::tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay) { return true; } bool handle_incoming_txs(const std::list<cryptonote::blobdata>& tx_blob, std::vector<cryptonote::tx_verification_context>& tvc, bool keeped_by_block, bool relayed, bool do_not_relay) { return true; } bool handle_incoming_block(const cryptonote::blobdata& block_blob, cryptonote::block_verification_context& bvc, bool update_miner_blocktemplate = true) { return true; } @@ -77,6 +77,8 @@ public: uint8_t get_ideal_hard_fork_version(uint64_t height) const { return 0; } uint8_t get_hard_fork_version(uint64_t height) const { return 0; } cryptonote::difficulty_type get_block_cumulative_difficulty(uint64_t height) const { return 0; } + bool fluffy_blocks_enabled() const { return false; } + uint64_t prevalidate_block_hashes(uint64_t height, const std::list<crypto::hash> &hashes) { return 0; } }; typedef nodetool::node_server<cryptonote::t_cryptonote_protocol_handler<test_core>> Server; diff --git a/tests/unit_tests/base58.cpp b/tests/unit_tests/base58.cpp index c9d6245cb..75a1930b5 100644 --- a/tests/unit_tests/base58.cpp +++ b/tests/unit_tests/base58.cpp @@ -474,43 +474,43 @@ TEST(get_account_address_as_str, works_correctly) { cryptonote::account_public_address addr; ASSERT_TRUE(serialization::parse_binary(test_serialized_keys, addr)); - std::string addr_str = cryptonote::get_account_address_as_str(false, addr); + std::string addr_str = cryptonote::get_account_address_as_str(false, false, addr); ASSERT_EQ(addr_str, test_keys_addr_str); } TEST(get_account_address_from_str, handles_valid_address) { - cryptonote::account_public_address addr; - ASSERT_TRUE(cryptonote::get_account_address_from_str(addr, false, test_keys_addr_str)); + cryptonote::address_parse_info info; + ASSERT_TRUE(cryptonote::get_account_address_from_str(info, false, test_keys_addr_str)); std::string blob; - ASSERT_TRUE(serialization::dump_binary(addr, blob)); + ASSERT_TRUE(serialization::dump_binary(info.address, blob)); ASSERT_EQ(blob, test_serialized_keys); } TEST(get_account_address_from_str, fails_on_invalid_address_format) { - cryptonote::account_public_address addr; + cryptonote::address_parse_info info; std::string addr_str = test_keys_addr_str; addr_str[0] = '0'; - ASSERT_FALSE(cryptonote::get_account_address_from_str(addr, false, addr_str)); + ASSERT_FALSE(cryptonote::get_account_address_from_str(info, false, addr_str)); } TEST(get_account_address_from_str, fails_on_invalid_address_prefix) { std::string addr_str = base58::encode_addr(0, test_serialized_keys); - cryptonote::account_public_address addr; - ASSERT_FALSE(cryptonote::get_account_address_from_str(addr, false, addr_str)); + cryptonote::address_parse_info info; + ASSERT_FALSE(cryptonote::get_account_address_from_str(info, false, addr_str)); } TEST(get_account_address_from_str, fails_on_invalid_address_content) { std::string addr_str = base58::encode_addr(config::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX, test_serialized_keys.substr(1)); - cryptonote::account_public_address addr; - ASSERT_FALSE(cryptonote::get_account_address_from_str(addr, false, addr_str)); + cryptonote::address_parse_info info; + ASSERT_FALSE(cryptonote::get_account_address_from_str(info, false, addr_str)); } TEST(get_account_address_from_str, fails_on_invalid_address_spend_key) @@ -519,8 +519,8 @@ TEST(get_account_address_from_str, fails_on_invalid_address_spend_key) serialized_keys_copy[0] = '\0'; std::string addr_str = base58::encode_addr(config::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX, serialized_keys_copy); - cryptonote::account_public_address addr; - ASSERT_FALSE(cryptonote::get_account_address_from_str(addr, false, addr_str)); + cryptonote::address_parse_info info; + ASSERT_FALSE(cryptonote::get_account_address_from_str(info, false, addr_str)); } TEST(get_account_address_from_str, fails_on_invalid_address_view_key) @@ -529,12 +529,12 @@ TEST(get_account_address_from_str, fails_on_invalid_address_view_key) serialized_keys_copy.back() = '\x01'; std::string addr_str = base58::encode_addr(config::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX, serialized_keys_copy); - cryptonote::account_public_address addr; - ASSERT_FALSE(cryptonote::get_account_address_from_str(addr, false, addr_str)); + cryptonote::address_parse_info info; + ASSERT_FALSE(cryptonote::get_account_address_from_str(info, false, addr_str)); } TEST(get_account_address_from_str, parses_old_address_format) { - cryptonote::account_public_address addr; - ASSERT_TRUE(cryptonote::get_account_address_from_str(addr, false, "002391bbbb24dea6fd95232e97594a27769d0153d053d2102b789c498f57a2b00b69cd6f2f5c529c1660f2f4a2b50178d6640c20ce71fe26373041af97c5b10236fc")); + cryptonote::address_parse_info info; + ASSERT_TRUE(cryptonote::get_account_address_from_str(info, false, "002391bbbb24dea6fd95232e97594a27769d0153d053d2102b789c498f57a2b00b69cd6f2f5c529c1660f2f4a2b50178d6640c20ce71fe26373041af97c5b10236fc")); } diff --git a/tests/unit_tests/checkpoints.cpp b/tests/unit_tests/checkpoints.cpp index d3c4d3b2b..f6015db2f 100644 --- a/tests/unit_tests/checkpoints.cpp +++ b/tests/unit_tests/checkpoints.cpp @@ -30,7 +30,7 @@ #include "gtest/gtest.h" -#include "cryptonote_basic/checkpoints.cpp" +#include "checkpoints/checkpoints.cpp" using namespace cryptonote; diff --git a/tests/unit_tests/epee_utils.cpp b/tests/unit_tests/epee_utils.cpp index e8ddbe3f5..f6cb0c163 100644 --- a/tests/unit_tests/epee_utils.cpp +++ b/tests/unit_tests/epee_utils.cpp @@ -27,6 +27,7 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <array> +#include <boost/endian/conversion.hpp> #include <boost/range/algorithm/equal.hpp> #include <boost/range/algorithm_ext/iota.hpp> #include <cstdint> @@ -42,7 +43,11 @@ # include <arpa/inet.h> #endif +#include "boost/archive/portable_binary_iarchive.hpp" +#include "boost/archive/portable_binary_oarchive.hpp" #include "hex.h" +#include "net/net_utils_base.h" +#include "p2p/net_peerlist_boost_serialization.h" #include "span.h" #include "string_tools.h" @@ -100,6 +105,40 @@ namespace boost::range::iota(out, 0); return out; } + + #define CHECK_EQUAL(lhs, rhs) \ + EXPECT_TRUE( lhs == rhs ); \ + EXPECT_TRUE( rhs == lhs ); \ + EXPECT_FALSE( lhs != rhs ); \ + EXPECT_FALSE( rhs != lhs ); \ + EXPECT_FALSE( lhs < rhs ); \ + EXPECT_FALSE( rhs < lhs ); \ + EXPECT_TRUE( lhs <= rhs ); \ + EXPECT_TRUE( rhs <= lhs ); \ + EXPECT_FALSE( lhs > rhs ); \ + EXPECT_FALSE( rhs > lhs ); \ + EXPECT_TRUE( lhs >= rhs ); \ + EXPECT_TRUE( rhs >= lhs ) + + #define CHECK_LESS(lhs, rhs) \ + EXPECT_FALSE( lhs == rhs ); \ + EXPECT_FALSE( rhs == lhs ); \ + EXPECT_TRUE( lhs != rhs ); \ + EXPECT_TRUE( rhs != lhs ); \ + EXPECT_TRUE( lhs < rhs ); \ + EXPECT_FALSE( rhs < lhs ); \ + EXPECT_TRUE( lhs <= rhs ); \ + EXPECT_FALSE( rhs <= lhs ); \ + EXPECT_FALSE( lhs > rhs ); \ + EXPECT_TRUE( rhs > lhs ); \ + EXPECT_FALSE( lhs >= rhs ); \ + EXPECT_TRUE( rhs >= lhs ) + + #ifdef BOOST_LITTLE_ENDIAN + #define CHECK_LESS_ENDIAN(lhs, rhs) CHECK_LESS( rhs , lhs ) + #else + #define CHECK_LESS_ENDIAN(lhs, rhs) CHECK_LESS( lhs , rhs ) + #endif } TEST(Span, Traits) @@ -419,3 +458,195 @@ TEST(StringTools, GetIpInt32) EXPECT_EQ(htonl(0xff0aff00), ip); } +TEST(NetUtils, IPv4NetworkAddress) +{ + const auto ip1 = boost::endian::native_to_big(0x330012FFu); + const auto ip_loopback = boost::endian::native_to_big(0x7F000001u); + const auto ip_local = boost::endian::native_to_big(0x0A000000u); + + epee::net_utils::ipv4_network_address address1{ip1, 65535}; + CHECK_EQUAL(address1, address1); + EXPECT_STREQ("51.0.18.255:65535", address1.str().c_str()); + EXPECT_STREQ("51.0.18.255", address1.host_str().c_str()); + EXPECT_FALSE(address1.is_loopback()); + EXPECT_FALSE(address1.is_local()); + EXPECT_EQ(epee::net_utils::ipv4_network_address::ID, address1.get_type_id()); + EXPECT_EQ(ip1, address1.ip()); + EXPECT_EQ(65535, address1.port()); + EXPECT_TRUE(epee::net_utils::ipv4_network_address{std::move(address1)} == address1); + EXPECT_TRUE(epee::net_utils::ipv4_network_address{address1} == address1); + + const epee::net_utils::ipv4_network_address loopback{ip_loopback, 0}; + CHECK_EQUAL(loopback, loopback); + CHECK_LESS_ENDIAN(address1, loopback); + EXPECT_STREQ("127.0.0.1:0", loopback.str().c_str()); + EXPECT_STREQ("127.0.0.1", loopback.host_str().c_str()); + EXPECT_TRUE(loopback.is_loopback()); + EXPECT_FALSE(loopback.is_local()); + EXPECT_EQ(epee::net_utils::ipv4_network_address::ID, address1.get_type_id()); + EXPECT_EQ(ip_loopback, loopback.ip()); + EXPECT_EQ(0, loopback.port()); + + const epee::net_utils::ipv4_network_address local{ip_local, 8080}; + CHECK_EQUAL(local, local); + CHECK_LESS(local, address1); + CHECK_LESS(local, loopback); + EXPECT_FALSE(local.is_loopback()); + EXPECT_TRUE(local.is_local()); + + epee::net_utils::ipv4_network_address address2{ip1, 55}; + CHECK_EQUAL(address2, address2); + CHECK_LESS_ENDIAN(address2, loopback); + CHECK_LESS(local, address2); + EXPECT_STREQ("51.0.18.255:55", address2.str().c_str()); + EXPECT_STREQ("51.0.18.255", address2.host_str().c_str()); + + + address2 = std::move(address1); + CHECK_EQUAL(address2, address1); + + address2 = local; + CHECK_EQUAL(address2, local); + CHECK_LESS(address2, address1); + + { + std::stringstream stream; + { + boost::archive::portable_binary_oarchive ostream{stream}; + ostream << address1; + } + { + boost::archive::portable_binary_iarchive istream{stream}; + istream >> address2; + } + } + CHECK_EQUAL(address1, address2); + EXPECT_EQ(ip1, address2.ip()); + EXPECT_EQ(65535, address2.port()); +} + +TEST(NetUtils, NetworkAddress) +{ + const auto ip1 = boost::endian::native_to_big(0x330012FFu); + const auto ip_loopback = boost::endian::native_to_big(0x7F000001u); + const auto ip_local = boost::endian::native_to_big(0x0A000000u); + + struct custom_address { + constexpr static bool equal(const custom_address&) noexcept { return false; } + constexpr static bool less(const custom_address&) noexcept { return false; } + constexpr static bool is_same_host(const custom_address&) noexcept { return false; } + constexpr static bool is_loopback() noexcept { return false; } + constexpr static bool is_local() noexcept { return false; } + static std::string str() { return {}; } + static std::string host_str() { return {}; } + constexpr static uint8_t get_type_id() noexcept { return uint8_t(-1); } + }; + + const epee::net_utils::network_address empty; + CHECK_EQUAL(empty, empty); + EXPECT_TRUE(empty.is_same_host(empty)); + EXPECT_STREQ("<none>", empty.str().c_str()); + EXPECT_STREQ("<none>", empty.host_str().c_str()); + EXPECT_FALSE(empty.is_loopback()); + EXPECT_FALSE(empty.is_local()); + EXPECT_EQ(0, empty.get_type_id()); + EXPECT_THROW(empty.as<custom_address>(), std::bad_cast); + + epee::net_utils::network_address address1{ + epee::net_utils::ipv4_network_address{ip1, 65535} + }; + CHECK_EQUAL(address1, address1); + CHECK_EQUAL(epee::net_utils::network_address{address1}, address1); + CHECK_LESS(empty, address1); + EXPECT_TRUE(address1.is_same_host(address1)); + EXPECT_FALSE(empty.is_same_host(address1)); + EXPECT_FALSE(address1.is_same_host(empty)); + EXPECT_STREQ("51.0.18.255:65535", address1.str().c_str()); + EXPECT_STREQ("51.0.18.255", address1.host_str().c_str()); + EXPECT_FALSE(address1.is_loopback()); + EXPECT_FALSE(address1.is_local()); + EXPECT_EQ(epee::net_utils::ipv4_network_address::ID, address1.get_type_id()); + EXPECT_NO_THROW(address1.as<epee::net_utils::ipv4_network_address>()); + EXPECT_THROW(address1.as<custom_address>(), std::bad_cast); + + const epee::net_utils::network_address loopback{ + epee::net_utils::ipv4_network_address{ip_loopback, 0} + }; + CHECK_EQUAL(loopback, loopback); + CHECK_LESS(empty, loopback); + CHECK_LESS_ENDIAN(address1, loopback); + EXPECT_TRUE(loopback.is_same_host(loopback)); + EXPECT_FALSE(loopback.is_same_host(address1)); + EXPECT_FALSE(address1.is_same_host(loopback)); + EXPECT_STREQ("127.0.0.1:0", loopback.str().c_str()); + EXPECT_STREQ("127.0.0.1", loopback.host_str().c_str()); + EXPECT_TRUE(loopback.is_loopback()); + EXPECT_FALSE(loopback.is_local()); + EXPECT_EQ(epee::net_utils::ipv4_network_address::ID, address1.get_type_id()); + + const epee::net_utils::network_address local{ + epee::net_utils::ipv4_network_address{ip_local, 8080} + }; + CHECK_EQUAL(local, local); + CHECK_LESS(local, loopback); + CHECK_LESS(local, address1); + EXPECT_FALSE(local.is_loopback()); + EXPECT_TRUE(local.is_local()); + + epee::net_utils::network_address address2{ + epee::net_utils::ipv4_network_address{ip1, 55} + }; + CHECK_EQUAL(address2, address2); + CHECK_LESS(address2, address1); + CHECK_LESS(local, address2); + CHECK_LESS_ENDIAN(address2, loopback); + EXPECT_TRUE(address1.is_same_host(address2)); + EXPECT_TRUE(address2.is_same_host(address1)); + EXPECT_STREQ("51.0.18.255:55", address2.str().c_str()); + EXPECT_STREQ("51.0.18.255", address2.host_str().c_str()); + + address2 = std::move(address1); + CHECK_EQUAL(address1, address1); + CHECK_EQUAL(empty, address1); + CHECK_LESS(address1, address2); + EXPECT_FALSE(address1.is_same_host(address2)); + EXPECT_FALSE(address2.is_same_host(address1)); + EXPECT_STREQ("51.0.18.255:65535", address2.str().c_str()); + EXPECT_STREQ("51.0.18.255", address2.host_str().c_str()); + EXPECT_FALSE(address1.is_loopback()); + EXPECT_FALSE(address1.is_local()); + EXPECT_THROW(address1.as<epee::net_utils::ipv4_network_address>(), std::bad_cast); + EXPECT_NO_THROW(address2.as<epee::net_utils::ipv4_network_address>()); + + address2 = local; + CHECK_EQUAL(address2, local); + CHECK_LESS(address1, address2); + EXPECT_TRUE(address2.is_same_host(local)); + EXPECT_TRUE(local.is_same_host(address2)); + EXPECT_FALSE(address2.is_same_host(address1)); + EXPECT_FALSE(address1.is_same_host(address2)); + + { + std::stringstream stream; + { + boost::archive::portable_binary_oarchive ostream{stream}; + ostream << address2; + } + { + boost::archive::portable_binary_iarchive istream{stream}; + istream >> address1; + } + } + CHECK_EQUAL(address1, address2); + EXPECT_TRUE(address1.is_same_host(address2)); + EXPECT_TRUE(address2.is_same_host(address1)); + EXPECT_NO_THROW(address1.as<epee::net_utils::ipv4_network_address>()); + + address1 = custom_address{}; + CHECK_EQUAL(address1, address1); + CHECK_LESS(address2, address1); + EXPECT_FALSE(address1.is_same_host(loopback)); + EXPECT_FALSE(loopback.is_same_host(address1)); + EXPECT_THROW(address1.as<epee::net_utils::ipv4_network_address>(), std::bad_cast); + EXPECT_NO_THROW(address1.as<custom_address>()); +} diff --git a/tests/unit_tests/hardfork.cpp b/tests/unit_tests/hardfork.cpp index c30feb461..2b0904224 100644 --- a/tests/unit_tests/hardfork.cpp +++ b/tests/unit_tests/hardfork.cpp @@ -53,7 +53,7 @@ public: virtual std::string get_db_name() const { return std::string(); } virtual bool lock() { return true; } virtual void unlock() { } - virtual bool batch_start(uint64_t batch_num_blocks=0) { return true; } + virtual bool batch_start(uint64_t batch_num_blocks=0, uint64_t batch_bytes=0) { return true; } virtual void batch_stop() {} virtual void set_batch_transactions(bool) {} virtual void block_txn_start(bool readonly=false) {} diff --git a/tests/unit_tests/hashchain.cpp b/tests/unit_tests/hashchain.cpp new file mode 100644 index 000000000..0fa0f784a --- /dev/null +++ b/tests/unit_tests/hashchain.cpp @@ -0,0 +1,129 @@ +// Copyright (c) 2014-2017, 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" + +static crypto::hash make_hash(uint64_t n) +{ + union + { + crypto::hash hash; + uint64_t n; + } hash; + hash.hash = crypto::null_hash; + hash.n = n; + return hash.hash; +} + +TEST(hashchain, empty) +{ + tools::hashchain hashchain; + ASSERT_EQ(hashchain.size(), 0); + ASSERT_EQ(hashchain.offset(), 0); +} + +TEST(hashchain, genesis) +{ + tools::hashchain hashchain; + hashchain.push_back(make_hash(1)); + ASSERT_EQ(hashchain.size(), 1); + ASSERT_EQ(hashchain.genesis(), make_hash(1)); + hashchain.push_back(make_hash(2)); + ASSERT_EQ(hashchain.size(), 2); + ASSERT_EQ(hashchain.genesis(), make_hash(1)); +} + +TEST(hashchain, push_back) +{ + tools::hashchain hashchain; + hashchain.push_back(make_hash(1)); + hashchain.push_back(make_hash(2)); + hashchain.push_back(make_hash(3)); + ASSERT_EQ(hashchain[0], make_hash(1)); + ASSERT_EQ(hashchain[1], make_hash(2)); + ASSERT_EQ(hashchain[2], make_hash(3)); +} + +TEST(hashchain, clear_empty) +{ + tools::hashchain hashchain; + ASSERT_TRUE(hashchain.empty()); + hashchain.push_back(make_hash(1)); + ASSERT_FALSE(hashchain.empty()); + hashchain.push_back(make_hash(2)); + ASSERT_FALSE(hashchain.empty()); + hashchain.clear(); + ASSERT_TRUE(hashchain.empty()); +} + +TEST(hashchain, crop) +{ + tools::hashchain hashchain; + hashchain.push_back(make_hash(1)); + hashchain.push_back(make_hash(2)); + hashchain.push_back(make_hash(3)); + ASSERT_EQ(hashchain.size(), 3); + ASSERT_EQ(hashchain[0], make_hash(1)); + ASSERT_EQ(hashchain[1], make_hash(2)); + ASSERT_EQ(hashchain[2], make_hash(3)); + hashchain.crop(3); + ASSERT_EQ(hashchain.size(), 3); + hashchain.crop(2); + ASSERT_EQ(hashchain.size(), 2); + ASSERT_EQ(hashchain[0], make_hash(1)); + ASSERT_EQ(hashchain[1], make_hash(2)); + ASSERT_EQ(hashchain.genesis(), make_hash(1)); + hashchain.crop(0); + ASSERT_TRUE(hashchain.empty()); + ASSERT_EQ(hashchain.size(), 0); + hashchain.push_back(make_hash(5)); + ASSERT_EQ(hashchain.genesis(), make_hash(5)); + ASSERT_EQ(hashchain.size(), 1); +} + +TEST(hashchain, trim) +{ + tools::hashchain hashchain; + hashchain.push_back(make_hash(1)); + hashchain.push_back(make_hash(2)); + hashchain.push_back(make_hash(3)); + ASSERT_EQ(hashchain.offset(), 0); + hashchain.trim(2); + ASSERT_EQ(hashchain.offset(), 2); + ASSERT_EQ(hashchain.size(), 3); + ASSERT_EQ(hashchain[2], make_hash(3)); + hashchain.trim(3); + ASSERT_EQ(hashchain.offset(), 3); + ASSERT_EQ(hashchain.size(), 3); + ASSERT_FALSE(hashchain.empty()); + ASSERT_EQ(hashchain.genesis(), make_hash(1)); +} diff --git a/tests/unit_tests/main.cpp b/tests/unit_tests/main.cpp index b470249a3..b2abad942 100644 --- a/tests/unit_tests/main.cpp +++ b/tests/unit_tests/main.cpp @@ -30,14 +30,32 @@ #include "gtest/gtest.h" +#include <boost/filesystem.hpp> + #include "include_base_utils.h" +#include "unit_tests_utils.h" + +boost::filesystem::path unit_test::data_dir; int main(int argc, char** argv) { - epee::string_tools::set_module_name_and_folder(argv[0]); mlog_configure(mlog_get_default_log_path("unit_tests.log"), true); epee::debug::get_set_enable_assert(true, false); ::testing::InitGoogleTest(&argc, argv); + + // Process remaining arguments + if (argc == 2 && argv[1] != NULL) { // one arg: path to dir with test data + unit_test::data_dir = argv[1]; + } else if (argc == 1) { // legacy: assume test binaries in 'build/release' + epee::string_tools::set_module_name_and_folder(argv[0]); + unit_test::data_dir = boost::filesystem::path(epee::string_tools::get_current_module_folder()) + .parent_path().parent_path().parent_path().parent_path() + .append("tests").append("data"); + } else { + std::cerr << "Usage: " << argv[0] << " [<path-to-test-data-dir>]" << std::endl; + return 1; + } + return RUN_ALL_TESTS(); } diff --git a/tests/unit_tests/mnemonics.cpp b/tests/unit_tests/mnemonics.cpp index 05894adf4..39ec394a1 100644 --- a/tests/unit_tests/mnemonics.cpp +++ b/tests/unit_tests/mnemonics.cpp @@ -45,6 +45,7 @@ #include "mnemonics/french.h" #include "mnemonics/dutch.h" #include "mnemonics/esperanto.h" +#include "mnemonics/lojban.h" #include "mnemonics/english_old.h" #include "mnemonics/language_base.h" #include "mnemonics/singleton.h" @@ -167,7 +168,8 @@ TEST(mnemonics, all_languages) Language::Singleton<Language::Russian>::instance(), Language::Singleton<Language::French>::instance(), Language::Singleton<Language::Dutch>::instance(), - Language::Singleton<Language::Esperanto>::instance() + Language::Singleton<Language::Esperanto>::instance(), + Language::Singleton<Language::Lojban>::instance() }); for (std::vector<Language::Base*>::iterator it = languages.begin(); it != languages.end(); it++) diff --git a/tests/unit_tests/output_selection.cpp b/tests/unit_tests/output_selection.cpp index ed436dffd..6ff73b107 100644 --- a/tests/unit_tests/output_selection.cpp +++ b/tests/unit_tests/output_selection.cpp @@ -42,7 +42,7 @@ static tools::wallet2::transfer_container make_transfers_container(size_t N) 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 = crypto::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; diff --git a/tests/unit_tests/serialization.cpp b/tests/unit_tests/serialization.cpp index 6f9fe7d11..202c8718f 100644 --- a/tests/unit_tests/serialization.cpp +++ b/tests/unit_tests/serialization.cpp @@ -47,6 +47,7 @@ #include "serialization/binary_utils.h" #include "wallet/wallet2.h" #include "gtest/gtest.h" +#include "unit_tests_utils.h" using namespace std; struct Struct @@ -671,12 +672,12 @@ TEST(Serialization, portability_wallet) const bool testnet = true; const bool restricted = false; tools::wallet2 w(testnet, restricted); - string wallet_file = epee::string_tools::get_current_module_folder() + "/../../../../tests/data/wallet_9svHk1"; + const boost::filesystem::path wallet_file = unit_test::data_dir / "wallet_9svHk1"; string password = "test"; bool r = false; try { - w.load(wallet_file, password); + w.load(wallet_file.native(), password); r = true; } catch (const exception& e) @@ -791,9 +792,9 @@ TEST(Serialization, portability_wallet) TEST(Serialization, portability_outputs) { // read file - const std::string filename = epee::string_tools::get_current_module_folder() + "/../../../../tests/data/outputs"; + const boost::filesystem::path filename = unit_test::data_dir / "outputs"; std::string data; - bool r = epee::file_io_utils::load_file_to_string(filename, data); + bool r = epee::file_io_utils::load_file_to_string(filename.native(), data); ASSERT_TRUE(r); const size_t magiclen = strlen(OUTPUT_EXPORT_FILE_MAGIC); ASSERT_FALSE(data.size() < magiclen || memcmp(data.data(), OUTPUT_EXPORT_FILE_MAGIC, magiclen)); @@ -906,10 +907,10 @@ TEST(Serialization, portability_outputs) #define UNSIGNED_TX_PREFIX "Monero unsigned tx set\003" TEST(Serialization, portability_unsigned_tx) { - const string filename = epee::string_tools::get_current_module_folder() + "/../../../../tests/data/unsigned_monero_tx"; + const boost::filesystem::path filename = unit_test::data_dir / "unsigned_monero_tx"; std::string s; const bool testnet = true; - bool r = epee::file_io_utils::load_file_to_string(filename, s); + bool r = epee::file_io_utils::load_file_to_string(filename.native(), s); ASSERT_TRUE(r); const size_t magiclen = strlen(UNSIGNED_TX_PREFIX); ASSERT_FALSE(strncmp(s.c_str(), UNSIGNED_TX_PREFIX, magiclen)); @@ -986,15 +987,15 @@ TEST(Serialization, portability_unsigned_tx) ASSERT_TRUE(epee::string_tools::pod_to_hex(tse.mask) == "789bafff169ef206aa21219342c69ca52ce1d78d776c10b21d14bdd960fc7703"); // tcd.change_dts ASSERT_TRUE(tcd.change_dts.amount == 9631208773403); - ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, tcd.change_dts.addr) == "9svHk1wHPo3ULf2AZykghzcye6sitaRE4MaDjPC6uanTHCynHjJHZaiAb922PojE1GexhhRt1LVf5DC43feyrRZMLXQr3mk"); + ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, false, tcd.change_dts.addr) == "9svHk1wHPo3ULf2AZykghzcye6sitaRE4MaDjPC6uanTHCynHjJHZaiAb922PojE1GexhhRt1LVf5DC43feyrRZMLXQr3mk"); // tcd.splitted_dsts ASSERT_TRUE(tcd.splitted_dsts.size() == 2); auto& splitted_dst0 = tcd.splitted_dsts[0]; auto& splitted_dst1 = tcd.splitted_dsts[1]; ASSERT_TRUE(splitted_dst0.amount == 1400000000000); ASSERT_TRUE(splitted_dst1.amount == 9631208773403); - ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, splitted_dst0.addr) == "9xnhrMczQkPeoGi6dyu6BgKAYX4tZsDs6KHCkyTStDBKL4M4pM1gfCR3utmTAcSaKHGa1R5o266FbdnubErmij3oMdLyYgA"); - ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, splitted_dst1.addr) == "9svHk1wHPo3ULf2AZykghzcye6sitaRE4MaDjPC6uanTHCynHjJHZaiAb922PojE1GexhhRt1LVf5DC43feyrRZMLXQr3mk"); + ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, false, splitted_dst0.addr) == "9xnhrMczQkPeoGi6dyu6BgKAYX4tZsDs6KHCkyTStDBKL4M4pM1gfCR3utmTAcSaKHGa1R5o266FbdnubErmij3oMdLyYgA"); + ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, false, splitted_dst1.addr) == "9svHk1wHPo3ULf2AZykghzcye6sitaRE4MaDjPC6uanTHCynHjJHZaiAb922PojE1GexhhRt1LVf5DC43feyrRZMLXQr3mk"); // tcd.selected_transfers ASSERT_TRUE(tcd.selected_transfers.size() == 1); ASSERT_TRUE(tcd.selected_transfers.front() == 2); @@ -1007,7 +1008,7 @@ TEST(Serialization, portability_unsigned_tx) ASSERT_TRUE(tcd.dests.size() == 1); auto& dest = tcd.dests[0]; ASSERT_TRUE(dest.amount == 1400000000000); - ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, dest.addr) == "9xnhrMczQkPeoGi6dyu6BgKAYX4tZsDs6KHCkyTStDBKL4M4pM1gfCR3utmTAcSaKHGa1R5o266FbdnubErmij3oMdLyYgA"); + ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, false, dest.addr) == "9xnhrMczQkPeoGi6dyu6BgKAYX4tZsDs6KHCkyTStDBKL4M4pM1gfCR3utmTAcSaKHGa1R5o266FbdnubErmij3oMdLyYgA"); // transfers ASSERT_TRUE(exported_txs.transfers.size() == 3); auto& td0 = exported_txs.transfers[0]; @@ -1054,10 +1055,10 @@ TEST(Serialization, portability_unsigned_tx) #define SIGNED_TX_PREFIX "Monero signed tx set\003" TEST(Serialization, portability_signed_tx) { - const string filename = epee::string_tools::get_current_module_folder() + "/../../../../tests/data/signed_monero_tx"; + const boost::filesystem::path filename = unit_test::data_dir / "signed_monero_tx"; const bool testnet = true; std::string s; - bool r = epee::file_io_utils::load_file_to_string(filename, s); + bool r = epee::file_io_utils::load_file_to_string(filename.native(), s); ASSERT_TRUE(r); const size_t magiclen = strlen(SIGNED_TX_PREFIX); ASSERT_FALSE(strncmp(s.c_str(), SIGNED_TX_PREFIX, magiclen)); @@ -1100,7 +1101,7 @@ TEST(Serialization, portability_signed_tx) ASSERT_FALSE(ptx.dust_added_to_fee); // ptx.change.{amount, addr} ASSERT_TRUE(ptx.change_dts.amount == 9631208773403); - ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, ptx.change_dts.addr) == "9svHk1wHPo3ULf2AZykghzcye6sitaRE4MaDjPC6uanTHCynHjJHZaiAb922PojE1GexhhRt1LVf5DC43feyrRZMLXQr3mk"); + ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, false, ptx.change_dts.addr) == "9svHk1wHPo3ULf2AZykghzcye6sitaRE4MaDjPC6uanTHCynHjJHZaiAb922PojE1GexhhRt1LVf5DC43feyrRZMLXQr3mk"); // ptx.selected_transfers ASSERT_TRUE(ptx.selected_transfers.size() == 1); ASSERT_TRUE(ptx.selected_transfers.front() == 2); @@ -1110,7 +1111,7 @@ TEST(Serialization, portability_signed_tx) // ptx.dests ASSERT_TRUE(ptx.dests.size() == 1); ASSERT_TRUE(ptx.dests[0].amount == 1400000000000); - ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, ptx.dests[0].addr) == "9xnhrMczQkPeoGi6dyu6BgKAYX4tZsDs6KHCkyTStDBKL4M4pM1gfCR3utmTAcSaKHGa1R5o266FbdnubErmij3oMdLyYgA"); + ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, false, ptx.dests[0].addr) == "9xnhrMczQkPeoGi6dyu6BgKAYX4tZsDs6KHCkyTStDBKL4M4pM1gfCR3utmTAcSaKHGa1R5o266FbdnubErmij3oMdLyYgA"); // ptx.construction_data auto& tcd = ptx.construction_data; ASSERT_TRUE(tcd.sources.size() == 1); @@ -1141,15 +1142,15 @@ TEST(Serialization, portability_signed_tx) ASSERT_TRUE(epee::string_tools::pod_to_hex(tse.mask) == "789bafff169ef206aa21219342c69ca52ce1d78d776c10b21d14bdd960fc7703"); // ptx.construction_data.change_dts ASSERT_TRUE(tcd.change_dts.amount == 9631208773403); - ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, tcd.change_dts.addr) == "9svHk1wHPo3ULf2AZykghzcye6sitaRE4MaDjPC6uanTHCynHjJHZaiAb922PojE1GexhhRt1LVf5DC43feyrRZMLXQr3mk"); + ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, false, tcd.change_dts.addr) == "9svHk1wHPo3ULf2AZykghzcye6sitaRE4MaDjPC6uanTHCynHjJHZaiAb922PojE1GexhhRt1LVf5DC43feyrRZMLXQr3mk"); // ptx.construction_data.splitted_dsts ASSERT_TRUE(tcd.splitted_dsts.size() == 2); auto& splitted_dst0 = tcd.splitted_dsts[0]; auto& splitted_dst1 = tcd.splitted_dsts[1]; ASSERT_TRUE(splitted_dst0.amount == 1400000000000); ASSERT_TRUE(splitted_dst1.amount == 9631208773403); - ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, splitted_dst0.addr) == "9xnhrMczQkPeoGi6dyu6BgKAYX4tZsDs6KHCkyTStDBKL4M4pM1gfCR3utmTAcSaKHGa1R5o266FbdnubErmij3oMdLyYgA"); - ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, splitted_dst1.addr) == "9svHk1wHPo3ULf2AZykghzcye6sitaRE4MaDjPC6uanTHCynHjJHZaiAb922PojE1GexhhRt1LVf5DC43feyrRZMLXQr3mk"); + ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, false, splitted_dst0.addr) == "9xnhrMczQkPeoGi6dyu6BgKAYX4tZsDs6KHCkyTStDBKL4M4pM1gfCR3utmTAcSaKHGa1R5o266FbdnubErmij3oMdLyYgA"); + ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, false, splitted_dst1.addr) == "9svHk1wHPo3ULf2AZykghzcye6sitaRE4MaDjPC6uanTHCynHjJHZaiAb922PojE1GexhhRt1LVf5DC43feyrRZMLXQr3mk"); // ptx.construction_data.selected_transfers ASSERT_TRUE(tcd.selected_transfers.size() == 1); ASSERT_TRUE(tcd.selected_transfers.front() == 2); @@ -1162,7 +1163,7 @@ TEST(Serialization, portability_signed_tx) ASSERT_TRUE(tcd.dests.size() == 1); auto& dest = tcd.dests[0]; ASSERT_TRUE(dest.amount == 1400000000000); - ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, dest.addr) == "9xnhrMczQkPeoGi6dyu6BgKAYX4tZsDs6KHCkyTStDBKL4M4pM1gfCR3utmTAcSaKHGa1R5o266FbdnubErmij3oMdLyYgA"); + ASSERT_TRUE(cryptonote::get_account_address_as_str(testnet, false, dest.addr) == "9xnhrMczQkPeoGi6dyu6BgKAYX4tZsDs6KHCkyTStDBKL4M4pM1gfCR3utmTAcSaKHGa1R5o266FbdnubErmij3oMdLyYgA"); // key_images ASSERT_TRUE(exported_txs.key_images.size() == 3); auto& ki0 = exported_txs.key_images[0]; diff --git a/tests/unit_tests/test_peerlist.cpp b/tests/unit_tests/test_peerlist.cpp index 4a546b50d..849020d25 100644 --- a/tests/unit_tests/test_peerlist.cpp +++ b/tests/unit_tests/test_peerlist.cpp @@ -38,7 +38,7 @@ TEST(peer_list, peer_list_general) { nodetool::peerlist_manager plm; plm.init(false); -#define MAKE_IPV4_ADDRESS(a,b,c,d,e) new epee::net_utils::ipv4_network_address(MAKE_IP(a,b,c,d),e) +#define MAKE_IPV4_ADDRESS(a,b,c,d,e) epee::net_utils::ipv4_network_address{MAKE_IP(a,b,c,d),e} #define ADD_GRAY_NODE(addr_, id_, last_seen_) { nodetool::peerlist_entry ple; ple.last_seen=last_seen_;ple.adr = addr_; ple.id = id_;plm.append_with_peer_gray(ple);} #define ADD_WHITE_NODE(addr_, id_, last_seen_) { nodetool::peerlist_entry ple;ple.last_seen=last_seen_; ple.adr = addr_; ple.id = id_;plm.append_with_peer_white(ple);} diff --git a/tests/unit_tests/test_tx_utils.cpp b/tests/unit_tests/test_tx_utils.cpp index 0ff91c247..4ce62e2f5 100644 --- a/tests/unit_tests/test_tx_utils.cpp +++ b/tests/unit_tests/test_tx_utils.cpp @@ -141,7 +141,7 @@ TEST(parse_and_validate_tx_extra, is_valid_tx_extra_parsed) cryptonote::blobdata b = "dsdsdfsdfsf"; ASSERT_TRUE(cryptonote::construct_miner_tx(0, 0, 10000000000000, 1000, TEST_FEE, acc.get_keys().m_account_address, tx, b, 1)); crypto::public_key tx_pub_key = cryptonote::get_tx_pub_key_from_extra(tx); - ASSERT_NE(tx_pub_key, cryptonote::null_pkey); + ASSERT_NE(tx_pub_key, crypto::null_pkey); } TEST(parse_and_validate_tx_extra, fails_on_big_extra_nonce) { diff --git a/tests/unit_tests/thread_group.cpp b/tests/unit_tests/thread_group.cpp deleted file mode 100644 index c0ba3d38b..000000000 --- a/tests/unit_tests/thread_group.cpp +++ /dev/null @@ -1,177 +0,0 @@ -// Copyright (c) 2014-2017, 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 "gtest/gtest.h" - -#include <atomic> -#include "common/task_region.h" -#include "common/thread_group.h" - -TEST(ThreadGroup, NoThreads) -{ - tools::task_region(tools::thread_group(0), [] (tools::task_region_handle& region) { - std::atomic<bool> completed{false}; - region.run([&] { completed = true; }); - EXPECT_TRUE(completed); - }); - { - tools::thread_group group(0); - std::atomic<bool> completed{false}; - group.dispatch([&] { completed = true; }); - EXPECT_TRUE(completed); - } -} - -TEST(ThreadGroup, OneThread) -{ - tools::thread_group group(1); - - for (unsigned i = 0; i < 3; ++i) { - std::atomic<bool> completed{false}; - tools::task_region(group, [&] (tools::task_region_handle& region) { - region.run([&] { completed = true; }); - }); - EXPECT_TRUE(completed); - } -} - - -TEST(ThreadGroup, UseActiveThreadOnSync) -{ - tools::thread_group group(1); - - for (unsigned i = 0; i < 3; ++i) { - std::atomic<bool> completed{false}; - tools::task_region(group, [&] (tools::task_region_handle& region) { - region.run([&] { while (!completed); }); - region.run([&] { completed = true; }); - }); - EXPECT_TRUE(completed); - } -} - -TEST(ThreadGroup, InOrder) -{ - tools::thread_group group(1); - - for (unsigned i = 0; i < 3; ++i) { - std::atomic<unsigned> count{0}; - std::atomic<bool> completed{false}; - tools::task_region(group, [&] (tools::task_region_handle& region) { - region.run([&] { while (!completed); }); - region.run([&] { if (count == 0) completed = true; }); - region.run([&] { ++count; }); - }); - EXPECT_TRUE(completed); - EXPECT_EQ(1u, count); - } -} - -TEST(ThreadGroup, TwoThreads) -{ - tools::thread_group group(2); - - for (unsigned i = 0; i < 3; ++i) { - std::atomic<bool> completed{false}; - tools::task_region(group, [&] (tools::task_region_handle& region) { - region.run([&] { while (!completed); }); - region.run([&] { while (!completed); }); - region.run([&] { completed = true; }); - }); - EXPECT_TRUE(completed); - } -} - -TEST(ThreadGroup, Nested) { - struct fib { - unsigned operator()(tools::thread_group& group, unsigned value) const { - if (value == 0 || value == 1) { - return value; - } - unsigned left = 0; - unsigned right = 0; - tools::task_region(group, [&, value] (tools::task_region_handle& region) { - region.run([&, value] { left = fib{}(group, value - 1); }); - region.run([&, value] { right = fib{}(group, value - 2); } ); - }); - return left + right; - } - - unsigned operator()(tools::thread_group&& group, unsigned value) const { - return (*this)(group, value); - } - }; - // be careful of depth on asynchronous version - EXPECT_EQ(6765, fib{}(tools::thread_group(0), 20)); - EXPECT_EQ(377, fib{}(tools::thread_group(1), 14)); -} - -TEST(ThreadGroup, Many) -{ - tools::thread_group group(1); - - for (unsigned i = 0; i < 3; ++i) { - std::atomic<unsigned> count{0}; - tools::task_region(group, [&] (tools::task_region_handle& region) { - for (unsigned tasks = 0; tasks < 1000; ++tasks) { - region.run([&] { ++count; }); - } - }); - EXPECT_EQ(1000u, count); - } -} - -TEST(ThreadGroup, ThrowInTaskRegion) -{ - class test_exception final : std::exception { - public: - explicit test_exception() : std::exception() {} - - virtual const char* what() const noexcept override { - return "test_exception"; - } - }; - - tools::thread_group group(1); - - for (unsigned i = 0; i < 3; ++i) { - std::atomic<unsigned> count{0}; - EXPECT_THROW( - [&] { - tools::task_region(group, [&] (tools::task_region_handle& region) { - for (unsigned tasks = 0; tasks < 1000; ++tasks) { - region.run([&] { ++count; }); - } - throw test_exception(); - }); - }(), - test_exception - ); - EXPECT_GE(1000u, count); - } -} diff --git a/tests/unit_tests/unit_tests_utils.h b/tests/unit_tests/unit_tests_utils.h index 11230c0db..a07eaf02b 100644 --- a/tests/unit_tests/unit_tests_utils.h +++ b/tests/unit_tests/unit_tests_utils.h @@ -31,9 +31,12 @@ #pragma once #include <atomic> +#include <boost/filesystem.hpp> namespace unit_test { + extern boost::filesystem::path data_dir; + class call_counter { public: diff --git a/translations/monero_it.ts b/translations/monero_it.ts index c30413219..10c1599df 100644 --- a/translations/monero_it.ts +++ b/translations/monero_it.ts @@ -6,17 +6,17 @@ <message> <location filename="../src/wallet/api/address_book.cpp" line="55"/> <source>Invalid destination address</source> - <translation>Indirizzo destinatario invalido</translation> + <translation>Indirizzo destinatario non valido</translation> </message> <message> <location filename="../src/wallet/api/address_book.cpp" line="65"/> <source>Invalid payment ID. Short payment ID should only be used in an integrated address</source> - <translation>ID pagamento invalido. Il pagamento id corto dovrebbe essere usato solo in un indirizzo integrato</translation> + <translation>ID pagamento non valido. Il pagamento ID corto dovrebbe essere usato solo in un indirizzo integrato</translation> </message> <message> <location filename="../src/wallet/api/address_book.cpp" line="72"/> <source>Invalid payment ID</source> - <translation>ID pagamento invalido</translation> + <translation>ID pagamento non valido</translation> </message> <message> <location filename="../src/wallet/api/address_book.cpp" line="79"/> @@ -54,7 +54,7 @@ <message> <location filename="../src/wallet/api/pending_transaction.cpp" line="126"/> <source>. Reason: </source> - <translation>Motivo: </translation> + <translation>. Motivo: </translation> </message> <message> <location filename="../src/wallet/api/pending_transaction.cpp" line="128"/> @@ -93,12 +93,12 @@ <message> <location filename="../src/wallet/api/unsigned_transaction.cpp" line="151"/> <source>Change goes to more than one address</source> - <translation>Il cambiamento ha effetto su più di un in indirizzo</translation> + <translation>Il cambiamento ha effetto su più di un indirizzo</translation> </message> <message> <location filename="../src/wallet/api/unsigned_transaction.cpp" line="164"/> <source>sending %s to %s</source> - <translation>Inviando %s a %s</translation> + <translation>inviando %s a %s</translation> </message> <message> <location filename="../src/wallet/api/unsigned_transaction.cpp" line="170"/> @@ -113,12 +113,12 @@ <message> <location filename="../src/wallet/api/unsigned_transaction.cpp" line="179"/> <source>no change</source> - <translation>nessun cambiamento</translation> + <translation>nessuna modifica</translation> </message> <message> <location filename="../src/wallet/api/unsigned_transaction.cpp" line="181"/> <source>Loaded %lu transactions, for %s, fee %s, %s, %s, with min mixin %lu. %s</source> - <translation>Caricato %lu transazioni, per %s, tassa %s, %s, %s, %s, con mixin %lu. %s</translation> + <translation>Caricato %lu transazioni, per %s, commissione %s, %s, %s, %s, con mixin %lu. %s</translation> </message> </context> <context> @@ -154,53 +154,53 @@ <message> <location filename="../src/wallet/api/wallet.cpp" line="1081"/> <source>failed to get random outputs to mix</source> - <translation>Impossibile raccogliere outputs random da mixare</translation> + <translation>impossibile recuperare outputs random da mixare</translation> </message> <message> <location filename="../src/wallet/api/wallet.cpp" line="994"/> <location filename="../src/wallet/api/wallet.cpp" line="1088"/> <source>not enough money to transfer, available only %s, sent amount %s</source> - <translation>non hai abbastanza soldi da trasferire, sono disponibili solo %s, ammontare inviato %s</translation> + <translation>non hai abbastanza fondi da trasferire, sono disponibili solo %s, ammontare inviato %s</translation> </message> <message> <location filename="../src/wallet/api/wallet.cpp" line="403"/> <source>failed to parse address</source> - <translation>Analisi(parse) indirizzo fallita</translation> + <translation>parsing indirizzo fallito</translation> </message> <message> <location filename="../src/wallet/api/wallet.cpp" line="415"/> <source>failed to parse secret spend key</source> - <translation>Impossibile analizzare(parse) la chiave segreta spendibile</translation> + <translation>impossibile fare il parsing della chiave segreta di spesa</translation> </message> <message> <location filename="../src/wallet/api/wallet.cpp" line="425"/> <source>No view key supplied, cancelled</source> - <translation>Non è stata fornita nessuna chiave per visualizzazione</translation> + <translation>Non è stata fornita nessuna chiave di visualizzazione</translation> </message> <message> <location filename="../src/wallet/api/wallet.cpp" line="432"/> <source>failed to parse secret view key</source> - <translation>Impossibile analizzare(parse) la chiave segreta per visualizzazione</translation> + <translation>impossibile fare il parsing della chiave segreta di visualizzazione</translation> </message> <message> <location filename="../src/wallet/api/wallet.cpp" line="442"/> <source>failed to verify secret spend key</source> - <translation>impossibile verificare chiave segreta spendibile</translation> + <translation>impossibile verificare chiave segreta di spesa</translation> </message> <message> <location filename="../src/wallet/api/wallet.cpp" line="447"/> <source>spend key does not match address</source> - <translation>la chiave spendibile non corrisponde all'indirizzo</translation> + <translation>la chiave di spesa non corrisponde all'indirizzo</translation> </message> <message> <location filename="../src/wallet/api/wallet.cpp" line="453"/> <source>failed to verify secret view key</source> - <translation>verifica chiave segreta per visualizzazione fallita</translation> + <translation>verifica chiave segreta di visualizzazione fallita</translation> </message> <message> <location filename="../src/wallet/api/wallet.cpp" line="458"/> <source>view key does not match address</source> - <translation>La chiave per visualizzazione non corrisponde all'indirizzo</translation> + <translation>la chiave di visualizzazione non corrisponde all'indirizzo</translation> </message> <message> <location filename="../src/wallet/api/wallet.cpp" line="477"/> @@ -241,7 +241,7 @@ <location filename="../src/wallet/api/wallet.cpp" line="1003"/> <location filename="../src/wallet/api/wallet.cpp" line="1097"/> <source>not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee)</source> - <translation>Non hai abbastanza ssoldi da trasferire, disponibili solo %s, ammontare transazione %s = %s + %s (tassa)</translation> + <translation>non hai abbastanza fondi da trasferire, disponibili solo %s, ammontare transazione %s = %s + %s (commissione)</translation> </message> <message> <location filename="../src/wallet/api/wallet.cpp" line="1012"/> @@ -301,7 +301,7 @@ <location filename="../src/wallet/api/wallet.cpp" line="1042"/> <location filename="../src/wallet/api/wallet.cpp" line="1136"/> <source>unexpected error: </source> - <translation>errore insaspettato: </translation> + <translation>errore inaspettato: </translation> </message> <message> <location filename="../src/wallet/api/wallet.cpp" line="1045"/> @@ -312,7 +312,7 @@ <message> <location filename="../src/wallet/api/wallet.cpp" line="1419"/> <source>Rescan spent can only be used with a trusted daemon</source> - <translation>"riscannerizza spesi" può essere utilizzato solo da un daemon fidato</translation> + <translation>"Riscannerizza spesi" può essere utilizzato solo da un daemon fidato</translation> </message> </context> <context> @@ -320,38 +320,38 @@ <message> <location filename="../src/wallet/api/wallet_manager.cpp" line="192"/> <source>failed to parse txid</source> - <translation>analisi(parse) txid fallita</translation> + <translation>analisi txid fallita</translation> </message> <message> <location filename="../src/wallet/api/wallet_manager.cpp" line="199"/> <location filename="../src/wallet/api/wallet_manager.cpp" line="206"/> <source>failed to parse tx key</source> - <translation>analisi(parse) chiave tx fallita</translation> + <translation>parsing chiave tx fallito</translation> </message> <message> <location filename="../src/wallet/api/wallet_manager.cpp" line="217"/> <source>failed to parse address</source> - <translation>analisi(parse) indirizzo fallita</translation> + <translation>parsing indirizzo fallito</translation> </message> <message> <location filename="../src/wallet/api/wallet_manager.cpp" line="227"/> <source>failed to get transaction from daemon</source> - <translation>impossibile recuperare transazione da daemon</translation> + <translation>impossibile recuperare la transazione dal daemon</translation> </message> <message> <location filename="../src/wallet/api/wallet_manager.cpp" line="238"/> <source>failed to parse transaction from daemon</source> - <translation>impossibile analizzare(parse) transazione dal daemon</translation> + <translation>impossibile fare il parsing della transazione dal daemon</translation> </message> <message> <location filename="../src/wallet/api/wallet_manager.cpp" line="245"/> <source>failed to validate transaction from daemon</source> - <translation>convalida transazione da daemon fallita</translation> + <translation>convalida transazione dal daemon fallita</translation> </message> <message> <location filename="../src/wallet/api/wallet_manager.cpp" line="250"/> <source>failed to get the right transaction from daemon</source> - <translation>Impossibile recuperare la giusta transazione dal daemon</translation> + <translation>impossibile recuperare la giusta transazione dal daemon</translation> </message> <message> <location filename="../src/wallet/api/wallet_manager.cpp" line="257"/> @@ -376,7 +376,7 @@ <message> <location filename="../src/wallet/api/wallet_manager.cpp" line="323"/> <source>received nothing in txid</source> - <translation>ricevuto nulla in txid</translation> + <translation>nulla ricevuto in txid</translation> </message> </context> <context> @@ -384,17 +384,17 @@ <message> <location filename="../src/wallet/api/wallet.cpp" line="212"/> <source>Failed to parse address</source> - <translation>Analisi(parse) indirizzo fallita</translation> + <translation>Parsing indirizzo fallito</translation> </message> <message> <location filename="../src/wallet/api/wallet.cpp" line="219"/> <source>Failed to parse key</source> - <translation>Analisi(parse) key fallita</translation> + <translation>Parsing chiave fallito</translation> </message> <message> <location filename="../src/wallet/api/wallet.cpp" line="227"/> <source>failed to verify key</source> - <translation>verifica key fallita</translation> + <translation>verifica chiave fallita</translation> </message> <message> <location filename="../src/wallet/api/wallet.cpp" line="237"/> @@ -430,7 +430,7 @@ <message> <location filename="../src/rpc/rpc_args.cpp" line="66"/> <source>Invalid IP address given for --</source> - <translation>Invalido indirizzo IP dato per --</translation> + <translation>Indirizzo IP non valido dato per --</translation> </message> <message> <location filename="../src/rpc/rpc_args.cpp" line="74"/> @@ -445,7 +445,7 @@ <message> <location filename="../src/rpc/rpc_args.cpp" line="89"/> <source> cannot be empty</source> - <translation> non puoò essere vuoto</translation> + <translation> non può essere vuoto</translation> </message> </context> <context> @@ -458,7 +458,7 @@ <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1557"/> <source>failed to read wallet password</source> - <translation>impossibile leggere password portafoglio</translation> + <translation>impossibile leggere la password del portafoglio</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1325"/> @@ -468,12 +468,12 @@ <message> <location filename="../src/simplewallet/simplewallet.cpp" line="697"/> <source>start_mining [<number_of_threads>] - Start mining in daemon</source> - <translation>inizia a minare [<number_of_threads>] - Inizia a minare nel daemon</translation> + <translation>start_mining [<number_of_threads>] - Avvia mining nel daemon</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="698"/> <source>Stop mining in daemon</source> - <translation>interrompi mining nel daemon</translation> + <translation>Interrompi mining nel daemon</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="699"/> @@ -493,7 +493,7 @@ <message> <location filename="../src/simplewallet/simplewallet.cpp" line="715"/> <source>Show current wallet public address</source> - <translation>Mostra indirizzo pubblico del corrente portafoglio</translation> + <translation>Mostra indirizzo pubblico del portafoglio corrente</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="744"/> @@ -503,7 +503,7 @@ <message> <location filename="../src/simplewallet/simplewallet.cpp" line="788"/> <source>set seed: needs an argument. available options: language</source> - <translation>imposta seme: richiede una definizione. opzioni disponibili: lingua</translation> + <translation>imposta seed: richiede una definizione. opzioni disponibili: lingua</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="811"/> @@ -538,12 +538,12 @@ <message> <location filename="../src/simplewallet/simplewallet.cpp" line="728"/> <source>Check tx proof for payment going to <address> in <txid></source> - <translation type="unfinished"></translation> + <translation>Verifica prova tx per pagamento inviato a <indirizzo> in <txid></translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="743"/> <source>Generate a new random full size payment id - these will be unencrypted on the blockchain, see integrated_address for encrypted short payment ids</source> - <translation type="unfinished"></translation> + <translation>Genera un nuovo id pagamento casuale di dimensione completa - queste non saranno criptate nel blockchain, considera integrated_address per payment ids corti crittati</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="774"/> @@ -564,7 +564,7 @@ <message> <location filename="../src/simplewallet/simplewallet.cpp" line="800"/> <source>integer >= 2</source> - <translation type="unfinished"></translation> + <translation>integrale >= 2</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="803"/> @@ -585,12 +585,12 @@ <message> <location filename="../src/simplewallet/simplewallet.cpp" line="973"/> <source>specify a recovery parameter with the --electrum-seed="words list here"</source> - <translation>Specificare un parametro di ripristino con --electrum-seed="lista parole qui"</translation> + <translation>specificare un parametro di ripristino con --electrum-seed="lista parole qui"</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1261"/> <source>wallet failed to connect to daemon: </source> - <translation>impossibile connettere portafoglio a daemon: </translation> + <translation>impossibile connettere il portafoglio al daemon: </translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1269"/> @@ -600,7 +600,7 @@ <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1288"/> <source>List of available languages for your wallet's seed:</source> - <translation>Lista delle lingue disponibili per il seme del tuo portafoglio:</translation> + <translation>Lista delle lingue disponibili per il seed del tuo portafoglio:</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1297"/> @@ -611,7 +611,7 @@ <location filename="../src/simplewallet/simplewallet.cpp" line="1354"/> <source>You had been using a deprecated version of the wallet. Please use the new seed that we provide. </source> - <translation>Hai usato una versione obsoleta del portafoglio. Per favore usa il nuovo seme che ti abbiamo fornito.</translation> + <translation>Hai usato una versione obsoleta del portafoglio. Per favore usa il nuovo seed che ti abbiamo fornito.</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1368"/> @@ -666,12 +666,12 @@ <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1613"/> <source>Mining started in daemon</source> - <translation>Mining partito nel daemon</translation> + <translation>Mining avviato nel daemon</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1615"/> <source>mining has NOT been started: </source> - <translation>il mining NON è partito: </translation> + <translation>il mining NON è stato avviato: </translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1634"/> @@ -681,7 +681,7 @@ <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1636"/> <source>mining has NOT been stopped: </source> - <translation>Il mining NON è stato interrotto: </translation> + <translation>il mining NON è stato interrotto: </translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1655"/> @@ -710,7 +710,7 @@ <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1689"/> <source>spent </source> - <translation>speso/i</translation> + <translation>speso/i </translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1701"/> @@ -720,7 +720,7 @@ <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1718"/> <source>Starting refresh...</source> - <translation>Iniziando refresh...</translation> + <translation>Sto iniziando il refresh...</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1731"/> @@ -736,7 +736,7 @@ <message> <location filename="../src/simplewallet/simplewallet.cpp" line="2201"/> <source>bad locked_blocks parameter:</source> - <translation>parametro locked_blocks difettoso:</translation> + <translation>parametro locked_blocks non corretto:</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="2228"/> @@ -762,17 +762,17 @@ <message> <location filename="../src/simplewallet/simplewallet.cpp" line="2323"/> <source>Sending %s. </source> - <translation>Inviando %s. </translation> + <translation>Sto inviando %s. </translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="2326"/> <source>Your transaction needs to be split into %llu transactions. This will result in a transaction fee being applied to each transaction, for a total fee of %s</source> - <translation>La tua transazione deve essere divisa in %llu transazioni. Una tassa verrà applicata per ogni transazione, per un totale di %s tasse</translation> + <translation>La tua transazione deve essere divisa in %llu transazioni. Una commissione verrà applicata per ogni transazione, per un totale di %s commissioni</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="2332"/> <source>The transaction fee is %s</source> - <translation>la tassa per la transazione è %s</translation> + <translation>La commissione per la transazione è %s</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="2335"/> @@ -793,7 +793,8 @@ <location filename="../src/simplewallet/simplewallet.cpp" line="2341"/> <source>. This transaction will unlock on block %llu, in approximately %s days (assuming 2 minutes per block)</source> - <translation>Questa transazione verrà sbloccata al blocco %llu, in approssimativamente %s giorni (supponendo 2 minuti per blocco)</translation> + <translation>. +Questa transazione verrà sbloccata al blocco %llu, in approssimativamente %s giorni (supponendo 2 minuti per blocco)</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="2367"/> @@ -823,7 +824,7 @@ This transaction will unlock on block %llu, in approximately %s days (assuming 2 <location filename="../src/simplewallet/simplewallet.cpp" line="2853"/> <location filename="../src/simplewallet/simplewallet.cpp" line="3166"/> <source>Failed to find a way to create transactions. This is usually due to dust which is so small it cannot pay for itself in fees, or trying to send more money than the unlocked balance, or not leaving enough for fees</source> - <translation>Impossibile creare transazioni. Questo succede di solito perchè l'ammontare di polvere è così piccolo da non poter pagare le proprie tasse, oppure stai provando a mandare più soldi di quelli che possiedi nel bilancio sbloccato, o non hai aggiunto abbastanza tasse</translation> + <translation>Impossibile creare transazioni. Questo succede di solito perchè l'ammontare di polvere è così piccolo da non poter pagare le proprie commissioni, oppure stai provando a mandare più fondi di quelli che possiedi nel bilancio sbloccato, o non hai aggiunto abbastanza commissioni</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="2435"/> @@ -837,7 +838,7 @@ This transaction will unlock on block %llu, in approximately %s days (assuming 2 <location filename="../src/simplewallet/simplewallet.cpp" line="2624"/> <location filename="../src/simplewallet/simplewallet.cpp" line="2885"/> <source>failed to find a suitable way to split transactions</source> - <translation>Impossibile trovare un modo adatto per dividere le transazioni</translation> + <translation>impossibile trovare un modo adatto per dividere le transazioni</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="2503"/> @@ -862,7 +863,7 @@ This transaction will unlock on block %llu, in approximately %s days (assuming 2 <message> <location filename="../src/simplewallet/simplewallet.cpp" line="3035"/> <source>sending %s to %s</source> - <translation>mandando %s a %s</translation> + <translation>sto mandando %s a %s</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="3041"/> @@ -882,7 +883,7 @@ This transaction will unlock on block %llu, in approximately %s days (assuming 2 <message> <location filename="../src/simplewallet/simplewallet.cpp" line="3120"/> <source>Failed to load transaction from file</source> - <translation>caricamento transazione da file fallito</translation> + <translation>Caricamento transazione da file fallito</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="3137"/> @@ -902,7 +903,7 @@ This transaction will unlock on block %llu, in approximately %s days (assuming 2 <message> <location filename="../src/simplewallet/simplewallet.cpp" line="312"/> <source>wallet is watch-only and has no spend key</source> - <translation>il portafoglio è solo-vista e non ha una chiave spendibile</translation> + <translation>il portafoglio è solo-vista e non ha una chiave di spesa</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="390"/> @@ -998,7 +999,7 @@ This transaction will unlock on block %llu, in approximately %s days (assuming 2 <location filename="../src/simplewallet/simplewallet.cpp" line="1157"/> <location filename="../src/simplewallet/simplewallet.cpp" line="1184"/> <source>bad m_restore_height parameter: </source> - <translation>parametro m_restore_height scorretto: </translation> + <translation>parametro m_restore_height non corretto: </translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1162"/> @@ -1008,18 +1009,18 @@ This transaction will unlock on block %llu, in approximately %s days (assuming 2 <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1175"/> <source>Restore height is: </source> - <translation>ripristina altezza è: </translation> + <translation>Ripristina altezza è: </translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1176"/> <location filename="../src/simplewallet/simplewallet.cpp" line="2348"/> <source>Is this okay? (Y/Yes/N/No): </source> - <translation>va bene? (S/Sì/N/No): </translation> + <translation>Va bene? (S/Sì/N/No): </translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1212"/> <source>Daemon is local, assuming trusted</source> - <translation>Il daemon è locale, assunto per fidato</translation> + <translation>Il daemon è locale, viene considerato fidato</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1553"/> @@ -1100,12 +1101,12 @@ This transaction will unlock on block %llu, in approximately %s days (assuming 2 <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1868"/> <source>No incoming transfers</source> - <translation>nessun trasferimento in entrata</translation> + <translation>Nessun trasferimento in entrata</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1872"/> <source>No incoming available transfers</source> - <translation>nessun trasferimento in entrata disponibile</translation> + <translation>Nessun trasferimento in entrata disponibile</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1876"/> @@ -1130,7 +1131,7 @@ This transaction will unlock on block %llu, in approximately %s days (assuming 2 <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1896"/> <source>height</source> - <translation>blocco</translation> + <translation>altezza</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1896"/> @@ -1140,14 +1141,14 @@ This transaction will unlock on block %llu, in approximately %s days (assuming 2 <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1908"/> <source>No payments with id </source> - <translation>nessun pagamento con id </translation> + <translation>Nessun pagamento con id </translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1960"/> <location filename="../src/simplewallet/simplewallet.cpp" line="2026"/> <location filename="../src/simplewallet/simplewallet.cpp" line="2280"/> <source>failed to get blockchain height: </source> - <translation>impossibile recuperare dalla </translation> + <translation>impossibile recuperare altezza blockchain: </translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="2016"/> @@ -1158,7 +1159,8 @@ This transaction will unlock on block %llu, in approximately %s days (assuming 2 <location filename="../src/simplewallet/simplewallet.cpp" line="2034"/> <source> Transaction %llu/%llu: txid=%s</source> - <translation>Transazione %llu/%llu: txid=%s</translation> + <translation> +Transazione %llu/%llu: txid=%s</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="2044"/> @@ -1174,13 +1176,14 @@ Input %llu/%llu: amount=%s</source> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="2068"/> <source>output key's originating block height shouldn't be higher than the blockchain height</source> - <translation type="unfinished"></translation> + <translation>l'altezza del blocco di origine della chiave di output non dovrebbe essere più alta dell'altezza della blockchain</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="2072"/> <source> Originating block heights: </source> - <translation>Originando blocchi: </translation> + <translation> +Originando blocchi: </translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="2087"/> @@ -1201,24 +1204,25 @@ Originating block heights: </source> <location filename="../src/simplewallet/simplewallet.cpp" line="2104"/> <source> Warning: Some input keys being spent are from </source> - <translation>Avvertimento: alcune chiavi di input spese vengono da </translation> + <translation> +Avviso: alcune chiavi di input spese vengono da </translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="2106"/> <source>, which can break the anonymity of ring signature. Make sure this is intentional!</source> - <translation>, che potrebbe rempere l'anonimità delle firme ad anello. Assicurati di farlo intenzionalmente!</translation> + <translation>, che potrebbe compromettere l'anonimità della ring signature. Assicurati di farlo intenzionalmente!</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="2152"/> <location filename="../src/simplewallet/simplewallet.cpp" line="2937"/> <source>wrong number of arguments</source> - <translation>errato numero di argomenti</translation> + <translation>numero di argomenti errato</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="2257"/> <location filename="../src/simplewallet/simplewallet.cpp" line="2744"/> <source>No payment id is included with this transaction. Is this okay? (Y/Yes/N/No): </source> - <translation>Nessun id pagamento incluso in questa transazione. Questo è corretto? (S/Sì/N/No): </translation> + <translation>Nessun id pagamento è incluso in questa transazione. Questo è corretto? (S/Sì/N/No): </translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="2298"/> @@ -1238,12 +1242,12 @@ Warning: Some input keys being spent are from </source> <location filename="../src/simplewallet/simplewallet.cpp" line="2518"/> <location filename="../src/simplewallet/simplewallet.cpp" line="2779"/> <source>Sweeping %s in %llu transactions for a total fee of %s. Is this okay? (Y/Yes/N/No): </source> - <translation>Eseguendo lo sweep di %s nelle transazioni %llu per un totale di tasse di %s. Va bene? (S/Sì/N/No): </translation> + <translation>Sto eseguendo lo sweep di %s nelle transazioni %llu per un totale commissioni di %s. Va bene? (S/Sì/N/No): </translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="2524"/> <source>Sweeping %s for a total fee of %s. Is this okay? (Y/Yes/N/No): </source> - <translation>Eseguendo lo sweep di %s per un totale di tasse di %s. Va bene? (S/Sì/N/No): </translation> + <translation>Sto eseguendo lo sweep di %s per un totale commissioni di %s. Va bene? (S/Sì/N/No): </translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="2969"/> @@ -1253,12 +1257,12 @@ Warning: Some input keys being spent are from </source> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="3053"/> <source>Loaded %lu transactions, for %s, fee %s, %s, %s, with min mixin %lu. %sIs this okay? (Y/Yes/N/No): </source> - <translation>Caricate %lu transazioni, per %s, tasse %s, %s, %s, con mixaggio %lu. %sQuesto è corretto? (S/Sì/N/No): </translation> + <translation>Caricate %lu transazioni, per %s, commissioni %s, %s, %s, con mixaggio %lu. %sQuesto è corretto? (S/Sì/N/No): </translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="3077"/> <source>This is a watch only wallet</source> - <translation>questo è un portafoglio solo-vista</translation> + <translation>Questo è un portafoglio solo-vista</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="4443"/> @@ -1278,19 +1282,19 @@ Warning: Some input keys being spent are from </source> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="266"/> <source>failed to parse refresh type</source> - <translation>impossibile analizzare (parse) tipo di refresh</translation> + <translation>impossibile fare il parsing del tipo di refresh</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="330"/> <location filename="../src/simplewallet/simplewallet.cpp" line="362"/> <source>wallet is watch-only and has no seed</source> - <translation>il portafoglio è solo-vista e non possiede un seme</translation> + <translation>il portafoglio è solo-vista e non possiede un seed</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="353"/> <location filename="../src/simplewallet/simplewallet.cpp" line="367"/> <source>wallet is non-deterministic and has no seed</source> - <translation>il portafoglio è non-deterministico e non possiede un seme</translation> + <translation>il portafoglio è non-deterministico e non possiede un seed</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="450"/> @@ -1308,7 +1312,7 @@ Warning: Some input keys being spent are from </source> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="501"/> <source>could not change default mixin</source> - <translation>impossibile cambiare mixxaggio standard</translation> + <translation>impossibile cambiare mixin standard</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="545"/> @@ -1338,7 +1342,7 @@ Warning: Some input keys being spent are from </source> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="707"/> <source>locked_transfer [<mixin_count>] <addr> <amount> <lockblocks>(Number of blocks to lock the transaction for, max 1000000) [<payment_id>]</source> - <translation type="unfinished"></translation> + <translation>locked_transfer [<mixin_count>] <addr> <amount> <lockblocks>(Numero di blocchi durante i quali bloccare la transazione, max 1000000) [<payment_id>]</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="708"/> @@ -1353,12 +1357,12 @@ Warning: Some input keys being spent are from </source> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="713"/> <source>Submit a signed transaction from a file</source> - <translation>Invia una transazione dirmata da file</translation> + <translation>Invia una transazione firmata da file</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="716"/> <source>integrated_address [PID] - Encode a payment ID into an integrated address for the current wallet public address (no argument uses a random payment ID), or decode an integrated address to standard address and payment ID</source> - <translation type="unfinished"></translation> + <translation>integrated_address [PID] - Codifica un ID pagamento in un indirizzo integrato per l'indirizzo pubblico del portafoglio corrente (nessun parametro usa un payment ID casuale), oppure decodifica un indirizzo integrato in indirizzo standard e payment ID</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="718"/> @@ -1378,22 +1382,22 @@ Warning: Some input keys being spent are from </source> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="721"/> <source>Display private spend key</source> - <translation>Visualizza chiave privata spendibile</translation> + <translation>Visualizza chiave di spesa privata</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="722"/> <source>Display Electrum-style mnemonic seed</source> - <translation>Visualizza il seme mnemonico in stile Electrum</translation> + <translation>Visualizza il seed mnemonico in stile Electrum</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="723"/> <source>Available options: seed language - set wallet seed language; always-confirm-transfers <1|0> - whether to confirm unsplit txes; print-ring-members <1|0> - whether to print detailed information about ring members during confirmation; store-tx-info <1|0> - whether to store outgoing tx info (destination address, payment ID, tx secret key) for future reference; default-mixin <n> - set default mixin (default is 4); auto-refresh <1|0> - whether to automatically sync new blocks from the daemon; refresh-type <full|optimize-coinbase|no-coinbase|default> - set wallet refresh behaviour; priority [0|1|2|3|4] - default/unimportant/normal/elevated/priority fee; confirm-missing-payment-id <1|0>; ask-password <1|0>; unit <monero|millinero|micronero|nanonero|piconero> - set default monero (sub-)unit; min-outputs-count [n] - try to keep at least that many outputs of value at least min-outputs-value; min-outputs-value [n] - try to keep at least min-outputs-count outputs of at least that value; merge-destinations <1|0> - whether to merge multiple payments to the same destination address</source> - <translation type="unfinished"></translation> + <translation>Opzioni disponibili: seed language - seleziona lingua del seed per il portafoglio; always-confirm-transfers <1|0> - se confermare unsplit txes; print-ring-members <1|0> - se mostrare informazioni dettagliate sui ring members durante le conferme; store-tx-info <1|0> - se salvare le informazioni tx in uscita (indirizzo di destinazione, payment ID, chiave tx segreta) per riferimento futuro; default-mixin <n> - imposta default mixin (default è 4); auto-refresh <1|0> - se sincronizzare automaticamente i nuovi blocchi dal daemon; refresh-type <full|optimize-coinbase|no-coinbase|default> - imposta modalità wallet refresh; priority [0|1|2|3|4] - default/unimportant/normal/elevated/priority fee; confirm-missing-payment-id <1|0>; ask-password <1|0>; unit <monero|millinero|micronero|nanonero|piconero> - imposta default monero (sub-)unit; min-outputs-count [n] - cerca di mantenere come minimo tanti outputs quanti il valore di min-outputs-value; min-outputs-value [n] - cerca di mantenere i min-outputs-count outputs come minimo a questo valore; merge-destinations <1|0> - se fondere pagamenti multipli allo stessp indirizzo di destinazione</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="724"/> <source>Rescan blockchain for spent outputs</source> - <translation>Riscannerizza blockchain in cerca di outputs non spesi</translation> + <translation>Riscannerizza blockchain in cerca di outputs spesi</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="725"/> @@ -1408,12 +1412,12 @@ Warning: Some input keys being spent are from </source> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="730"/> <source>unspent_outputs [<min_amount> <max_amount>] - Show unspent outputs within an optional amount range</source> - <translation type="unfinished"></translation> + <translation>unspent_outputs [<min_amount> <max_amount>] - Mostra gli outputs non spesi entro un intervallo di valori opzionale</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="731"/> <source>Rescan blockchain from scratch</source> - <translation>Riscannerizza blockchain dal principio</translation> + <translation>Avvia scansione blockchain dal principio</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="732"/> @@ -1423,7 +1427,7 @@ Warning: Some input keys being spent are from </source> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="733"/> <source>Get a string note for a txid</source> - <translation type="unfinished"></translation> + <translation>Ricevi una stringa di annotazione per un txid</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="734"/> @@ -1463,12 +1467,12 @@ Warning: Some input keys being spent are from </source> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="802"/> <source>full (slowest, no assumptions); optimize-coinbase (fast, assumes the whole coinbase is paid to a single address); no-coinbase (fastest, assumes we receive no coinbase transaction), default (same as optimize-coinbase)</source> - <translation type="unfinished"></translation> + <translation>completo (più lento, nessuna ipotesi); optimize-coinbase (veloce, ipotizza che l'intero coinbase viene pagato ad un indirizzo singolo); no-coinbase (il più veloce, ipotizza di non ricevere una transazione coinbase), default (come optimize-coinbase)</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="806"/> <source>monero, millinero, micronero, nanonero, piconero</source> - <translation type="unfinished"></translation> + <translation>monero, millinero, micronero, nanonero, piconero</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="851"/> @@ -1478,12 +1482,12 @@ Warning: Some input keys being spent are from </source> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="868"/> <source>Wallet and key files found, loading...</source> - <translation>Portafoglio e chiavi trovatr, sto caricando...</translation> + <translation>Portafoglio e chiavi trovate, sto caricando...</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="874"/> <source>Key file found but not wallet file. Regenerating...</source> - <translation>Ho trovato la chiave ma non il portafoglio. Rigenerando...</translation> + <translation>Ho trovato la chiave ma non il portafoglio. Sto rigenerando...</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="880"/> @@ -1535,25 +1539,25 @@ Warning: Some input keys being spent are from </source> <location filename="../src/simplewallet/simplewallet.cpp" line="4048"/> <location filename="../src/simplewallet/simplewallet.cpp" line="4239"/> <source>failed to parse address</source> - <translation>impossibile analizzare(parse) indirizzo</translation> + <translation>impossibile fare il parsing dell'indirizzo</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1017"/> <location filename="../src/simplewallet/simplewallet.cpp" line="1085"/> <source>failed to parse view key secret key</source> - <translation>impossibile analizzare(parse) chiave per visualizzazione chiave segreta</translation> + <translation>impossibile fare il parsing chiave di visualizzazione chiave segreta</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1027"/> <location filename="../src/simplewallet/simplewallet.cpp" line="1103"/> <source>failed to verify view key secret key</source> - <translation>impossibile verificare chiave segreta vista</translation> + <translation>impossibile verificare chiave di visualizzazione chiave segreta</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1031"/> <location filename="../src/simplewallet/simplewallet.cpp" line="1107"/> <source>view key does not match standard address</source> - <translation>la chiave per visualizzazione non corrisponde all'indirizzo standard</translation> + <translation>la chiave di visualizzazione non corrisponde all'indirizzo standard</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1036"/> @@ -1565,22 +1569,22 @@ Warning: Some input keys being spent are from </source> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1069"/> <source>failed to parse spend key secret key</source> - <translation>impossibile analizzare (parse) chiave spendibile chiave segreta</translation> + <translation>impossibile fare il parsing chiave di spesa chiave segreta</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1095"/> <source>failed to verify spend key secret key</source> - <translation>impossibile verificare chiave spendibile chiave segreta</translation> + <translation>impossibile verificare chiave di spesa chiave segreta</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1099"/> <source>spend key does not match standard address</source> - <translation>la chiave spendibile non corrisponde all'indirizzo standard</translation> + <translation>la chiave di spesa non corrisponde all'indirizzo standard</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1123"/> <source>specify a wallet path with --generate-new-wallet (not --wallet-file)</source> - <translation>specifica un nuovo percorso per il portafoglio con --generate-new-wallet (non --wallet-file)</translation> + <translation>specifica un percorso per il portafoglio con --generate-new-wallet (non --wallet-file)</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1199"/> @@ -1611,7 +1615,7 @@ Warning: Some input keys being spent are from </source> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1370"/> <source>View key: </source> - <translation>Chiave per visualizzazione: </translation> + <translation>Chiave di visualizzazione: </translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1385"/> @@ -1648,7 +1652,7 @@ di nuovo il tuo portafoglio (le chiavi del tuo portafoglio NON sono a rischio in <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1657"/> <source>blockchain can't be saved: </source> - <translation>impossibile salvare blockchain: </translation> + <translation>impossibile salvare la blockchain: </translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1736"/> @@ -1671,7 +1675,7 @@ di nuovo il tuo portafoglio (le chiavi del tuo portafoglio NON sono a rischio in <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1750"/> <source>refresh error: </source> - <translation>refresh errore: </translation> + <translation>errore refresh: </translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="1794"/> @@ -1737,17 +1741,17 @@ di nuovo il tuo portafoglio (le chiavi del tuo portafoglio NON sono a rischio in <message> <location filename="../src/simplewallet/simplewallet.cpp" line="2105"/> <source>the same transaction</source> - <translation type="unfinished"></translation> + <translation>la stessa transazione</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="2105"/> <source>blocks that are temporally very close</source> - <translation type="unfinished"></translation> + <translation>i blocchi che sono temporalmente molto vicini</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="2206"/> <source>Locked blocks too high, max 1000000 (˜4 yrs)</source> - <translation type="unfinished"></translation> + <translation>I blocchi bloccati sono troppo alti, max 1000000 (˜4 anni)</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="2921"/> @@ -1772,7 +1776,7 @@ di nuovo il tuo portafoglio (le chiavi del tuo portafoglio NON sono a rischio in <message> <location filename="../src/simplewallet/simplewallet.cpp" line="3289"/> <source>failed to parse tx_key</source> - <translation>impossibile analizzare (parse) tx_key</translation> + <translation>impossibile fare il parsing del tx_key</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="3298"/> @@ -1797,7 +1801,7 @@ di nuovo il tuo portafoglio (le chiavi del tuo portafoglio NON sono a rischio in <message> <location filename="../src/simplewallet/simplewallet.cpp" line="3539"/> <source>Signature header check error</source> - <translation>errore controllo firma intestazione</translation> + <translation>Errore controllo firma intestazione</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="3550"/> @@ -1844,7 +1848,7 @@ di nuovo il tuo portafoglio (le chiavi del tuo portafoglio NON sono a rischio in <message> <location filename="../src/simplewallet/simplewallet.cpp" line="4027"/> <source>failed to parse payment ID or address</source> - <translation>impossibile analizzare (parse) ID pagamento o indirizzo</translation> + <translation>impossibile fare il parsing di ID pagamento o indirizzo</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="4038"/> @@ -1854,12 +1858,12 @@ di nuovo il tuo portafoglio (le chiavi del tuo portafoglio NON sono a rischio in <message> <location filename="../src/simplewallet/simplewallet.cpp" line="4070"/> <source>failed to parse payment ID</source> - <translation>impossibile analizzare (parse) ID pagamento</translation> + <translation>impossibile fare il parsing di ID pagamento</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="4088"/> <source>failed to parse index</source> - <translation>impossibile analizzare (parse) indice</translation> + <translation>impossibile fare il parsing dell'indice</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="4096"/> @@ -1899,7 +1903,7 @@ di nuovo il tuo portafoglio (le chiavi del tuo portafoglio NON sono a rischio in <message> <location filename="../src/simplewallet/simplewallet.cpp" line="4193"/> <source>usage: sign <filename></source> - <translation type="unfinished"></translation> + <translation>uso: sign <filename></translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="4198"/> @@ -1911,7 +1915,7 @@ di nuovo il tuo portafoglio (le chiavi del tuo portafoglio NON sono a rischio in <location filename="../src/simplewallet/simplewallet.cpp" line="4230"/> <location filename="../src/simplewallet/simplewallet.cpp" line="4374"/> <source>failed to read file </source> - <translation>impossibile leggere file </translation> + <translation>impossibile leggere il file </translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="4219"/> @@ -1921,7 +1925,7 @@ di nuovo il tuo portafoglio (le chiavi del tuo portafoglio NON sono a rischio in <message> <location filename="../src/simplewallet/simplewallet.cpp" line="4246"/> <source>Bad signature from </source> - <translation>Firma invalida da </translation> + <translation>Firma non valida da </translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="4250"/> @@ -1957,7 +1961,7 @@ di nuovo il tuo portafoglio (le chiavi del tuo portafoglio NON sono a rischio in <message> <location filename="../src/simplewallet/simplewallet.cpp" line="4323"/> <source>usage: export_outputs <filename></source> - <translation>usage: export_outputs <filename></translation> + <translation>uso: export_outputs <filename></translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="4357"/> @@ -1973,7 +1977,7 @@ di nuovo il tuo portafoglio (le chiavi del tuo portafoglio NON sono a rischio in <location filename="../src/simplewallet/simplewallet.cpp" line="2246"/> <location filename="../src/simplewallet/simplewallet.cpp" line="3818"/> <source>amount is wrong: </source> - <translation>l'ammontare è scorretto: </translation> + <translation>l'ammontare non è corretto: </translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="2247"/> @@ -2042,7 +2046,7 @@ di nuovo il tuo portafoglio (le chiavi del tuo portafoglio NON sono a rischio in <message> <location filename="../src/simplewallet/simplewallet.cpp" line="3195"/> <source>Failed to find a suitable way to split transactions</source> - <translation>Impossibile trovare un modo corretto per dividere transazioni</translation> + <translation>Impossibile trovare un modo corretto per dividere le transazioni</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="2452"/> @@ -2055,19 +2059,19 @@ di nuovo il tuo portafoglio (le chiavi del tuo portafoglio NON sono a rischio in <message> <location filename="../src/simplewallet/simplewallet.cpp" line="2516"/> <source>Sweeping </source> - <translation>Pulendo (sweeping) </translation> + <translation>Eseguendo lo sweeping </translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="2785"/> <source>Sweeping %s for a total fee of %s. Is this okay? (Y/Yes/N/No)</source> - <translation>Eseguendo lo sweeping di %s per un totale di tasse di %s. Va bene? (S/Sì/N/No)</translation> + <translation>Eseguendo lo sweeping di %s per un totale commissioni di %s. Va bene? (S/Sì/N/No)</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="2555"/> <location filename="../src/simplewallet/simplewallet.cpp" line="2816"/> <location filename="../src/simplewallet/simplewallet.cpp" line="3129"/> <source>Money successfully sent, transaction: </source> - <translation>Soldi inviati con successo, transazione: </translation> + <translation>Fondi inviati con successo, transazione: </translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="3047"/> @@ -2098,7 +2102,7 @@ di nuovo il tuo portafoglio (le chiavi del tuo portafoglio NON sono a rischio in <location filename="../src/simplewallet/simplewallet.cpp" line="4150"/> <location filename="../src/simplewallet/simplewallet.cpp" line="4450"/> <source>failed to parse txid</source> - <translation>analisi(parse) txid fallita</translation> + <translation>parsing txid fallito</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="3245"/> @@ -2119,19 +2123,19 @@ di nuovo il tuo portafoglio (le chiavi del tuo portafoglio NON sono a rischio in <location filename="../src/simplewallet/simplewallet.cpp" line="3361"/> <location filename="../src/simplewallet/simplewallet.cpp" line="3368"/> <source>failed to parse tx key</source> - <translation>impossibile analizzare (parse) chiave tx</translation> + <translation>impossibile fare il parsing della chiave tx</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="3400"/> <location filename="../src/simplewallet/simplewallet.cpp" line="3573"/> <source>failed to get transaction from daemon</source> - <translation>impossibil recuperare transazione dal daemon</translation> + <translation>impossibile recuperare transazione dal daemon</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="3411"/> <location filename="../src/simplewallet/simplewallet.cpp" line="3584"/> <source>failed to parse transaction from daemon</source> - <translation>impossibile analizzare (parse) la transazione dal daemon</translation> + <translation>impossibile fare il parsing della transazione dal daemon</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="3418"/> @@ -2168,12 +2172,12 @@ di nuovo il tuo portafoglio (le chiavi del tuo portafoglio NON sono a rischio in <message> <location filename="../src/simplewallet/simplewallet.cpp" line="3481"/> <source>received nothing in txid</source> - <translation>ricevuto niente in txid</translation> + <translation>nulla ricevuto in txid</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="3485"/> <source>WARNING: this transaction is not yet included in the blockchain!</source> - <translation>AVVERTIMENTO: questa transazione non è ancora inclusa nella blockchain!</translation> + <translation>AVVISO: questa transazione non è ancora inclusa nella blockchain!</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="3494"/> @@ -2183,7 +2187,7 @@ di nuovo il tuo portafoglio (le chiavi del tuo portafoglio NON sono a rischio in <message> <location filename="../src/simplewallet/simplewallet.cpp" line="3498"/> <source>WARNING: failed to determine number of confirmations!</source> - <translation>AVVERTIMENTO: impossibile determinare numero di conferme!</translation> + <translation>AVVISO: impossibile determinare il numero di conferme!</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="3661"/> @@ -2193,12 +2197,12 @@ di nuovo il tuo portafoglio (le chiavi del tuo portafoglio NON sono a rischio in <message> <location filename="../src/simplewallet/simplewallet.cpp" line="3700"/> <source>bad min_height parameter:</source> - <translation>parametro min_height scorretto:</translation> + <translation>parametro min_height non corretto:</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="3712"/> <source>bad max_height parameter:</source> - <translation>parametro max_height scorretto:</translation> + <translation>parametro max_height non corretto:</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="3760"/> @@ -2235,7 +2239,8 @@ di nuovo il tuo portafoglio (le chiavi del tuo portafoglio NON sono a rischio in <location filename="../src/simplewallet/simplewallet.cpp" line="3856"/> <source> Amount: </source> - <translation>Ammontare: </translation> + <translation> +Ammontare: </translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="3856"/> @@ -2251,43 +2256,49 @@ Amount: </source> <location filename="../src/simplewallet/simplewallet.cpp" line="3866"/> <source> Min block height: </source> - <translation>Altezza minima blocco: </translation> + <translation> +Altezza minima blocco: </translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="3867"/> <source> Max block height: </source> - <translation>Altezza massima blocco: </translation> + <translation> +Altezza massima blocco: </translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="3868"/> <source> Min amount found: </source> - <translation>Ammontare minimo trovato: </translation> + <translation> +Ammontare minimo trovato: </translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="3869"/> <source> Max amount found: </source> - <translation>Ammontare massimo trovato: </translation> + <translation> +Ammontare massimo trovato: </translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="3870"/> <source> Total count: </source> - <translation>Conto totale: </translation> + <translation> +Conto totale: </translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="3910"/> <source> Bin size: </source> - <translation>Dimensione Bin</translation> + <translation> +Dimensione Bin: </translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="3911"/> <source> Outputs per *: </source> - <translation></translation> + <translation type="unfinished"></translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="3913"/> @@ -2356,7 +2367,7 @@ Outputs per *: </source> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="117"/> <source>Generate incoming-only wallet from view key</source> - <translation>Genera un portafoglio solo-ricezione da chiave per visualizzazione</translation> + <translation>Genera un portafoglio solo-ricezione da chiave di visualizzazione</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="118"/> @@ -2366,17 +2377,17 @@ Outputs per *: </source> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="120"/> <source>Specify Electrum seed for wallet recovery/creation</source> - <translation>Specifica il seme stile Electrum per recuperare/creare il portafoglio</translation> + <translation>Specifica il seed stile Electrum per recuperare/creare il portafoglio</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="121"/> <source>Recover wallet using Electrum-style mnemonic seed</source> - <translation>Recupera portafoglio usando il seme mnemonico stile-Electrum</translation> + <translation>Recupera portafoglio usando il seed mnemonico stile-Electrum</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="122"/> <source>Create non-deterministic view and spend keys</source> - <translation>Crea chiavi per visualizzione e chiavi spendibili non-deterministiche</translation> + <translation>Crea chiavi di visualizzione e chiavi di spesa non-deterministiche</translation> </message> <message> <location filename="../src/simplewallet/simplewallet.cpp" line="123"/> @@ -2424,7 +2435,7 @@ Outputs per *: </source> <message> <location filename="../src/common/dns_utils.cpp" line="434"/> <source>WARNING: DNSSEC validation was unsuccessful, this address may not be correct!</source> - <translation>AVVERTIMENTO: convalida DNSSEC fallita, questo indirizzo potrebbe non essere corretto!</translation> + <translation>AVVISO: convalida DNSSEC fallita, questo indirizzo potrebbe non essere corretto!</translation> </message> <message> <location filename="../src/common/dns_utils.cpp" line="437"/> @@ -2434,7 +2445,7 @@ Outputs per *: </source> <message> <location filename="../src/common/dns_utils.cpp" line="439"/> <source> Monero Address = </source> - <translation>Indirizzo Monero = </translation> + <translation> Indirizzo Monero = </translation> </message> <message> <location filename="../src/common/dns_utils.cpp" line="441"/> @@ -2444,7 +2455,7 @@ Outputs per *: </source> <message> <location filename="../src/common/dns_utils.cpp" line="451"/> <source>you have cancelled the transfer request</source> - <translation>hai cancelliato la richiesta di transferimento</translation> + <translation>hai cancellato la richiesta di transferimento</translation> </message> </context> <context> @@ -2477,12 +2488,12 @@ Outputs per *: </source> <message> <location filename="../src/wallet/wallet2.cpp" line="112"/> <source>For testnet. Daemon must also be launched with --testnet flag</source> - <translation>Per testnet. Il Daemon può anche essere lanciato con la flag --testnet</translation> + <translation>Per testnet. Il daemon può anche essere lanciato con la flag --testnet</translation> </message> <message> <location filename="../src/wallet/wallet2.cpp" line="113"/> <source>Restricts to view-only commands</source> - <translation>Restringi i comandi a solo-vista</translation> + <translation>Restringi a comandi di tipo solo-vista</translation> </message> <message> <location filename="../src/wallet/wallet2.cpp" line="152"/> @@ -2517,7 +2528,7 @@ Outputs per *: </source> <message> <location filename="../src/wallet/wallet2.cpp" line="108"/> <source>Wallet password (escape/quote as needed)</source> - <translation type="unfinished"></translation> + <translation>Wallet password (escape/quote se necessario)</translation> </message> <message> <location filename="../src/wallet/wallet2.cpp" line="111"/> @@ -2527,46 +2538,46 @@ Outputs per *: </source> <message> <location filename="../src/wallet/wallet2.cpp" line="233"/> <source>Failed to parse JSON</source> - <translation>Impossibile analizzare (parse) JSON</translation> + <translation>Impossibile fare il parsing di JSON</translation> </message> <message> <location filename="../src/wallet/wallet2.cpp" line="240"/> <source>Version %u too new, we can only grok up to %u</source> - <translation type="unfinished"></translation> + <translation>La versione %u è troppo recente, possiamo comprendere solo fino alla versione %u</translation> </message> <message> <location filename="../src/wallet/wallet2.cpp" line="258"/> <source>failed to parse view key secret key</source> - <translation>impossibile analizzare (parse) chiave per visualizzazione chiave segreta</translation> + <translation>impossibile fare il parsing di chiave di visualizzazione chiave segreta</translation> </message> <message> <location filename="../src/wallet/wallet2.cpp" line="264"/> <location filename="../src/wallet/wallet2.cpp" line="331"/> <location filename="../src/wallet/wallet2.cpp" line="373"/> <source>failed to verify view key secret key</source> - <translation>impossibile verificare chiave per visualizzazione chiave segreta</translation> + <translation>impossibile verificare chiave di visualizzazione chiave segreta</translation> </message> <message> <location filename="../src/wallet/wallet2.cpp" line="276"/> <source>failed to parse spend key secret key</source> - <translation>impossibile analizzare (parse) chiave spendibile chiave segreta</translation> + <translation>impossibile fare il parsing chiave di spesa chiave segreta</translation> </message> <message> <location filename="../src/wallet/wallet2.cpp" line="282"/> <location filename="../src/wallet/wallet2.cpp" line="343"/> <location filename="../src/wallet/wallet2.cpp" line="394"/> <source>failed to verify spend key secret key</source> - <translation>impossibile verificare chiave spendibile chiave segreta</translation> + <translation>impossibile verificare chiave di spesa chiave segreta</translation> </message> <message> <location filename="../src/wallet/wallet2.cpp" line="295"/> <source>Electrum-style word list failed verification</source> - <translation>verifica lista di parole stile-Electrum fallita</translation> + <translation>Verifica lista di parole stile-Electrum fallita</translation> </message> <message> <location filename="../src/wallet/wallet2.cpp" line="306"/> <source>At least one of Electrum-style word list and private view key must be specified</source> - <translation></translation> + <translation>Almeno una parola della lista stile-Electrum e una chiave privata di visualizzazione devono essere specificate</translation> </message> <message> <location filename="../src/wallet/wallet2.cpp" line="311"/> @@ -2581,12 +2592,12 @@ Outputs per *: </source> <message> <location filename="../src/wallet/wallet2.cpp" line="335"/> <source>view key does not match standard address</source> - <translation>la chiave per visualizzazione non corrisponde all'indirizzo standard</translation> + <translation>la chiave di visualizzazione non corrisponde all'indirizzo standard</translation> </message> <message> <location filename="../src/wallet/wallet2.cpp" line="347"/> <source>spend key does not match standard address</source> - <translation>la chiave spendibile non corrisponde all'indirizzo standard</translation> + <translation>la chiave di spesa non corrisponde all'indirizzo standard</translation> </message> <message> <location filename="../src/wallet/wallet2.cpp" line="356"/> @@ -2609,7 +2620,7 @@ Outputs per *: </source> <message> <location filename="../src/wallet/wallet_rpc_server.cpp" line="151"/> <source>Daemon is local, assuming trusted</source> - <translation>Il daemon è locale, assunto per fidato</translation> + <translation>Il daemon è locale, viene considerato fidato</translation> </message> <message> <location filename="../src/wallet/wallet_rpc_server.cpp" line="171"/> @@ -2639,7 +2650,7 @@ Outputs per *: </source> <message> <location filename="../src/wallet/wallet_rpc_server.cpp" line="212"/> <source>RPC username/password is stored in file </source> - <translation>Username/password RPC conservate nel file </translation> + <translation>Username/password RPC conservato nel file </translation> </message> <message> <location filename="../src/wallet/wallet_rpc_server.cpp" line="1748"/> @@ -2654,19 +2665,19 @@ Outputs per *: </source> <message> <location filename="../src/wallet/wallet_rpc_server.cpp" line="1764"/> <source>Loading wallet...</source> - <translation>Caricando il portafoglio...</translation> + <translation>Sto caricando il portafoglio...</translation> </message> <message> <location filename="../src/wallet/wallet_rpc_server.cpp" line="1789"/> <location filename="../src/wallet/wallet_rpc_server.cpp" line="1814"/> <source>Storing wallet...</source> - <translation>Conservando il portafoglio...</translation> + <translation>Sto salvando il portafoglio...</translation> </message> <message> <location filename="../src/wallet/wallet_rpc_server.cpp" line="1791"/> <location filename="../src/wallet/wallet_rpc_server.cpp" line="1816"/> <source>Stored ok</source> - <translation>Conservato con successo</translation> + <translation>Salvato con successo</translation> </message> <message> <location filename="../src/wallet/wallet_rpc_server.cpp" line="1794"/> @@ -2686,7 +2697,7 @@ Outputs per *: </source> <message> <location filename="../src/wallet/wallet_rpc_server.cpp" line="1809"/> <source>Starting wallet rpc server</source> - <translation>Server RPC portafoglio in partenza</translation> + <translation>Server RPC portafoglio in avvio</translation> </message> <message> <location filename="../src/wallet/wallet_rpc_server.cpp" line="1811"/> @@ -2696,7 +2707,7 @@ Outputs per *: </source> <message> <location filename="../src/wallet/wallet_rpc_server.cpp" line="1820"/> <source>Failed to store wallet: </source> - <translation>Impossibile conservare portafoglio: </translation> + <translation>Impossibile salvare portafoglio: </translation> </message> </context> <context> @@ -2725,7 +2736,7 @@ Outputs per *: </source> <message> <location filename="../src/wallet/wallet_args.cpp" line="88"/> <source>Specify log file</source> - <translation>Specificare log file</translation> + <translation>Specificare file di log</translation> </message> <message> <location filename="../src/wallet/wallet_args.cpp" line="89"/> @@ -2745,12 +2756,12 @@ Outputs per *: </source> <message> <location filename="../src/wallet/wallet_args.cpp" line="172"/> <source>Logging to: </source> - <translation>Loggando in: </translation> + <translation>Sto salvando il Log in: </translation> </message> <message> <location filename="../src/wallet/wallet_args.cpp" line="173"/> <source>Logging to %s</source> - <translation>Loggando in %s</translation> + <translation>Sto salvando il Log in %s</translation> </message> <message> <location filename="../src/wallet/wallet_args.cpp" line="153"/> diff --git a/utils/systemd/monerod.service b/utils/systemd/monerod.service index 12395eb8c..96e88a2d3 100644 --- a/utils/systemd/monerod.service +++ b/utils/systemd/monerod.service @@ -6,12 +6,13 @@ After=network.target User=monero Group=monero WorkingDirectory=~ +RuntimeDirectory=monero Type=forking -PIDFile=/var/run/monerod.pid +PIDFile=/run/monero/monerod.pid ExecStart=/usr/bin/monerod --config-file /etc/monerod.conf \ - --detach --pidfile /var/run/monerod.pid + --detach --pidfile /run/monero/monerod.pid [Install] WantedBy=multi-user.target diff --git a/version.cmake b/version.cmake index 80f1c40b8..75343c381 100644 --- a/version.cmake +++ b/version.cmake @@ -1,32 +1,49 @@ +# Copyright (c) 2014-2017, 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. + function (write_static_version_header hash) set(VERSIONTAG "${hash}") - configure_file("src/version.h.in" "version/version.h") - add_custom_target(version ALL) + configure_file("src/version.cpp.in" "version.cpp") endfunction () -file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/version") find_package(Git QUIET) if ("$Format:$" STREQUAL "") # We're in a tarball; use hard-coded variables. write_static_version_header("release") elseif (GIT_FOUND OR Git_FOUND) message(STATUS "Found Git: ${GIT_EXECUTABLE}") - set(extra_output) - if (CMAKE_GENERATOR MATCHES "Ninja") - # Ninja will not rerun the command every time if the file doesn't change, - # so inject this bogus output so that it always runs. - set(extra_output "${CMAKE_SOURCE_DIR}/.force-git-version-check") - endif () - add_custom_command( - OUTPUT "${CMAKE_BINARY_DIR}/version/version.h" - ${extra_output} + add_custom_target(genversion ALL COMMAND "${CMAKE_COMMAND}" "-D" "GIT=${GIT_EXECUTABLE}" - "-D" "TO=${CMAKE_BINARY_DIR}/version/version.h" + "-D" "TO=${CMAKE_BINARY_DIR}/version.cpp" "-P" "src/version.cmake" + BYPRODUCTS "${CMAKE_BINARY_DIR}/version.cpp" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") - add_custom_target(version ALL - DEPENDS "${CMAKE_BINARY_DIR}/version/version.h") else() message(STATUS "WARNING: Git was not found!") write_static_version_header("unknown") |