diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e2d3c3a15..52bc52eac8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,8 +24,10 @@ if(MSVC) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src/Platform/Windows) elseif(APPLE) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src/Platform/OSX) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src/Platform/Posix) else() include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src/Platform/Linux) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src/Platform/Posix) endif() set(STATIC ${MSVC} CACHE BOOL "Link libraries statically") @@ -53,7 +55,7 @@ else() endif() set(WARNINGS "-Wall -Wextra -Wpointer-arith -Wundef -Wvla -Wwrite-strings -Werror -Wno-error=extra -Wno-error=unused-function -Wno-error=deprecated-declarations -Wno-error=sign-compare -Wno-error=strict-aliasing -Wno-error=type-limits -Wno-unused-parameter -Wno-error=unused-variable -Wno-error=undef -Wno-error=uninitialized -Wno-error=unused-result") if(CMAKE_C_COMPILER_ID STREQUAL "Clang") - set(WARNINGS "${WARNINGS} -Wno-error=mismatched-tags -Wno-error=null-conversion -Wno-overloaded-shift-op-parentheses -Wno-error=shift-count-overflow -Wno-error=tautological-constant-out-of-range-compare -Wno-error=unused-private-field -Wno-error=unneeded-internal-declaration -Wno-error=unused-function") + set(WARNINGS "${WARNINGS} -Wno-error=mismatched-tags -Wno-error=null-conversion -Wno-overloaded-shift-op-parentheses -Wno-error=shift-count-overflow -Wno-error=tautological-constant-out-of-range-compare -Wno-error=unused-private-field -Wno-error=unneeded-internal-declaration -Wno-error=unused-function -Wno-error=missing-braces") else() set(WARNINGS "${WARNINGS} -Wlogical-op -Wno-error=maybe-uninitialized -Wno-error=clobbered -Wno-error=unused-but-set-variable") endif() diff --git a/ReleaseNotes.txt b/ReleaseNotes.txt index a48e57a80d..324f87fb8b 100644 --- a/ReleaseNotes.txt +++ b/ReleaseNotes.txt @@ -1,3 +1,8 @@ +Release notes 1.0.11 + +- New Bytecoin Wallet file format +- Daemon loading optimization + Release notes 1.0.10 - Blocksize increase diff --git a/include/IWallet.h b/include/IWallet.h index e9f8eae194..4d5674499c 100755 --- a/include/IWallet.h +++ b/include/IWallet.h @@ -44,6 +44,12 @@ enum WalletEventType { SYNC_COMPLETED, }; +enum class WalletSaveLevel : uint8_t { + SAVE_KEYS_ONLY, + SAVE_KEYS_AND_TRANSACTIONS, + SAVE_ALL +}; + struct WalletTransactionCreatedData { size_t transactionIndex; }; @@ -126,13 +132,15 @@ class IWallet { public: virtual ~IWallet() {} - virtual void initialize(const std::string& password) = 0; - virtual void initializeWithViewKey(const Crypto::SecretKey& viewSecretKey, const std::string& password) = 0; - virtual void load(std::istream& source, const std::string& password) = 0; + virtual void initialize(const std::string& path, const std::string& password) = 0; + virtual void initializeWithViewKey(const std::string& path, const std::string& password, const Crypto::SecretKey& viewSecretKey) = 0; + virtual void load(const std::string& path, const std::string& password, std::string& extra) = 0; + virtual void load(const std::string& path, const std::string& password) = 0; virtual void shutdown() = 0; virtual void changePassword(const std::string& oldPassword, const std::string& newPassword) = 0; - virtual void save(std::ostream& destination, bool saveDetails = true, bool saveCache = true, bool encrypt = true) = 0; + virtual void save(WalletSaveLevel saveLevel = WalletSaveLevel::SAVE_ALL, const std::string& extra = "") = 0; + virtual void exportWallet(const std::string& path, bool encrypt = true, WalletSaveLevel saveLevel = WalletSaveLevel::SAVE_ALL, const std::string& extra = "") = 0; virtual size_t getAddressCount() const = 0; virtual std::string getAddress(size_t index) const = 0; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f6ea4393b7..dc02ea299e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -18,9 +18,9 @@ file(GLOB_RECURSE SimpleWallet SimpleWallet/*) if(MSVC) file(GLOB_RECURSE System System/* Platform/Windows/System/*) elseif(APPLE) -file(GLOB_RECURSE System System/* Platform/OSX/System/*) +file(GLOB_RECURSE System System/* Platform/OSX/System/* Platform/Posix/System/*) else() -file(GLOB_RECURSE System System/* Platform/Linux/System/*) +file(GLOB_RECURSE System System/* Platform/Linux/System/* Platform/Posix/System/*) endif() file(GLOB_RECURSE Transfers Transfers/*) file(GLOB_RECURSE Wallet Wallet/*) diff --git a/src/Common/FileMappedVector.cpp b/src/Common/FileMappedVector.cpp new file mode 100644 index 0000000000..47f1d59e61 --- /dev/null +++ b/src/Common/FileMappedVector.cpp @@ -0,0 +1,22 @@ +// Copyright (c) 2012-2016, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#include "FileMappedVector.h" + +namespace { +char suppressMSVCWarningLNK4221; +} diff --git a/src/Common/FileMappedVector.h b/src/Common/FileMappedVector.h new file mode 100644 index 0000000000..c8a7da266e --- /dev/null +++ b/src/Common/FileMappedVector.h @@ -0,0 +1,915 @@ +// Copyright (c) 2012-2016, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#pragma once + +#include +#include +#include + +#include + +#include "System/MemoryMappedFile.h" + +#include "Common/ScopeExit.h" + +namespace Common { + +template +struct EnableIfPod { + typedef typename std::enable_if::value, EnableIfPod>::type type; +}; + +enum class FileMappedVectorOpenMode { + OPEN, + CREATE, + OPEN_OR_CREATE +}; + +template +class FileMappedVector : public EnableIfPod::type { +public: + typedef T value_type; + + const static uint64_t metadataSize = static_cast(2 * sizeof(uint64_t)); + const static uint64_t valueSize = static_cast(sizeof(T)); + + class const_iterator { + public: + typedef std::random_access_iterator_tag iterator_category; + typedef T value_type; + typedef ptrdiff_t difference_type; + typedef const T* pointer; + typedef const T& reference; + + const_iterator() : m_fileMappedVector(nullptr) { + } + + const_iterator(const FileMappedVector* fileMappedVector, size_t index) : + m_fileMappedVector(fileMappedVector), + m_index(index) { + } + + const T& operator*() const { + return (*m_fileMappedVector)[m_index]; + } + + const T* operator->() const { + return &(*m_fileMappedVector)[m_index]; + } + + const_iterator& operator++() { + ++m_index; + return *this; + } + + const_iterator operator++(int) { + const_iterator tmp = *this; + ++m_index; + return tmp; + } + + const_iterator& operator--() { + --m_index; + return *this; + } + + const_iterator operator--(int) { + const_iterator tmp = *this; + --m_index; + return tmp; + } + + const_iterator& operator+=(difference_type n) { + m_index += n; + return *this; + } + + const_iterator operator+(difference_type n) const { + return const_iterator(m_fileMappedVector, m_index + n); + } + + friend const_iterator operator+(difference_type n, const const_iterator& i) { + return const_iterator(i.m_fileMappedVector, n + i.m_index); + } + + const_iterator& operator-=(difference_type n) { + m_index -= n; + return *this; + } + + const_iterator operator-(difference_type n) const { + return const_iterator(m_fileMappedVector, m_index - n); + } + + difference_type operator-(const const_iterator& other) const { + return m_index - other.m_index; + } + + const T& operator[](difference_type offset) const { + return (*m_fileMappedVector)[m_index + offset]; + } + + bool operator==(const const_iterator& other) const { + return m_index == other.m_index; + } + + bool operator!=(const const_iterator& other) const { + return m_index != other.m_index; + } + + bool operator<(const const_iterator& other) const { + return m_index < other.m_index; + } + + bool operator>(const const_iterator& other) const { + return m_index > other.m_index; + } + + bool operator<=(const const_iterator& other) const { + return m_index <= other.m_index; + } + + bool operator>=(const const_iterator& other) const { + return m_index >= other.m_index; + } + + size_t index() const { + return m_index; + } + + protected: + const FileMappedVector* m_fileMappedVector; + size_t m_index; + }; + + class iterator : public const_iterator { + public: + typedef std::random_access_iterator_tag iterator_category; + typedef T value_type; + typedef ptrdiff_t difference_type; + typedef T* pointer; + typedef T& reference; + + iterator() : const_iterator() { + } + + iterator(const FileMappedVector* fileMappedVector, size_t index) : const_iterator(fileMappedVector, index) { + } + + T& operator*() const { + return const_cast((*const_iterator::m_fileMappedVector)[const_iterator::m_index]); + } + + T* operator->() const { + return const_cast(&(*const_iterator::m_fileMappedVector)[const_iterator::m_index]); + } + + iterator& operator++() { + ++const_iterator::m_index; + return *this; + } + + iterator operator++(int) { + iterator tmp = *this; + ++const_iterator::m_index; + return tmp; + } + + iterator& operator--() { + --const_iterator::m_index; + return *this; + } + + iterator operator--(int) { + iterator tmp = *this; + --const_iterator::m_index; + return tmp; + } + + iterator& operator+=(difference_type n) { + const_iterator::m_index += n; + return *this; + } + + iterator operator+(difference_type n) const { + return iterator(const_iterator::m_fileMappedVector, const_iterator::m_index + n); + } + + friend iterator operator+(difference_type n, const iterator& i) { + return iterator(i.m_fileMappedVector, n + i.m_index); + } + + iterator& operator-=(difference_type n) { + const_iterator::m_index -= n; + return *this; + } + + iterator operator-(difference_type n) const { + return iterator(const_iterator::m_fileMappedVector, const_iterator::m_index - n); + } + + difference_type operator-(const iterator& other) const { + return const_iterator::m_index - other.m_index; + } + + T& operator[](difference_type offset) const { + return (*const_iterator::m_fileMappedVector)[const_iterator::m_index + offset]; + } + }; + + FileMappedVector(); + FileMappedVector(const std::string& path, FileMappedVectorOpenMode mode = FileMappedVectorOpenMode::OPEN_OR_CREATE, uint64_t prefixSize = 0); + FileMappedVector(const FileMappedVector&) = delete; + FileMappedVector& operator=(const FileMappedVector&) = delete; + + void open(const std::string& path, FileMappedVectorOpenMode mode = FileMappedVectorOpenMode::OPEN_OR_CREATE, uint64_t prefixSize = 0); + void close(); + void close(std::error_code& ec); + bool isOpened() const; + + bool empty() const; + uint64_t capacity() const; + uint64_t size() const; + void reserve(uint64_t n); + void shrink_to_fit(); + + const_iterator begin() const; + iterator begin(); + const_iterator cbegin() const; + const_iterator end() const; + iterator end(); + const_iterator cend() const; + + const T& operator[](uint64_t index) const; + T& operator[](uint64_t index); + const T& at(uint64_t index) const; + T& at(uint64_t index); + const T& front() const; + T& front(); + const T& back() const; + T& back(); + const T* data() const; + T* data(); + + void clear(); + iterator erase(const_iterator position); + iterator erase(const_iterator first, const_iterator last); + iterator insert(const_iterator position, const T& val); + template + iterator insert(const_iterator position, InputIterator first, InputIterator last); + void pop_back(); + void push_back(const T& val); + void swap(FileMappedVector& other); + + void flush(); + + const uint8_t* prefix() const; + uint8_t* prefix(); + uint64_t prefixSize() const; + void resizePrefix(uint64_t newPrefixSize); + + const uint8_t* suffix() const; + uint8_t* suffix(); + uint64_t suffixSize() const; + void resizeSuffix(uint64_t newSuffixSize); + + void rename(const std::string& newPath, std::error_code& ec); + void rename(const std::string& newPath); + + template + void atomicUpdate(F&& func); + +private: + std::string m_path; + System::MemoryMappedFile m_file; + uint64_t m_prefixSize; + uint64_t m_suffixSize; + +private: + template + void atomicUpdate(uint64_t newSize, uint64_t newCapacity, uint64_t newPrefixSize, uint64_t newSuffixSize, F&& func); + template + void atomicUpdate0(uint64_t newCapacity, uint64_t newPrefixSize, uint64_t newSuffixSize, F&& func); + + void open(const std::string& path, uint64_t prefixSize); + void create(const std::string& path, uint64_t initialCapacity, uint64_t prefixSize, uint64_t suffixSize); + + uint8_t* prefixPtr(); + const uint8_t* prefixPtr() const; + uint64_t* capacityPtr(); + const uint64_t* capacityPtr() const; + const uint64_t* sizePtr() const; + uint64_t* sizePtr(); + T* vectorDataPtr(); + const T* vectorDataPtr() const; + uint8_t* suffixPtr(); + const uint8_t* suffixPtr() const; + + uint64_t vectorDataSize(); + + uint64_t nextCapacity(); + + void flushElement(uint64_t index); + void flushSize(); +}; + +template +FileMappedVector::FileMappedVector() { +} + +template +FileMappedVector::FileMappedVector(const std::string& path, FileMappedVectorOpenMode mode, uint64_t prefixSize) { + open(path, mode, prefixSize); +} + +template +void FileMappedVector::open(const std::string& path, FileMappedVectorOpenMode mode, uint64_t prefixSize) { + assert(!isOpened()); + + const uint64_t initialCapacity = 10; + + boost::filesystem::path filePath = path; + boost::filesystem::path bakPath = path + ".bak"; + bool fileExists; + if (boost::filesystem::exists(filePath)) { + if (boost::filesystem::exists(bakPath)) { + boost::filesystem::remove(bakPath); + } + + fileExists = true; + } else if (boost::filesystem::exists(bakPath)) { + boost::filesystem::rename(bakPath, filePath); + fileExists = true; + } else { + fileExists = false; + } + + if (mode == FileMappedVectorOpenMode::OPEN) { + open(path, prefixSize); + } else if (mode == FileMappedVectorOpenMode::CREATE) { + create(path, initialCapacity, prefixSize, 0); + } else if (mode == FileMappedVectorOpenMode::OPEN_OR_CREATE) { + if (fileExists) { + open(path, prefixSize); + } else { + create(path, initialCapacity, prefixSize, 0); + } + } else { + throw std::runtime_error("FileMappedVector: Unsupported open mode: " + std::to_string(static_cast(mode))); + } +} + +template +void FileMappedVector::close(std::error_code& ec) { + m_file.close(ec); + if (!ec) { + m_prefixSize = 0; + m_suffixSize = 0; + m_path.clear(); + } +} + +template +void FileMappedVector::close() { + std::error_code ec; + close(ec); + if (ec) { + throw std::system_error(ec, "FileMappedVector::close"); + } +} + +template +bool FileMappedVector::isOpened() const { + return m_file.isOpened(); +} + +template +bool FileMappedVector::empty() const { + assert(isOpened()); + + return size() == 0; +} + +template +uint64_t FileMappedVector::capacity() const { + assert(isOpened()); + + return *capacityPtr(); +} + +template +uint64_t FileMappedVector::size() const { + assert(isOpened()); + + return *sizePtr(); +} + +template +void FileMappedVector::reserve(uint64_t n) { + assert(isOpened()); + + if (n > capacity()) { + atomicUpdate(size(), n, prefixSize(), suffixSize(), [this](value_type* target) { + std::copy(cbegin(), cend(), target); + }); + } +} + +template +void FileMappedVector::shrink_to_fit() { + assert(isOpened()); + + if (size() < capacity()) { + atomicUpdate(size(), size(), prefixSize(), suffixSize(), [this](value_type* target) { + std::copy(cbegin(), cend(), target); + }); + } +} + +template +typename FileMappedVector::iterator FileMappedVector::begin() { + assert(isOpened()); + + return iterator(this, 0); +} + +template +typename FileMappedVector::const_iterator FileMappedVector::begin() const { + assert(isOpened()); + + return const_iterator(this, 0); +} + +template +typename FileMappedVector::const_iterator FileMappedVector::cbegin() const { + assert(isOpened()); + + return const_iterator(this, 0); +} + +template +typename FileMappedVector::const_iterator FileMappedVector::end() const { + assert(isOpened()); + + return const_iterator(this, size()); +} + +template +typename FileMappedVector::iterator FileMappedVector::end() { + assert(isOpened()); + + return iterator(this, size()); +} + +template +typename FileMappedVector::const_iterator FileMappedVector::cend() const { + assert(isOpened()); + + return const_iterator(this, size()); +} + +template +const T& FileMappedVector::operator[](uint64_t index) const { + assert(isOpened()); + + return vectorDataPtr()[index]; +} + +template +T& FileMappedVector::operator[](uint64_t index) { + assert(isOpened()); + + return vectorDataPtr()[index]; +} + +template +const T& FileMappedVector::at(uint64_t index) const { + assert(isOpened()); + + if (index >= size()) { + throw std::out_of_range("FileMappedVector::at " + std::to_string(index)); + } + + return vectorDataPtr()[index]; +} + +template +T& FileMappedVector::at(uint64_t index) { + assert(isOpened()); + + if (index >= size()) { + throw std::out_of_range("FileMappedVector::at " + std::to_string(index)); + } + + return vectorDataPtr()[index]; +} + +template +const T& FileMappedVector::front() const { + assert(isOpened()); + + return vectorDataPtr()[0]; +} + +template +T& FileMappedVector::front() { + assert(isOpened()); + + return vectorDataPtr()[0]; +} + +template +const T& FileMappedVector::back() const { + assert(isOpened()); + + return vectorDataPtr()[size() - 1]; +} + +template +T& FileMappedVector::back() { + assert(isOpened()); + + return vectorDataPtr()[size() - 1]; +} + +template +const T* FileMappedVector::data() const { + assert(isOpened()); + + return vectorDataPtr(); +} + +template +T* FileMappedVector::data() { + assert(isOpened()); + + return vectorDataPtr(); +} + +template +void FileMappedVector::clear() { + assert(isOpened()); + + *sizePtr() = 0; + flushSize(); +} + +template +typename FileMappedVector::iterator FileMappedVector::erase(const_iterator position) { + assert(isOpened()); + + return erase(position, std::next(position)); +} + +template +typename FileMappedVector::iterator FileMappedVector::erase(const_iterator first, const_iterator last) { + assert(isOpened()); + + uint64_t newSize = size() - std::distance(first, last); + + atomicUpdate(newSize, capacity(), prefixSize(), suffixSize(), [this, first, last](value_type* target) { + std::copy(cbegin(), first, target); + std::copy(last, cend(), target + std::distance(cbegin(), first)); + }); + + return iterator(this, first.index()); +} + +template +typename FileMappedVector::iterator FileMappedVector::insert(const_iterator position, const T& val) { + assert(isOpened()); + + return insert(position, &val, &val + 1); +} + +template +template +typename FileMappedVector::iterator FileMappedVector::insert(const_iterator position, InputIterator first, InputIterator last) { + assert(isOpened()); + + uint64_t newSize = size() + static_cast(std::distance(first, last)); + uint64_t newCapacity; + if (newSize > capacity()) { + newCapacity = nextCapacity(); + if (newSize > newCapacity) { + newCapacity = newSize; + } + } else { + newCapacity = capacity(); + } + + atomicUpdate(newSize, newCapacity, prefixSize(), suffixSize(), [this, position, first, last](value_type* target) { + std::copy(cbegin(), position, target); + std::copy(first, last, target + position.index()); + std::copy(position, cend(), target + position.index() + std::distance(first, last)); + }); + + return iterator(this, position.index()); +} + +template +void FileMappedVector::pop_back() { + assert(isOpened()); + + --(*sizePtr()); + flushSize(); +} + +template +void FileMappedVector::push_back(const T& val) { + assert(isOpened()); + + if (capacity() == size()) { + reserve(nextCapacity()); + } + + vectorDataPtr()[size()] = val; + flushElement(size()); + + ++(*sizePtr()); + flushSize(); +} + +template +void FileMappedVector::swap(FileMappedVector& other) { + m_path.swap(other.m_path); + m_file.swap(other.m_file); + std::swap(m_prefixSize, other.m_prefixSize); + std::swap(m_suffixSize, other.m_suffixSize); +} + +template +void FileMappedVector::flush() { + assert(isOpened()); + + m_file.flush(m_file.data(), m_file.size()); +} + +template +const uint8_t* FileMappedVector::prefix() const { + assert(isOpened()); + + return prefixPtr(); +} + +template +uint8_t* FileMappedVector::prefix() { + assert(isOpened()); + + return prefixPtr(); +} + +template +uint64_t FileMappedVector::prefixSize() const { + assert(isOpened()); + + return m_prefixSize; +} + +template +void FileMappedVector::resizePrefix(uint64_t newPrefixSize) { + assert(isOpened()); + + if (prefixSize() != newPrefixSize) { + atomicUpdate(size(), capacity(), newPrefixSize, suffixSize(), [this](value_type* target) { + std::copy(cbegin(), cend(), target); + }); + } +} + +template +const uint8_t* FileMappedVector::suffix() const { + assert(isOpened()); + + return suffixPtr(); +} + +template +uint8_t* FileMappedVector::suffix() { + assert(isOpened()); + + return suffixPtr(); +} + +template +uint64_t FileMappedVector::suffixSize() const { + assert(isOpened()); + + return m_suffixSize; +} + +template +void FileMappedVector::resizeSuffix(uint64_t newSuffixSize) { + assert(isOpened()); + + if (suffixSize() != newSuffixSize) { + atomicUpdate(size(), capacity(), prefixSize(), newSuffixSize, [this](value_type* target) { + std::copy(cbegin(), cend(), target); + }); + } +} + +template +void FileMappedVector::rename(const std::string& newPath, std::error_code& ec) { + m_file.rename(newPath, ec); + if (!ec) { + m_path = newPath; + } +} + +template +void FileMappedVector::rename(const std::string& newPath) { + m_file.rename(newPath); + m_path = newPath; +} + +template +template +void FileMappedVector::atomicUpdate(F&& func) { + atomicUpdate0(capacity(), prefixSize(), suffixSize(), std::move(func)); +} + +template +template +void FileMappedVector::atomicUpdate(uint64_t newSize, uint64_t newCapacity, uint64_t newPrefixSize, uint64_t newSuffixSize, F&& func) { + assert(newSize <= newCapacity); + + atomicUpdate0(newCapacity, newPrefixSize, newSuffixSize, [this, newSize, &func](FileMappedVector& newVector) { + if (prefixSize() != 0 && newVector.prefixSize() != 0) { + std::copy(prefixPtr(), prefixPtr() + std::min(prefixSize(), newVector.prefixSize()), newVector.prefix()); + } + + *newVector.sizePtr() = newSize; + func(newVector.data()); + + if (suffixSize() != 0 && newVector.suffixSize() != 0) { + std::copy(suffixPtr(), suffixPtr() + std::min(suffixSize(), newVector.suffixSize()), newVector.suffix()); + } + }); +} + +template +template +void FileMappedVector::atomicUpdate0(uint64_t newCapacity, uint64_t newPrefixSize, uint64_t newSuffixSize, F&& func) { + if (m_file.path() != m_path) { + throw std::runtime_error("Vector is mapped to a .bak file due to earlier errors"); + } + + boost::filesystem::path bakPath = m_path + ".bak"; + boost::filesystem::path tmpPath = boost::filesystem::unique_path(m_path + ".tmp.%%%%-%%%%"); + + if (boost::filesystem::exists(bakPath)) { + boost::filesystem::remove(bakPath); + } + + Tools::ScopeExit tmpFileDeleter([&tmpPath] { + boost::system::error_code ignore; + boost::filesystem::remove(tmpPath, ignore); + }); + + // Copy file. It is slow but atomic operation + FileMappedVector tmpVector; + tmpVector.create(tmpPath.string(), newCapacity, newPrefixSize, newSuffixSize); + func(tmpVector); + tmpVector.flush(); + + // Swap files + std::error_code ec; + std::error_code ignore; + m_file.rename(bakPath.string()); + tmpVector.rename(m_path, ec); + if (ec) { + // Try to restore and ignore errors + m_file.rename(m_path, ignore); + throw std::system_error(ec, "Failed to swap temporary and vector files"); + } + + m_path = bakPath.string(); + swap(tmpVector); + tmpFileDeleter.cancel(); + + // Remove .bak file and ignore errors + tmpVector.close(ignore); + boost::system::error_code boostError; + boost::filesystem::remove(bakPath, boostError); +} + +template +void FileMappedVector::open(const std::string& path, uint64_t prefixSize) { + m_prefixSize = prefixSize; + m_file.open(path); + m_path = path; + + if (m_file.size() < prefixSize + metadataSize) { + throw std::runtime_error("FileMappedVector::open() file is too small"); + } + + if (size() > capacity()) { + throw std::runtime_error("FileMappedVector::open() vector size is greater than capacity"); + } + + auto minRequiredFileSize = m_prefixSize + metadataSize + vectorDataSize(); + if (m_file.size() < minRequiredFileSize) { + throw std::runtime_error("FileMappedVector::open() invalid file size"); + } + + m_suffixSize = m_file.size() - minRequiredFileSize; +} + +template +void FileMappedVector::create(const std::string& path, uint64_t initialCapacity, uint64_t prefixSize, uint64_t suffixSize) { + m_file.create(path, prefixSize + metadataSize + initialCapacity * valueSize + suffixSize, false); + m_path = path; + m_prefixSize = prefixSize; + m_suffixSize = suffixSize; + *sizePtr() = 0; + *capacityPtr() = initialCapacity; + m_file.flush(reinterpret_cast(sizePtr()), metadataSize); +} + +template +uint8_t* FileMappedVector::prefixPtr() { + return m_file.data(); +} + +template +const uint8_t* FileMappedVector::prefixPtr() const { + return m_file.data(); +} + +template +uint64_t* FileMappedVector::capacityPtr() { + return reinterpret_cast(prefixPtr() + m_prefixSize); +} + +template +const uint64_t* FileMappedVector::capacityPtr() const { + return reinterpret_cast(prefixPtr() + m_prefixSize); +} + +template +const uint64_t* FileMappedVector::sizePtr() const { + return capacityPtr() + 1; +} + +template +uint64_t* FileMappedVector::sizePtr() { + return capacityPtr() + 1; +} + +template +T* FileMappedVector::vectorDataPtr() { + return reinterpret_cast(sizePtr() + 1); +} + +template +const T* FileMappedVector::vectorDataPtr() const { + return reinterpret_cast(sizePtr() + 1); +} + +template +uint8_t* FileMappedVector::suffixPtr() { + return reinterpret_cast(vectorDataPtr() + capacity()); +} + +template +const uint8_t* FileMappedVector::suffixPtr() const { + return reinterpret_cast(vectorDataPtr() + capacity()); +} + +template +uint64_t FileMappedVector::vectorDataSize() { + return capacity() * valueSize; +} + +template +uint64_t FileMappedVector::nextCapacity() { + return capacity() + capacity() / 2 + 1; +} + +template +void FileMappedVector::flushElement(uint64_t index) { + m_file.flush(reinterpret_cast(vectorDataPtr() + index), valueSize); +} + +template +void FileMappedVector::flushSize() { + m_file.flush(reinterpret_cast(sizePtr()), sizeof(uint64_t)); +} + +} diff --git a/src/CryptoNoteConfig.h b/src/CryptoNoteConfig.h index 9d1551662e..7bd5de508f 100644 --- a/src/CryptoNoteConfig.h +++ b/src/CryptoNoteConfig.h @@ -68,11 +68,11 @@ const size_t FUSION_TX_MAX_SIZE = CRYPTONOTE_BLOCK_ const size_t FUSION_TX_MIN_INPUT_COUNT = 12; const size_t FUSION_TX_MIN_IN_OUT_COUNT_RATIO = 4; -const uint64_t UPGRADE_HEIGHT_V2 = 546602; -const uint64_t UPGRADE_HEIGHT_V3 = 985548; +const uint32_t UPGRADE_HEIGHT_V2 = 546602; +const uint32_t UPGRADE_HEIGHT_V3 = 985548; const unsigned UPGRADE_VOTING_THRESHOLD = 90; // percent -const size_t UPGRADE_VOTING_WINDOW = EXPECTED_NUMBER_OF_BLOCKS_PER_DAY; // blocks -const size_t UPGRADE_WINDOW = EXPECTED_NUMBER_OF_BLOCKS_PER_DAY; // blocks +const uint32_t UPGRADE_VOTING_WINDOW = EXPECTED_NUMBER_OF_BLOCKS_PER_DAY; // blocks +const uint32_t UPGRADE_WINDOW = EXPECTED_NUMBER_OF_BLOCKS_PER_DAY; // blocks static_assert(0 < UPGRADE_VOTING_THRESHOLD && UPGRADE_VOTING_THRESHOLD <= 100, "Bad UPGRADE_VOTING_THRESHOLD"); static_assert(UPGRADE_VOTING_WINDOW > 1, "Bad UPGRADE_VOTING_WINDOW"); @@ -175,7 +175,8 @@ const CheckpointData CHECKPOINTS[] = { {979000, "d8290eb4eedbe638f5dbadebcaf3ea434857ce96168185dc04f75b6cc1f4fda6"}, {985548, "8d53e0d97594755a621feaee0978c0431fc01f42b85ff76a03af8641e2009d57"}, {985549, "dc6f8d9319282475c981896b98ff9772ae2499533c2302c32faf65115aaf2554"}, - {996000, "c9a9243049acc7773a3e58ae354d66f8ea83996ece93ffbaad0b8b42b5fb7223"} + {996000, "c9a9243049acc7773a3e58ae354d66f8ea83996ece93ffbaad0b8b42b5fb7223"}, + {1021000, "a0c4107d327ffeb31dabe135a7124191b0a5ef7c4fa34f06babc1f0546ab938e"} }; } // CryptoNote diff --git a/src/CryptoNoteCore/Blockchain.cpp b/src/CryptoNoteCore/Blockchain.cpp index f318201ffe..cdfdab964b 100644 --- a/src/CryptoNoteCore/Blockchain.cpp +++ b/src/CryptoNoteCore/Blockchain.cpp @@ -465,11 +465,38 @@ bool Blockchain::init(const std::string& config_folder, bool load_existing) { } } + uint32_t lastValidCheckpointHeight = 0; + if (!checkCheckpoints(lastValidCheckpointHeight)) { + logger(WARNING, BRIGHT_YELLOW) << "Invalid checkpoint found. Rollback blockchain to height=" << lastValidCheckpointHeight; + rollbackBlockchainTo(lastValidCheckpointHeight); + } + if (!m_upgradeDetectorV2.init() || !m_upgradeDetectorV3.init()) { logger(ERROR, BRIGHT_RED) << "Failed to initialize upgrade detector"; return false; } + bool reinitUpgradeDetectors = false; + if (!checkUpgradeHeight(m_upgradeDetectorV2)) { + uint32_t upgradeHeight = m_upgradeDetectorV2.upgradeHeight(); + assert(upgradeHeight != UpgradeDetectorBase::UNDEF_HEIGHT); + logger(WARNING, BRIGHT_YELLOW) << "Invalid block version at " << upgradeHeight + 1 << ": real=" << static_cast(m_blocks[upgradeHeight + 1].bl.majorVersion) << + " expected=" << static_cast(m_upgradeDetectorV2.targetVersion()) << ". Rollback blockchain to height=" << upgradeHeight; + rollbackBlockchainTo(upgradeHeight); + reinitUpgradeDetectors = true; + } else if (!checkUpgradeHeight(m_upgradeDetectorV3)) { + uint32_t upgradeHeight = m_upgradeDetectorV3.upgradeHeight(); + logger(WARNING, BRIGHT_YELLOW) << "Invalid block version at " << upgradeHeight + 1 << ": real=" << static_cast(m_blocks[upgradeHeight + 1].bl.majorVersion) << + " expected=" << static_cast(m_upgradeDetectorV3.targetVersion()) << ". Rollback blockchain to height=" << upgradeHeight; + rollbackBlockchainTo(upgradeHeight); + reinitUpgradeDetectors = true; + } + + if (reinitUpgradeDetectors && (!m_upgradeDetectorV2.init() || !m_upgradeDetectorV3.init())) { + logger(ERROR, BRIGHT_RED) << "Failed to initialize upgrade detector"; + return false; + } + update_next_comulative_size_limit(); uint64_t timestamp_diff = time(NULL) - m_blocks.back().bl.timestamp; @@ -700,7 +727,7 @@ bool Blockchain::rollback_blockchain_switching(std::list &original_chain, std::lock_guard lk(m_blockchain_lock); // remove failed subchain for (size_t i = m_blocks.size() - 1; i >= rollback_height; i--) { - popBlock(get_block_hash(m_blocks.back().bl)); + popBlock(); } // return back original chain @@ -738,7 +765,7 @@ bool Blockchain::switch_to_alternative_blockchain(std::list disconnected_chain; for (size_t i = m_blocks.size() - 1; i >= split_height; i--) { Block b = m_blocks[i].bl; - popBlock(get_block_hash(b)); + popBlock(); //if (!(r)) { logger(ERROR, BRIGHT_RED) << "failed to remove block on chain switching"; return false; } disconnected_chain.push_front(b); } @@ -773,9 +800,8 @@ bool Blockchain::switch_to_alternative_blockchain(std::list(); bool r = handle_alternative_block(old_ch_ent, get_block_hash(old_ch_ent), bvc, false); if (!r) { - logger(ERROR, BRIGHT_RED) << ("Failed to push ex-main chain blocks to alternative chain "); - rollback_blockchain_switching(disconnected_chain, split_height); - return false; + logger(WARNING, BRIGHT_YELLOW) << ("Failed to push ex-main chain blocks to alternative chain "); + break; } } } @@ -1910,7 +1936,7 @@ bool Blockchain::pushBlock(BlockEntry& block) { return true; } -void Blockchain::popBlock(const Crypto::Hash& blockHash) { +void Blockchain::popBlock() { if (m_blocks.empty()) { logger(ERROR, BRIGHT_RED) << "Attempt to pop block from empty blockchain."; @@ -1923,16 +1949,7 @@ void Blockchain::popBlock(const Crypto::Hash& blockHash) { } saveTransactions(transactions); - - popTransactions(m_blocks.back(), getObjectHash(m_blocks.back().bl.baseTransaction)); - - m_timestampIndex.remove(m_blocks.back().bl.timestamp, blockHash); - m_generatedTransactionsIndex.remove(m_blocks.back().bl); - - m_blocks.pop_back(); - m_blockIndex.pop(); - - assert(m_blockIndex.size() == m_blocks.size()); + removeLastBlock(); m_upgradeDetectorV2.blockPopped(); m_upgradeDetectorV3.blockPopped(); @@ -2164,6 +2181,61 @@ bool Blockchain::validateInput(const MultisignatureInput& input, const Crypto::H return true; } +bool Blockchain::checkCheckpoints(uint32_t& lastValidCheckpointHeight) { + std::vector checkpointHeights = m_checkpoints.getCheckpointHeights(); + for (const auto& checkpointHeight : checkpointHeights) { + if (m_blocks.size() <= checkpointHeight) { + return true; + } + + if(m_checkpoints.check_block(checkpointHeight, getBlockIdByHeight(checkpointHeight))) { + lastValidCheckpointHeight = checkpointHeight; + } else { + return false; + } + } + + return true; +} + +void Blockchain::rollbackBlockchainTo(uint32_t height) { + while (height + 1 < m_blocks.size()) { + removeLastBlock(); + } +} + +void Blockchain::removeLastBlock() { + if (m_blocks.empty()) { + logger(ERROR, BRIGHT_RED) << + "Attempt to pop block from empty blockchain."; + return; + } + + logger(DEBUGGING) << "Removing last block with height " << m_blocks.back().height; + popTransactions(m_blocks.back(), getObjectHash(m_blocks.back().bl.baseTransaction)); + + Crypto::Hash blockHash = getBlockIdByHeight(m_blocks.back().height); + m_timestampIndex.remove(m_blocks.back().bl.timestamp, blockHash); + m_generatedTransactionsIndex.remove(m_blocks.back().bl); + + m_blocks.pop_back(); + m_blockIndex.pop(); + + assert(m_blockIndex.size() == m_blocks.size()); +} + +bool Blockchain::checkUpgradeHeight(const UpgradeDetector& upgradeDetector) { + uint32_t upgradeHeight = upgradeDetector.upgradeHeight(); + if (upgradeHeight != UpgradeDetectorBase::UNDEF_HEIGHT && upgradeHeight + 1 < m_blocks.size()) { + logger(INFO) << "Checking block version at " << upgradeHeight + 1; + if (m_blocks[upgradeHeight + 1].bl.majorVersion != upgradeDetector.targetVersion()) { + return false; + } + } + + return true; +} + bool Blockchain::getLowerBound(uint64_t timestamp, uint64_t startOffset, uint32_t& height) { std::lock_guard lk(m_blockchain_lock); @@ -2350,7 +2422,7 @@ void Blockchain::saveTransactions(const std::vector& transactions) tx_verification_context context; for (size_t i = 0; i < transactions.size(); ++i) { if (!m_tx_pool.add_tx(transactions[transactions.size() - 1 - i], context, true)) { - throw std::runtime_error("Blockchain::saveTransactions, failed to add transaction to pool"); + logger(WARNING, BRIGHT_YELLOW) << "Blockchain::saveTransactions, failed to add transaction to pool"; } } } diff --git a/src/CryptoNoteCore/Blockchain.h b/src/CryptoNoteCore/Blockchain.h index 132cf1c11f..53e0ab8ec8 100755 --- a/src/CryptoNoteCore/Blockchain.h +++ b/src/CryptoNoteCore/Blockchain.h @@ -303,11 +303,15 @@ namespace CryptoNote { bool pushBlock(const Block& blockData, block_verification_context& bvc); bool pushBlock(const Block& blockData, const std::vector& transactions, block_verification_context& bvc); bool pushBlock(BlockEntry& block); - void popBlock(const Crypto::Hash& blockHash); + void popBlock(); bool pushTransaction(BlockEntry& block, const Crypto::Hash& transactionHash, TransactionIndex transactionIndex); void popTransaction(const Transaction& transaction, const Crypto::Hash& transactionHash); void popTransactions(const BlockEntry& block, const Crypto::Hash& minerTransactionHash); bool validateInput(const MultisignatureInput& input, const Crypto::Hash& transactionHash, const Crypto::Hash& transactionPrefixHash, const std::vector& transactionSignatures); + bool checkCheckpoints(uint32_t& lastValidCheckpointHeight); + void rollbackBlockchainTo(uint32_t height); + void removeLastBlock(); + bool checkUpgradeHeight(const UpgradeDetector& upgradeDetector); bool storeBlockchainIndices(); bool loadBlockchainIndices(); diff --git a/src/CryptoNoteCore/BlockchainIndices.cpp b/src/CryptoNoteCore/BlockchainIndices.cpp index 39fa83cba0..0b9f253286 100755 --- a/src/CryptoNoteCore/BlockchainIndices.cpp +++ b/src/CryptoNoteCore/BlockchainIndices.cpp @@ -25,7 +25,11 @@ namespace CryptoNote { -PaymentIdIndex::PaymentIdIndex(bool _enabled) : enabled(_enabled) { +namespace { + const size_t DEFAULT_BUCKET_COUNT = 5; +} + +PaymentIdIndex::PaymentIdIndex(bool _enabled) : enabled(_enabled), index(DEFAULT_BUCKET_COUNT, paymentIdHash) { } bool PaymentIdIndex::add(const Transaction& transaction) { diff --git a/src/CryptoNoteCore/BlockchainIndices.h b/src/CryptoNoteCore/BlockchainIndices.h index cdecc858bb..a64df511dc 100755 --- a/src/CryptoNoteCore/BlockchainIndices.h +++ b/src/CryptoNoteCore/BlockchainIndices.h @@ -17,9 +17,10 @@ #pragma once +#include +#include #include #include -#include #include "crypto/hash.h" #include "CryptoNoteBasic.h" @@ -28,6 +29,10 @@ namespace CryptoNote { class ISerializer; +inline size_t paymentIdHash(const Crypto::Hash& paymentId) { + return boost::hash_range(std::begin(paymentId.data), std::end(paymentId.data)); +} + class PaymentIdIndex { public: PaymentIdIndex(bool enabled); @@ -44,7 +49,7 @@ class PaymentIdIndex { archive & index; } private: - std::unordered_multimap index; + std::unordered_multimap> index; bool enabled = false; }; diff --git a/src/CryptoNoteCore/Checkpoints.cpp b/src/CryptoNoteCore/Checkpoints.cpp index 89bc947fdb..3c1129cea3 100644 --- a/src/CryptoNoteCore/Checkpoints.cpp +++ b/src/CryptoNoteCore/Checkpoints.cpp @@ -83,4 +83,15 @@ bool Checkpoints::is_alternative_block_allowed(uint32_t blockchain_height, uint32_t checkpoint_height = it->first; return checkpoint_height < block_height; } + +std::vector Checkpoints::getCheckpointHeights() const { + std::vector checkpointHeights; + checkpointHeights.reserve(m_points.size()); + for (const auto& it : m_points) { + checkpointHeights.push_back(it.first); + } + + return checkpointHeights; +} + } diff --git a/src/CryptoNoteCore/Checkpoints.h b/src/CryptoNoteCore/Checkpoints.h index 475a78dd68..8c69aff99f 100755 --- a/src/CryptoNoteCore/Checkpoints.h +++ b/src/CryptoNoteCore/Checkpoints.h @@ -32,6 +32,7 @@ namespace CryptoNote bool check_block(uint32_t height, const Crypto::Hash& h) const; bool check_block(uint32_t height, const Crypto::Hash& h, bool& is_a_checkpoint) const; bool is_alternative_block_allowed(uint32_t blockchain_height, uint32_t block_height) const; + std::vector getCheckpointHeights() const; private: std::map m_points; diff --git a/src/CryptoNoteCore/Currency.cpp b/src/CryptoNoteCore/Currency.cpp index 257c7a1d49..870679cd24 100755 --- a/src/CryptoNoteCore/Currency.cpp +++ b/src/CryptoNoteCore/Currency.cpp @@ -73,7 +73,7 @@ bool Currency::init() { if (isTestnet()) { m_upgradeHeightV2 = 0; - m_upgradeHeightV3 = static_cast(-1); + m_upgradeHeightV3 = static_cast(-1); m_blocksFileName = "testnet_" + m_blocksFileName; m_blocksCacheFileName = "testnet_" + m_blocksCacheFileName; m_blockIndexesFileName = "testnet_" + m_blockIndexesFileName; @@ -128,13 +128,13 @@ size_t Currency::blockGrantedFullRewardZoneByBlockVersion(uint8_t blockMajorVers } } -uint64_t Currency::upgradeHeight(uint8_t majorVersion) const { +uint32_t Currency::upgradeHeight(uint8_t majorVersion) const { if (majorVersion == BLOCK_MAJOR_VERSION_2) { return m_upgradeHeightV2; } else if (majorVersion == BLOCK_MAJOR_VERSION_3) { return m_upgradeHeightV3; } else { - return static_cast(-1); + return static_cast(-1); } } diff --git a/src/CryptoNoteCore/Currency.h b/src/CryptoNoteCore/Currency.h index 3046c4f271..3958781577 100755 --- a/src/CryptoNoteCore/Currency.h +++ b/src/CryptoNoteCore/Currency.h @@ -77,13 +77,13 @@ class Currency { size_t fusionTxMinInputCount() const { return m_fusionTxMinInputCount; } size_t fusionTxMinInOutCountRatio() const { return m_fusionTxMinInOutCountRatio; } - uint64_t upgradeHeight(uint8_t majorVersion) const; + uint32_t upgradeHeight(uint8_t majorVersion) const; unsigned int upgradeVotingThreshold() const { return m_upgradeVotingThreshold; } - size_t upgradeVotingWindow() const { return m_upgradeVotingWindow; } - size_t upgradeWindow() const { return m_upgradeWindow; } - size_t minNumberVotingBlocks() const { return (m_upgradeVotingWindow * m_upgradeVotingThreshold + 99) / 100; } - uint64_t maxUpgradeDistance() const { return static_cast(m_upgradeWindow); } - uint64_t calculateUpgradeHeight(uint64_t voteCompleteHeight) const { return voteCompleteHeight + m_upgradeWindow; } + uint32_t upgradeVotingWindow() const { return m_upgradeVotingWindow; } + uint32_t upgradeWindow() const { return m_upgradeWindow; } + uint32_t minNumberVotingBlocks() const { return (m_upgradeVotingWindow * m_upgradeVotingThreshold + 99) / 100; } + uint32_t maxUpgradeDistance() const { return 7 * m_upgradeWindow; } + uint32_t calculateUpgradeHeight(uint32_t voteCompleteHeight) const { return voteCompleteHeight + m_upgradeWindow; } const std::string& blocksFileName() const { return m_blocksFileName; } const std::string& blocksCacheFileName() const { return m_blocksCacheFileName; } @@ -176,11 +176,11 @@ class Currency { size_t m_fusionTxMinInputCount; size_t m_fusionTxMinInOutCountRatio; - uint64_t m_upgradeHeightV2; - uint64_t m_upgradeHeightV3; + uint32_t m_upgradeHeightV2; + uint32_t m_upgradeHeightV3; unsigned int m_upgradeVotingThreshold; - size_t m_upgradeVotingWindow; - size_t m_upgradeWindow; + uint32_t m_upgradeVotingWindow; + uint32_t m_upgradeWindow; std::string m_blocksFileName; std::string m_blocksCacheFileName; diff --git a/src/CryptoNoteCore/UpgradeDetector.h b/src/CryptoNoteCore/UpgradeDetector.h index 295bb5fe90..c7eb58aa6e 100755 --- a/src/CryptoNoteCore/UpgradeDetector.h +++ b/src/CryptoNoteCore/UpgradeDetector.h @@ -31,12 +31,12 @@ namespace CryptoNote { class UpgradeDetectorBase { public: - enum : uint64_t { - UNDEF_HEIGHT = static_cast(-1), + enum : uint32_t { + UNDEF_HEIGHT = static_cast(-1), }; }; - static_assert(CryptoNote::UpgradeDetectorBase::UNDEF_HEIGHT == UINT64_C(0xFFFFFFFFFFFFFFFF), "UpgradeDetectorBase::UNDEF_HEIGHT has invalid value"); + static_assert(CryptoNote::UpgradeDetectorBase::UNDEF_HEIGHT == UINT32_C(0xFFFFFFFF), "UpgradeDetectorBase::UNDEF_HEIGHT has invalid value"); template class BasicUpgradeDetector : public UpgradeDetectorBase { @@ -49,7 +49,7 @@ namespace CryptoNote { logger(log, "upgrade") { } bool init() { - auto upgradeHeight = m_currency.upgradeHeight(m_targetVersion); + uint32_t upgradeHeight = m_currency.upgradeHeight(m_targetVersion); if (upgradeHeight == UNDEF_HEIGHT) { if (m_blockchain.empty()) { m_votingCompleteHeight = UNDEF_HEIGHT; @@ -65,7 +65,7 @@ namespace CryptoNote { return false; } - uint64_t upgradeHeight = it - m_blockchain.begin(); + uint32_t upgradeHeight = it - m_blockchain.begin(); m_votingCompleteHeight = findVotingCompleteHeight(upgradeHeight); if (m_votingCompleteHeight == UNDEF_HEIGHT) { logger(Logging::ERROR, Logging::BRIGHT_RED) << "Internal error: voting complete height isn't found, upgrade height = " << upgradeHeight; @@ -105,9 +105,9 @@ namespace CryptoNote { } uint8_t targetVersion() const { return m_targetVersion; } - uint64_t votingCompleteHeight() const { return m_votingCompleteHeight; } + uint32_t votingCompleteHeight() const { return m_votingCompleteHeight; } - uint64_t upgradeHeight() const { + uint32_t upgradeHeight() const { if (m_currency.upgradeHeight(m_targetVersion) == UNDEF_HEIGHT) { return m_votingCompleteHeight == UNDEF_HEIGHT ? UNDEF_HEIGHT : m_currency.calculateUpgradeHeight(m_votingCompleteHeight); } else { @@ -120,9 +120,9 @@ namespace CryptoNote { if (m_currency.upgradeHeight(m_targetVersion) != UNDEF_HEIGHT) { if (m_blockchain.size() <= m_currency.upgradeHeight(m_targetVersion) + 1) { - assert(m_blockchain.back().bl.majorVersion == m_targetVersion - 1); + assert(m_blockchain.back().bl.majorVersion <= m_targetVersion - 1); } else { - assert(m_blockchain.back().bl.majorVersion == m_targetVersion); + assert(m_blockchain.back().bl.majorVersion >= m_targetVersion); } } else if (m_votingCompleteHeight != UNDEF_HEIGHT) { @@ -152,7 +152,7 @@ namespace CryptoNote { } } else { - uint64_t lastBlockHeight = m_blockchain.size() - 1; + uint32_t lastBlockHeight = m_blockchain.size() - 1; if (isVotingComplete(lastBlockHeight)) { m_votingCompleteHeight = lastBlockHeight; logger(Logging::TRACE, Logging::BRIGHT_GREEN) << "###### UPGRADE voting complete at block index " << m_votingCompleteHeight << @@ -174,8 +174,8 @@ namespace CryptoNote { } } - size_t getNumberOfVotes(uint64_t height) { - if (height < static_cast(m_currency.upgradeVotingWindow()) - 1) { + size_t getNumberOfVotes(uint32_t height) { + if (height < m_currency.upgradeVotingWindow() - 1) { return 0; } @@ -189,11 +189,10 @@ namespace CryptoNote { } private: - uint64_t findVotingCompleteHeight(uint64_t probableUpgradeHeight) { + uint32_t findVotingCompleteHeight(uint32_t probableUpgradeHeight) { assert(m_currency.upgradeHeight(m_targetVersion) == UNDEF_HEIGHT); - uint64_t probableVotingCompleteHeight = probableUpgradeHeight > m_currency.maxUpgradeDistance() ? - probableUpgradeHeight - m_currency.maxUpgradeDistance() : 0; + uint32_t probableVotingCompleteHeight = probableUpgradeHeight > m_currency.maxUpgradeDistance() ? probableUpgradeHeight - m_currency.maxUpgradeDistance() : 0; for (size_t i = probableVotingCompleteHeight; i <= probableUpgradeHeight; ++i) { if (isVotingComplete(i)) { return i; @@ -203,7 +202,7 @@ namespace CryptoNote { return UNDEF_HEIGHT; } - bool isVotingComplete(uint64_t height) { + bool isVotingComplete(uint32_t height) { assert(m_currency.upgradeHeight(m_targetVersion) == UNDEF_HEIGHT); assert(m_currency.upgradeVotingWindow() > 1); assert(m_currency.upgradeVotingThreshold() > 0 && m_currency.upgradeVotingThreshold() <= 100); @@ -217,6 +216,6 @@ namespace CryptoNote { const Currency& m_currency; BC& m_blockchain; uint8_t m_targetVersion; - uint64_t m_votingCompleteHeight; + uint32_t m_votingCompleteHeight; }; } diff --git a/src/PaymentGate/WalletService.cpp b/src/PaymentGate/WalletService.cpp index 4f9f916df1..7721b79a05 100755 --- a/src/PaymentGate/WalletService.cpp +++ b/src/PaymentGate/WalletService.cpp @@ -162,47 +162,6 @@ void validatePaymentId(const std::string& paymentId, Logging::LoggerRef logger) } } -bool createOutputBinaryFile(const std::string& filename, std::fstream& file) { - file.open(filename.c_str(), std::fstream::in | std::fstream::out | std::ofstream::binary); - if (file) { - file.close(); - return false; - } - - file.open(filename.c_str(), std::fstream::out | std::fstream::binary); - return true; -} - -std::string createTemporaryFile(const std::string& path, std::fstream& tempFile) { - bool created = false; - std::string temporaryName; - - for (size_t i = 1; i < 100; i++) { - temporaryName = path + "." + std::to_string(i++); - - if (createOutputBinaryFile(temporaryName, tempFile)) { - created = true; - break; - } - } - - if (!created) { - throw std::runtime_error("Couldn't create temporary file: " + temporaryName); - } - - return temporaryName; -} - -//returns true on success -bool deleteFile(const std::string& filename) { - boost::system::error_code err; - return boost::filesystem::remove(filename, err) && !err; -} - -void replaceWalletFiles(const std::string &path, const std::string &tempFilePath) { - Tools::replace_file(tempFilePath, path); -} - Crypto::Hash parseHash(const std::string& hashString, Logging::LoggerRef logger) { Crypto::Hash hash; @@ -345,47 +304,7 @@ std::vector convertWalletRpcOrdersToWalletOrders(const } -void createWalletFile(std::fstream& walletFile, const std::string& filename) { - boost::filesystem::path pathToWalletFile(filename); - boost::filesystem::path directory = pathToWalletFile.parent_path(); - if (!directory.empty() && !Tools::directoryExists(directory.string())) { - throw std::runtime_error("Directory does not exist: " + directory.string()); - } - - walletFile.open(filename.c_str(), std::fstream::in | std::fstream::out | std::fstream::binary); - if (walletFile) { - walletFile.close(); - throw std::runtime_error("Wallet file already exists"); - } - - walletFile.open(filename.c_str(), std::fstream::out); - walletFile.close(); - - walletFile.open(filename.c_str(), std::fstream::in | std::fstream::out | std::fstream::binary); -} - -void saveWallet(CryptoNote::IWallet& wallet, std::fstream& walletFile, bool saveDetailed = true, bool saveCache = true) { - wallet.save(walletFile, saveDetailed, saveCache); - walletFile.flush(); -} - -void secureSaveWallet(CryptoNote::IWallet& wallet, const std::string& path, bool saveDetailed = true, bool saveCache = true) { - std::fstream tempFile; - std::string tempFilePath = createTemporaryFile(path, tempFile); - - try { - saveWallet(wallet, tempFile, saveDetailed, saveCache); - } catch (std::exception&) { - deleteFile(tempFilePath); - tempFile.close(); - throw; - } - tempFile.close(); - - replaceWalletFiles(path, tempFilePath); -} - -void generateNewWallet(const CryptoNote::Currency ¤cy, const WalletConfiguration &conf, Logging::ILogger& logger, System::Dispatcher& dispatcher) { +void generateNewWallet(const CryptoNote::Currency& currency, const WalletConfiguration& conf, Logging::ILogger& logger, System::Dispatcher& dispatcher) { Logging::LoggerRef log(logger, "generateNewWallet"); CryptoNote::INode* nodeStub = NodeFactory::createNodeStub(); @@ -396,31 +315,15 @@ void generateNewWallet(const CryptoNote::Currency ¤cy, const WalletConfigu log(Logging::INFO, Logging::BRIGHT_WHITE) << "Generating new wallet"; - std::fstream walletFile; - createWalletFile(walletFile, conf.walletFile); - - wallet->initialize(conf.walletPassword); + wallet->initialize(conf.walletFile, conf.walletPassword); auto address = wallet->createAddress(); log(Logging::INFO, Logging::BRIGHT_WHITE) << "New wallet is generated. Address: " << address; - saveWallet(*wallet, walletFile, false, false); + wallet->save(CryptoNote::WalletSaveLevel::SAVE_KEYS_ONLY); log(Logging::INFO, Logging::BRIGHT_WHITE) << "Wallet is saved"; } -void importLegacyKeys(const std::string &legacyKeysFile, const WalletConfiguration &conf) { - std::stringstream archive; - - CryptoNote::importLegacyKeys(legacyKeysFile, conf.walletPassword, archive); - - std::fstream walletFile; - createWalletFile(walletFile, conf.walletFile); - - archive.flush(); - walletFile << archive.rdbuf(); - walletFile.flush(); -} - WalletService::WalletService(const CryptoNote::Currency& currency, System::Dispatcher& sys, CryptoNote::INode& node, CryptoNote::IWallet& wallet, CryptoNote::IFusionManager& fusionManager, const WalletConfiguration& conf, Logging::ILogger& logger) : currency(currency), @@ -455,21 +358,13 @@ void WalletService::init() { } void WalletService::saveWallet() { - PaymentService::secureSaveWallet(wallet, config.walletFile, true, true); + wallet.save(); logger(Logging::INFO, Logging::BRIGHT_WHITE) << "Wallet is saved"; } void WalletService::loadWallet() { - std::ifstream inputWalletFile; - inputWalletFile.open(config.walletFile.c_str(), std::fstream::in | std::fstream::binary); - if (!inputWalletFile) { - throw std::runtime_error("Couldn't open wallet file"); - } - logger(Logging::INFO, Logging::BRIGHT_WHITE) << "Loading wallet"; - - wallet.load(inputWalletFile, config.walletPassword); - + wallet.load(config.walletFile, config.walletPassword); logger(Logging::INFO, Logging::BRIGHT_WHITE) << "Wallet loading is finished."; } @@ -1119,7 +1014,7 @@ void WalletService::refresh() { } void WalletService::reset() { - PaymentService::secureSaveWallet(wallet, config.walletFile, false, false); + wallet.save(CryptoNote::WalletSaveLevel::SAVE_KEYS_ONLY); wallet.stop(); wallet.shutdown(); inited = false; @@ -1137,8 +1032,23 @@ void WalletService::replaceWithNewWallet(const Crypto::SecretKey& viewSecretKey) transactionIdIndex.clear(); + size_t i = 0; + for (;;) { + boost::system::error_code ec; + std::string backup = config.walletFile + ".backup"; + if (i != 0) { + backup += "." + std::to_string(i); + } + + if (!boost::filesystem::exists(backup)) { + boost::filesystem::rename(config.walletFile, backup); + logger(Logging::DEBUGGING) << "Walled file '" << config.walletFile << "' backed up to '" << backup << '\''; + break; + } + } + wallet.start(); - wallet.initializeWithViewKey(viewSecretKey, config.walletPassword); + wallet.initializeWithViewKey(config.walletFile, config.walletPassword, viewSecretKey); inited = true; } diff --git a/src/PaymentGate/WalletService.h b/src/PaymentGate/WalletService.h index 62708bfca0..542ad3fb20 100755 --- a/src/PaymentGate/WalletService.h +++ b/src/PaymentGate/WalletService.h @@ -44,7 +44,7 @@ struct WalletConfiguration { std::string walletPassword; }; -void generateNewWallet(const CryptoNote::Currency ¤cy, const WalletConfiguration &conf, Logging::ILogger &logger, System::Dispatcher& dispatcher); +void generateNewWallet(const CryptoNote::Currency& currency, const WalletConfiguration& conf, Logging::ILogger& logger, System::Dispatcher& dispatcher); struct TransactionsInBlockInfoFilter; diff --git a/src/Platform/Posix/System/MemoryMappedFile.cpp b/src/Platform/Posix/System/MemoryMappedFile.cpp new file mode 100644 index 0000000000..8d815db81f --- /dev/null +++ b/src/Platform/Posix/System/MemoryMappedFile.cpp @@ -0,0 +1,259 @@ +// Copyright (c) 2012-2016, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#include "MemoryMappedFile.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include "Common/ScopeExit.h" + +namespace System { + +MemoryMappedFile::MemoryMappedFile() : + m_file(-1), + m_size(0), + m_data(nullptr) { +} + +MemoryMappedFile::~MemoryMappedFile() { + std::error_code ignore; + close(ignore); +} + +const std::string& MemoryMappedFile::path() const { + assert(isOpened()); + + return m_path; +} + +uint64_t MemoryMappedFile::size() const { + assert(isOpened()); + + return m_size; +} + +const uint8_t* MemoryMappedFile::data() const { + assert(isOpened()); + + return m_data; +} + +uint8_t* MemoryMappedFile::data() { + assert(isOpened()); + + return m_data; +} + +bool MemoryMappedFile::isOpened() const { + return m_data != nullptr; +} + +void MemoryMappedFile::create(const std::string& path, uint64_t size, bool overwrite, std::error_code& ec) { + if (isOpened()) { + close(ec); + if (ec) { + return; + } + } + + Tools::ScopeExit failExitHandler([this, &ec] { + ec = std::error_code(errno, std::system_category()); + std::error_code ignore; + close(ignore); + }); + + m_file = ::open(path.c_str(), O_RDWR | O_CREAT | (overwrite ? O_TRUNC : O_EXCL), S_IRUSR | S_IWUSR); + if (m_file == -1) { + return; + } + + int result = ::ftruncate(m_file, static_cast(size)); + if (result == -1) { + return; + } + + m_data = reinterpret_cast(::mmap(nullptr, static_cast(size), PROT_READ | PROT_WRITE, MAP_SHARED, m_file, 0)); + if (m_data == MAP_FAILED) { + return; + } + + m_size = size; + m_path = path; + ec = std::error_code(); + + failExitHandler.cancel(); +} + +void MemoryMappedFile::create(const std::string& path, uint64_t size, bool overwrite) { + std::error_code ec; + create(path, size, overwrite, ec); + if (ec) { + throw std::system_error(ec, "MemoryMappedFile::create"); + } +} + +void MemoryMappedFile::open(const std::string& path, std::error_code& ec) { + if (isOpened()) { + close(ec); + if (ec) { + return; + } + } + + Tools::ScopeExit failExitHandler([this, &ec] { + ec = std::error_code(errno, std::system_category()); + std::error_code ignore; + close(ignore); + }); + + m_file = ::open(path.c_str(), O_RDWR, S_IRUSR | S_IWUSR); + if (m_file == -1) { + return; + } + + struct stat fileStat; + int result = ::fstat(m_file, &fileStat); + if (result == -1) { + return; + } + + m_size = static_cast(fileStat.st_size); + + m_data = reinterpret_cast(::mmap(nullptr, static_cast(m_size), PROT_READ | PROT_WRITE, MAP_SHARED, m_file, 0)); + if (m_data == MAP_FAILED) { + return; + } + + m_path = path; + ec = std::error_code(); + + failExitHandler.cancel(); +} + +void MemoryMappedFile::open(const std::string& path) { + std::error_code ec; + open(path, ec); + if (ec) { + throw std::system_error(ec, "MemoryMappedFile::open"); + } +} + +void MemoryMappedFile::rename(const std::string& newPath, std::error_code& ec) { + assert(isOpened()); + + int result = ::rename(m_path.c_str(), newPath.c_str()); + if (result == 0) { + m_path = newPath; + ec = std::error_code(); + } else { + ec = std::error_code(errno, std::system_category()); + } +} + +void MemoryMappedFile::rename(const std::string& newPath) { + assert(isOpened()); + + std::error_code ec; + rename(newPath, ec); + if (ec) { + throw std::system_error(ec, "MemoryMappedFile::rename"); + } +} + +void MemoryMappedFile::close(std::error_code& ec) { + int result; + if (m_data != nullptr) { + flush(m_data, m_size, ec); + if (ec) { + return; + } + + result = ::munmap(m_data, static_cast(m_size)); + if (result == 0) { + m_data = nullptr; + } else { + ec = std::error_code(errno, std::system_category()); + return; + } + } + + if (m_file != -1) { + result = ::close(m_file); + if (result == 0) { + m_file = -1; + ec = std::error_code(); + } else { + ec = std::error_code(errno, std::system_category()); + return; + } + } + + ec = std::error_code(); +} + +void MemoryMappedFile::close() { + std::error_code ec; + close(ec); + if (ec) { + throw std::system_error(ec, "MemoryMappedFile::close"); + } +} + +void MemoryMappedFile::flush(uint8_t* data, uint64_t size, std::error_code& ec) { + assert(isOpened()); + + uintptr_t pageSize = static_cast(sysconf(_SC_PAGESIZE)); + uintptr_t dataAddr = reinterpret_cast(data); + uintptr_t pageOffset = (dataAddr / pageSize) * pageSize; + + int result = ::msync(reinterpret_cast(pageOffset), static_cast(dataAddr % pageSize + size), MS_SYNC); + if (result == 0) { + result = ::fsync(m_file); + if (result == 0) { + ec = std::error_code(); + return; + } + } + + ec = std::error_code(errno, std::system_category()); +} + +void MemoryMappedFile::flush(uint8_t* data, uint64_t size) { + assert(isOpened()); + + std::error_code ec; + flush(data, size, ec); + if (ec) { + throw std::system_error(ec, "MemoryMappedFile::flush"); + } +} + +void MemoryMappedFile::swap(MemoryMappedFile& other) { + std::swap(m_file, other.m_file); + std::swap(m_path, other.m_path); + std::swap(m_data, other.m_data); + std::swap(m_size, other.m_size); +} + +} diff --git a/src/Platform/Posix/System/MemoryMappedFile.h b/src/Platform/Posix/System/MemoryMappedFile.h new file mode 100644 index 0000000000..042123fdfb --- /dev/null +++ b/src/Platform/Posix/System/MemoryMappedFile.h @@ -0,0 +1,59 @@ +// Copyright (c) 2012-2016, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#pragma once + +#include +#include +#include + +namespace System { + +class MemoryMappedFile { +public: + MemoryMappedFile(); + ~MemoryMappedFile(); + + void create(const std::string& path, uint64_t size, bool overwrite, std::error_code& ec); + void create(const std::string& path, uint64_t size, bool overwrite); + void open(const std::string& path, std::error_code& ec); + void open(const std::string& path); + void close(std::error_code& ec); + void close(); + + const std::string& path() const; + uint64_t size() const; + const uint8_t* data() const; + uint8_t* data(); + bool isOpened() const; + + void rename(const std::string& newPath, std::error_code& ec); + void rename(const std::string& newPath); + + void flush(uint8_t* data, uint64_t size, std::error_code& ec); + void flush(uint8_t* data, uint64_t size); + + void swap(MemoryMappedFile& other); + +private: + int m_file; + std::string m_path; + uint64_t m_size; + uint8_t* m_data; +}; + +} diff --git a/src/Platform/Windows/System/MemoryMappedFile.cpp b/src/Platform/Windows/System/MemoryMappedFile.cpp new file mode 100644 index 0000000000..4355ea81ad --- /dev/null +++ b/src/Platform/Windows/System/MemoryMappedFile.cpp @@ -0,0 +1,293 @@ +// Copyright (c) 2012-2016, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#include "MemoryMappedFile.h" + +#include + +#define NOMINMAX +#include + +#include "Common/ScopeExit.h" + +namespace System { + +MemoryMappedFile::MemoryMappedFile() : + m_fileHandle(INVALID_HANDLE_VALUE), + m_mappingHandle(INVALID_HANDLE_VALUE), + m_size(0), + m_data(nullptr) { +} + +MemoryMappedFile::~MemoryMappedFile() { + std::error_code ignore; + close(ignore); +} + +const std::string& MemoryMappedFile::path() const { + assert(isOpened()); + + return m_path; +} + +uint64_t MemoryMappedFile::size() const { + assert(isOpened()); + + return m_size; +} + +const uint8_t* MemoryMappedFile::data() const { + assert(isOpened()); + + return m_data; +} + +uint8_t* MemoryMappedFile::data() { + assert(isOpened()); + + return m_data; +} + +bool MemoryMappedFile::isOpened() const { + return m_data != nullptr; +} + +void MemoryMappedFile::create(const std::string& path, uint64_t size, bool overwrite, std::error_code& ec) { + if (isOpened()) { + close(ec); + if (ec) { + return; + } + } + + Tools::ScopeExit failExitHandler([this, &ec] { + ec = std::error_code(::GetLastError(), std::system_category()); + std::error_code ignore; + close(ignore); + }); + + m_fileHandle = ::CreateFile( + path.c_str(), + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_DELETE | FILE_SHARE_READ, + NULL, + overwrite ? CREATE_ALWAYS : CREATE_NEW, + FILE_ATTRIBUTE_NORMAL, + NULL); + if (m_fileHandle == INVALID_HANDLE_VALUE) { + return; + } + + LONG distanceToMoveHigh = static_cast((size >> 32) & UINT64_C(0xffffffff)); + DWORD filePointer = ::SetFilePointer(m_fileHandle, static_cast(size & UINT64_C(0xffffffff)), &distanceToMoveHigh, FILE_BEGIN); + if (filePointer == INVALID_SET_FILE_POINTER) { + return; + } + + BOOL result = ::SetEndOfFile(m_fileHandle); + if (!result) { + return; + } + + m_mappingHandle = ::CreateFileMapping(m_fileHandle, NULL, PAGE_READWRITE, 0, 0, NULL); + if (m_mappingHandle == NULL) { + return; + } + + m_data = reinterpret_cast(::MapViewOfFile(m_mappingHandle, FILE_MAP_ALL_ACCESS, 0, 0, 0)); + if (m_data == NULL) { + return; + } + + m_size = size; + m_path = path; + ec = std::error_code(); + + failExitHandler.cancel(); +} + +void MemoryMappedFile::create(const std::string& path, uint64_t size, bool overwrite) { + std::error_code ec; + create(path, size, overwrite, ec); + if (ec) { + throw std::system_error(ec, "MemoryMappedFile::create"); + } +} + +void MemoryMappedFile::open(const std::string& path, std::error_code& ec) { + if (isOpened()) { + close(ec); + if (ec) { + return; + } + } + + Tools::ScopeExit failExitHandler([this, &ec] { + ec = std::error_code(::GetLastError(), std::system_category()); + std::error_code ignore; + close(ignore); + }); + + m_fileHandle = ::CreateFile( + path.c_str(), + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_DELETE | FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + if (m_fileHandle == INVALID_HANDLE_VALUE) { + return; + } + + LARGE_INTEGER fileSize; + BOOL result = ::GetFileSizeEx(m_fileHandle, &fileSize); + if (!result) { + return; + } + + m_size = static_cast(fileSize.QuadPart); + + m_mappingHandle = ::CreateFileMapping(m_fileHandle, NULL, PAGE_READWRITE, 0, 0, NULL); + if (m_mappingHandle == NULL) { + return; + } + + m_data = reinterpret_cast(::MapViewOfFile(m_mappingHandle, FILE_MAP_ALL_ACCESS, 0, 0, 0)); + if (m_data == NULL) { + return; + } + + m_path = path; + ec = std::error_code(); + + failExitHandler.cancel(); +} + +void MemoryMappedFile::open(const std::string& path) { + std::error_code ec; + open(path, ec); + if (ec) { + throw std::system_error(ec, "MemoryMappedFile::open"); + } +} + +void MemoryMappedFile::rename(const std::string& newPath, std::error_code& ec) { + assert(isOpened()); + + BOOL result = ::MoveFileEx(m_path.c_str(), newPath.c_str(), MOVEFILE_REPLACE_EXISTING); + if (result) { + m_path = newPath; + ec = std::error_code(); + } else { + ec = std::error_code(::GetLastError(), std::system_category()); + } +} + +void MemoryMappedFile::rename(const std::string& newPath) { + assert(isOpened()); + + std::error_code ec; + rename(newPath, ec); + if (ec) { + throw std::system_error(ec, "MemoryMappedFile::rename"); + } +} + +void MemoryMappedFile::close(std::error_code& ec) { + BOOL result; + if (m_data != nullptr) { + flush(m_data, m_size, ec); + if (ec) { + return; + } + + result = ::UnmapViewOfFile(m_data); + if (result) { + m_data = nullptr; + } else { + ec = std::error_code(::GetLastError(), std::system_category()); + return; + } + } + + if (m_mappingHandle != INVALID_HANDLE_VALUE) { + result = ::CloseHandle(m_mappingHandle); + if (result) { + m_mappingHandle = INVALID_HANDLE_VALUE; + } else { + ec = std::error_code(::GetLastError(), std::system_category()); + return; + } + } + + if (m_fileHandle != INVALID_HANDLE_VALUE) { + result = ::CloseHandle(m_fileHandle); + if (result) { + m_fileHandle = INVALID_HANDLE_VALUE; + ec = std::error_code(); + } else { + ec = std::error_code(::GetLastError(), std::system_category()); + return; + } + } + + ec = std::error_code(); +} + +void MemoryMappedFile::close() { + std::error_code ec; + close(ec); + if (ec) { + throw std::system_error(ec, "MemoryMappedFile::close"); + } +} + +void MemoryMappedFile::flush(uint8_t* data, uint64_t size, std::error_code& ec) { + assert(isOpened()); + + BOOL result = ::FlushViewOfFile(data, static_cast(size)); + if (result) { + result = ::FlushFileBuffers(m_fileHandle); + if (result) { + ec = std::error_code(); + return; + } + } + + ec = std::error_code(::GetLastError(), std::system_category()); +} + +void MemoryMappedFile::flush(uint8_t* data, uint64_t size) { + assert(isOpened()); + + std::error_code ec; + flush(data, size, ec); + if (ec) { + throw std::system_error(ec, "MemoryMappedFile::flush"); + } +} + +void MemoryMappedFile::swap(MemoryMappedFile& other) { + std::swap(m_fileHandle, other.m_fileHandle); + std::swap(m_mappingHandle, other.m_mappingHandle); + std::swap(m_path, other.m_path); + std::swap(m_data, other.m_data); + std::swap(m_size, other.m_size); +} + +} diff --git a/src/Platform/Windows/System/MemoryMappedFile.h b/src/Platform/Windows/System/MemoryMappedFile.h new file mode 100644 index 0000000000..713f03626d --- /dev/null +++ b/src/Platform/Windows/System/MemoryMappedFile.h @@ -0,0 +1,60 @@ +// Copyright (c) 2012-2016, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#pragma once + +#include +#include +#include + +namespace System { + +class MemoryMappedFile { +public: + MemoryMappedFile(); + ~MemoryMappedFile(); + + void create(const std::string& path, uint64_t size, bool overwrite, std::error_code& ec); + void create(const std::string& path, uint64_t size, bool overwrite); + void open(const std::string& path, std::error_code& ec); + void open(const std::string& path); + void close(std::error_code& ec); + void close(); + + const std::string& path() const; + uint64_t size() const; + const uint8_t* data() const; + uint8_t* data(); + bool isOpened() const; + + void rename(const std::string& newPath, std::error_code& ec); + void rename(const std::string& newPath); + + void flush(uint8_t* data, uint64_t size, std::error_code& ec); + void flush(uint8_t* data, uint64_t size); + + void swap(MemoryMappedFile& other); + +private: + void* m_fileHandle; + void* m_mappingHandle; + std::string m_path; + uint64_t m_size; + uint8_t* m_data; +}; + +} diff --git a/src/Serialization/SerializationOverloads.h b/src/Serialization/SerializationOverloads.h index 5bccb0f88d..3189bd6f64 100644 --- a/src/Serialization/SerializationOverloads.h +++ b/src/Serialization/SerializationOverloads.h @@ -81,7 +81,10 @@ template bool serializeContainer(Cont& value, Common::StringView name, CryptoNote::ISerializer& serializer) { size_t size = value.size(); if (!serializer.beginArray(size, name)) { - value.clear(); + if (serializer.type() == ISerializer::INPUT) { + value.clear(); + } + return false; } @@ -128,7 +131,10 @@ bool serializeMap(MapT& value, Common::StringView name, CryptoNote::ISerializer& size_t size = value.size(); if (!serializer.beginArray(size, name)) { - value.clear(); + if (serializer.type() == ISerializer::INPUT) { + value.clear(); + } + return false; } @@ -164,7 +170,10 @@ bool serializeSet(SetT& value, Common::StringView name, CryptoNote::ISerializer& size_t size = value.size(); if (!serializer.beginArray(size, name)) { - value.clear(); + if (serializer.type() == ISerializer::INPUT) { + value.clear(); + } + return false; } diff --git a/src/Transfers/TransfersSynchronizer.cpp b/src/Transfers/TransfersSynchronizer.cpp index 155f23e9d2..ba3325021e 100755 --- a/src/Transfers/TransfersSynchronizer.cpp +++ b/src/Transfers/TransfersSynchronizer.cpp @@ -20,6 +20,7 @@ #include "Common/StdInputStream.h" #include "Common/StdOutputStream.h" +#include "CryptoNoteCore/CryptoNoteBasicImpl.h" #include "Serialization/BinaryInputStreamSerializer.h" #include "Serialization/BinaryOutputStreamSerializer.h" @@ -279,15 +280,21 @@ void TransfersSyncronizer::load(std::istream& is) { auto prevState = getObjectState(sub->getContainer()); setObjectState(sub->getContainer(), state); updatedStates.back().subscriptionStates.push_back(std::make_pair(acc, prevState)); + } else { + m_logger(Logging::DEBUGGING) << "Subscription not found: " << m_currency.accountAddressAsString(acc); } s.endObject(); } + s.endArray(); + } else { + m_logger(Logging::DEBUGGING) << "Consumer not found: " << viewKey; } + + s.endObject(); } - s.endObject(); s.endArray(); } catch (...) { diff --git a/src/Wallet/LegacyKeysImporter.cpp b/src/Wallet/LegacyKeysImporter.cpp index 6ad3fb5760..4e2dcd1614 100755 --- a/src/Wallet/LegacyKeysImporter.cpp +++ b/src/Wallet/LegacyKeysImporter.cpp @@ -30,6 +30,7 @@ #include "WalletLegacy/WalletLegacySerializer.h" #include "WalletLegacy/WalletUserTransactionsCache.h" +#include "Wallet/WalletUtils.h" #include "Wallet/WalletErrors.h" using namespace Crypto; @@ -46,12 +47,6 @@ struct keys_file_data { } }; -bool verify_keys(const SecretKey& sec, const PublicKey& expected_pub) { - PublicKey pub; - bool r = secret_key_to_public_key(sec, pub); - return r && expected_pub == pub; -} - void loadKeysFromFile(const std::string& filename, const std::string& password, CryptoNote::AccountBase& account) { keys_file_data keys_file_data; std::string buf; @@ -71,15 +66,13 @@ void loadKeysFromFile(const std::string& filename, const std::string& password, account_data.resize(keys_file_data.account_data.size()); chacha8(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); - const CryptoNote::AccountKeys& keys = account.getAccountKeys(); - - if (CryptoNote::loadFromBinaryKeyValue(account, account_data) && - verify_keys(keys.viewSecretKey, keys.address.viewPublicKey) && - verify_keys(keys.spendSecretKey, keys.address.spendPublicKey)) { - return; + if (!CryptoNote::loadFromBinaryKeyValue(account, account_data)) { + throw std::system_error(make_error_code(CryptoNote::error::WRONG_PASSWORD)); } - throw std::system_error(make_error_code(CryptoNote::error::WRONG_PASSWORD)); + const CryptoNote::AccountKeys& keys = account.getAccountKeys(); + CryptoNote::throwIfKeysMissmatch(keys.viewSecretKey, keys.address.viewPublicKey); + CryptoNote::throwIfKeysMissmatch(keys.spendSecretKey, keys.address.spendPublicKey); } } diff --git a/src/Wallet/WalletGreen.cpp b/src/Wallet/WalletGreen.cpp index 3d8c5dac69..84e7e96a67 100755 --- a/src/Wallet/WalletGreen.cpp +++ b/src/Wallet/WalletGreen.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -36,16 +37,19 @@ #include "Common/StdInputStream.h" #include "Common/StdOutputStream.h" #include "Common/StreamTools.h" +#include "Common/StringOutputStream.h" #include "Common/StringTools.h" #include "CryptoNoteCore/Account.h" #include "CryptoNoteCore/Currency.h" #include "CryptoNoteCore/CryptoNoteBasicImpl.h" #include "CryptoNoteCore/CryptoNoteFormatUtils.h" +#include "CryptoNoteCore/CryptoNoteSerialization.h" #include "CryptoNoteCore/CryptoNoteTools.h" #include "CryptoNoteCore/TransactionApi.h" #include "crypto/crypto.h" #include "Transfers/TransfersContainer.h" -#include "WalletSerialization.h" +#include "WalletSerializationV1.h" +#include "WalletSerializationV2.h" #include "WalletErrors.h" #include "WalletUtils.h" @@ -154,23 +158,23 @@ WalletGreen::~WalletGreen() { m_dispatcher.yield(); //let remote spawns finish } -void WalletGreen::initialize(const std::string& password) { +void WalletGreen::initialize(const std::string& path, const std::string& password) { Crypto::PublicKey viewPublicKey; Crypto::SecretKey viewSecretKey; Crypto::generate_keys(viewPublicKey, viewSecretKey); - initWithKeys(viewPublicKey, viewSecretKey, password); + initWithKeys(path, password, viewPublicKey, viewSecretKey); m_logger(INFO, BRIGHT_WHITE) << "New container initialized, public view key " << viewPublicKey; } -void WalletGreen::initializeWithViewKey(const Crypto::SecretKey& viewSecretKey, const std::string& password) { +void WalletGreen::initializeWithViewKey(const std::string& path, const std::string& password, const Crypto::SecretKey& viewSecretKey) { Crypto::PublicKey viewPublicKey; if (!Crypto::secret_key_to_public_key(viewSecretKey, viewPublicKey)) { m_logger(ERROR, BRIGHT_RED) << "initializeWithViewKey(" << viewSecretKey << ") Failed to convert secret key to public key"; throw std::system_error(make_error_code(CryptoNote::error::KEY_GENERATION_ERROR)); } - initWithKeys(viewPublicKey, viewSecretKey, password); + initWithKeys(path, password, viewPublicKey, viewSecretKey); m_logger(INFO, BRIGHT_WHITE) << "Container initialized with view secret key, public view key " << viewPublicKey; } @@ -191,7 +195,9 @@ void WalletGreen::doShutdown() { stopBlockchainSynchronizer(); m_blockchainSynchronizer.removeObserver(this); - clearCaches(); + m_containerStorage.close(); + m_walletsContainer.clear(); + clearCaches(true, true); std::queue noEvents; std::swap(m_events, noEvents); @@ -199,23 +205,111 @@ void WalletGreen::doShutdown() { m_state = WalletState::NOT_INITIALIZED; } -void WalletGreen::clearCaches() { - std::vector subscriptions; - m_synchronizer.getSubscriptions(subscriptions); - std::for_each(subscriptions.begin(), subscriptions.end(), [this] (const AccountPublicAddress& address) { m_synchronizer.removeSubscription(address); }); +void WalletGreen::clearCaches(bool clearTransactions, bool clearCachedData) { + if (clearTransactions) { + m_transactions.clear(); + m_transfers.clear(); + } - m_walletsContainer.clear(); - m_unlockTransactionsJob.clear(); - m_transactions.clear(); - m_transfers.clear(); - m_uncommitedTransactions.clear(); - m_actualBalance = 0; - m_pendingBalance = 0; - m_fusionTxsCache.clear(); - m_blockchain.clear(); + if (clearCachedData) { + size_t walletIndex = 0; + for (auto it = m_walletsContainer.begin(); it != m_walletsContainer.end(); ++it) { + m_walletsContainer.modify(it, [&walletIndex](WalletRecord& wallet) { + wallet.actualBalance = 0; + wallet.pendingBalance = 0; + wallet.container = reinterpret_cast(walletIndex++); //dirty hack. container field must be unique + }); + } + + if (!clearTransactions) { + for (auto it = m_transactions.begin(); it != m_transactions.end(); ++it) { + m_transactions.modify(it, [](WalletTransaction& tx) { + tx.state = WalletTransactionState::CANCELLED; + tx.blockHeight = WALLET_UNCONFIRMED_TRANSACTION_HEIGHT; + }); + } + } + + std::vector subscriptions; + m_synchronizer.getSubscriptions(subscriptions); + std::for_each(subscriptions.begin(), subscriptions.end(), [this](const AccountPublicAddress& address) { m_synchronizer.removeSubscription(address); }); + + m_uncommitedTransactions.clear(); + m_unlockTransactionsJob.clear(); + m_actualBalance = 0; + m_pendingBalance = 0; + m_fusionTxsCache.clear(); + m_blockchain.clear(); + } +} + +void WalletGreen::decryptKeyPair(const EncryptedWalletRecord& cipher, PublicKey& publicKey, SecretKey& secretKey, + uint64_t& creationTimestamp, const Crypto::chacha8_key& key) { + + std::array buffer; + chacha8(cipher.data, sizeof(cipher.data), key, cipher.iv, buffer.data()); + + MemoryInputStream stream(buffer.data(), buffer.size()); + BinaryInputStreamSerializer serializer(stream); + + serializer(publicKey, "publicKey"); + serializer(secretKey, "secretKey"); + serializer.binary(&creationTimestamp, sizeof(uint64_t), "creationTimestamp"); +} + +void WalletGreen::decryptKeyPair(const EncryptedWalletRecord& cipher, PublicKey& publicKey, SecretKey& secretKey, uint64_t& creationTimestamp) const { + decryptKeyPair(cipher, publicKey, secretKey, creationTimestamp, m_key); +} + +EncryptedWalletRecord WalletGreen::encryptKeyPair(const PublicKey& publicKey, const SecretKey& secretKey, uint64_t creationTimestamp, + const Crypto::chacha8_key& key, const Crypto::chacha8_iv& iv) { + + EncryptedWalletRecord result; + + std::string serializedKeys; + StringOutputStream outputStream(serializedKeys); + BinaryOutputStreamSerializer serializer(outputStream); + + serializer(const_cast(publicKey), "publicKey"); + serializer(const_cast(secretKey), "secretKey"); + serializer.binary(&creationTimestamp, sizeof(uint64_t), "creationTimestamp"); + + assert(serializedKeys.size() == sizeof(result.data)); + + result.iv = iv; + chacha8(serializedKeys.data(), serializedKeys.size(), key, result.iv, reinterpret_cast(result.data)); + + return result; +} + +EncryptedWalletRecord WalletGreen::encryptKeyPair(const PublicKey& publicKey, const SecretKey& secretKey, uint64_t creationTimestamp) const { + return encryptKeyPair(publicKey, secretKey, creationTimestamp, m_key, getNextIv()); +} + +Crypto::chacha8_iv WalletGreen::getNextIv() const { + const auto* prefix = reinterpret_cast(m_containerStorage.prefix()); + return prefix->nextIv; } -void WalletGreen::initWithKeys(const Crypto::PublicKey& viewPublicKey, const Crypto::SecretKey& viewSecretKey, const std::string& password) { +void WalletGreen::incIv(Crypto::chacha8_iv& iv) { + static_assert(sizeof(uint64_t) == sizeof(Crypto::chacha8_iv), "Bad Crypto::chacha8_iv size"); + uint64_t* i = reinterpret_cast(&iv); + if (*i < std::numeric_limits::max()) { + ++(*i); + } else { + *i = 0; + } +} + +void WalletGreen::incNextIv() { + static_assert(sizeof(uint64_t) == sizeof(Crypto::chacha8_iv), "Bad Crypto::chacha8_iv size"); + auto* prefix = reinterpret_cast(m_containerStorage.prefix()); + incIv(prefix->nextIv); +} + +void WalletGreen::initWithKeys(const std::string& path, const std::string& password, + const Crypto::PublicKey& viewPublicKey, const Crypto::SecretKey& viewSecretKey) { + if (m_state != WalletState::NOT_INITIALIZED) { m_logger(ERROR, BRIGHT_RED) << "Failed to initialize with keys: already initialized. Current state: " << m_state; throw std::system_error(make_error_code(CryptoNote::error::ALREADY_INITIALIZED)); @@ -223,9 +317,25 @@ void WalletGreen::initWithKeys(const Crypto::PublicKey& viewPublicKey, const Cry throwIfStopped(); + ContainerStorage newStorage(path, Common::FileMappedVectorOpenMode::CREATE, sizeof(ContainerStoragePrefix)); + ContainerStoragePrefix* prefix = reinterpret_cast(newStorage.prefix()); + prefix->version = static_cast(WalletSerializerV2::SERIALIZATION_VERSION); + prefix->nextIv = Crypto::rand(); + + Crypto::cn_context cnContext; + Crypto::generate_chacha8_key(cnContext, password, m_key); + + uint64_t creationTimestamp = time(nullptr); + prefix->encryptedViewKeys = encryptKeyPair(viewPublicKey, viewSecretKey, creationTimestamp, m_key, prefix->nextIv); + + newStorage.flush(); + m_containerStorage.swap(newStorage); + incNextIv(); + m_viewPublicKey = viewPublicKey; m_viewSecretKey = viewSecretKey; m_password = password; + m_path = path; m_logger = Logging::LoggerRef(m_logger.getLogger(), "WalletGreen/" + podToHex(m_viewPublicKey).substr(0, 5)); assert(m_blockchain.empty()); @@ -236,7 +346,7 @@ void WalletGreen::initWithKeys(const Crypto::PublicKey& viewPublicKey, const Cry m_state = WalletState::INITIALIZED; } -void WalletGreen::save(std::ostream& destination, bool saveDetails, bool saveCache, bool encrypt) { +void WalletGreen::save(WalletSaveLevel saveLevel, const std::string& extra) { m_logger(INFO, BRIGHT_WHITE) << "Saving container..."; throwIfNotInitialized(); @@ -245,9 +355,10 @@ void WalletGreen::save(std::ostream& destination, bool saveDetails, bool saveCac stopBlockchainSynchronizer(); try { - unsafeSave(destination, saveDetails, saveCache, encrypt); + saveWalletCache(m_containerStorage, m_key, saveLevel, extra); } catch (const std::exception& e) { m_logger(ERROR, BRIGHT_RED) << "Failed to save container: " << e.what(); + startBlockchainSynchronizer(); throw; } @@ -255,43 +366,53 @@ void WalletGreen::save(std::ostream& destination, bool saveDetails, bool saveCac m_logger(INFO, BRIGHT_WHITE) << "Container saved"; } -void WalletGreen::unsafeSave(std::ostream& destination, bool saveDetails, bool saveCache, bool encrypt) { - WalletTransactions transactions; - WalletTransfers transfers; +void WalletGreen::exportWallet(const std::string& path, bool encrypt, WalletSaveLevel saveLevel, const std::string& extra) { + m_logger(INFO, BRIGHT_WHITE) << "Exporting container..."; - if (saveDetails && !saveCache) { - filterOutTransactions(transactions, transfers, [] (const WalletTransaction& tx) { - return tx.state == WalletTransactionState::CREATED || tx.state == WalletTransactionState::DELETED; - }); - } else if (saveDetails) { - filterOutTransactions(transactions, transfers, [] (const WalletTransaction& tx) { - return tx.state == WalletTransactionState::DELETED; + throwIfNotInitialized(); + throwIfStopped(); + + stopBlockchainSynchronizer(); + + try { + bool storageCreated = false; + Tools::ScopeExit failExitHandler([path, &storageCreated] { + // Don't delete file if it has existed + if (storageCreated) { + boost::system::error_code ignore; + boost::filesystem::remove(path, ignore); + } }); - } - WalletSerializer s( - *this, - m_viewPublicKey, - m_viewSecretKey, - m_actualBalance, - m_pendingBalance, - m_walletsContainer, - m_synchronizer, - m_unlockTransactionsJob, - transactions, - transfers, - m_transactionSoftLockTime, - m_uncommitedTransactions - ); + ContainerStorage newStorage(path, FileMappedVectorOpenMode::CREATE, m_containerStorage.prefixSize()); + storageCreated = true; + + chacha8_key newStorageKey; + if (encrypt) { + newStorageKey = m_key; + } else { + cn_context cnContext; + generate_chacha8_key(cnContext, "", newStorageKey); + } + + copyContainerStoragePrefix(m_containerStorage, m_key, newStorage, newStorageKey); + copyContainerStorageKeys(m_containerStorage, m_key, newStorage, newStorageKey); + saveWalletCache(newStorage, newStorageKey, saveLevel, extra); - { - StdOutputStream output(destination); - s.save(encrypt ? m_password : "", output, saveDetails, saveCache); + failExitHandler.cancel(); + + m_logger(DEBUGGING) << "Container export finished"; + } catch (const std::exception& e) { + m_logger(ERROR, BRIGHT_RED) << "Failed to export container: " << e.what(); + startBlockchainSynchronizer(); + throw; } - m_logger(DEBUGGING) << "Container saving finished"; + + startBlockchainSynchronizer(); + m_logger(INFO, BRIGHT_WHITE) << "Container exported"; } -void WalletGreen::load(std::istream& source, const std::string& password) { +void WalletGreen::load(const std::string& path, const std::string& password, std::string& extra) { m_logger(INFO, BRIGHT_WHITE) << "Loading container..."; if (m_state != WalletState::NOT_INITIALIZED) { @@ -303,17 +424,65 @@ void WalletGreen::load(std::istream& source, const std::string& password) { stopBlockchainSynchronizer(); - try { - unsafeLoad(source, password); - } catch (const std::exception& e) { - m_logger(ERROR, BRIGHT_RED) << "Failed to load container: " << e.what(); - throw; + Crypto::cn_context cnContext; + generate_chacha8_key(cnContext, password, m_key); + + std::ifstream walletFileStream(path, std::ios_base::binary); + int version = walletFileStream.peek(); + if (version == EOF) { + m_logger(ERROR, BRIGHT_RED) << "Failed to read wallet version"; + throw std::system_error(make_error_code(error::WRONG_VERSION), "Failed to read wallet version"); + } + + if (version < WalletSerializerV2::MIN_VERSION) { + convertAndLoadWalletFile(path, std::move(walletFileStream)); + } else { + walletFileStream.close(); + + if (version > WalletSerializerV2::SERIALIZATION_VERSION) { + m_logger(ERROR, BRIGHT_RED) << "Unsupported wallet version: " << version; + throw std::system_error(make_error_code(error::WRONG_VERSION), "Unsupported wallet version"); + } + + loadContainerStorage(path); + subscribeWallets(); + + if (m_containerStorage.suffixSize() > 0) { + try { + std::unordered_set addedSpendKeys; + std::unordered_set deletedSpendKeys; + loadWalletCache(addedSpendKeys, deletedSpendKeys, extra); + + if (!addedSpendKeys.empty()) { + m_logger(WARNING, BRIGHT_YELLOW) << "Found addresses not saved in container cache. Resynchronize container"; + clearCaches(false, true); + subscribeWallets(); + } + + if (!deletedSpendKeys.empty()) { + m_logger(WARNING, BRIGHT_YELLOW) << "Found deleted addresses saved in container cache. Remove its transactions"; + deleteOrphanTransactions(deletedSpendKeys); + } + + if (!addedSpendKeys.empty() || !deletedSpendKeys.empty()) { + saveWalletCache(m_containerStorage, m_key, WalletSaveLevel::SAVE_ALL, extra); + } + } catch (const std::exception& e) { + m_logger(ERROR, BRIGHT_RED) << "Failed to load cache: " << e.what() << ", reset wallet data"; + clearCaches(true, true); + subscribeWallets(); + } + } } + m_blockchainSynchronizer.addObserver(this); + + initTransactionPool(); + assert(m_blockchain.empty()); if (m_walletsContainer.get().size() != 0) { m_synchronizer.subscribeConsumerNotifications(m_viewPublicKey, this); - getViewKeyKnownBlocks(m_viewPublicKey); + initBlockchain(m_viewPublicKey); startBlockchainSynchronizer(); } else { @@ -321,6 +490,10 @@ void WalletGreen::load(std::istream& source, const std::string& password) { m_logger(DEBUGGING) << "Add genesis block hash to blockchain"; } + m_password = password; + m_path = path; + m_extra = extra; + m_state = WalletState::INITIALIZED; m_logger(INFO, BRIGHT_WHITE) << "Container loaded, view public key " << m_viewPublicKey << ", wallet count " << m_walletsContainer.size() << @@ -328,8 +501,43 @@ void WalletGreen::load(std::istream& source, const std::string& password) { ", pending balance " << m_currency.formatAmount(m_pendingBalance); } -void WalletGreen::unsafeLoad(std::istream& source, const std::string& password) { - WalletSerializer s( +void WalletGreen::load(const std::string& path, const std::string& password) { + std::string extra; + load(path, password, extra); +} + +void WalletGreen::loadContainerStorage(const std::string& path) { + try { + m_containerStorage.open(path, FileMappedVectorOpenMode::OPEN, sizeof(ContainerStoragePrefix)); + + ContainerStoragePrefix* prefix = reinterpret_cast(m_containerStorage.prefix()); + assert(prefix->version >= WalletSerializerV2::MIN_VERSION); + + uint64_t creationTimestamp; + decryptKeyPair(prefix->encryptedViewKeys, m_viewPublicKey, m_viewSecretKey, creationTimestamp); + throwIfKeysMissmatch(m_viewSecretKey, m_viewPublicKey, "Restored view public key doesn't correspond to secret key"); + m_logger = Logging::LoggerRef(m_logger.getLogger(), "WalletGreen/" + podToHex(m_viewPublicKey).substr(0, 5)); + + loadSpendKeys(); + + m_logger(DEBUGGING) << "Container keys were successfully loaded"; + } catch (const std::exception& e) { + m_logger(ERROR, BRIGHT_RED) << "Failed to load container keys: " << e.what(); + + m_walletsContainer.clear(); + m_containerStorage.close(); + + throw; + } +} + +void WalletGreen::loadWalletCache(std::unordered_set& addedKeys, std::unordered_set& deletedKeys, std::string& extra) { + assert(m_containerStorage.isOpened()); + + BinaryArray contanerData; + loadAndDecryptContainerData(m_containerStorage, m_key, contanerData); + + WalletSerializerV2 s( *this, m_viewPublicKey, m_viewSecretKey, @@ -340,17 +548,280 @@ void WalletGreen::unsafeLoad(std::istream& source, const std::string& password) m_unlockTransactionsJob, m_transactions, m_transfers, - m_transactionSoftLockTime, - m_uncommitedTransactions + m_uncommitedTransactions, + extra, + m_transactionSoftLockTime ); - StdInputStream inputStream(source); - s.load(password, inputStream); - m_logger = Logging::LoggerRef(m_logger.getLogger(), "WalletGreen/" + podToHex(m_viewPublicKey).substr(0, 5)); - m_logger(DEBUGGING) << "Container loading finished"; + Common::MemoryInputStream containerStream(contanerData.data(), contanerData.size()); + s.load(containerStream, reinterpret_cast(m_containerStorage.prefix())->version); + addedKeys = std::move(s.addedKeys()); + deletedKeys = std::move(s.deletedKeys()); - m_password = password; - m_blockchainSynchronizer.addObserver(this); + m_logger(DEBUGGING) << "Container cache loaded"; +} + +void WalletGreen::saveWalletCache(ContainerStorage& storage, const Crypto::chacha8_key& key, WalletSaveLevel saveLevel, const std::string& extra) { + WalletTransactions transactions; + WalletTransfers transfers; + + if (saveLevel == WalletSaveLevel::SAVE_KEYS_AND_TRANSACTIONS) { + filterOutTransactions(transactions, transfers, [](const WalletTransaction& tx) { + return tx.state == WalletTransactionState::CREATED || tx.state == WalletTransactionState::DELETED; + }); + + for (auto it = transactions.begin(); it != transactions.end(); ++it) { + transactions.modify(it, [](WalletTransaction& tx) { + tx.state = WalletTransactionState::CANCELLED; + tx.blockHeight = WALLET_UNCONFIRMED_TRANSACTION_HEIGHT; + }); + } + } else if (saveLevel == WalletSaveLevel::SAVE_ALL) { + filterOutTransactions(transactions, transfers, [](const WalletTransaction& tx) { + return tx.state == WalletTransactionState::DELETED; + }); + } + + std::string containerData; + Common::StringOutputStream containerStream(containerData); + + WalletSerializerV2 s( + *this, + m_viewPublicKey, + m_viewSecretKey, + m_actualBalance, + m_pendingBalance, + m_walletsContainer, + m_synchronizer, + m_unlockTransactionsJob, + transactions, + transfers, + m_uncommitedTransactions, + const_cast(extra), + m_transactionSoftLockTime + ); + + s.save(containerStream, saveLevel); + + encryptAndSaveContainerData(storage, key, containerData.data(), containerData.size()); + storage.flush(); + + m_extra = extra; + + m_logger(DEBUGGING) << "Container saving finished"; +} + +void WalletGreen::copyContainerStorageKeys(ContainerStorage& src, const chacha8_key& srcKey, ContainerStorage& dst, const chacha8_key& dstKey) { + dst.reserve(src.size()); + + for (auto& encryptedSpendKeys : src) { + Crypto::PublicKey publicKey; + Crypto::SecretKey secretKey; + uint64_t creationTimestamp; + decryptKeyPair(encryptedSpendKeys, publicKey, secretKey, creationTimestamp, srcKey); + + // push_back() can resize container, and dstPrefix address can be changed, so it is requested for each key pair + ContainerStoragePrefix* dstPrefix = reinterpret_cast(dst.prefix()); + Crypto::chacha8_iv keyPairIv = dstPrefix->nextIv; + incIv(dstPrefix->nextIv); + + dst.push_back(encryptKeyPair(publicKey, secretKey, creationTimestamp, dstKey, keyPairIv)); + } +} + +void WalletGreen::copyContainerStoragePrefix(ContainerStorage& src, const chacha8_key& srcKey, ContainerStorage& dst, const chacha8_key& dstKey) { + ContainerStoragePrefix* srcPrefix = reinterpret_cast(src.prefix()); + ContainerStoragePrefix* dstPrefix = reinterpret_cast(dst.prefix()); + dstPrefix->version = srcPrefix->version; + dstPrefix->nextIv = Crypto::rand(); + + Crypto::PublicKey publicKey; + Crypto::SecretKey secretKey; + uint64_t creationTimestamp; + decryptKeyPair(srcPrefix->encryptedViewKeys, publicKey, secretKey, creationTimestamp, srcKey); + dstPrefix->encryptedViewKeys = encryptKeyPair(publicKey, secretKey, creationTimestamp, dstKey, dstPrefix->nextIv); + incIv(dstPrefix->nextIv); +} + +void WalletGreen::encryptAndSaveContainerData(ContainerStorage& storage, const Crypto::chacha8_key& key, const void* containerData, size_t containerDataSize) { + ContainerStoragePrefix* prefix = reinterpret_cast(storage.prefix()); + + Crypto::chacha8_iv suffixIv = prefix->nextIv; + incIv(prefix->nextIv); + + BinaryArray encryptedContainer; + encryptedContainer.resize(containerDataSize); + chacha8(containerData, containerDataSize, key, suffixIv, reinterpret_cast(encryptedContainer.data())); + + std::string suffix; + Common::StringOutputStream suffixStream(suffix); + BinaryOutputStreamSerializer suffixSerializer(suffixStream); + suffixSerializer(suffixIv, "suffixIv"); + suffixSerializer(encryptedContainer, "encryptedContainer"); + + storage.resizeSuffix(suffix.size()); + std::copy(suffix.begin(), suffix.end(), storage.suffix()); +} + +void WalletGreen::loadAndDecryptContainerData(ContainerStorage& storage, const Crypto::chacha8_key& key, BinaryArray& containerData) { + Common::MemoryInputStream suffixStream(storage.suffix(), storage.suffixSize()); + BinaryInputStreamSerializer suffixSerializer(suffixStream); + Crypto::chacha8_iv suffixIv; + BinaryArray encryptedContainer; + suffixSerializer(suffixIv, "suffixIv"); + suffixSerializer(encryptedContainer, "encryptedContainer"); + + containerData.resize(encryptedContainer.size()); + chacha8(encryptedContainer.data(), encryptedContainer.size(), key, suffixIv, reinterpret_cast(containerData.data())); +} + +void WalletGreen::initTransactionPool() { + std::unordered_set uncommitedTransactionsSet; + std::transform(m_uncommitedTransactions.begin(), m_uncommitedTransactions.end(), std::inserter(uncommitedTransactionsSet, uncommitedTransactionsSet.end()), + [](const UncommitedTransactions::value_type& pair) { + return getObjectHash(pair.second); + }); + m_synchronizer.initTransactionPool(uncommitedTransactionsSet); +} + +void WalletGreen::deleteOrphanTransactions(const std::unordered_set& deletedKeys) { + for (auto spendPublicKey : deletedKeys) { + AccountPublicAddress deletedAccountAddress; + deletedAccountAddress.spendPublicKey = spendPublicKey; + deletedAccountAddress.viewPublicKey = m_viewPublicKey; + auto deletedAddressString = m_currency.accountAddressAsString(deletedAccountAddress); + + std::vector deletedTransactions; + std::vector updatedTransactions = deleteTransfersForAddress(deletedAddressString, deletedTransactions); + deleteFromUncommitedTransactions(deletedTransactions); + } +} + +void WalletGreen::loadSpendKeys() { + bool isTrackingMode; + for (size_t i = 0; i < m_containerStorage.size(); ++i) { + WalletRecord wallet; + uint64_t creationTimestamp; + decryptKeyPair(m_containerStorage[i], wallet.spendPublicKey, wallet.spendSecretKey, creationTimestamp); + wallet.creationTimestamp = creationTimestamp; + + if (i == 0) { + isTrackingMode = wallet.spendSecretKey == NULL_SECRET_KEY; + } else if ((isTrackingMode && wallet.spendSecretKey != NULL_SECRET_KEY) || (!isTrackingMode && wallet.spendSecretKey == NULL_SECRET_KEY)) { + throw std::system_error(make_error_code(error::BAD_ADDRESS), "All addresses must be whether tracking or not"); + } + + if (wallet.spendSecretKey != NULL_SECRET_KEY) { + throwIfKeysMissmatch(wallet.spendSecretKey, wallet.spendPublicKey, "Restored spend public key doesn't correspond to secret key"); + } else { + if (!Crypto::check_key(wallet.spendPublicKey)) { + throw std::system_error(make_error_code(error::WRONG_PASSWORD), "Public spend key is incorrect"); + } + } + + wallet.actualBalance = 0; + wallet.pendingBalance = 0; + wallet.container = reinterpret_cast(i); //dirty hack. container field must be unique + + m_walletsContainer.emplace_back(std::move(wallet)); + } +} + +void WalletGreen::subscribeWallets() { + try { + auto& index = m_walletsContainer.get(); + + for (auto it = index.begin(); it != index.end(); ++it) { + const auto& wallet = *it; + + AccountSubscription sub; + sub.keys.address.viewPublicKey = m_viewPublicKey; + sub.keys.address.spendPublicKey = wallet.spendPublicKey; + sub.keys.viewSecretKey = m_viewSecretKey; + sub.keys.spendSecretKey = wallet.spendSecretKey; + sub.transactionSpendableAge = m_transactionSoftLockTime; + sub.syncStart.height = 0; + sub.syncStart.timestamp = std::max(static_cast(wallet.creationTimestamp), ACCOUNT_CREATE_TIME_ACCURACY) - ACCOUNT_CREATE_TIME_ACCURACY; + + auto& subscription = m_synchronizer.addSubscription(sub); + bool r = index.modify(it, [&subscription](WalletRecord& rec) { rec.container = &subscription.getContainer(); }); + assert(r); + + subscription.addObserver(this); + } + } catch (const std::exception& e) { + m_logger(ERROR, BRIGHT_RED) << "Failed to subscribe wallets: " << e.what(); + + std::vector subscriptionList; + m_synchronizer.getSubscriptions(subscriptionList); + for (auto& subscription : subscriptionList) { + m_synchronizer.removeSubscription(subscription); + } + + throw; + } +} + +void WalletGreen::convertAndLoadWalletFile(const std::string& path, std::ifstream&& walletFileStream) { + WalletSerializerV1 s( + *this, + m_viewPublicKey, + m_viewSecretKey, + m_actualBalance, + m_pendingBalance, + m_walletsContainer, + m_synchronizer, + m_unlockTransactionsJob, + m_transactions, + m_transfers, + m_uncommitedTransactions, + m_transactionSoftLockTime + ); + + StdInputStream stream(walletFileStream); + s.load(m_key, stream); + walletFileStream.close(); + + boost::filesystem::path bakPath = path + ".backup"; + boost::filesystem::path tmpPath = boost::filesystem::unique_path(path + ".tmp.%%%%-%%%%"); + + if (boost::filesystem::exists(bakPath)) { + throw std::system_error(make_error_code(std::errc::file_exists), ".backup file already exists"); + } + + Tools::ScopeExit tmpFileDeleter([&tmpPath] { + boost::system::error_code ignore; + boost::filesystem::remove(tmpPath, ignore); + }); + + m_containerStorage.open(tmpPath.string(), Common::FileMappedVectorOpenMode::CREATE, sizeof(ContainerStoragePrefix)); + ContainerStoragePrefix* prefix = reinterpret_cast(m_containerStorage.prefix()); + prefix->version = WalletSerializerV2::SERIALIZATION_VERSION; + prefix->nextIv = Crypto::rand(); + + uint64_t creationTimestamp = time(nullptr); + prefix->encryptedViewKeys = encryptKeyPair(m_viewPublicKey, m_viewSecretKey, creationTimestamp); + + for (auto spendKeys : m_walletsContainer.get()) { + m_containerStorage.push_back(encryptKeyPair(spendKeys.spendPublicKey, spendKeys.spendSecretKey, spendKeys.creationTimestamp)); + incNextIv(); + } + + saveWalletCache(m_containerStorage, m_key, WalletSaveLevel::SAVE_ALL, ""); + + boost::filesystem::rename(path, bakPath); + std::error_code ec; + m_containerStorage.rename(path, ec); + if (ec) { + m_logger(ERROR, BRIGHT_RED) << "Failed to rename " << tmpPath << " to " << path; + + boost::system::error_code ignore; + boost::filesystem::rename(bakPath, path, ignore); + throw std::system_error(ec, "Failed to replace wallet file"); + } + + tmpFileDeleter.cancel(); + m_logger(INFO, BRIGHT_WHITE) << "Wallet file converted! Previous version: " << bakPath; } void WalletGreen::changePassword(const std::string& oldPassword, const std::string& newPassword) { @@ -362,7 +833,28 @@ void WalletGreen::changePassword(const std::string& oldPassword, const std::stri throw std::system_error(make_error_code(error::WRONG_PASSWORD)); } + if (oldPassword == newPassword) { + return; + } + + Crypto::cn_context cnContext; + Crypto::chacha8_key newKey; + Crypto::generate_chacha8_key(cnContext, newPassword, newKey); + + m_containerStorage.atomicUpdate([this, newKey](ContainerStorage& newStorage) { + copyContainerStoragePrefix(m_containerStorage, m_key, newStorage, newKey); + copyContainerStorageKeys(m_containerStorage, m_key, newStorage, newKey); + + if (m_containerStorage.suffixSize() > 0) { + BinaryArray containerData; + loadAndDecryptContainerData(m_containerStorage, m_key, containerData); + encryptAndSaveContainerData(newStorage, newKey, containerData.data(), containerData.size()); + } + }); + + m_key = newKey; m_password = newPassword; + m_logger(INFO, BRIGHT_WHITE) << "Container password changed"; } @@ -462,10 +954,9 @@ std::string WalletGreen::doCreateAddress(const Crypto::PublicKey& spendPublicKey auto currentTime = static_cast(time(nullptr)); if (creationTimestamp + m_currency.blockFutureTimeLimit() < currentTime) { - std::stringstream ss; - unsafeSave(ss, true, false, false); + save(WalletSaveLevel::SAVE_KEYS_AND_TRANSACTIONS, m_extra); shutdown(); - load(ss, ""); + load(m_path, m_password); } } catch (const std::exception& e) { m_logger(ERROR, BRIGHT_RED) << "Failed to add wallet: " << e.what(); @@ -498,36 +989,51 @@ std::string WalletGreen::addWallet(const Crypto::PublicKey& spendPublicKey, cons throw std::system_error(make_error_code(error::ADDRESS_ALREADY_EXISTS)); } - AccountSubscription sub; - sub.keys.address.viewPublicKey = m_viewPublicKey; - sub.keys.address.spendPublicKey = spendPublicKey; - sub.keys.viewSecretKey = m_viewSecretKey; - sub.keys.spendSecretKey = spendSecretKey; - sub.transactionSpendableAge = m_transactionSoftLockTime; - sub.syncStart.height = 0; - sub.syncStart.timestamp = std::max(creationTimestamp, ACCOUNT_CREATE_TIME_ACCURACY) - ACCOUNT_CREATE_TIME_ACCURACY; + m_containerStorage.push_back(encryptKeyPair(spendPublicKey, spendSecretKey, creationTimestamp)); + incNextIv(); - auto& trSubscription = m_synchronizer.addSubscription(sub); - ITransfersContainer* container = &trSubscription.getContainer(); + try { + AccountSubscription sub; + sub.keys.address.viewPublicKey = m_viewPublicKey; + sub.keys.address.spendPublicKey = spendPublicKey; + sub.keys.viewSecretKey = m_viewSecretKey; + sub.keys.spendSecretKey = spendSecretKey; + sub.transactionSpendableAge = m_transactionSoftLockTime; + sub.syncStart.height = 0; + sub.syncStart.timestamp = std::max(creationTimestamp, ACCOUNT_CREATE_TIME_ACCURACY) - ACCOUNT_CREATE_TIME_ACCURACY; + + auto& trSubscription = m_synchronizer.addSubscription(sub); + ITransfersContainer* container = &trSubscription.getContainer(); + + WalletRecord wallet; + wallet.spendPublicKey = spendPublicKey; + wallet.spendSecretKey = spendSecretKey; + wallet.container = container; + wallet.creationTimestamp = static_cast(creationTimestamp); + trSubscription.addObserver(this); + + index.insert(insertIt, std::move(wallet)); + m_logger(DEBUGGING) << "Wallet count " << m_walletsContainer.size(); + + if (index.size() == 1) { + m_synchronizer.subscribeConsumerNotifications(m_viewPublicKey, this); + initBlockchain(m_viewPublicKey); + } - WalletRecord wallet; - wallet.spendPublicKey = spendPublicKey; - wallet.spendSecretKey = spendSecretKey; - wallet.container = container; - wallet.creationTimestamp = static_cast(creationTimestamp); - trSubscription.addObserver(this); + auto address = m_currency.accountAddressAsString({ spendPublicKey, m_viewPublicKey }); + m_logger(DEBUGGING) << "Wallet added " << address << ", creation timestamp " << creationTimestamp; + return address; + } catch (const std::exception& e) { + m_logger(ERROR) << "Failed to add wallet: " << e.what(); - index.insert(insertIt, std::move(wallet)); - m_logger(DEBUGGING) << "Wallet count " << m_walletsContainer.size(); + try { + m_containerStorage.pop_back(); + } catch (...) { + m_logger(ERROR) << "Failed to rollback adding wallet to storage"; + } - if (index.size() == 1) { - m_synchronizer.subscribeConsumerNotifications(m_viewPublicKey, this); - getViewKeyKnownBlocks(m_viewPublicKey); + throw; } - - auto address = m_currency.accountAddressAsString({ spendPublicKey, m_viewPublicKey }); - m_logger(DEBUGGING) << "Wallet added " << address << ", creation timestamp " << creationTimestamp; - return address; } void WalletGreen::deleteAddress(const std::string& address) { @@ -552,6 +1058,20 @@ void WalletGreen::deleteAddress(const std::string& address) { ", pending " << m_currency.formatAmount(m_pendingBalance); } + auto addressIndex = std::distance(m_walletsContainer.get().begin(), m_walletsContainer.project(it)); + +#if !defined(NDEBUG) + Crypto::PublicKey publicKey; + Crypto::SecretKey secretKey; + uint64_t creationTimestamp; + decryptKeyPair(m_containerStorage[addressIndex], publicKey, secretKey, creationTimestamp); + assert(publicKey == it->spendPublicKey); + assert(secretKey == it->spendSecretKey); + assert(creationTimestamp == static_cast(it->creationTimestamp)); +#endif + + m_containerStorage.erase(std::next(m_containerStorage.begin(), addressIndex)); + m_synchronizer.removeSubscription(pubAddr); deleteContainerFromUnlockTransactionJobs(it->container); @@ -2660,7 +3180,7 @@ void WalletGreen::filterOutTransactions(WalletTransactions& transactions, Wallet } } -void WalletGreen::getViewKeyKnownBlocks(const Crypto::PublicKey& viewPublicKey) { +void WalletGreen::initBlockchain(const Crypto::PublicKey& viewPublicKey) { std::vector blockchain = m_synchronizer.getViewKeyKnownBlocks(m_viewPublicKey); m_blockchain.insert(m_blockchain.end(), blockchain.begin(), blockchain.end()); } @@ -2745,6 +3265,7 @@ std::vector WalletGreen::deleteTransfersForAddress(const std::string& ad if (!transfersLeft) { transaction.state = WalletTransactionState::DELETED; + transaction.blockHeight = WALLET_UNCONFIRMED_TRANSACTION_HEIGHT; m_logger(DEBUGGING) << "Transaction state changed, ID " << transactionId << ", hash " << transaction.hash << ", new state " << transaction.state; diff --git a/src/Wallet/WalletGreen.h b/src/Wallet/WalletGreen.h index b403af5c74..a7ef1ed96e 100755 --- a/src/Wallet/WalletGreen.h +++ b/src/Wallet/WalletGreen.h @@ -42,13 +42,15 @@ class WalletGreen : public IWallet, WalletGreen(System::Dispatcher& dispatcher, const Currency& currency, INode& node, Logging::ILogger& logger, uint32_t transactionSoftLockTime = 1); virtual ~WalletGreen(); - virtual void initialize(const std::string& password) override; - virtual void initializeWithViewKey(const Crypto::SecretKey& viewSecretKey, const std::string& password) override; - virtual void load(std::istream& source, const std::string& password) override; + virtual void initialize(const std::string& path, const std::string& password) override; + virtual void initializeWithViewKey(const std::string& path, const std::string& password, const Crypto::SecretKey& viewSecretKey) override; + virtual void load(const std::string& path, const std::string& password, std::string& extra) override; + virtual void load(const std::string& path, const std::string& password) override; virtual void shutdown() override; virtual void changePassword(const std::string& oldPassword, const std::string& newPassword) override; - virtual void save(std::ostream& destination, bool saveDetails = true, bool saveCache = true, bool encrypt = true) override; + virtual void save(WalletSaveLevel saveLevel = WalletSaveLevel::SAVE_ALL, const std::string& extra = "") override; + virtual void exportWallet(const std::string& path, bool encrypt = true, WalletSaveLevel saveLevel = WalletSaveLevel::SAVE_ALL, const std::string& extra = "") override; virtual size_t getAddressCount() const override; virtual std::string getAddress(size_t index) const override; @@ -98,8 +100,18 @@ class WalletGreen : public IWallet, void throwIfStopped() const; void throwIfTrackingMode() const; void doShutdown(); - void clearCaches(); - void initWithKeys(const Crypto::PublicKey& viewPublicKey, const Crypto::SecretKey& viewSecretKey, const std::string& password); + void clearCaches(bool clearTransactions, bool clearCachedData); + void convertAndLoadWalletFile(const std::string& path, std::ifstream&& walletFileStream); + static void decryptKeyPair(const EncryptedWalletRecord& cipher, Crypto::PublicKey& publicKey, Crypto::SecretKey& secretKey, + uint64_t& creationTimestamp, const Crypto::chacha8_key& key); + void decryptKeyPair(const EncryptedWalletRecord& cipher, Crypto::PublicKey& publicKey, Crypto::SecretKey& secretKey, uint64_t& creationTimestamp) const; + static EncryptedWalletRecord encryptKeyPair(const Crypto::PublicKey& publicKey, const Crypto::SecretKey& secretKey, uint64_t creationTimestamp, + const Crypto::chacha8_key& key, const Crypto::chacha8_iv& iv); + EncryptedWalletRecord encryptKeyPair(const Crypto::PublicKey& publicKey, const Crypto::SecretKey& secretKey, uint64_t creationTimestamp) const; + Crypto::chacha8_iv getNextIv() const; + static void incIv(Crypto::chacha8_iv& iv); + void incNextIv(); + void initWithKeys(const std::string& path, const std::string& password, const Crypto::PublicKey& viewPublicKey, const Crypto::SecretKey& viewSecretKey); std::string doCreateAddress(const Crypto::PublicKey& spendPublicKey, const Crypto::SecretKey& spendSecretKey, uint64_t creationTimestamp); struct InputInfo { @@ -135,6 +147,14 @@ class WalletGreen : public IWallet, AddressAmounts amounts; }; +#pragma pack(push, 1) + struct ContainerStoragePrefix { + uint8_t version; + Crypto::chacha8_iv nextIv; + EncryptedWalletRecord encryptedViewKeys; + }; +#pragma pack(pop) + typedef std::unordered_map TransfersMap; virtual void onError(ITransfersSubscription* object, uint32_t height, std::error_code ec) override; @@ -261,8 +281,17 @@ class WalletGreen : public IWallet, void addUnconfirmedTransaction(const ITransactionReader& transaction); void removeUnconfirmedTransaction(const Crypto::Hash& transactionHash); - void unsafeLoad(std::istream& source, const std::string& password); - void unsafeSave(std::ostream& destination, bool saveDetails, bool saveCache, bool encrypt); + static void copyContainerStorageKeys(ContainerStorage& src, const Crypto::chacha8_key& srcKey, ContainerStorage& dst, const Crypto::chacha8_key& dstKey); + static void copyContainerStoragePrefix(ContainerStorage& src, const Crypto::chacha8_key& srcKey, ContainerStorage& dst, const Crypto::chacha8_key& dstKey); + void deleteOrphanTransactions(const std::unordered_set& deletedKeys); + static void encryptAndSaveContainerData(ContainerStorage& storage, const Crypto::chacha8_key& key, const void* containerData, size_t containerDataSize); + static void loadAndDecryptContainerData(ContainerStorage& storage, const Crypto::chacha8_key& key, BinaryArray& containerData); + void initTransactionPool(); + void loadSpendKeys(); + void loadContainerStorage(const std::string& path); + void loadWalletCache(std::unordered_set& addedKeys, std::unordered_set& deletedKeys, std::string& extra); + void saveWalletCache(ContainerStorage& storage, const Crypto::chacha8_key& key, WalletSaveLevel saveLevel, const std::string& extra); + void subscribeWallets(); std::vector pickRandomFusionInputs(const std::vector& addresses, uint64_t threshold, size_t minInputCount, size_t maxInputCount); @@ -287,7 +316,7 @@ class WalletGreen : public IWallet, std::vector getTransactionTransfers(const WalletTransaction& transaction) const; void filterOutTransactions(WalletTransactions& transactions, WalletTransfers& transfers, std::function&& pred) const; - void getViewKeyKnownBlocks(const Crypto::PublicKey& viewPublicKey); + void initBlockchain(const Crypto::PublicKey& viewPublicKey); CryptoNote::AccountPublicAddress getChangeDestination(const std::string& changeDestinationAddress, const std::vector& sourceAddresses) const; bool isMyAddress(const std::string& address) const; @@ -302,6 +331,7 @@ class WalletGreen : public IWallet, bool m_stopped; WalletsContainer m_walletsContainer; + ContainerStorage m_containerStorage; UnlockTransactionJobs m_unlockTransactionsJob; WalletTransactions m_transactions; WalletTransfers m_transfers; //sorted @@ -319,6 +349,9 @@ class WalletGreen : public IWallet, WalletState m_state; std::string m_password; + Crypto::chacha8_key m_key; + std::string m_path; + std::string m_extra; // workaround for wallet reset Crypto::PublicKey m_viewPublicKey; Crypto::SecretKey m_viewSecretKey; diff --git a/src/Wallet/WalletIndices.h b/src/Wallet/WalletIndices.h index e26a765577..449848d4ed 100644 --- a/src/Wallet/WalletIndices.h +++ b/src/Wallet/WalletIndices.h @@ -22,7 +22,6 @@ #include "ITransfersContainer.h" #include "IWallet.h" -#include "IWalletLegacy.h" //TODO: make common types for all of our APIs (such as PublicKey, KeyPair, etc) #include #include @@ -31,6 +30,9 @@ #include #include +#include "Common/FileMappedVector.h" +#include "crypto/chacha8.h" + namespace CryptoNote { const uint64_t ACCOUNT_CREATE_TIME_ACCURACY = 60 * 60 * 24; @@ -44,6 +46,14 @@ struct WalletRecord { time_t creationTimestamp; }; +#pragma pack(push, 1) +struct EncryptedWalletRecord { + Crypto::chacha8_iv iv; + // Secret key, public key and creation timestamp + uint8_t data[sizeof(Crypto::PublicKey) + sizeof(Crypto::SecretKey) + sizeof(uint64_t)]; +}; +#pragma pack(pop) + struct RandomAccessIndex {}; struct KeysIndex {}; struct TransfersContainerIndex {}; @@ -98,6 +108,7 @@ typedef boost::multi_index_container < > > WalletTransactions; +typedef Common::FileMappedVector ContainerStorage; typedef std::pair TransactionTransferPair; typedef std::vector WalletTransfers; typedef std::map UncommitedTransactions; diff --git a/src/Wallet/WalletSerialization.cpp b/src/Wallet/WalletSerializationV1.cpp old mode 100755 new mode 100644 similarity index 59% rename from src/Wallet/WalletSerialization.cpp rename to src/Wallet/WalletSerializationV1.cpp index c6160a8f7a..419b0f8eb5 --- a/src/Wallet/WalletSerialization.cpp +++ b/src/Wallet/WalletSerializationV1.cpp @@ -15,23 +15,12 @@ // You should have received a copy of the GNU Lesser General Public License // along with Bytecoin. If not, see . -#include "WalletSerialization.h" - -#include -#include -#include +#include "WalletSerializationV1.h" #include "Common/MemoryInputStream.h" -#include "Common/StdInputStream.h" -#include "Common/StdOutputStream.h" -#include "CryptoNoteCore/CryptoNoteSerialization.h" #include "CryptoNoteCore/CryptoNoteTools.h" - -#include "Serialization/BinaryOutputStreamSerializer.h" -#include "Serialization/BinaryInputStreamSerializer.h" -#include "Serialization/SerializationOverloads.h" - #include "Wallet/WalletErrors.h" +#include "Wallet/WalletUtils.h" #include "WalletLegacy/KeysStorage.h" #include "WalletLegacy/WalletLegacySerialization.h" @@ -167,40 +156,6 @@ void serialize(WalletTransferDto& value, CryptoNote::ISerializer& serializer) { } } -template -std::string serialize(Object& obj, const std::string& name) { - std::stringstream stream; - StdOutputStream output(stream); - CryptoNote::BinaryOutputStreamSerializer s(output); - - s(obj, Common::StringView(name)); - - stream.flush(); - return stream.str(); -} - -std::string encrypt(const std::string& plain, CryptoNote::CryptoContext& cryptoContext) { - std::string cipher; - cipher.resize(plain.size()); - - Crypto::chacha8(plain.data(), plain.size(), cryptoContext.key, cryptoContext.iv, &cipher[0]); - - return cipher; -} - -void addToStream(const std::string& cipher, const std::string& name, Common::IOutputStream& destination) { - CryptoNote::BinaryOutputStreamSerializer s(destination); - s(const_cast(cipher), name); -} - -template -void serializeEncrypted(Object& obj, const std::string& name, CryptoNote::CryptoContext& cryptoContext, Common::IOutputStream& destination) { - std::string plain = serialize(obj, name); - std::string cipher = encrypt(plain, cryptoContext); - - addToStream(cipher, name, destination); -} - std::string readCipher(Common::IInputStream& source, const std::string& name) { std::string cipher; CryptoNote::BinaryInputStreamSerializer s(source); @@ -209,7 +164,7 @@ std::string readCipher(Common::IInputStream& source, const std::string& name) { return cipher; } -std::string decrypt(const std::string& cipher, CryptoNote::CryptoContext& cryptoContext) { +std::string decrypt(const std::string& cipher, CryptoNote::WalletSerializerV1::CryptoContext& cryptoContext) { std::string plain; plain.resize(cipher.size()); @@ -225,25 +180,13 @@ void deserialize(Object& obj, const std::string& name, const std::string& plain) } template -void deserializeEncrypted(Object& obj, const std::string& name, CryptoNote::CryptoContext& cryptoContext, Common::IInputStream& source) { +void deserializeEncrypted(Object& obj, const std::string& name, CryptoNote::WalletSerializerV1::CryptoContext& cryptoContext, Common::IInputStream& source) { std::string cipher = readCipher(source, name); std::string plain = decrypt(cipher, cryptoContext); deserialize(obj, name, plain); } -bool verifyKeys(const SecretKey& sec, const PublicKey& expected_pub) { - PublicKey pub; - bool r = Crypto::secret_key_to_public_key(sec, pub); - - return r && expected_pub == pub; -} - -void throwIfKeysMissmatch(const SecretKey& sec, const PublicKey& expected_pub) { - if (!verifyKeys(sec, expected_pub)) - throw std::system_error(make_error_code(CryptoNote::error::WRONG_PASSWORD)); -} - CryptoNote::WalletTransaction convert(const CryptoNote::WalletLegacyTransaction& tx) { CryptoNote::WalletTransaction mtx; @@ -274,17 +217,17 @@ CryptoNote::WalletTransfer convert(const CryptoNote::WalletLegacyTransfer& tr) { namespace CryptoNote { -const uint32_t WalletSerializer::SERIALIZATION_VERSION = 5; +const uint32_t WalletSerializerV1::SERIALIZATION_VERSION = 5; -void CryptoContext::incIv() { - uint64_t * i = reinterpret_cast(&iv.data[0]); - (*i)++; +void WalletSerializerV1::CryptoContext::incIv() { + uint64_t* i = reinterpret_cast(&iv.data[0]); + *i = (*i == std::numeric_limits::max()) ? 0 : (*i + 1); } -WalletSerializer::WalletSerializer( +WalletSerializerV1::WalletSerializerV1( ITransfersObserver& transfersObserver, - PublicKey& viewPublicKey, - SecretKey& viewSecretKey, + Crypto::PublicKey& viewPublicKey, + Crypto::SecretKey& viewSecretKey, uint64_t& actualBalance, uint64_t& pendingBalance, WalletsContainer& walletsContainer, @@ -292,8 +235,8 @@ WalletSerializer::WalletSerializer( UnlockTransactionJobs& unlockTransactions, WalletTransactions& transactions, WalletTransfers& transfers, - uint32_t transactionSoftLockTime, - UncommitedTransactions& uncommitedTransactions + UncommitedTransactions& uncommitedTransactions, + uint32_t transactionSoftLockTime ) : m_transfersObserver(transfersObserver), m_viewPublicKey(viewPublicKey), @@ -305,187 +248,11 @@ WalletSerializer::WalletSerializer( m_unlockTransactions(unlockTransactions), m_transactions(transactions), m_transfers(transfers), - m_transactionSoftLockTime(transactionSoftLockTime), - uncommitedTransactions(uncommitedTransactions) + m_uncommitedTransactions(uncommitedTransactions), + m_transactionSoftLockTime(transactionSoftLockTime) { } -void WalletSerializer::save(const std::string& password, Common::IOutputStream& destination, bool saveDetails, bool saveCache) { - CryptoContext cryptoContext = generateCryptoContext(password); - - CryptoNote::BinaryOutputStreamSerializer s(destination); - s.beginObject("wallet"); - - saveVersion(destination); - saveIv(destination, cryptoContext.iv); - - saveKeys(destination, cryptoContext); - saveWallets(destination, saveCache, cryptoContext); - saveFlags(saveDetails, saveCache, destination, cryptoContext); - - if (saveDetails) { - saveTransactions(destination, cryptoContext); - saveTransfers(destination, cryptoContext); - } - - if (saveCache) { - saveBalances(destination, saveCache, cryptoContext); - saveTransfersSynchronizer(destination, cryptoContext); - saveUnlockTransactionsJobs(destination, cryptoContext); - saveUncommitedTransactions(destination, cryptoContext); - } - - s.endObject(); -} - -CryptoContext WalletSerializer::generateCryptoContext(const std::string& password) { - CryptoContext context; - - Crypto::cn_context c; - Crypto::generate_chacha8_key(c, password, context.key); - - context.iv = Crypto::rand(); - - return context; -} - -void WalletSerializer::saveVersion(Common::IOutputStream& destination) { - uint32_t version = SERIALIZATION_VERSION; - - BinaryOutputStreamSerializer s(destination); - s(version, "version"); -} - -void WalletSerializer::saveIv(Common::IOutputStream& destination, Crypto::chacha8_iv& iv) { - BinaryOutputStreamSerializer s(destination); - s.binary(reinterpret_cast(&iv.data), sizeof(iv.data), "chacha_iv"); -} - -void WalletSerializer::saveKeys(Common::IOutputStream& destination, CryptoContext& cryptoContext) { - savePublicKey(destination, cryptoContext); - saveSecretKey(destination, cryptoContext); -} - -void WalletSerializer::savePublicKey(Common::IOutputStream& destination, CryptoContext& cryptoContext) { - serializeEncrypted(m_viewPublicKey, "public_key", cryptoContext, destination); - cryptoContext.incIv(); -} - -void WalletSerializer::saveSecretKey(Common::IOutputStream& destination, CryptoContext& cryptoContext) { - serializeEncrypted(m_viewSecretKey, "secret_key", cryptoContext, destination); - cryptoContext.incIv(); -} - -void WalletSerializer::saveFlags(bool saveDetails, bool saveCache, Common::IOutputStream& destination, CryptoContext& cryptoContext) { - serializeEncrypted(saveDetails, "details", cryptoContext, destination); - cryptoContext.incIv(); - - serializeEncrypted(saveCache, "cache", cryptoContext, destination); - cryptoContext.incIv(); -} - -void WalletSerializer::saveWallets(Common::IOutputStream& destination, bool saveCache, CryptoContext& cryptoContext) { - auto& index = m_walletsContainer.get(); - - uint64_t count = index.size(); - serializeEncrypted(count, "wallets_count", cryptoContext, destination); - cryptoContext.incIv(); - - for (const auto& w: index) { - WalletRecordDto dto; - dto.spendPublicKey = w.spendPublicKey; - dto.spendSecretKey = w.spendSecretKey; - dto.pendingBalance = saveCache ? w.pendingBalance : 0; - dto.actualBalance = saveCache ? w.actualBalance : 0; - dto.creationTimestamp = static_cast(w.creationTimestamp); - - serializeEncrypted(dto, "", cryptoContext, destination); - cryptoContext.incIv(); - } -} - -void WalletSerializer::saveBalances(Common::IOutputStream& destination, bool saveCache, CryptoContext& cryptoContext) { - uint64_t actual = saveCache ? m_actualBalance : 0; - uint64_t pending = saveCache ? m_pendingBalance : 0; - - serializeEncrypted(actual, "actual_balance", cryptoContext, destination); - cryptoContext.incIv(); - - serializeEncrypted(pending, "pending_balance", cryptoContext, destination); - cryptoContext.incIv(); -} - -void WalletSerializer::saveTransfersSynchronizer(Common::IOutputStream& destination, CryptoContext& cryptoContext) { - std::stringstream stream; - m_synchronizer.save(stream); - stream.flush(); - - std::string plain = stream.str(); - serializeEncrypted(plain, "transfers_synchronizer", cryptoContext, destination); - cryptoContext.incIv(); -} - -void WalletSerializer::saveUnlockTransactionsJobs(Common::IOutputStream& destination, CryptoContext& cryptoContext) { - auto& index = m_unlockTransactions.get(); - auto& wallets = m_walletsContainer.get(); - - uint64_t jobsCount = index.size(); - serializeEncrypted(jobsCount, "unlock_transactions_jobs_count", cryptoContext, destination); - cryptoContext.incIv(); - - for (const auto& j: index) { - auto containerIt = wallets.find(j.container); - assert(containerIt != wallets.end()); - - auto rndIt = m_walletsContainer.project(containerIt); - assert(rndIt != m_walletsContainer.get().end()); - - uint64_t walletIndex = std::distance(m_walletsContainer.get().begin(), rndIt); - - UnlockTransactionJobDto dto; - dto.blockHeight = j.blockHeight; - dto.transactionHash = j.transactionHash; - dto.walletIndex = walletIndex; - - serializeEncrypted(dto, "", cryptoContext, destination); - cryptoContext.incIv(); - } -} - -void WalletSerializer::saveUncommitedTransactions(Common::IOutputStream& destination, CryptoContext& cryptoContext) { - serializeEncrypted(uncommitedTransactions, "uncommited_transactions", cryptoContext, destination); -} - -void WalletSerializer::saveTransactions(Common::IOutputStream& destination, CryptoContext& cryptoContext) { - uint64_t count = m_transactions.size(); - serializeEncrypted(count, "transactions_count", cryptoContext, destination); - cryptoContext.incIv(); - - for (const auto& tx: m_transactions) { - WalletTransactionDto dto(tx); - serializeEncrypted(dto, "", cryptoContext, destination); - cryptoContext.incIv(); - } -} - -void WalletSerializer::saveTransfers(Common::IOutputStream& destination, CryptoContext& cryptoContext) { - uint64_t count = m_transfers.size(); - serializeEncrypted(count, "transfers_count", cryptoContext, destination); - cryptoContext.incIv(); - - for (const auto& kv: m_transfers) { - uint64_t txId = kv.first; - - WalletTransferDto tr(kv.second, SERIALIZATION_VERSION); - - serializeEncrypted(txId, "transaction_id", cryptoContext, destination); - cryptoContext.incIv(); - - serializeEncrypted(tr, "transfer", cryptoContext, destination); - cryptoContext.incIv(); - } -} - -void WalletSerializer::load(const std::string& password, Common::IInputStream& source) { +void WalletSerializerV1::load(const Crypto::chacha8_key& key, Common::IInputStream& source) { CryptoNote::BinaryInputStreamSerializer s(source); s.beginObject("wallet"); @@ -494,22 +261,22 @@ void WalletSerializer::load(const std::string& password, Common::IInputStream& s if (version > SERIALIZATION_VERSION) { throw std::system_error(make_error_code(error::WRONG_VERSION)); } else if (version != 1) { - loadWallet(source, password, version); + loadWallet(source, key, version); } else { - loadWalletV1(source, password); + loadWalletV1(source, key); } s.endObject(); } -void WalletSerializer::loadWallet(Common::IInputStream& source, const std::string& password, uint32_t version) { - CryptoNote::CryptoContext cryptoContext; +void WalletSerializerV1::loadWallet(Common::IInputStream& source, const Crypto::chacha8_key& key, uint32_t version) { + CryptoContext cryptoContext; bool details = false; bool cache = false; loadIv(source, cryptoContext.iv); - generateKey(password, cryptoContext.key); + cryptoContext.key = key; loadKeys(source, cryptoContext); checkKeys(); @@ -544,10 +311,6 @@ void WalletSerializer::loadWallet(Common::IInputStream& source, const std::strin if (version > 3) { loadUncommitedTransactions(source, cryptoContext); - - if (version >= 5) { - initTransactionPool(); - } } } else { resetCachedBalance(); @@ -558,13 +321,13 @@ void WalletSerializer::loadWallet(Common::IInputStream& source, const std::strin } } -void WalletSerializer::loadWalletV1(Common::IInputStream& source, const std::string& password) { - CryptoNote::CryptoContext cryptoContext; +void WalletSerializerV1::loadWalletV1(Common::IInputStream& source, const Crypto::chacha8_key& key) { + CryptoContext cryptoContext; CryptoNote::BinaryInputStreamSerializer encrypted(source); encrypted(cryptoContext.iv, "iv"); - generateKey(password, cryptoContext.key); + cryptoContext.key = key; std::string cipher; encrypted(cipher, "data"); @@ -587,7 +350,7 @@ void WalletSerializer::loadWalletV1(Common::IInputStream& source, const std::str } } -void WalletSerializer::loadWalletV1Keys(CryptoNote::BinaryInputStreamSerializer& serializer) { +void WalletSerializerV1::loadWalletV1Keys(CryptoNote::BinaryInputStreamSerializer& serializer) { CryptoNote::KeysStorage keys; try { @@ -609,7 +372,7 @@ void WalletSerializer::loadWalletV1Keys(CryptoNote::BinaryInputStreamSerializer& m_walletsContainer.get().push_back(wallet); } -void WalletSerializer::loadWalletV1Details(CryptoNote::BinaryInputStreamSerializer& serializer) { +void WalletSerializerV1::loadWalletV1Details(CryptoNote::BinaryInputStreamSerializer& serializer) { std::vector txs; std::vector trs; @@ -619,7 +382,7 @@ void WalletSerializer::loadWalletV1Details(CryptoNote::BinaryInputStreamSerializ addWalletV1Details(txs, trs); } -uint32_t WalletSerializer::loadVersion(Common::IInputStream& source) { +uint32_t WalletSerializerV1::loadVersion(Common::IInputStream& source) { CryptoNote::BinaryInputStreamSerializer s(source); uint32_t version = std::numeric_limits::max(); @@ -628,18 +391,13 @@ uint32_t WalletSerializer::loadVersion(Common::IInputStream& source) { return version; } -void WalletSerializer::loadIv(Common::IInputStream& source, Crypto::chacha8_iv& iv) { +void WalletSerializerV1::loadIv(Common::IInputStream& source, Crypto::chacha8_iv& iv) { CryptoNote::BinaryInputStreamSerializer s(source); s.binary(static_cast(&iv.data), sizeof(iv.data), "chacha_iv"); } -void WalletSerializer::generateKey(const std::string& password, Crypto::chacha8_key& key) { - Crypto::cn_context context; - Crypto::generate_chacha8_key(context, password, key); -} - -void WalletSerializer::loadKeys(Common::IInputStream& source, CryptoContext& cryptoContext) { +void WalletSerializerV1::loadKeys(Common::IInputStream& source, CryptoContext& cryptoContext) { try { loadPublicKey(source, cryptoContext); loadSecretKey(source, cryptoContext); @@ -648,21 +406,21 @@ void WalletSerializer::loadKeys(Common::IInputStream& source, CryptoContext& cry } } -void WalletSerializer::loadPublicKey(Common::IInputStream& source, CryptoContext& cryptoContext) { +void WalletSerializerV1::loadPublicKey(Common::IInputStream& source, CryptoContext& cryptoContext) { deserializeEncrypted(m_viewPublicKey, "public_key", cryptoContext, source); cryptoContext.incIv(); } -void WalletSerializer::loadSecretKey(Common::IInputStream& source, CryptoContext& cryptoContext) { +void WalletSerializerV1::loadSecretKey(Common::IInputStream& source, CryptoContext& cryptoContext) { deserializeEncrypted(m_viewSecretKey, "secret_key", cryptoContext, source); cryptoContext.incIv(); } -void WalletSerializer::checkKeys() { +void WalletSerializerV1::checkKeys() { throwIfKeysMissmatch(m_viewSecretKey, m_viewPublicKey); } -void WalletSerializer::loadFlags(bool& details, bool& cache, Common::IInputStream& source, CryptoContext& cryptoContext) { +void WalletSerializerV1::loadFlags(bool& details, bool& cache, Common::IInputStream& source, CryptoContext& cryptoContext) { deserializeEncrypted(details, "details", cryptoContext, source); cryptoContext.incIv(); @@ -670,7 +428,7 @@ void WalletSerializer::loadFlags(bool& details, bool& cache, Common::IInputStrea cryptoContext.incIv(); } -void WalletSerializer::loadWallets(Common::IInputStream& source, CryptoContext& cryptoContext) { +void WalletSerializerV1::loadWallets(Common::IInputStream& source, CryptoContext& cryptoContext) { auto& index = m_walletsContainer.get(); uint64_t count = 0; @@ -691,12 +449,7 @@ void WalletSerializer::loadWallets(Common::IInputStream& source, CryptoContext& } if (dto.spendSecretKey != NULL_SECRET_KEY) { - Crypto::PublicKey restoredPublicKey; - bool r = Crypto::secret_key_to_public_key(dto.spendSecretKey, restoredPublicKey); - - if (!r || dto.spendPublicKey != restoredPublicKey) { - throw std::system_error(make_error_code(error::WRONG_PASSWORD), "Restored spend public key doesn't correspond to secret key"); - } + throwIfKeysMissmatch(dto.spendSecretKey, dto.spendPublicKey, "Restored spend public key doesn't correspond to secret key"); } else { if (!Crypto::check_key(dto.spendPublicKey)) { throw std::system_error(make_error_code(error::WRONG_PASSWORD), "Public spend key is incorrect"); @@ -715,7 +468,7 @@ void WalletSerializer::loadWallets(Common::IInputStream& source, CryptoContext& } } -void WalletSerializer::subscribeWallets() { +void WalletSerializerV1::subscribeWallets() { auto& index = m_walletsContainer.get(); for (auto it = index.begin(); it != index.end(); ++it) { @@ -738,7 +491,7 @@ void WalletSerializer::subscribeWallets() { } } -void WalletSerializer::loadBalances(Common::IInputStream& source, CryptoContext& cryptoContext) { +void WalletSerializerV1::loadBalances(Common::IInputStream& source, CryptoContext& cryptoContext) { deserializeEncrypted(m_actualBalance, "actual_balance", cryptoContext, source); cryptoContext.incIv(); @@ -746,7 +499,7 @@ void WalletSerializer::loadBalances(Common::IInputStream& source, CryptoContext& cryptoContext.incIv(); } -void WalletSerializer::loadTransfersSynchronizer(Common::IInputStream& source, CryptoContext& cryptoContext) { +void WalletSerializerV1::loadTransfersSynchronizer(Common::IInputStream& source, CryptoContext& cryptoContext) { std::string deciphered; deserializeEncrypted(deciphered, "transfers_synchronizer", cryptoContext, source); cryptoContext.incIv(); @@ -757,7 +510,7 @@ void WalletSerializer::loadTransfersSynchronizer(Common::IInputStream& source, C m_synchronizer.load(stream); } -void WalletSerializer::loadObsoleteSpentOutputs(Common::IInputStream& source, CryptoContext& cryptoContext) { +void WalletSerializerV1::loadObsoleteSpentOutputs(Common::IInputStream& source, CryptoContext& cryptoContext) { uint64_t count = 0; deserializeEncrypted(count, "spent_outputs_count", cryptoContext, source); cryptoContext.incIv(); @@ -769,7 +522,7 @@ void WalletSerializer::loadObsoleteSpentOutputs(Common::IInputStream& source, Cr } } -void WalletSerializer::loadUnlockTransactionsJobs(Common::IInputStream& source, CryptoContext& cryptoContext) { +void WalletSerializerV1::loadUnlockTransactionsJobs(Common::IInputStream& source, CryptoContext& cryptoContext) { auto& index = m_unlockTransactions.get(); auto& walletsIndex = m_walletsContainer.get(); const uint64_t walletsSize = walletsIndex.size(); @@ -794,7 +547,7 @@ void WalletSerializer::loadUnlockTransactionsJobs(Common::IInputStream& source, } } -void WalletSerializer::loadObsoleteChange(Common::IInputStream& source, CryptoContext& cryptoContext) { +void WalletSerializerV1::loadObsoleteChange(Common::IInputStream& source, CryptoContext& cryptoContext) { uint64_t count = 0; deserializeEncrypted(count, "changes_count", cryptoContext, source); cryptoContext.incIv(); @@ -806,20 +559,11 @@ void WalletSerializer::loadObsoleteChange(Common::IInputStream& source, CryptoCo } } -void WalletSerializer::loadUncommitedTransactions(Common::IInputStream& source, CryptoContext& cryptoContext) { - deserializeEncrypted(uncommitedTransactions, "uncommited_transactions", cryptoContext, source); -} - -void WalletSerializer::initTransactionPool() { - std::unordered_set uncommitedTransactionsSet; - std::transform(uncommitedTransactions.begin(), uncommitedTransactions.end(), std::inserter(uncommitedTransactionsSet, uncommitedTransactionsSet.end()), - [](const UncommitedTransactions::value_type& pair) { - return getObjectHash(pair.second); - }); - m_synchronizer.initTransactionPool(uncommitedTransactionsSet); +void WalletSerializerV1::loadUncommitedTransactions(Common::IInputStream& source, CryptoContext& cryptoContext) { + deserializeEncrypted(m_uncommitedTransactions, "uncommited_transactions", cryptoContext, source); } -void WalletSerializer::resetCachedBalance() { +void WalletSerializerV1::resetCachedBalance() { for (auto it = m_walletsContainer.begin(); it != m_walletsContainer.end(); ++it) { m_walletsContainer.modify(it, [](WalletRecord& wallet) { wallet.actualBalance = 0; @@ -829,7 +573,7 @@ void WalletSerializer::resetCachedBalance() { } // can't do it in loadTransactions, TransfersContainer is not yet loaded -void WalletSerializer::updateTransactionsBaseStatus() { +void WalletSerializerV1::updateTransactionsBaseStatus() { auto& transactions = m_transactions.get(); auto begin = std::begin(transactions); auto end = std::end(transactions); @@ -847,7 +591,7 @@ void WalletSerializer::updateTransactionsBaseStatus() { } } -void WalletSerializer::updateTransfersSign() { +void WalletSerializerV1::updateTransfersSign() { auto it = m_transfers.begin(); while (it != m_transfers.end()) { if (it->second.amount < 0) { @@ -859,7 +603,7 @@ void WalletSerializer::updateTransfersSign() { } } -void WalletSerializer::loadTransactions(Common::IInputStream& source, CryptoContext& cryptoContext) { +void WalletSerializerV1::loadTransactions(Common::IInputStream& source, CryptoContext& cryptoContext) { uint64_t count = 0; deserializeEncrypted(count, "transactions_count", cryptoContext, source); cryptoContext.incIv(); @@ -887,7 +631,7 @@ void WalletSerializer::loadTransactions(Common::IInputStream& source, CryptoCont } } -void WalletSerializer::loadTransfers(Common::IInputStream& source, CryptoContext& cryptoContext, uint32_t version) { +void WalletSerializerV1::loadTransfers(Common::IInputStream& source, CryptoContext& cryptoContext, uint32_t version) { uint64_t count = 0; deserializeEncrypted(count, "transfers_count", cryptoContext, source); cryptoContext.incIv(); @@ -917,7 +661,7 @@ void WalletSerializer::loadTransfers(Common::IInputStream& source, CryptoContext } } -void WalletSerializer::addWalletV1Details(const std::vector& txs, const std::vector& trs) { +void WalletSerializerV1::addWalletV1Details(const std::vector& txs, const std::vector& trs) { size_t txId = 0; m_transfers.reserve(trs.size()); diff --git a/src/Wallet/WalletSerialization.h b/src/Wallet/WalletSerializationV1.h old mode 100755 new mode 100644 similarity index 63% rename from src/Wallet/WalletSerialization.h rename to src/Wallet/WalletSerializationV1.h index 5e5b5c03ae..d935974dcc --- a/src/Wallet/WalletSerialization.h +++ b/src/Wallet/WalletSerializationV1.h @@ -17,27 +17,19 @@ #pragma once -#include "IWallet.h" -#include "WalletIndices.h" -#include "Common/IInputStream.h" -#include "Common/IOutputStream.h" -#include "Transfers/TransfersSynchronizer.h" -#include "Serialization/BinaryInputStreamSerializer.h" +#include "IWalletLegacy.h" +#include "Common/IInputStream.h" #include "crypto/chacha8.h" +#include "Serialization/BinaryInputStreamSerializer.h" +#include "Transfers/TransfersSynchronizer.h" +#include "Wallet/WalletIndices.h" namespace CryptoNote { -struct CryptoContext { - Crypto::chacha8_key key; - Crypto::chacha8_iv iv; - - void incIv(); -}; - -class WalletSerializer { +class WalletSerializerV1 { public: - WalletSerializer( + WalletSerializerV1( ITransfersObserver& transfersObserver, Crypto::PublicKey& viewPublicKey, Crypto::SecretKey& viewSecretKey, @@ -48,38 +40,27 @@ class WalletSerializer { UnlockTransactionJobs& unlockTransactions, WalletTransactions& transactions, WalletTransfers& transfers, - uint32_t transactionSoftLockTime, - UncommitedTransactions& uncommitedTransactions + UncommitedTransactions& uncommitedTransactions, + uint32_t transactionSoftLockTime ); - void save(const std::string& password, Common::IOutputStream& destination, bool saveDetails, bool saveCache); - void load(const std::string& password, Common::IInputStream& source); + void load(const Crypto::chacha8_key& key, Common::IInputStream& source); -private: - static const uint32_t SERIALIZATION_VERSION; + struct CryptoContext { + Crypto::chacha8_key key; + Crypto::chacha8_iv iv; - void loadWallet(Common::IInputStream& source, const std::string& password, uint32_t version); - void loadWalletV1(Common::IInputStream& source, const std::string& password); + void incIv(); + }; - CryptoContext generateCryptoContext(const std::string& password); +private: + static const uint32_t SERIALIZATION_VERSION; - void saveVersion(Common::IOutputStream& destination); - void saveIv(Common::IOutputStream& destination, Crypto::chacha8_iv& iv); - void saveKeys(Common::IOutputStream& destination, CryptoContext& cryptoContext); - void savePublicKey(Common::IOutputStream& destination, CryptoContext& cryptoContext); - void saveSecretKey(Common::IOutputStream& destination, CryptoContext& cryptoContext); - void saveFlags(bool saveDetails, bool saveCache, Common::IOutputStream& destination, CryptoContext& cryptoContext); - void saveWallets(Common::IOutputStream& destination, bool saveCache, CryptoContext& cryptoContext); - void saveBalances(Common::IOutputStream& destination, bool saveCache, CryptoContext& cryptoContext); - void saveTransfersSynchronizer(Common::IOutputStream& destination, CryptoContext& cryptoContext); - void saveUnlockTransactionsJobs(Common::IOutputStream& destination, CryptoContext& cryptoContext); - void saveUncommitedTransactions(Common::IOutputStream& destination, CryptoContext& cryptoContext); - void saveTransactions(Common::IOutputStream& destination, CryptoContext& cryptoContext); - void saveTransfers(Common::IOutputStream& destination, CryptoContext& cryptoContext); + void loadWallet(Common::IInputStream& source, const Crypto::chacha8_key& key, uint32_t version); + void loadWalletV1(Common::IInputStream& source, const Crypto::chacha8_key& key); uint32_t loadVersion(Common::IInputStream& source); void loadIv(Common::IInputStream& source, Crypto::chacha8_iv& iv); - void generateKey(const std::string& password, Crypto::chacha8_key& key); void loadKeys(Common::IInputStream& source, CryptoContext& cryptoContext); void loadPublicKey(Common::IInputStream& source, CryptoContext& cryptoContext); void loadSecretKey(Common::IInputStream& source, CryptoContext& cryptoContext); @@ -99,7 +80,6 @@ class WalletSerializer { void loadWalletV1Keys(CryptoNote::BinaryInputStreamSerializer& serializer); void loadWalletV1Details(CryptoNote::BinaryInputStreamSerializer& serializer); void addWalletV1Details(const std::vector& txs, const std::vector& trs); - void initTransactionPool(); void resetCachedBalance(); void updateTransactionsBaseStatus(); void updateTransfersSign(); @@ -114,8 +94,8 @@ class WalletSerializer { UnlockTransactionJobs& m_unlockTransactions; WalletTransactions& m_transactions; WalletTransfers& m_transfers; + UncommitedTransactions& m_uncommitedTransactions; uint32_t m_transactionSoftLockTime; - UncommitedTransactions& uncommitedTransactions; }; } //namespace CryptoNote diff --git a/src/Wallet/WalletSerializationV2.cpp b/src/Wallet/WalletSerializationV2.cpp new file mode 100644 index 0000000000..59bc35a301 --- /dev/null +++ b/src/Wallet/WalletSerializationV2.cpp @@ -0,0 +1,390 @@ +// Copyright (c) 2012-2016, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#include "WalletSerializationV2.h" + +#include "CryptoNoteCore/CryptoNoteSerialization.h" +#include "Serialization/BinaryInputStreamSerializer.h" +#include "Serialization/BinaryOutputStreamSerializer.h" + +using namespace Common; +using namespace Crypto; + +namespace { + +//DO NOT CHANGE IT +struct UnlockTransactionJobDtoV2 { + uint32_t blockHeight; + Hash transactionHash; + Crypto::PublicKey walletSpendPublicKey; +}; + +//DO NOT CHANGE IT +struct WalletTransactionDtoV2 { + WalletTransactionDtoV2() { + } + + WalletTransactionDtoV2(const CryptoNote::WalletTransaction& wallet) { + state = wallet.state; + timestamp = wallet.timestamp; + blockHeight = wallet.blockHeight; + hash = wallet.hash; + totalAmount = wallet.totalAmount; + fee = wallet.fee; + creationTime = wallet.creationTime; + unlockTime = wallet.unlockTime; + extra = wallet.extra; + isBase = wallet.isBase; + } + + CryptoNote::WalletTransactionState state; + uint64_t timestamp; + uint32_t blockHeight; + Hash hash; + int64_t totalAmount; + uint64_t fee; + uint64_t creationTime; + uint64_t unlockTime; + std::string extra; + bool isBase; +}; + +//DO NOT CHANGE IT +struct WalletTransferDtoV2 { + WalletTransferDtoV2() { + } + + WalletTransferDtoV2(const CryptoNote::WalletTransfer& tr) { + address = tr.address; + amount = tr.amount; + type = static_cast(tr.type); + } + + std::string address; + uint64_t amount; + uint8_t type; +}; + +void serialize(UnlockTransactionJobDtoV2& value, CryptoNote::ISerializer& serializer) { + serializer(value.blockHeight, "blockHeight"); + serializer(value.transactionHash, "transactionHash"); + serializer(value.walletSpendPublicKey, "walletSpendPublicKey"); +} + +void serialize(WalletTransactionDtoV2& value, CryptoNote::ISerializer& serializer) { + typedef std::underlying_type::type StateType; + + StateType state = static_cast(value.state); + serializer(state, "state"); + value.state = static_cast(state); + + serializer(value.timestamp, "timestamp"); + CryptoNote::serializeBlockHeight(serializer, value.blockHeight, "blockHeight"); + serializer(value.hash, "hash"); + serializer(value.totalAmount, "totalAmount"); + serializer(value.fee, "fee"); + serializer(value.creationTime, "creationTime"); + serializer(value.unlockTime, "unlockTime"); + serializer(value.extra, "extra"); + serializer(value.isBase, "isBase"); +} + +void serialize(WalletTransferDtoV2& value, CryptoNote::ISerializer& serializer) { + serializer(value.address, "address"); + serializer(value.amount, "amount"); + serializer(value.type, "type"); +} + +} + +namespace CryptoNote { + +WalletSerializerV2::WalletSerializerV2( + ITransfersObserver& transfersObserver, + Crypto::PublicKey& viewPublicKey, + Crypto::SecretKey& viewSecretKey, + uint64_t& actualBalance, + uint64_t& pendingBalance, + WalletsContainer& walletsContainer, + TransfersSyncronizer& synchronizer, + UnlockTransactionJobs& unlockTransactions, + WalletTransactions& transactions, + WalletTransfers& transfers, + UncommitedTransactions& uncommitedTransactions, + std::string& extra, + uint32_t transactionSoftLockTime +) : + m_transfersObserver(transfersObserver), + m_actualBalance(actualBalance), + m_pendingBalance(pendingBalance), + m_walletsContainer(walletsContainer), + m_synchronizer(synchronizer), + m_unlockTransactions(unlockTransactions), + m_transactions(transactions), + m_transfers(transfers), + m_uncommitedTransactions(uncommitedTransactions), + m_extra(extra), + m_transactionSoftLockTime(transactionSoftLockTime) +{ +} + +void WalletSerializerV2::load(Common::IInputStream& source, uint8_t version) { + CryptoNote::BinaryInputStreamSerializer s(source); + + uint8_t saveLevelValue; + s(saveLevelValue, "saveLevel"); + WalletSaveLevel saveLevel = static_cast(saveLevelValue); + + loadKeyListAndBanalces(s, saveLevel == WalletSaveLevel::SAVE_ALL); + + if (saveLevel == WalletSaveLevel::SAVE_KEYS_AND_TRANSACTIONS || saveLevel == WalletSaveLevel::SAVE_ALL) { + loadTransactions(s); + loadTransfers(s); + } + + if (saveLevel == WalletSaveLevel::SAVE_ALL) { + loadTransfersSynchronizer(s); + loadUnlockTransactionsJobs(s); + s(m_uncommitedTransactions, "uncommitedTransactions"); + } + + s(m_extra, "extra"); +} + +void WalletSerializerV2::save(Common::IOutputStream& destination, WalletSaveLevel saveLevel) { + CryptoNote::BinaryOutputStreamSerializer s(destination); + + uint8_t saveLevelValue = static_cast(saveLevel); + s(saveLevelValue, "saveLevel"); + + saveKeyListAndBanalces(s, saveLevel == WalletSaveLevel::SAVE_ALL); + + if (saveLevel == WalletSaveLevel::SAVE_KEYS_AND_TRANSACTIONS || saveLevel == WalletSaveLevel::SAVE_ALL) { + saveTransactions(s); + saveTransfers(s); + } + + if (saveLevel == WalletSaveLevel::SAVE_ALL) { + saveTransfersSynchronizer(s); + saveUnlockTransactionsJobs(s); + s(m_uncommitedTransactions, "uncommitedTransactions"); + } + + s(m_extra, "extra"); +} + +std::unordered_set& WalletSerializerV2::addedKeys() { + return m_addedKeys; +} + +std::unordered_set& WalletSerializerV2::deletedKeys() { + return m_deletedKeys; +} + +void WalletSerializerV2::loadKeyListAndBanalces(CryptoNote::ISerializer& serializer, bool saveCache) { + size_t walletCount; + serializer(walletCount, "walletCount"); + + m_actualBalance = 0; + m_pendingBalance = 0; + m_deletedKeys.clear(); + + std::unordered_set cachedKeySet; + auto& index = m_walletsContainer.get(); + for (size_t i = 0; i < walletCount; ++i) { + Crypto::PublicKey spendPublicKey; + uint64_t actualBalance; + uint64_t pendingBalance; + serializer(spendPublicKey, "spendPublicKey"); + + if (saveCache) { + serializer(actualBalance, "actualBalance"); + serializer(pendingBalance, "pendingBalance"); + } + + cachedKeySet.insert(spendPublicKey); + + auto it = index.find(spendPublicKey); + if (it == index.end()) { + m_deletedKeys.emplace(std::move(spendPublicKey)); + } else if (saveCache) { + m_actualBalance += actualBalance; + m_pendingBalance += pendingBalance; + + index.modify(it, [actualBalance, pendingBalance](WalletRecord& wallet) { + wallet.actualBalance = actualBalance; + wallet.pendingBalance = pendingBalance; + }); + } + } + + for (auto wallet : index) { + if (cachedKeySet.count(wallet.spendPublicKey) == 0) { + m_addedKeys.insert(wallet.spendPublicKey); + } + } +} + +void WalletSerializerV2::saveKeyListAndBanalces(CryptoNote::ISerializer& serializer, bool saveCache) { + auto walletCount = m_walletsContainer.get().size(); + serializer(walletCount, "walletCount"); + for (auto wallet : m_walletsContainer.get()) { + serializer(wallet.spendPublicKey, "spendPublicKey"); + + if (saveCache) { + serializer(wallet.actualBalance, "actualBalance"); + serializer(wallet.pendingBalance, "pendingBalance"); + } + } +} + +void WalletSerializerV2::loadTransactions(CryptoNote::ISerializer& serializer) { + uint64_t count = 0; + serializer(count, "transactionCount"); + + m_transactions.get().reserve(count); + + for (uint64_t i = 0; i < count; ++i) { + WalletTransactionDtoV2 dto; + serializer(dto, "transaction"); + + WalletTransaction tx; + tx.state = dto.state; + tx.timestamp = dto.timestamp; + tx.blockHeight = dto.blockHeight; + tx.hash = dto.hash; + tx.totalAmount = dto.totalAmount; + tx.fee = dto.fee; + tx.creationTime = dto.creationTime; + tx.unlockTime = dto.unlockTime; + tx.extra = dto.extra; + tx.isBase = dto.isBase; + + m_transactions.get().emplace_back(std::move(tx)); + } +} + +void WalletSerializerV2::saveTransactions(CryptoNote::ISerializer& serializer) { + uint64_t count = m_transactions.size(); + serializer(count, "transactionCount"); + + for (const auto& tx : m_transactions) { + WalletTransactionDtoV2 dto(tx); + serializer(dto, "transaction"); + } +} + +void WalletSerializerV2::loadTransfers(CryptoNote::ISerializer& serializer) { + uint64_t count = 0; + serializer(count, "transferCount"); + + m_transfers.reserve(count); + + for (uint64_t i = 0; i < count; ++i) { + uint64_t txId = 0; + serializer(txId, "transactionId"); + + WalletTransferDtoV2 dto; + serializer(dto, "transfer"); + + WalletTransfer tr; + tr.address = dto.address; + tr.amount = dto.amount; + tr.type = static_cast(dto.type); + + m_transfers.emplace_back(std::piecewise_construct, std::forward_as_tuple(txId), std::forward_as_tuple(std::move(tr))); + } +} + +void WalletSerializerV2::saveTransfers(CryptoNote::ISerializer& serializer) { + uint64_t count = m_transfers.size(); + serializer(count, "transferCount"); + + for (const auto& kv : m_transfers) { + uint64_t txId = kv.first; + + WalletTransferDtoV2 tr(kv.second); + + serializer(txId, "transactionId"); + serializer(tr, "transfer"); + } +} + +void WalletSerializerV2::loadTransfersSynchronizer(CryptoNote::ISerializer& serializer) { + std::string transfersSynchronizerData; + serializer(transfersSynchronizerData, "transfersSynchronizer"); + + std::stringstream stream(transfersSynchronizerData); + m_synchronizer.load(stream); +} + +void WalletSerializerV2::saveTransfersSynchronizer(CryptoNote::ISerializer& serializer) { + std::stringstream stream; + m_synchronizer.save(stream); + stream.flush(); + + std::string transfersSynchronizerData = stream.str(); + serializer(transfersSynchronizerData, "transfersSynchronizer"); +} + +void WalletSerializerV2::loadUnlockTransactionsJobs(CryptoNote::ISerializer& serializer) { + auto& index = m_unlockTransactions.get(); + auto& walletsIndex = m_walletsContainer.get(); + + uint64_t jobsCount = 0; + serializer(jobsCount, "unlockTransactionsJobsCount"); + + for (uint64_t i = 0; i < jobsCount; ++i) { + UnlockTransactionJobDtoV2 dto; + serializer(dto, "unlockTransactionsJob"); + + auto walletIt = walletsIndex.find(dto.walletSpendPublicKey); + if (walletIt != walletsIndex.end()) { + UnlockTransactionJob job; + job.blockHeight = dto.blockHeight; + job.transactionHash = dto.transactionHash; + job.container = walletIt->container; + + index.emplace(std::move(job)); + } + } +} + +void WalletSerializerV2::saveUnlockTransactionsJobs(CryptoNote::ISerializer& serializer) { + auto& index = m_unlockTransactions.get(); + auto& wallets = m_walletsContainer.get(); + + uint64_t jobsCount = index.size(); + serializer(jobsCount, "unlockTransactionsJobsCount"); + + for (const auto& j : index) { + auto containerIt = wallets.find(j.container); + assert(containerIt != wallets.end()); + + auto keyIt = m_walletsContainer.project(containerIt); + assert(keyIt != m_walletsContainer.get().end()); + + UnlockTransactionJobDtoV2 dto; + dto.blockHeight = j.blockHeight; + dto.transactionHash = j.transactionHash; + dto.walletSpendPublicKey = keyIt->spendPublicKey; + + serializer(dto, "unlockTransactionsJob"); + } +} + +} //namespace CryptoNote diff --git a/src/Wallet/WalletSerializationV2.h b/src/Wallet/WalletSerializationV2.h new file mode 100644 index 0000000000..fc6715a3a2 --- /dev/null +++ b/src/Wallet/WalletSerializationV2.h @@ -0,0 +1,87 @@ +// Copyright (c) 2012-2016, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#pragma once + +#include "Common/IInputStream.h" +#include "Common/IOutputStream.h" +#include "Serialization/ISerializer.h" +#include "Transfers/TransfersSynchronizer.h" +#include "Wallet/WalletIndices.h" + +namespace CryptoNote { + +class WalletSerializerV2 { +public: + WalletSerializerV2( + ITransfersObserver& transfersObserver, + Crypto::PublicKey& viewPublicKey, + Crypto::SecretKey& viewSecretKey, + uint64_t& actualBalance, + uint64_t& pendingBalance, + WalletsContainer& walletsContainer, + TransfersSyncronizer& synchronizer, + UnlockTransactionJobs& unlockTransactions, + WalletTransactions& transactions, + WalletTransfers& transfers, + UncommitedTransactions& uncommitedTransactions, + std::string& extra, + uint32_t transactionSoftLockTime + ); + + void load(Common::IInputStream& source, uint8_t version); + void save(Common::IOutputStream& destination, WalletSaveLevel saveLevel); + + std::unordered_set& addedKeys(); + std::unordered_set& deletedKeys(); + + static const uint8_t MIN_VERSION = 6; + static const uint8_t SERIALIZATION_VERSION = 6; + +private: + void loadKeyListAndBanalces(CryptoNote::ISerializer& serializer, bool saveCache); + void saveKeyListAndBanalces(CryptoNote::ISerializer& serializer, bool saveCache); + + void loadTransactions(CryptoNote::ISerializer& serializer); + void saveTransactions(CryptoNote::ISerializer& serializer); + + void loadTransfers(CryptoNote::ISerializer& serializer); + void saveTransfers(CryptoNote::ISerializer& serializer); + + void loadTransfersSynchronizer(CryptoNote::ISerializer& serializer); + void saveTransfersSynchronizer(CryptoNote::ISerializer& serializer); + + void loadUnlockTransactionsJobs(CryptoNote::ISerializer& serializer); + void saveUnlockTransactionsJobs(CryptoNote::ISerializer& serializer); + + ITransfersObserver& m_transfersObserver; + uint64_t& m_actualBalance; + uint64_t& m_pendingBalance; + WalletsContainer& m_walletsContainer; + TransfersSyncronizer& m_synchronizer; + UnlockTransactionJobs& m_unlockTransactions; + WalletTransactions& m_transactions; + WalletTransfers& m_transfers; + UncommitedTransactions& m_uncommitedTransactions; + std::string& m_extra; + uint32_t m_transactionSoftLockTime; + + std::unordered_set m_addedKeys; + std::unordered_set m_deletedKeys; +}; + +} //namespace CryptoNote diff --git a/src/Wallet/WalletUtils.cpp b/src/Wallet/WalletUtils.cpp index e3a12074ad..8586a869c6 100644 --- a/src/Wallet/WalletUtils.cpp +++ b/src/Wallet/WalletUtils.cpp @@ -18,9 +18,19 @@ #include "WalletUtils.h" #include "CryptoNote.h" +#include "crypto/crypto.h" +#include "Wallet/WalletErrors.h" namespace CryptoNote { +void throwIfKeysMissmatch(const Crypto::SecretKey& secretKey, const Crypto::PublicKey& expectedPublicKey, const std::string& message) { + Crypto::PublicKey pub; + bool r = Crypto::secret_key_to_public_key(secretKey, pub); + if (!r || expectedPublicKey != pub) { + throw std::system_error(make_error_code(CryptoNote::error::WRONG_PASSWORD), message); + } +} + bool validateAddress(const std::string& address, const CryptoNote::Currency& currency) { CryptoNote::AccountPublicAddress ignore; return currency.parseAccountAddressString(address, ignore); diff --git a/src/Wallet/WalletUtils.h b/src/Wallet/WalletUtils.h index e2adc680ff..7aac69a0a0 100644 --- a/src/Wallet/WalletUtils.h +++ b/src/Wallet/WalletUtils.h @@ -25,6 +25,7 @@ namespace CryptoNote { +void throwIfKeysMissmatch(const Crypto::SecretKey& secretKey, const Crypto::PublicKey& expectedPublicKey, const std::string& message = ""); bool validateAddress(const std::string& address, const CryptoNote::Currency& currency); std::ostream& operator<<(std::ostream& os, CryptoNote::WalletTransactionState state); diff --git a/src/WalletLegacy/WalletLegacySerializer.cpp b/src/WalletLegacy/WalletLegacySerializer.cpp index 06b3a5e208..fd617156fe 100755 --- a/src/WalletLegacy/WalletLegacySerializer.cpp +++ b/src/WalletLegacy/WalletLegacySerializer.cpp @@ -28,25 +28,11 @@ #include "CryptoNoteCore/CryptoNoteSerialization.h" #include "WalletLegacy/WalletUserTransactionsCache.h" #include "Wallet/WalletErrors.h" +#include "Wallet/WalletUtils.h" #include "WalletLegacy/KeysStorage.h" using namespace Common; -namespace { - -bool verifyKeys(const Crypto::SecretKey& sec, const Crypto::PublicKey& expected_pub) { - Crypto::PublicKey pub; - bool r = Crypto::secret_key_to_public_key(sec, pub); - return r && expected_pub == pub; -} - -void throwIfKeysMissmatch(const Crypto::SecretKey& sec, const Crypto::PublicKey& expected_pub) { - if (!verifyKeys(sec, expected_pub)) - throw std::system_error(make_error_code(CryptoNote::error::WRONG_PASSWORD)); -} - -} - namespace CryptoNote { WalletLegacySerializer::WalletLegacySerializer(CryptoNote::AccountBase& account, WalletUserTransactionsCache& transactionsCache) : diff --git a/src/version.h.in b/src/version.h.in index ca3a425248..a46542f33b 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -1,4 +1,4 @@ #define BUILD_COMMIT_ID "@VERSION@" -#define PROJECT_VERSION "1.0.10.1" -#define PROJECT_VERSION_BUILD_NO "692" +#define PROJECT_VERSION "1.0.11" +#define PROJECT_VERSION_BUILD_NO "697" #define PROJECT_VERSION_LONG PROJECT_VERSION "." PROJECT_VERSION_BUILD_NO " (" BUILD_COMMIT_ID ")" diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f2fdd381e9..ac493a496f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -48,7 +48,7 @@ if (MSVC) endif () target_link_libraries(TransfersTests IntegrationTestLibrary Wallet gtest_main InProcessNode NodeRpcProxy P2P Rpc Http BlockchainExplorer CryptoNoteCore Serialization System Logging Transfers Common Crypto upnpc-static ${Boost_LIBRARIES}) -target_link_libraries(UnitTests gtest_main PaymentGate Wallet TestGenerator InProcessNode NodeRpcProxy Rpc Http Transfers Serialization System Logging BlockchainExplorer Common CryptoNoteCore Crypto ${Boost_LIBRARIES}) +target_link_libraries(UnitTests gtest_main PaymentGate Wallet TestGenerator InProcessNode NodeRpcProxy Rpc Http Transfers Serialization System Logging BlockchainExplorer CryptoNoteCore Common Crypto ${Boost_LIBRARIES}) target_link_libraries(DifficultyTests CryptoNoteCore Serialization Crypto Logging Common ${Boost_LIBRARIES}) target_link_libraries(HashTargetTests CryptoNoteCore Crypto) diff --git a/tests/IntegrationTests/Node.cpp b/tests/IntegrationTests/Node.cpp index b4bf347824..99c7ae7f40 100644 --- a/tests/IntegrationTests/Node.cpp +++ b/tests/IntegrationTests/Node.cpp @@ -108,12 +108,12 @@ void loadBlockchainInfo(const std::string& filename, BlockchainInfo& bc) { class NodeTest: public BaseTest { - protected: - void startNetworkWithBlockchain(const std::string& sourcePath); void readBlockchainInfo(INode& node, BlockchainInfo& bc); void dumpBlockchainInfo(INode& node); + + const std::string TEST_WALLET_FILE = "wallet.bin"; }; void NodeTest::startNetworkWithBlockchain(const std::string& sourcePath) { @@ -180,7 +180,10 @@ void NodeTest::dumpBlockchainInfo(INode& node) { TEST_F(NodeTest, generateBlockchain) { - + if (boost::filesystem::exists(TEST_WALLET_FILE)) { + boost::filesystem::remove(TEST_WALLET_FILE); + } + auto networkCfg = TestNetworkBuilder(2, Topology::Ring).build(); networkCfg[0].cleanupDataDir = false; network.addNodes(networkCfg); @@ -195,7 +198,7 @@ TEST_F(NodeTest, generateBlockchain) { std::string password = "pass"; CryptoNote::WalletGreen wallet(dispatcher, currency, *mainNode, logger); - wallet.initialize(password); + wallet.initialize(TEST_WALLET_FILE, password); std::string minerAddress = wallet.createAddress(); daemon.startMining(1, minerAddress); @@ -209,8 +212,7 @@ TEST_F(NodeTest, generateBlockchain) { daemon.stopMining(); - std::ofstream walletFile("wallet.bin", std::ios::binary | std::ios::trunc); - wallet.save(walletFile); + wallet.save(); wallet.shutdown(); dumpBlockchainInfo(*mainNode); @@ -242,11 +244,7 @@ TEST_F(NodeTest, addMoreBlocks) { std::string password = "pass"; CryptoNote::WalletGreen wallet(dispatcher, currency, *mainNode, logger); - - { - std::ifstream walletFile("wallet.bin", std::ios::binary); - wallet.load(walletFile, password); - } + wallet.load(TEST_WALLET_FILE, password); std::string minerAddress = wallet.getAddress(0); daemon.startMining(1, minerAddress); @@ -260,8 +258,7 @@ TEST_F(NodeTest, addMoreBlocks) { daemon.stopMining(); - std::ofstream walletFile("wallet.bin", std::ios::binary | std::ios::trunc); - wallet.save(walletFile); + wallet.save(); wallet.shutdown(); dumpBlockchainInfo(*mainNode); diff --git a/tests/UnitTests/TestFileMappedVector.cpp b/tests/UnitTests/TestFileMappedVector.cpp new file mode 100644 index 0000000000..febf832eee --- /dev/null +++ b/tests/UnitTests/TestFileMappedVector.cpp @@ -0,0 +1,1057 @@ +// Copyright (c) 2012-2016, The CryptoNote developers, The Bytecoin developers +// +// This file is part of Bytecoin. +// +// Bytecoin is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Bytecoin 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with Bytecoin. If not, see . + +#include +#include + +#include + +#include "gtest/gtest.h" + +#include "Common/FileMappedVector.h" +#include "Common/StringTools.h" +#include "crypto/crypto.h" + +using namespace Common; + +namespace Common { + +void PrintTo(const FileMappedVector::const_iterator& it, ::std::ostream* os) { + *os << ""; +} + +void PrintTo(const FileMappedVector::iterator& it, ::std::ostream* os) { + *os << ""; +} + +} + +namespace { + +const std::string TEST_FILE_NAME = "FileMappedVectorTest.dat"; +const std::string TEST_FILE_NAME_2 = "FileMappedVectorTest2.dat"; +const std::string TEST_FILE_NAME_BAK = TEST_FILE_NAME + ".bak"; +const std::string TEST_FILE_PREFIX = "!prefix!"; +const std::string TEST_FILE_SUFFIX = "suffix"; +const std::string TEST_VECTOR_DATA = "bytecoin"; +const uint64_t TEST_VECTOR_SIZE = static_cast(TEST_VECTOR_DATA.size()); +const uint64_t TEST_VECTOR_CAPACITY = TEST_VECTOR_SIZE + 7; + +class FileMappedVectorTest : public ::testing::Test { +public: + +protected: + virtual void SetUp() override { + clean(); + } + + virtual void TearDown() override { + clean(); + } + + void clean() { + if (boost::filesystem::exists(TEST_FILE_NAME)) { + boost::filesystem::remove_all(TEST_FILE_NAME); + } + + if (boost::filesystem::exists(TEST_FILE_NAME_2)) { + boost::filesystem::remove_all(TEST_FILE_NAME_2); + } + + if (boost::filesystem::exists(TEST_FILE_NAME_BAK)) { + boost::filesystem::remove_all(TEST_FILE_NAME_BAK); + } + } + + template + void createTestFile(const std::string& path, uint64_t capacity, Iterator first, Iterator last, const std::string& prefix, const std::string& suffix) { + uint64_t size = static_cast(std::distance(first, last)); + if (capacity < size) { + throw std::invalid_argument("capacity is less than size"); + } + + std::ofstream stream(path, std::ios_base::binary); + stream.exceptions(std::ifstream::failbit | std::ifstream::badbit); + + stream.write(prefix.data(), prefix.size()); + stream.write(reinterpret_cast(&capacity), sizeof(capacity)); + stream.write(reinterpret_cast(&size), sizeof(size)); + std::copy(first, last, std::ostream_iterator(stream)); + + for (size_t i = size; i < capacity; ++i) { + stream.put('w'); + } + + stream.write(suffix.data(), suffix.size()); + } + + void createTestFile(const std::string& path, uint64_t capacity, const std::string& data) { + createTestFile(path, capacity, data.begin(), data.end(), "", ""); + } + + void createTestFile(const std::string& path) { + createTestFile(path, TEST_VECTOR_CAPACITY, TEST_VECTOR_DATA.begin(), TEST_VECTOR_DATA.end(), "", ""); + } + + void createTestFileWithPrefixAndSuffix(const std::string& path) { + createTestFile(path, TEST_VECTOR_CAPACITY, TEST_VECTOR_DATA.begin(), TEST_VECTOR_DATA.end(), TEST_FILE_PREFIX, TEST_FILE_SUFFIX); + } + + static void readVectorFile(const std::string& path, uint64_t prefixSize, uint64_t* capacity = nullptr, uint64_t* size = nullptr, + std::vector* data = nullptr, std::vector* prefix = nullptr, std::vector* suffix = nullptr) { + + std::ifstream stream(path, std::ios_base::binary); + stream.exceptions(std::ifstream::failbit | std::ifstream::badbit); + + stream.seekg(0, std::ios_base::end); + auto fileSize = stream.tellg(); + stream.seekg(0, std::ios_base::beg); + + std::vector tmp; + tmp.resize(prefixSize); + if (!tmp.empty()) { + stream.read(tmp.data(), tmp.size()); + } + + if (prefix != nullptr) { + prefix->swap(tmp); + } + + if (capacity != nullptr) { + stream.read(reinterpret_cast(capacity), sizeof(*capacity)); + + if (size != nullptr) { + stream.read(reinterpret_cast(size), sizeof(*size)); + + if (data != nullptr) { + data->resize(*size); + if (!data->empty()) { + stream.read(data->data(), data->size()); + } + + if (suffix != nullptr) { + tmp.resize(*capacity - *size); + if (!tmp.empty()) { + stream.read(tmp.data(), tmp.size()); + } + + suffix->resize(fileSize - stream.tellg()); + if (!suffix->empty()) { + stream.read(suffix->data(), suffix->size()); + } + } + } + } + } + } + + static void readVectorFile(const std::string& path, uint64_t* capacity = nullptr, uint64_t* size = nullptr, std::vector* data = nullptr) { + readVectorFile(path, 0, capacity, size, data); + } +}; + +TEST_F(FileMappedVectorTest, constructorOpensFileIfModeIsOpenAndFileExists) { + createTestFile(TEST_FILE_NAME); + + ASSERT_TRUE(boost::filesystem::exists(TEST_FILE_NAME)); + ASSERT_FALSE(boost::filesystem::exists(TEST_FILE_NAME_BAK)); + + ASSERT_NO_THROW({ + FileMappedVector vec(TEST_FILE_NAME, FileMappedVectorOpenMode::OPEN); + }); +} + +TEST_F(FileMappedVectorTest, constructorThrowsExceptionIfModeIsOpenAndFileDoesNotExist) { + ASSERT_FALSE(boost::filesystem::exists(TEST_FILE_NAME)); + ASSERT_FALSE(boost::filesystem::exists(TEST_FILE_NAME_BAK)); + + ASSERT_ANY_THROW({ + FileMappedVector vec(TEST_FILE_NAME, FileMappedVectorOpenMode::OPEN); + }); +} + +TEST_F(FileMappedVectorTest, constructorCreatesFileIfModeIsCreateAndFileDoesNotExists) { + ASSERT_FALSE(boost::filesystem::exists(TEST_FILE_NAME)); + ASSERT_FALSE(boost::filesystem::exists(TEST_FILE_NAME_BAK)); + + ASSERT_NO_THROW({ + FileMappedVector vec(TEST_FILE_NAME, FileMappedVectorOpenMode::CREATE); + }); +} + +TEST_F(FileMappedVectorTest, constructorThrowsExceptionIfModeIsCreateAndFileExists) { + createTestFile(TEST_FILE_NAME); + + ASSERT_TRUE(boost::filesystem::exists(TEST_FILE_NAME)); + ASSERT_FALSE(boost::filesystem::exists(TEST_FILE_NAME_BAK)); + + ASSERT_ANY_THROW({ + FileMappedVector vec(TEST_FILE_NAME, FileMappedVectorOpenMode::CREATE); + }); +} + +TEST_F(FileMappedVectorTest, constructorThrowsExceptionIfModeIsCreateAndBakFileExists) { + createTestFile(TEST_FILE_NAME_BAK); + + ASSERT_FALSE(boost::filesystem::exists(TEST_FILE_NAME)); + ASSERT_TRUE(boost::filesystem::exists(TEST_FILE_NAME_BAK)); + + ASSERT_ANY_THROW({ + FileMappedVector vec(TEST_FILE_NAME, FileMappedVectorOpenMode::CREATE); + }); +} + +TEST_F(FileMappedVectorTest, constructorOpensFileIfModeIsOpenOrCreateAndFileExists) { + createTestFile(TEST_FILE_NAME); + + ASSERT_TRUE(boost::filesystem::exists(TEST_FILE_NAME)); + ASSERT_FALSE(boost::filesystem::exists(TEST_FILE_NAME_BAK)); + + ASSERT_NO_THROW({ + FileMappedVector vec(TEST_FILE_NAME, FileMappedVectorOpenMode::OPEN_OR_CREATE); + }); +} + +TEST_F(FileMappedVectorTest, constructorCreatesFileIfModeIsOpenOrCreateAndFileDoesNotExist) { + ASSERT_FALSE(boost::filesystem::exists(TEST_FILE_NAME)); + ASSERT_FALSE(boost::filesystem::exists(TEST_FILE_NAME_BAK)); + + ASSERT_NO_THROW({ + FileMappedVector vec(TEST_FILE_NAME, FileMappedVectorOpenMode::OPEN_OR_CREATE); + }); +} + +TEST_F(FileMappedVectorTest, constructorCreatesEmptyFile) { + ASSERT_FALSE(boost::filesystem::exists(TEST_FILE_NAME)); + ASSERT_FALSE(boost::filesystem::exists(TEST_FILE_NAME_BAK)); + + { + FileMappedVector vec(TEST_FILE_NAME, FileMappedVectorOpenMode::CREATE); + } + + ASSERT_TRUE(boost::filesystem::exists(TEST_FILE_NAME)); + ASSERT_GT(boost::filesystem::file_size(TEST_FILE_NAME), sizeof(uint64_t)); + + uint64_t capacity; + uint64_t size; + readVectorFile(TEST_FILE_NAME, &capacity, &size); + ASSERT_LE(0, capacity); + ASSERT_EQ(0, size); +} + +TEST_F(FileMappedVectorTest, constructorCreatesEmptyFileWithPrefix) { + ASSERT_FALSE(boost::filesystem::exists(TEST_FILE_NAME)); + ASSERT_FALSE(boost::filesystem::exists(TEST_FILE_NAME_BAK)); + + { + FileMappedVector vec(TEST_FILE_NAME, FileMappedVectorOpenMode::CREATE, TEST_FILE_PREFIX.size()); + } + + ASSERT_TRUE(boost::filesystem::exists(TEST_FILE_NAME)); + ASSERT_GT(boost::filesystem::file_size(TEST_FILE_NAME), sizeof(uint64_t)); + + uint64_t capacity; + uint64_t size; + std::vector data; + std::vector prefix; + std::vector suffix; + readVectorFile(TEST_FILE_NAME, TEST_FILE_PREFIX.size(), &capacity, &size, &data, &prefix, &suffix); + ASSERT_LE(0, capacity); + ASSERT_EQ(0, size); + ASSERT_TRUE(data.empty()); + ASSERT_EQ(TEST_FILE_PREFIX.size(), prefix.size()); + ASSERT_TRUE(suffix.empty()); +} + +TEST_F(FileMappedVectorTest, constructorCorrectlyOpensExistentFile) { + createTestFile(TEST_FILE_NAME); + + ASSERT_TRUE(boost::filesystem::exists(TEST_FILE_NAME)); + ASSERT_FALSE(boost::filesystem::exists(TEST_FILE_NAME_BAK)); + + FileMappedVector vec(TEST_FILE_NAME, FileMappedVectorOpenMode::OPEN); + ASSERT_EQ(TEST_VECTOR_SIZE, vec.size()); + ASSERT_EQ(TEST_VECTOR_CAPACITY, vec.capacity()); + ASSERT_EQ(TEST_VECTOR_DATA, std::string(vec.data(), vec.size())); +} + +TEST_F(FileMappedVectorTest, constructorCorrectlyOpensFileWithPrefixAndSuffix) { + createTestFileWithPrefixAndSuffix(TEST_FILE_NAME); + + ASSERT_TRUE(boost::filesystem::exists(TEST_FILE_NAME)); + ASSERT_FALSE(boost::filesystem::exists(TEST_FILE_NAME_BAK)); + + FileMappedVector vec(TEST_FILE_NAME, FileMappedVectorOpenMode::OPEN, TEST_FILE_PREFIX.size()); + ASSERT_EQ(TEST_VECTOR_SIZE, vec.size()); + ASSERT_EQ(TEST_VECTOR_CAPACITY, vec.capacity()); + ASSERT_EQ(TEST_VECTOR_DATA, std::string(vec.data(), vec.size())); + ASSERT_EQ(TEST_FILE_PREFIX, std::string(vec.prefix(), vec.prefix() + vec.prefixSize())); + ASSERT_EQ(TEST_FILE_SUFFIX, std::string(vec.suffix(), vec.suffix() + vec.suffixSize())); +} + +TEST_F(FileMappedVectorTest, constructorOpensFileIfItExistsAndRemovesBakFileIfItExists) { + createTestFile(TEST_FILE_NAME); + createTestFile(TEST_FILE_NAME_BAK, 10, std::string("bak")); + + ASSERT_TRUE(boost::filesystem::exists(TEST_FILE_NAME)); + ASSERT_TRUE(boost::filesystem::exists(TEST_FILE_NAME_BAK)); + + FileMappedVector vec(TEST_FILE_NAME, FileMappedVectorOpenMode::OPEN); + + ASSERT_TRUE(boost::filesystem::exists(TEST_FILE_NAME)); + ASSERT_FALSE(boost::filesystem::exists(TEST_FILE_NAME_BAK)); + + ASSERT_EQ(TEST_VECTOR_SIZE, vec.size()); + ASSERT_EQ(TEST_VECTOR_CAPACITY, vec.capacity()); + ASSERT_EQ(TEST_VECTOR_DATA, std::string(vec.data(), vec.size())); +} + +TEST_F(FileMappedVectorTest, constructorOpensAndRenamesBakFileIfItExists) { + createTestFile(TEST_FILE_NAME_BAK); + + ASSERT_FALSE(boost::filesystem::exists(TEST_FILE_NAME)); + ASSERT_TRUE(boost::filesystem::exists(TEST_FILE_NAME_BAK)); + + FileMappedVector vec(TEST_FILE_NAME, FileMappedVectorOpenMode::OPEN); + + ASSERT_TRUE(boost::filesystem::exists(TEST_FILE_NAME)); + ASSERT_FALSE(boost::filesystem::exists(TEST_FILE_NAME_BAK)); + + ASSERT_EQ(TEST_VECTOR_SIZE, vec.size()); + ASSERT_EQ(TEST_VECTOR_CAPACITY, vec.capacity()); + ASSERT_EQ(TEST_VECTOR_DATA, std::string(vec.data(), vec.size())); +} + +TEST_F(FileMappedVectorTest, constructorThrowsExceptionIfFailedToOpenExistentFile) { + boost::filesystem::create_directory(TEST_FILE_NAME); + + ASSERT_TRUE(boost::filesystem::exists(TEST_FILE_NAME)); + ASSERT_FALSE(boost::filesystem::exists(TEST_FILE_NAME_BAK)); + + try { + FileMappedVector vec(TEST_FILE_NAME, FileMappedVectorOpenMode::OPEN); + ASSERT_TRUE(false); + } catch (...) { + } +} + +TEST_F(FileMappedVectorTest, constructorThrowsExceptionIfFailedToOpenExistentBakFile) { + boost::filesystem::create_directory(TEST_FILE_NAME_BAK); + + ASSERT_FALSE(boost::filesystem::exists(TEST_FILE_NAME)); + ASSERT_TRUE(boost::filesystem::exists(TEST_FILE_NAME_BAK)); + + try { + FileMappedVector vec(TEST_FILE_NAME, FileMappedVectorOpenMode::OPEN); + ASSERT_TRUE(false); + } catch (...) { + } +} + +TEST_F(FileMappedVectorTest, constructorThrowsExceptionIfFailedToRemoveBakFile) { + createTestFile(TEST_FILE_NAME); + boost::filesystem::create_directory(TEST_FILE_NAME_BAK); + boost::filesystem::create_directory(boost::filesystem::path(TEST_FILE_NAME_BAK) / TEST_FILE_NAME); + + ASSERT_TRUE(boost::filesystem::exists(TEST_FILE_NAME)); + ASSERT_TRUE(boost::filesystem::exists(TEST_FILE_NAME_BAK)); + + try { + FileMappedVector vec(TEST_FILE_NAME, FileMappedVectorOpenMode::OPEN); + ASSERT_TRUE(false); + } catch (...) { + } +} + +TEST_F(FileMappedVectorTest, constructorThrowsExceptionIfFileDoesNotContainMetadata) { + std::ofstream stream(TEST_FILE_NAME, std::ios_base::binary); + stream.exceptions(std::ifstream::failbit | std::ifstream::badbit); + std::string content(FileMappedVector::metadataSize - 1, '\0'); + stream.write(content.data(), content.size()); + stream.close(); + + ASSERT_TRUE(boost::filesystem::exists(TEST_FILE_NAME)); + + try { + FileMappedVector vec(TEST_FILE_NAME); + ASSERT_TRUE(false); + } catch (const std::runtime_error&) { + } +} + +TEST_F(FileMappedVectorTest, constructorThrowsExceptionIfFileSizeIsLessThanCapacity) { + std::ofstream stream(TEST_FILE_NAME, std::ios_base::binary); + stream.exceptions(std::ifstream::failbit | std::ifstream::badbit); + uint64_t capacity = 1; + uint64_t size = 0; + stream.write(reinterpret_cast(&capacity), sizeof(capacity)); + stream.write(reinterpret_cast(&size), sizeof(size)); + stream.close(); + + ASSERT_TRUE(boost::filesystem::exists(TEST_FILE_NAME)); + + try { + FileMappedVector vec(TEST_FILE_NAME); + ASSERT_TRUE(false); + } catch (const std::runtime_error&) { + } +} + +TEST_F(FileMappedVectorTest, constructorThrowsExceptionIfFileCapacityIsLessThanVectorSize) { + std::ofstream stream(TEST_FILE_NAME, std::ios_base::binary); + stream.exceptions(std::ifstream::failbit | std::ifstream::badbit); + uint64_t capacity = 0; + uint64_t size = 1; + stream.write(reinterpret_cast(&capacity), sizeof(capacity)); + stream.write(reinterpret_cast(&size), sizeof(size)); + stream.close(); + + ASSERT_TRUE(boost::filesystem::exists(TEST_FILE_NAME)); + + try { + FileMappedVector vec(TEST_FILE_NAME); + ASSERT_TRUE(false); + } catch (const std::runtime_error&) { + } +} + +TEST_F(FileMappedVectorTest, constructorCanOpenFileWithZeroCapacity) { + createTestFile(TEST_FILE_NAME, 0, ""); + + ASSERT_TRUE(boost::filesystem::exists(TEST_FILE_NAME)); + + FileMappedVector vec(TEST_FILE_NAME, FileMappedVectorOpenMode::OPEN); + ASSERT_EQ(0, vec.size()); + ASSERT_EQ(0, vec.capacity()); +} + +TEST_F(FileMappedVectorTest, destructorFlushesAllChangesToDisk) { + { + FileMappedVector vec(TEST_FILE_NAME); + vec.push_back('a'); + vec[0] = 'b'; + } + + uint64_t capacity; + uint64_t size; + std::vector data; + readVectorFile(TEST_FILE_NAME, &capacity, &size, &data); + ASSERT_LE(1, capacity); + ASSERT_EQ(1, size); + ASSERT_EQ('b', data.front()); +} + +TEST_F(FileMappedVectorTest, newVectorIsEmpty) { + FileMappedVector vec(TEST_FILE_NAME); + ASSERT_TRUE(vec.empty()); + ASSERT_EQ(0, vec.size()); +} + +TEST_F(FileMappedVectorTest, reserveIncreasesCapacity) { + FileMappedVector vec(TEST_FILE_NAME); + uint64_t newCapacity = vec.capacity() + 1; + vec.reserve(newCapacity); + ASSERT_EQ(newCapacity, vec.capacity()); +} + +TEST_F(FileMappedVectorTest, reserveDoesNotDecreaseCapacity) { + FileMappedVector vec(TEST_FILE_NAME); + uint64_t initialCapacity = vec.capacity(); + vec.reserve(initialCapacity + 1); + vec.reserve(initialCapacity - 1); + ASSERT_EQ(initialCapacity + 1, vec.capacity()); +} + +TEST_F(FileMappedVectorTest, reservePreservesFilePrefixAndSuffix) { + createTestFileWithPrefixAndSuffix(TEST_FILE_NAME); + uint64_t newCapacity = TEST_VECTOR_CAPACITY + 1; + + { + FileMappedVector vec(TEST_FILE_NAME, FileMappedVectorOpenMode::OPEN, TEST_FILE_PREFIX.size()); + vec.reserve(newCapacity); + } + + uint64_t capacity; + uint64_t size; + std::vector data; + std::vector prefix; + std::vector suffix; + readVectorFile(TEST_FILE_NAME, TEST_FILE_PREFIX.size(), &capacity, &size, &data, &prefix, &suffix); + ASSERT_EQ(newCapacity, capacity); + ASSERT_EQ(TEST_VECTOR_SIZE, size); + ASSERT_EQ(TEST_VECTOR_DATA, asString(data.data(), data.size())); + ASSERT_EQ(TEST_FILE_PREFIX, asString(prefix.data(), prefix.size())); + ASSERT_EQ(TEST_FILE_SUFFIX, asString(suffix.data(), suffix.size())); +} + +TEST_F(FileMappedVectorTest, shrinkToFitSetCapacityToSize) { + FileMappedVector vec(TEST_FILE_NAME); + while (vec.size() == vec.capacity()) { + vec.push_back('w'); + } + + ASSERT_LT(vec.size(), vec.capacity()); + vec.shrink_to_fit(); + ASSERT_EQ(vec.size(), vec.capacity()); +} + +TEST_F(FileMappedVectorTest, beginReturnsIteratorPointsToFirstElement) { + FileMappedVector vec(TEST_FILE_NAME); + vec.push_back('a'); + vec.push_back('b'); + ASSERT_EQ('a', *vec.begin()); +} + +TEST_F(FileMappedVectorTest, beginReturnsNonConstIterator) { + FileMappedVector vec(TEST_FILE_NAME); + vec.push_back('a'); + auto it = vec.begin(); + *it = 'b'; + ASSERT_EQ('b', vec[0]); +} + +TEST_F(FileMappedVectorTest, beginAndEndAreEqualForEmptyVector) { + FileMappedVector vec(TEST_FILE_NAME); + ASSERT_EQ(vec.begin(), vec.end()); +} + +TEST_F(FileMappedVectorTest, endReturnsIteratorPointsAfterLastElement) { + FileMappedVector vec(TEST_FILE_NAME); + vec.push_back('w'); + auto it = vec.begin(); + ++it; + ASSERT_EQ(it, vec.end()); +} + +TEST_F(FileMappedVectorTest, squareBracketsOperatorReturnsCorrectElement) { + FileMappedVector vec(TEST_FILE_NAME); + vec.push_back('a'); + vec.push_back('b'); + vec.push_back('c'); + + ASSERT_EQ('a', vec[0]); + ASSERT_EQ('b', vec[1]); + ASSERT_EQ('c', vec[2]); +} + +TEST_F(FileMappedVectorTest, squareBracketsOperatorReturnsNonConstReference) { + FileMappedVector vec(TEST_FILE_NAME); + vec.push_back('a'); + vec[0] = 'b'; + ASSERT_EQ('b', vec[0]); +} + +TEST_F(FileMappedVectorTest, atThrowsOutOfRangeException) { + FileMappedVector vec(TEST_FILE_NAME); + + ASSERT_ANY_THROW(vec.at(0)); + + vec.push_back('a'); + vec[0] = 'b'; + ASSERT_THROW(vec.at(1), std::out_of_range); +} + +TEST_F(FileMappedVectorTest, atReturnsNonConstReference) { + FileMappedVector vec(TEST_FILE_NAME); + vec.push_back('a'); + vec.at(0) = 'b'; + ASSERT_EQ('b', vec.at(0)); +} + +TEST_F(FileMappedVectorTest, frontReturnsFirstElement) { + FileMappedVector vec(TEST_FILE_NAME); + vec.push_back('a'); + vec.push_back('b'); + ASSERT_EQ('a', vec.front()); +} + +TEST_F(FileMappedVectorTest, frontReturnsNonConstIterator) { + FileMappedVector vec(TEST_FILE_NAME); + vec.push_back('a'); + vec.front() = 'w'; + ASSERT_EQ('w', vec[0]); +} + +TEST_F(FileMappedVectorTest, backReturnsLastElement) { + FileMappedVector vec(TEST_FILE_NAME); + vec.push_back('a'); + vec.push_back('b'); + ASSERT_EQ('b', vec.back()); +} + +TEST_F(FileMappedVectorTest, backReturnsNonConstIterator) { + FileMappedVector vec(TEST_FILE_NAME); + vec.push_back('a'); + vec.push_back('b'); + vec.back() = 'w'; + ASSERT_EQ('w', vec[1]); +} + +TEST_F(FileMappedVectorTest, dataReturnsPointerToVectorData) { + FileMappedVector vec(TEST_FILE_NAME); + vec.push_back('a'); + vec.push_back('b'); + auto* data = vec.data(); + ASSERT_EQ('a', data[0]); + ASSERT_EQ('b', data[1]); +} + +TEST_F(FileMappedVectorTest, vectorDataCanBeChangedViaPointer) { + FileMappedVector vec(TEST_FILE_NAME); + vec.push_back('a'); + auto* data = vec.data(); + data[0] = 'b'; + ASSERT_EQ('b', vec[0]); +} + +TEST_F(FileMappedVectorTest, clearRemovesAllElements) { + createTestFile(TEST_FILE_NAME); + FileMappedVector vec(TEST_FILE_NAME); + + ASSERT_FALSE(vec.empty()); + vec.clear(); + ASSERT_TRUE(vec.empty()); +} + +TEST_F(FileMappedVectorTest, eraseCanRemoveTheFirstElement) { + createTestFile(TEST_FILE_NAME); + FileMappedVector vec(TEST_FILE_NAME); + + vec.erase(vec.begin()); + ASSERT_EQ(TEST_VECTOR_SIZE - 1, vec.size()); + ASSERT_TRUE(std::equal(vec.begin(), vec.end(), TEST_VECTOR_DATA.begin() + 1)); +} + +TEST_F(FileMappedVectorTest, eraseCanRemoveTheLastElement) { + createTestFile(TEST_FILE_NAME); + FileMappedVector vec(TEST_FILE_NAME); + + vec.erase(vec.end() - 1); + ASSERT_EQ(TEST_VECTOR_SIZE - 1, vec.size()); + ASSERT_TRUE(std::equal(vec.begin(), vec.end(), TEST_VECTOR_DATA.begin())); +} + +TEST_F(FileMappedVectorTest, eraseCanRemoveOneMiddleElement) { + createTestFile(TEST_FILE_NAME); + FileMappedVector vec(TEST_FILE_NAME); + + vec.erase(vec.begin() + 1); + ASSERT_EQ(TEST_VECTOR_SIZE - 1, vec.size()); + ASSERT_EQ(TEST_VECTOR_DATA[0], vec[0]); + ASSERT_TRUE(std::equal(vec.begin() + 1, vec.end(), TEST_VECTOR_DATA.begin() + 2)); +} + +TEST_F(FileMappedVectorTest, eraseCanRemoveAllMiddleElements) { + createTestFile(TEST_FILE_NAME); + FileMappedVector vec(TEST_FILE_NAME); + + vec.erase(vec.begin() + 1, vec.end() - 1); + ASSERT_EQ(2, vec.size()); + ASSERT_EQ(TEST_VECTOR_DATA.front(), vec.front()); + ASSERT_EQ(TEST_VECTOR_DATA.back(), vec.back()); +} + +TEST_F(FileMappedVectorTest, eraseCanRemoveAllElements) { + createTestFile(TEST_FILE_NAME); + FileMappedVector vec(TEST_FILE_NAME); + + vec.erase(vec.begin(), vec.end()); + ASSERT_TRUE(vec.empty()); +} + +TEST_F(FileMappedVectorTest, eraseReturnsIteratorPointsToFirstElementAfterErased) { + createTestFile(TEST_FILE_NAME); + FileMappedVector vec(TEST_FILE_NAME); + + auto it = vec.erase(vec.begin() + 1); + ASSERT_EQ(vec.cbegin() + 1, it); +} + +TEST_F(FileMappedVectorTest, erasePreservesFilePrefixAndSuffix) { + createTestFileWithPrefixAndSuffix(TEST_FILE_NAME); + + { + FileMappedVector vec(TEST_FILE_NAME, FileMappedVectorOpenMode::OPEN, TEST_FILE_PREFIX.size()); + vec.erase(vec.begin() + 1, vec.end()); + } + + uint64_t capacity; + uint64_t size; + std::vector data; + std::vector prefix; + std::vector suffix; + readVectorFile(TEST_FILE_NAME, TEST_FILE_PREFIX.size(), &capacity, &size, &data, &prefix, &suffix); + ASSERT_EQ(TEST_VECTOR_CAPACITY, capacity); + ASSERT_EQ(1, size); + ASSERT_EQ(1, data.size()); + ASSERT_EQ(TEST_VECTOR_DATA.front(), data.front()); + ASSERT_EQ(TEST_FILE_PREFIX, asString(prefix.data(), prefix.size())); + ASSERT_EQ(TEST_FILE_SUFFIX, asString(suffix.data(), suffix.size())); +} + +TEST_F(FileMappedVectorTest, insertCanAddElementsToEmptyVector) { + FileMappedVector vec(TEST_FILE_NAME); + + char c = 'w'; + vec.insert(vec.begin(), c); + ASSERT_EQ(1, vec.size()); + ASSERT_EQ(c, vec.front()); +} + +TEST_F(FileMappedVectorTest, insertCanAddElementToFront) { + createTestFile(TEST_FILE_NAME); + FileMappedVector vec(TEST_FILE_NAME); + + char c = 'w'; + vec.insert(vec.begin(), c); + ASSERT_EQ(TEST_VECTOR_SIZE + 1, vec.size()); + ASSERT_EQ(c, vec.front()); + ASSERT_TRUE(std::equal(vec.begin() + 1, vec.end(), TEST_VECTOR_DATA.begin())); +} + +TEST_F(FileMappedVectorTest, insertCanAddElementToBack) { + createTestFile(TEST_FILE_NAME); + FileMappedVector vec(TEST_FILE_NAME); + + char c = 'w'; + vec.insert(vec.end(), c); + ASSERT_EQ(TEST_VECTOR_SIZE + 1, vec.size()); + ASSERT_EQ(c, vec.back()); + ASSERT_TRUE(std::equal(vec.begin(), vec.end() - 1, TEST_VECTOR_DATA.begin())); +} + +TEST_F(FileMappedVectorTest, insertCanAddOneElementToMiddle) { + createTestFile(TEST_FILE_NAME); + FileMappedVector vec(TEST_FILE_NAME); + + char c = 'w'; + vec.insert(vec.begin() + 1, c); + ASSERT_EQ(TEST_VECTOR_SIZE + 1, vec.size()); + ASSERT_EQ(TEST_VECTOR_DATA[0], vec[0]); + ASSERT_EQ(c, vec[1]); + ASSERT_TRUE(std::equal(vec.begin() + 2, vec.end(), TEST_VECTOR_DATA.begin() + 1)); +} + +TEST_F(FileMappedVectorTest, insertCanAddSeveralElementsToMiddle) { + createTestFile(TEST_FILE_NAME); + FileMappedVector vec(TEST_FILE_NAME); + + std::string str = "www"; + vec.insert(vec.begin() + 1, str.begin(), str.end()); + ASSERT_EQ(TEST_VECTOR_SIZE + str.size(), vec.size()); + ASSERT_EQ(TEST_VECTOR_DATA[0], vec[0]); + ASSERT_TRUE(std::equal(vec.begin() + 1, vec.begin() + 1 + str.size(), str.begin())); + ASSERT_TRUE(std::equal(vec.begin() + 1 + str.size(), vec.end(), TEST_VECTOR_DATA.begin() + 1)); +} + +TEST_F(FileMappedVectorTest, insertCanIncreaseCapacity) { + FileMappedVector vec(TEST_FILE_NAME); + + char c = 'w'; + uint64_t initialCapacity = vec.capacity(); + uint64_t insertCount = initialCapacity - vec.size() + 1; + for (uint64_t i = 0; i < insertCount; ++i) { + vec.insert(vec.begin(), c); + } + + ASSERT_LT(initialCapacity, vec.capacity()); +} + +TEST_F(FileMappedVectorTest, insertReturnsIteratorPointsToFirstInsertedElement) { + createTestFile(TEST_FILE_NAME); + FileMappedVector vec(TEST_FILE_NAME); + + std::string str = "abc"; + auto it = vec.insert(vec.begin() + 1, str.begin(), str.end()); + ASSERT_EQ(vec.cbegin() + 1, it); + ASSERT_EQ('a', *it); +} + +TEST_F(FileMappedVectorTest, insertPreservesFilePrefixAndSuffix) { + createTestFileWithPrefixAndSuffix(TEST_FILE_NAME); + + uint64_t insertDataSize = TEST_VECTOR_CAPACITY - TEST_VECTOR_SIZE + 1; + std::string insertData(insertDataSize, 'w'); + uint64_t newVectorSize = TEST_VECTOR_SIZE + insertDataSize; + + { + FileMappedVector vec(TEST_FILE_NAME, FileMappedVectorOpenMode::OPEN, TEST_FILE_PREFIX.size()); + vec.insert(vec.begin(), insertData.begin(), insertData.end()); + } + + uint64_t capacity; + uint64_t size; + std::vector data; + std::vector prefix; + std::vector suffix; + readVectorFile(TEST_FILE_NAME, TEST_FILE_PREFIX.size(), &capacity, &size, &data, &prefix, &suffix); + ASSERT_LT(TEST_VECTOR_CAPACITY, capacity); + ASSERT_EQ(newVectorSize, size); + ASSERT_EQ(insertData + TEST_VECTOR_DATA, asString(data.data(), data.size())); + ASSERT_EQ(TEST_FILE_PREFIX, asString(prefix.data(), prefix.size())); + ASSERT_EQ(TEST_FILE_SUFFIX, asString(suffix.data(), suffix.size())); +} + +TEST_F(FileMappedVectorTest, pushBackCanAppendElementToEmptyVector) { + FileMappedVector vec(TEST_FILE_NAME); + + char c = 'w'; + vec.push_back(c); + ASSERT_EQ(1, vec.size()); + ASSERT_EQ(c, vec.front()); +} + +TEST_F(FileMappedVectorTest, pushBackCanAppendElementToNonEmptyVector) { + FileMappedVector vec(TEST_FILE_NAME); + + char c1 = 'w'; + char c2 = 'q'; + vec.push_back(c1); + vec.push_back(c2); + ASSERT_EQ(2, vec.size()); + ASSERT_EQ(c1, vec.front()); + ASSERT_EQ(c2, vec.back()); +} + +TEST_F(FileMappedVectorTest, pushBackCanIncreaseCapacity) { + FileMappedVector vec(TEST_FILE_NAME); + + char c = 'w'; + uint64_t initialCapacity = vec.capacity(); + uint64_t insertCount = initialCapacity - vec.size() + 1; + for (uint64_t i = 0; i < insertCount; ++i) { + vec.push_back(c); + } + + ASSERT_LT(initialCapacity, vec.capacity()); +} + +TEST_F(FileMappedVectorTest, pushBackFlushesDataToDiskImmediately) { + char c = Crypto::rand(); + + FileMappedVector vec(TEST_FILE_NAME); + vec.push_back(c); + + uint64_t capacity; + uint64_t size; + std::vector data; + readVectorFile(TEST_FILE_NAME, &capacity, &size, &data); + ASSERT_LE(1, capacity); + ASSERT_EQ(1, size); + ASSERT_EQ(c, data.front()); +} + +TEST_F(FileMappedVectorTest, popBackRemovesLastElement) { + createTestFile(TEST_FILE_NAME); + FileMappedVector vec(TEST_FILE_NAME); + + vec.pop_back(); + ASSERT_EQ(TEST_VECTOR_DATA.size() - 1, vec.size()); + ASSERT_TRUE(std::equal(vec.begin(), vec.end(), TEST_VECTOR_DATA.begin())); +} + +TEST_F(FileMappedVectorTest, popBackRemovesTheOnlyElement) { + FileMappedVector vec(TEST_FILE_NAME); + + vec.push_back('w'); + vec.pop_back(); + ASSERT_TRUE(vec.empty()); +} + +TEST_F(FileMappedVectorTest, swapWorksCorrectly) { + FileMappedVector vec1(TEST_FILE_NAME); + FileMappedVector vec2(TEST_FILE_NAME_2); + + vec1.push_back('a'); + vec1.push_back('b'); + + vec2.push_back('c'); + + vec1.swap(vec2); + + ASSERT_EQ(1, vec1.size()); + ASSERT_EQ('c', vec1[0]); + + ASSERT_EQ(2, vec2.size()); + ASSERT_EQ('a', vec2[0]); + ASSERT_EQ('b', vec2[1]); +} + +TEST_F(FileMappedVectorTest, resizePrefixCorrectlyShrinksPrefix) { + createTestFileWithPrefixAndSuffix(TEST_FILE_NAME); + + ASSERT_TRUE(boost::filesystem::exists(TEST_FILE_NAME)); + ASSERT_FALSE(boost::filesystem::exists(TEST_FILE_NAME_BAK)); + + FileMappedVector vec(TEST_FILE_NAME, FileMappedVectorOpenMode::OPEN, TEST_FILE_PREFIX.size()); + vec.resizePrefix(TEST_FILE_PREFIX.size() - 1); + + ASSERT_EQ(TEST_FILE_PREFIX.size() - 1, vec.prefixSize()); + ASSERT_EQ(TEST_FILE_PREFIX.substr(0, TEST_FILE_PREFIX.size() - 1), std::string(vec.prefix(), vec.prefix() + vec.prefixSize())); + + ASSERT_EQ(TEST_VECTOR_SIZE, vec.size()); + ASSERT_EQ(TEST_VECTOR_CAPACITY, vec.capacity()); + ASSERT_EQ(TEST_VECTOR_DATA, std::string(vec.data(), vec.size())); + ASSERT_EQ(TEST_FILE_SUFFIX, std::string(vec.suffix(), vec.suffix() + vec.suffixSize())); +} + +TEST_F(FileMappedVectorTest, resizePrefixCorrectlyExpandsPrefix) { + createTestFileWithPrefixAndSuffix(TEST_FILE_NAME); + + ASSERT_TRUE(boost::filesystem::exists(TEST_FILE_NAME)); + ASSERT_FALSE(boost::filesystem::exists(TEST_FILE_NAME_BAK)); + + FileMappedVector vec(TEST_FILE_NAME, FileMappedVectorOpenMode::OPEN, TEST_FILE_PREFIX.size()); + vec.resizePrefix(TEST_FILE_PREFIX.size() + 1); + + ASSERT_EQ(TEST_FILE_PREFIX.size() + 1, vec.prefixSize()); + ASSERT_EQ(TEST_FILE_PREFIX, std::string(vec.prefix(), vec.prefix() + vec.prefixSize() - 1)); + + ASSERT_EQ(TEST_VECTOR_SIZE, vec.size()); + ASSERT_EQ(TEST_VECTOR_CAPACITY, vec.capacity()); + ASSERT_EQ(TEST_VECTOR_DATA, std::string(vec.data(), vec.size())); + ASSERT_EQ(TEST_FILE_SUFFIX, std::string(vec.suffix(), vec.suffix() + vec.suffixSize())); +} + +TEST_F(FileMappedVectorTest, resizePrefixCanRemovePrefix) { + createTestFileWithPrefixAndSuffix(TEST_FILE_NAME); + + ASSERT_TRUE(boost::filesystem::exists(TEST_FILE_NAME)); + ASSERT_FALSE(boost::filesystem::exists(TEST_FILE_NAME_BAK)); + + FileMappedVector vec(TEST_FILE_NAME, FileMappedVectorOpenMode::OPEN, TEST_FILE_PREFIX.size()); + ASSERT_LT(0, vec.prefixSize()); + vec.resizePrefix(0); + ASSERT_EQ(0, vec.prefixSize()); + + ASSERT_EQ(TEST_VECTOR_SIZE, vec.size()); + ASSERT_EQ(TEST_VECTOR_CAPACITY, vec.capacity()); + ASSERT_EQ(TEST_VECTOR_DATA, std::string(vec.data(), vec.size())); + ASSERT_EQ(TEST_FILE_SUFFIX, std::string(vec.suffix(), vec.suffix() + vec.suffixSize())); +} + +TEST_F(FileMappedVectorTest, resizePrefixCanAddPrefixIfItDidNotExist) { + FileMappedVector vec(TEST_FILE_NAME, FileMappedVectorOpenMode::CREATE, 0); + ASSERT_EQ(0, vec.prefixSize()); + + vec.resizePrefix(TEST_FILE_PREFIX.size()); + std::copy(TEST_FILE_PREFIX.begin(), TEST_FILE_PREFIX.end(), vec.prefix()); + + ASSERT_EQ(TEST_FILE_PREFIX.size(), vec.prefixSize()); + ASSERT_EQ(TEST_FILE_PREFIX, std::string(vec.prefix(), vec.prefix() + vec.prefixSize())); + + ASSERT_EQ(0, vec.size()); + ASSERT_LE(0, vec.capacity()); +} + +TEST_F(FileMappedVectorTest, resizeSuffixCorrectlyShrinksSuffix) { + createTestFileWithPrefixAndSuffix(TEST_FILE_NAME); + + ASSERT_TRUE(boost::filesystem::exists(TEST_FILE_NAME)); + ASSERT_FALSE(boost::filesystem::exists(TEST_FILE_NAME_BAK)); + + FileMappedVector vec(TEST_FILE_NAME, FileMappedVectorOpenMode::OPEN, TEST_FILE_PREFIX.size()); + vec.resizeSuffix(TEST_FILE_SUFFIX.size() - 1); + + ASSERT_EQ(TEST_FILE_SUFFIX.size() - 1, vec.suffixSize()); + ASSERT_EQ(TEST_FILE_SUFFIX.substr(0, TEST_FILE_SUFFIX.size() - 1), std::string(vec.suffix(), vec.suffix() + vec.suffixSize())); + + ASSERT_EQ(TEST_VECTOR_SIZE, vec.size()); + ASSERT_EQ(TEST_VECTOR_CAPACITY, vec.capacity()); + ASSERT_EQ(TEST_VECTOR_DATA, std::string(vec.data(), vec.size())); + ASSERT_EQ(TEST_FILE_PREFIX, std::string(vec.prefix(), vec.prefix() + vec.prefixSize())); +} + +TEST_F(FileMappedVectorTest, resizeSuffixCorrectlyExpandsSuffix) { + createTestFileWithPrefixAndSuffix(TEST_FILE_NAME); + + ASSERT_TRUE(boost::filesystem::exists(TEST_FILE_NAME)); + ASSERT_FALSE(boost::filesystem::exists(TEST_FILE_NAME_BAK)); + + FileMappedVector vec(TEST_FILE_NAME, FileMappedVectorOpenMode::OPEN, TEST_FILE_PREFIX.size()); + vec.resizeSuffix(TEST_FILE_SUFFIX.size() + 1); + + ASSERT_EQ(TEST_FILE_SUFFIX.size() + 1, vec.suffixSize()); + ASSERT_EQ(TEST_FILE_SUFFIX, std::string(vec.suffix(), vec.suffix() + vec.suffixSize() - 1)); + + ASSERT_EQ(TEST_VECTOR_SIZE, vec.size()); + ASSERT_EQ(TEST_VECTOR_CAPACITY, vec.capacity()); + ASSERT_EQ(TEST_VECTOR_DATA, std::string(vec.data(), vec.size())); + ASSERT_EQ(TEST_FILE_PREFIX, std::string(vec.prefix(), vec.prefix() + vec.prefixSize())); +} + +TEST_F(FileMappedVectorTest, resizeSuffixCanRemoveSuffix) { + createTestFileWithPrefixAndSuffix(TEST_FILE_NAME); + + ASSERT_TRUE(boost::filesystem::exists(TEST_FILE_NAME)); + ASSERT_FALSE(boost::filesystem::exists(TEST_FILE_NAME_BAK)); + + FileMappedVector vec(TEST_FILE_NAME, FileMappedVectorOpenMode::OPEN, TEST_FILE_PREFIX.size()); + ASSERT_LT(0, vec.suffixSize()); + vec.resizeSuffix(0); + ASSERT_EQ(0, vec.suffixSize()); + + ASSERT_EQ(TEST_VECTOR_SIZE, vec.size()); + ASSERT_EQ(TEST_VECTOR_CAPACITY, vec.capacity()); + ASSERT_EQ(TEST_VECTOR_DATA, std::string(vec.data(), vec.size())); + ASSERT_EQ(TEST_FILE_PREFIX, std::string(vec.prefix(), vec.prefix() + vec.prefixSize())); +} + +TEST_F(FileMappedVectorTest, resizeSuffixCanAddSuffixIfItDidNotExist) { + FileMappedVector vec(TEST_FILE_NAME, FileMappedVectorOpenMode::CREATE, 0); + ASSERT_EQ(0, vec.suffixSize()); + + vec.resizeSuffix(TEST_FILE_SUFFIX.size()); + std::copy(TEST_FILE_SUFFIX.begin(), TEST_FILE_SUFFIX.end(), vec.suffix()); + + ASSERT_EQ(TEST_FILE_SUFFIX.size(), vec.suffixSize()); + ASSERT_EQ(TEST_FILE_SUFFIX, std::string(vec.suffix(), vec.suffix() + vec.suffixSize())); + + ASSERT_EQ(0, vec.size()); + ASSERT_LE(0, vec.capacity()); +} + +TEST_F(FileMappedVectorTest, atomicUpdateThrowsExceptionIfFailedToRemoveExistentBakFile) { + FileMappedVector vec(TEST_FILE_NAME); + vec.push_back('a'); + + boost::filesystem::create_directory(TEST_FILE_NAME_BAK); + boost::filesystem::create_directory(boost::filesystem::path(TEST_FILE_NAME_BAK) / TEST_FILE_NAME); + ASSERT_ANY_THROW(vec.insert(vec.begin(), 'b')); +} + +TEST_F(FileMappedVectorTest, atomicUpdateFailureDoesNotBrokeVector) { + FileMappedVector vec(TEST_FILE_NAME); + vec.push_back('a'); + vec.push_back('b'); + vec.push_back('c'); + + try { + boost::filesystem::create_directory(TEST_FILE_NAME_BAK); + boost::filesystem::create_directory(boost::filesystem::path(TEST_FILE_NAME_BAK) / TEST_FILE_NAME); + vec.insert(vec.begin(), 'w'); + ASSERT_FALSE(true); + } catch (...) { + } + + ASSERT_EQ(3, vec.size()); + ASSERT_EQ('a', vec[0]); + ASSERT_EQ('b', vec[1]); + ASSERT_EQ('c', vec[2]); +} + +} diff --git a/tests/UnitTests/TestUpgradeDetector.cpp b/tests/UnitTests/TestUpgradeDetector.cpp index 89d8b92e55..fb6ceb73ad 100644 --- a/tests/UnitTests/TestUpgradeDetector.cpp +++ b/tests/UnitTests/TestUpgradeDetector.cpp @@ -40,13 +40,13 @@ namespace { class UpgradeTest : public ::testing::Test { public: - CryptoNote::Currency createCurrency(uint64_t upgradeHeight = UpgradeDetector::UNDEF_HEIGHT) { + CryptoNote::Currency createCurrency(uint32_t upgradeHeight = UpgradeDetector::UNDEF_HEIGHT) { CryptoNote::CurrencyBuilder currencyBuilder(logger); currencyBuilder.upgradeVotingThreshold(90); currencyBuilder.upgradeVotingWindow(720); currencyBuilder.upgradeWindow(720); currencyBuilder.upgradeHeightV2(upgradeHeight); - currencyBuilder.upgradeHeightV3(UpgradeDetector::UNDEF_HEIGHT); + currencyBuilder.upgradeHeightV3(CryptoNote::UpgradeDetectorBase::UNDEF_HEIGHT); return currencyBuilder.currency(); } @@ -160,22 +160,22 @@ namespace { BlockVector blocks; createBlocks(blocks, currency.upgradeVotingWindow(), BLOCK_MAJOR_VERSION_1, BLOCK_MINOR_VERSION_1); - uint64_t votingCompleteHeigntV2 = blocks.size() - 1; - uint64_t upgradeHeightV2 = currency.calculateUpgradeHeight(votingCompleteHeigntV2); + uint32_t votingCompleteHeigntV2 = blocks.size() - 1; + uint32_t upgradeHeightV2 = currency.calculateUpgradeHeight(votingCompleteHeigntV2); createBlocks(blocks, upgradeHeightV2 - blocks.size(), BLOCK_MAJOR_VERSION_1, BLOCK_MINOR_VERSION_0); // Upgrade to v2 is here createBlocks(blocks, 1, BLOCK_MAJOR_VERSION_2, BLOCK_MINOR_VERSION_0); - createBlocks(blocks, currency.upgradeVotingWindow(), BLOCK_MAJOR_VERSION_2, BLOCK_MINOR_VERSION_1); - uint64_t votingCompleteHeigntV3 = blocks.size() - 1; - uint64_t upgradeHeightV3 = currency.calculateUpgradeHeight(votingCompleteHeigntV3); + createBlocks(blocks, currency.upgradeVotingWindow() * currency.upgradeVotingThreshold() / 100, BLOCK_MAJOR_VERSION_2, BLOCK_MINOR_VERSION_1); + uint32_t votingCompleteHeigntV3 = blocks.size() - 1; + uint32_t upgradeHeightV3 = currency.calculateUpgradeHeight(votingCompleteHeigntV3); createBlocks(blocks, upgradeHeightV3 - blocks.size(), BLOCK_MAJOR_VERSION_2, BLOCK_MINOR_VERSION_0); // Upgrade to v3 is here createBlocks(blocks, 1, BLOCK_V3, BLOCK_MINOR_VERSION_0); - createBlocks(blocks, currency.upgradeVotingWindow(), BLOCK_V3, BLOCK_MINOR_VERSION_1); - uint64_t votingCompleteHeigntV4 = blocks.size() - 1; - uint64_t upgradeHeightV4 = currency.calculateUpgradeHeight(votingCompleteHeigntV4); + createBlocks(blocks, currency.upgradeVotingWindow() * currency.upgradeVotingThreshold() / 100, BLOCK_V3, BLOCK_MINOR_VERSION_1); + uint32_t votingCompleteHeigntV4 = blocks.size() - 1; + uint32_t upgradeHeightV4 = currency.calculateUpgradeHeight(votingCompleteHeigntV4); createBlocks(blocks, upgradeHeightV4 - blocks.size(), BLOCK_V3, BLOCK_MINOR_VERSION_0); // Upgrade to v4 is here createBlocks(blocks, 1, BLOCK_V4, BLOCK_MINOR_VERSION_0); diff --git a/tests/UnitTests/TestWallet.cpp b/tests/UnitTests/TestWallet.cpp index b3c699bd99..a45a13a5d7 100755 --- a/tests/UnitTests/TestWallet.cpp +++ b/tests/UnitTests/TestWallet.cpp @@ -17,9 +17,10 @@ #include "gtest/gtest.h" -#include #include +#include #include +#include #include #include "Common/StringTools.h" @@ -32,6 +33,7 @@ #include #include "Wallet/WalletErrors.h" #include "Wallet/WalletGreen.h" +#include "Wallet/WalletSerializationV2.h" #include "Wallet/WalletUtils.h" #include "WalletLegacy/WalletUserTransactionsCache.h" #include "WalletLegacy/WalletLegacySerializer.h" @@ -144,12 +146,17 @@ class WalletApi: public ::testing::Test { alice(dispatcher, currency, node, logger), FEE(currency.minimumFee()), FUSION_THRESHOLD(currency.defaultDustThreshold() * 10) - { } + { + CryptoNote::AccountBase randomAccount; + randomAccount.generate(); + RANDOM_ADDRESS = currency.accountAddressAsString(randomAccount); + } virtual void SetUp() override; virtual void TearDown() override; protected: + void cleanUpWalletFiles() const; CryptoNote::AccountPublicAddress parseAddress(const std::string& address); void generateBlockReward(); void generateBlockReward(const std::string& address); @@ -189,7 +196,10 @@ class WalletApi: public ::testing::Test { size_t sendMoneyToRandomAddressFrom(const std::string& address, uint64_t amount, uint64_t fee, const std::string& changeDestination); size_t sendMoneyToRandomAddressFrom(const std::string& address, const std::string& changeDestination); - size_t sendMoney(CryptoNote::WalletGreen& wallet, const std::string& to, uint64_t amount, uint64_t fee, uint64_t mixIn = 0, const std::string& extra = "", uint64_t unlockTimestamp = 0); + static size_t sendMoney(CryptoNote::WalletGreen& wallet, const std::vector& sourceAdresses, const std::string& to, + uint64_t amount, uint64_t fee, uint64_t mixIn = 0, const std::string& extra = "", uint64_t unlockTimestamp = 0); + static size_t sendMoney(CryptoNote::WalletGreen& wallet, const std::string& to, uint64_t amount, uint64_t fee, + uint64_t mixIn = 0, const std::string& extra = "", uint64_t unlockTimestamp = 0); size_t sendMoney(const std::string& to, uint64_t amount, uint64_t fee, uint64_t mixIn = 0, const std::string& extra = "", uint64_t unlockTimestamp = 0); size_t sendMoneyWithDonation(const std::string& to, uint64_t amount, uint64_t fee, const std::string& donationAddress, uint64_t donationAmount, uint64_t mixIn = 0, const std::string& extra = "", uint64_t unlockTimestamp = 0); @@ -219,15 +229,41 @@ class WalletApi: public ::testing::Test { const uint64_t SENT = 1122334455; const uint64_t FEE; - const std::string RANDOM_ADDRESS = "2634US2FAz86jZT73YmM8u5GPCknT2Wxj8bUCKivYKpThFhF2xsjygMGxbxZzM42zXhKUhym6Yy6qHHgkuWtruqiGkDpX6m"; + std::string RANDOM_ADDRESS; const uint64_t FUSION_THRESHOLD; + const std::string ALICE_WALLET_PATH = "alice.wallet"; + const std::string BOB_WALLET_PATH = "bob.wallet"; + const std::string BOB_WALLET_BACKUP_PATH = BOB_WALLET_PATH + ".backup"; }; void WalletApi::SetUp() { - alice.initialize("pass"); + cleanUpWalletFiles(); + + alice.initialize(ALICE_WALLET_PATH, "pass"); aliceAddress = alice.createAddress(); } +void WalletApi::TearDown() { + alice.shutdown(); + wait(100); //ObserverManager bug workaround + + cleanUpWalletFiles(); +} + +void WalletApi::cleanUpWalletFiles() const { + if (boost::filesystem::exists(ALICE_WALLET_PATH)) { + boost::filesystem::remove(ALICE_WALLET_PATH); + } + + if (boost::filesystem::exists(BOB_WALLET_PATH)) { + boost::filesystem::remove(BOB_WALLET_PATH); + } + + if (boost::filesystem::exists(BOB_WALLET_BACKUP_PATH)) { + boost::filesystem::remove(BOB_WALLET_BACKUP_PATH); + } +} + void WalletApi::setMinerTo(CryptoNote::WalletGreen& wallet) { AccountBase base; AccountKeys keys; @@ -238,15 +274,9 @@ void WalletApi::setMinerTo(CryptoNote::WalletGreen& wallet) { keys.viewSecretKey = viewKey.secretKey; keys.spendSecretKey = spendKey.secretKey; base.setAccountKeys(keys); - // mine to alice's address to make it recieve block base transaction generator.setMinerAccount(base); } -void WalletApi::TearDown() { - alice.shutdown(); - wait(100); //ObserverManager bug workaround -} - CryptoNote::AccountPublicAddress WalletApi::parseAddress(const std::string& address) { CryptoNote::AccountPublicAddress pubAddr; if (!currency.parseAccountAddressString(address, pubAddr)) { @@ -480,13 +510,16 @@ void WalletApi::fillWalletWithDetailsCache() { } } -size_t WalletApi::sendMoney(CryptoNote::WalletGreen& wallet, const std::string& to, uint64_t amount, uint64_t fee, uint64_t mixIn, const std::string& extra, uint64_t unlockTimestamp) { +size_t WalletApi::sendMoney(CryptoNote::WalletGreen& wallet, const std::vector& sourceAdresses, const std::string& to, + uint64_t amount, uint64_t fee, uint64_t mixIn, const std::string& extra, uint64_t unlockTimestamp) { + CryptoNote::WalletOrder order; order.address = to; order.amount = amount; CryptoNote::TransactionParameters params; - params.destinations = {order}; + params.sourceAddresses = sourceAdresses; + params.destinations = { order }; params.fee = fee; params.mixIn = mixIn; params.extra = extra; @@ -496,6 +529,10 @@ size_t WalletApi::sendMoney(CryptoNote::WalletGreen& wallet, const std::string& return wallet.transfer(params); } +size_t WalletApi::sendMoney(CryptoNote::WalletGreen& wallet, const std::string& to, uint64_t amount, uint64_t fee, uint64_t mixIn, const std::string& extra, uint64_t unlockTimestamp) { + return sendMoney(wallet, {}, to, amount, fee, mixIn, extra, unlockTimestamp); +} + size_t WalletApi::sendMoney(const std::string& to, uint64_t amount, uint64_t fee, uint64_t mixIn, const std::string& extra, uint64_t unlockTimestamp) { return sendMoney(alice, to, amount, fee, mixIn, extra, unlockTimestamp); } @@ -621,6 +658,92 @@ std::vector getTransfersFromTransaction(CryptoNote:: static const uint64_t TEST_BLOCK_REWARD = 70368744177663; +TEST_F(WalletApi, initializeThrowsExceptionIfWalletFileAlreadyExists) { + std::ofstream file(BOB_WALLET_PATH); + file.close(); + ASSERT_TRUE(boost::filesystem::exists(BOB_WALLET_PATH)); + + CryptoNote::WalletGreen bob(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); + ASSERT_THROW(bob.initialize(BOB_WALLET_PATH, "pass2"), std::system_error); + + wait(100); +} + +TEST_F(WalletApi, initializeCreatesWalletThatCanBeLoaded) { + CryptoNote::WalletGreen bob(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); + bob.initialize(BOB_WALLET_PATH, "pass2"); + auto walletViewKey = bob.getViewKey(); + bob.shutdown(); + + CryptoNote::WalletGreen carol(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); + carol.load(BOB_WALLET_PATH, "pass2"); + ASSERT_EQ(walletViewKey.publicKey, carol.getViewKey().publicKey); + ASSERT_EQ(walletViewKey.secretKey, carol.getViewKey().secretKey); + carol.shutdown(); + + wait(100); +} + +TEST_F(WalletApi, addressPreservedEvenIfSaveWasNotCalled) { + CryptoNote::WalletGreen bob(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); + bob.initialize(BOB_WALLET_PATH, "pass2"); + auto address = bob.createAddress(); + bob.shutdown(); + + CryptoNote::WalletGreen carol(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); + carol.load(BOB_WALLET_PATH, "pass2"); + ASSERT_EQ(1, carol.getAddressCount()); + ASSERT_EQ(address, carol.getAddress(0)); + carol.shutdown(); + + wait(100); +} + +TEST_F(WalletApi, loadThrowsExceptionIfWalletFileDoesNotExist) { + CryptoNote::WalletGreen bob(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); + ASSERT_THROW(bob.load(BOB_WALLET_PATH, "pass2"), std::system_error); +} + +TEST_F(WalletApi, loadThrowsExceptionIfWalletFileHasInvalidVersion) { + CryptoNote::WalletGreen bob(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); + bob.initialize(BOB_WALLET_PATH, "pass2"); + bob.shutdown(); + + std::fstream file(BOB_WALLET_PATH, std::ios::in | std::ios::out | std::ios::binary); + file.seekp(0); + char wrongVersion = static_cast(CryptoNote::WalletSerializerV2::SERIALIZATION_VERSION + 1); + file.write(&wrongVersion, sizeof(wrongVersion)); + file.close(); + + CryptoNote::WalletGreen carol(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); + ASSERT_THROW(carol.load(BOB_WALLET_PATH, "pass2"), std::system_error); +} + +TEST_F(WalletApi, loadThrowsExceptionIfWalletFileIsEmpty) { + std::ofstream file(BOB_WALLET_PATH); + file.close(); + ASSERT_TRUE(boost::filesystem::exists(BOB_WALLET_PATH)); + + CryptoNote::WalletGreen bob(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); + ASSERT_THROW(bob.load(BOB_WALLET_PATH, "pass2"), std::system_error); +} + +TEST_F(WalletApi, loadThrowsExceptionIfWalletFileIsCorrupted) { + CryptoNote::WalletGreen bob(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); + bob.initialize(BOB_WALLET_PATH, "pass2"); + bob.shutdown(); + + boost::filesystem::rename(BOB_WALLET_PATH, BOB_WALLET_BACKUP_PATH); + std::ifstream src(BOB_WALLET_BACKUP_PATH, std::ios::binary); + std::ofstream dst(BOB_WALLET_PATH, std::ios::binary); + std::copy_n(std::istreambuf_iterator(src), boost::filesystem::file_size(BOB_WALLET_BACKUP_PATH) - 1, std::ostreambuf_iterator(dst)); + src.close(); + dst.close(); + + CryptoNote::WalletGreen carol(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); + ASSERT_ANY_THROW(carol.load(BOB_WALLET_PATH, "pass2")); +} + TEST_F(WalletApi, emptyBalance) { ASSERT_EQ(0, alice.getActualBalance()); ASSERT_EQ(0, alice.getPendingBalance()); @@ -649,7 +772,7 @@ TEST_F(WalletApi, unlockMoney) { TEST_F(WalletApi, transferFromOneAddress) { CryptoNote::WalletGreen bob(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); - bob.initialize("pass2"); + bob.initialize(BOB_WALLET_PATH, "pass2"); std::string bobAddress = bob.createAddress(); generateAndUnlockMoney(); @@ -694,7 +817,7 @@ TEST_F(WalletApi, moneyLockedIfTransactionIsSoftLocked) { generateAndUnlockMoney(); CryptoNote::WalletGreen bob(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); - bob.initialize("pass2"); + bob.initialize(BOB_WALLET_PATH, "pass2"); sendMoney(bob.createAddress(), SENT, FEE); generator.generateEmptyBlocks(static_cast(TRANSACTION_SOFTLOCK_TIME - 1)); @@ -744,7 +867,7 @@ TEST_F(WalletApi, transferFromTwoAddresses) { waitForActualBalance(2 * TEST_BLOCK_REWARD); CryptoNote::WalletGreen bob(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); - bob.initialize("pass2"); + bob.initialize(BOB_WALLET_PATH, "pass2"); std::string bobAddress = bob.createAddress(); const uint64_t sent = 2 * TEST_BLOCK_REWARD - 10 * FEE; @@ -780,7 +903,7 @@ TEST_F(WalletApi, transferTooBigTransaction) { INodeTrivialRefreshStub n(gen); CryptoNote::WalletGreen wallet(dispatcher, cur, n, logger, TRANSACTION_SOFTLOCK_TIME); - wallet.initialize("pass"); + wallet.initialize(BOB_WALLET_PATH, "pass"); wallet.createAddress(); gen.getBlockRewardForAddress(parseAddress(wallet.getAddress(0))); @@ -808,7 +931,7 @@ TEST_F(WalletApi, transferCanSpendAllWalletOutputsIncludingDustOutputs) { INodeTrivialRefreshStub node(generator); CryptoNote::WalletGreen wallet(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); - wallet.initialize("pass"); + wallet.initialize(BOB_WALLET_PATH, "pass"); std::string src = wallet.createAddress(); std::string dst = wallet.createAddress(); @@ -909,11 +1032,11 @@ TEST_F(WalletApi, transferFromSpecificAddress) { } TEST_F(WalletApi, loadEmptyWallet) { - std::stringstream data; - alice.save(data, true, true); + alice.save(); + boost::filesystem::copy(ALICE_WALLET_PATH, BOB_WALLET_PATH); WalletGreen bob(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); - bob.load(data, "pass"); + bob.load(BOB_WALLET_PATH, "pass"); ASSERT_EQ(alice.getAddressCount(), bob.getAddressCount()); ASSERT_EQ(alice.getActualBalance(), bob.getActualBalance()); @@ -925,7 +1048,7 @@ TEST_F(WalletApi, loadEmptyWallet) { } TEST_F(WalletApi, walletGetsBaseTransaction) { - // mine to alice's address to make it recieve block base transaction + // mine to alice's address to make it receive block base transaction setMinerTo(alice); generateAndUnlockMoney(); ASSERT_TRUE(alice.getTransaction(0).isBase); @@ -937,31 +1060,32 @@ TEST_F(WalletApi, walletGetsNonBaseTransaction) { } TEST_F(WalletApi, loadWalletWithBaseTransaction) { - // mine to alice's address to make it recieve block base transaction + // mine to alice's address to make it receive block base transaction setMinerTo(alice); generateAndUnlockMoney(); - std::stringstream data; - alice.save(data, true, true); + alice.save(); + boost::filesystem::copy(ALICE_WALLET_PATH, BOB_WALLET_PATH); WalletGreen bob(dispatcher, currency, node, logger); - bob.load(data, "pass"); + bob.load(BOB_WALLET_PATH, "pass"); ASSERT_TRUE(bob.getTransaction(0).isBase); + bob.shutdown(); wait(100); } TEST_F(WalletApi, updateBaseTransactionAfterLoad) { - // mine to alice's address to make it recieve block base transaction + // mine to alice's address to make it receive block base transaction setMinerTo(alice); generateAndUnlockMoney(); - std::stringstream data; - alice.save(data, true, false); + alice.save(WalletSaveLevel::SAVE_KEYS_AND_TRANSACTIONS); + boost::filesystem::copy(ALICE_WALLET_PATH, BOB_WALLET_PATH); WalletGreen bob(dispatcher, currency, node, logger); - bob.load(data, "pass"); + bob.load(BOB_WALLET_PATH, "pass"); waitForWalletEvent(bob, CryptoNote::SYNC_COMPLETED, std::chrono::seconds(5)); ASSERT_TRUE(bob.getTransaction(0).isBase); @@ -970,15 +1094,15 @@ TEST_F(WalletApi, updateBaseTransactionAfterLoad) { } TEST_F(WalletApi, setBaseTransactionAfterInSynchronization) { - // mine to alice's address to make it recieve block base transaction + // mine to alice's address to make it receive block base transaction setMinerTo(alice); generateAndUnlockMoney(); - std::stringstream data; - alice.save(data, false, false); + alice.save(WalletSaveLevel::SAVE_KEYS_ONLY); + boost::filesystem::copy(ALICE_WALLET_PATH, BOB_WALLET_PATH); WalletGreen bob(dispatcher, currency, node, logger); - bob.load(data, "pass"); + bob.load(BOB_WALLET_PATH, "pass"); waitForWalletEvent(bob, CryptoNote::SYNC_COMPLETED, std::chrono::seconds(5)); ASSERT_TRUE(bob.getTransaction(0).isBase); @@ -988,14 +1112,13 @@ TEST_F(WalletApi, setBaseTransactionAfterInSynchronization) { TEST_F(WalletApi, loadWalletWithoutAddresses) { WalletGreen bob(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); - bob.initialize("pass"); + bob.initialize(BOB_WALLET_PATH, "pass"); - std::stringstream data; - bob.save(data, false, false); + bob.save(WalletSaveLevel::SAVE_KEYS_ONLY); bob.shutdown(); WalletGreen carol(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); - carol.load(data, "pass"); + carol.load(BOB_WALLET_PATH, "pass"); ASSERT_EQ(0, carol.getAddressCount()); carol.shutdown(); @@ -1025,47 +1148,82 @@ void compareWalletsPendingBalance(const CryptoNote::WalletGreen& alice, const Cr } } -void compareWalletsTransactionTransfers(const CryptoNote::WalletGreen& alice, const CryptoNote::WalletGreen& bob) { - ASSERT_EQ(alice.getTransactionCount(), bob.getTransactionCount()); - for (size_t i = 0; i < bob.getTransactionCount(); ++i) { - ASSERT_EQ(alice.getTransaction(i), bob.getTransaction(i)); +CryptoNote::WalletTransaction prepareTxForComparison(const CryptoNote::WalletTransaction& tx, bool strict) { + auto result = tx; - ASSERT_EQ(alice.getTransactionTransferCount(i), bob.getTransactionTransferCount(i)); + if (!strict) { + // creationTime is assigned to time() for outgoing transaction, after wallet reset it is changed to the block time + result.creationTime = 0; + // When wallet saved without cache, transaction state is set to WalletTransactionState::CANCELLED and block height to WALLET_UNCONFIRMED_TRANSACTION_HEIGHT + result.state = WalletTransactionState::CANCELLED; + result.blockHeight = WALLET_UNCONFIRMED_TRANSACTION_HEIGHT; + } + + return result; +} + +std::vector exportWalletTransactions(const CryptoNote::WalletGreen& wallet) { + std::vector result; + result.reserve(wallet.getTransactionCount()); + for (size_t i = 0; i < wallet.getTransactionCount(); ++i) { + auto txHash = wallet.getTransaction(i).hash; + result.emplace_back(wallet.getTransaction(txHash)); + } + + return result; +} - size_t trCount = bob.getTransactionTransferCount(i); - for (size_t j = 0; j < trCount; ++j) { - ASSERT_EQ(alice.getTransactionTransfer(i, j), bob.getTransactionTransfer(i, j)); +void compareWalletsTransactionTransfers(const std::vector& aliceTransactions, + const CryptoNote::WalletGreen& bob, bool strict) { + + ASSERT_EQ(aliceTransactions.size(), bob.getTransactionCount()); + for (size_t i = 0; i < bob.getTransactionCount(); ++i) { + ASSERT_EQ(prepareTxForComparison(aliceTransactions[i].transaction, strict), prepareTxForComparison(bob.getTransaction(i), strict)); + auto& aliceTransfers = aliceTransactions[i].transfers; + ASSERT_EQ(aliceTransfers.size(), bob.getTransactionTransferCount(i)); + + if (strict) { + size_t trCount = bob.getTransactionTransferCount(i); + for (size_t j = 0; j < trCount; ++j) { + ASSERT_EQ(aliceTransfers[j], bob.getTransactionTransfer(i, j)); + } } } } -TEST_F(WalletApi, loadCacheDetails) { +void compareWalletsTransactionTransfers(const CryptoNote::WalletGreen& alice, const CryptoNote::WalletGreen& bob, bool strict) { + compareWalletsTransactionTransfers(exportWalletTransactions(alice), bob, strict); +} + +TEST_F(WalletApi, loadAll) { fillWalletWithDetailsCache(); node.waitForAsyncContexts(); waitForWalletEvent(alice, CryptoNote::SYNC_COMPLETED, std::chrono::seconds(5)); - std::stringstream data; - alice.save(data, true, true); + alice.save(WalletSaveLevel::SAVE_ALL); - WalletGreen bob(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); - bob.load(data, "pass"); + boost::filesystem::copy(ALICE_WALLET_PATH, BOB_WALLET_PATH); + + WalletGreen bob(dispatcher, currency, node, logger); + bob.load(BOB_WALLET_PATH, "pass"); compareWalletsAddresses(alice, bob); compareWalletsActualBalance(alice, bob); compareWalletsPendingBalance(alice, bob); - compareWalletsTransactionTransfers(alice, bob); + compareWalletsTransactionTransfers(alice, bob, true); bob.shutdown(); wait(100); //ObserverManager bug workaround } -TEST_F(WalletApi, loadNoCacheNoDetails) { +TEST_F(WalletApi, loadKeysOnly) { fillWalletWithDetailsCache(); - std::stringstream data; - alice.save(data, false, false); + alice.save(WalletSaveLevel::SAVE_KEYS_ONLY); - WalletGreen bob(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); - bob.load(data, "pass"); + boost::filesystem::copy(ALICE_WALLET_PATH, BOB_WALLET_PATH); + + WalletGreen bob(dispatcher, currency, node, logger); + bob.load(BOB_WALLET_PATH, "pass"); compareWalletsAddresses(alice, bob); @@ -1073,55 +1231,57 @@ TEST_F(WalletApi, loadNoCacheNoDetails) { ASSERT_EQ(0, bob.getPendingBalance()); ASSERT_EQ(0, bob.getTransactionCount()); + waitForWalletEvent(bob, CryptoNote::SYNC_COMPLETED, std::chrono::seconds(30)); + + compareWalletsAddresses(alice, bob); + compareWalletsActualBalance(alice, bob); + compareWalletsPendingBalance(alice, bob); + compareWalletsTransactionTransfers(alice, bob, false); + bob.shutdown(); wait(100); } -TEST_F(WalletApi, loadNoCacheDetails) { +TEST_F(WalletApi, loadKeysAndTransactions) { fillWalletWithDetailsCache(); - std::stringstream data; - alice.save(data, true, false); + alice.save(WalletSaveLevel::SAVE_KEYS_AND_TRANSACTIONS); - WalletGreen bob(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); - bob.load(data, "pass"); + boost::filesystem::copy(ALICE_WALLET_PATH, BOB_WALLET_PATH); + + WalletGreen bob(dispatcher, currency, node, logger); + bob.load(BOB_WALLET_PATH, "pass"); compareWalletsAddresses(alice, bob); ASSERT_EQ(0, bob.getActualBalance()); ASSERT_EQ(0, bob.getPendingBalance()); - compareWalletsTransactionTransfers(alice, bob); - - bob.shutdown(); - wait(100); -} - -TEST_F(WalletApi, loadCacheNoDetails) { - fillWalletWithDetailsCache(); + compareWalletsTransactionTransfers(alice, bob, false); - std::stringstream data; - alice.save(data, false, true); - - WalletGreen bob(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); - bob.load(data, "pass"); + waitForWalletEvent(bob, CryptoNote::SYNC_COMPLETED, std::chrono::seconds(30)); compareWalletsAddresses(alice, bob); compareWalletsActualBalance(alice, bob); compareWalletsPendingBalance(alice, bob); - - ASSERT_EQ(0, bob.getTransactionCount()); + compareWalletsTransactionTransfers(alice, bob, true); bob.shutdown(); wait(100); } TEST_F(WalletApi, loadWithWrongPassword) { - std::stringstream data; - alice.save(data, false, false); + alice.save(WalletSaveLevel::SAVE_KEYS_ONLY); + boost::filesystem::copy(ALICE_WALLET_PATH, BOB_WALLET_PATH); WalletGreen bob(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); - ASSERT_ANY_THROW(bob.load(data, "pass2")); + + try { + bob.load(BOB_WALLET_PATH, "pass2"); + ASSERT_FALSE(true); + } catch (const std::system_error& e) { + ASSERT_EQ(error::WRONG_PASSWORD, e.code().value()); + } } void WalletApi::testIWalletDataCompatibility(bool details, const std::string& cache, const std::vector& txs, @@ -1147,11 +1307,13 @@ void WalletApi::testIWalletDataCompatibility(bool details, const std::string& ca iWalletCache.onTransactionUpdated(item.first, item.second); } - std::stringstream stream; + std::ofstream stream(BOB_WALLET_PATH, std::ios_base::binary); walletSerializer.serialize(stream, "pass", details, std::string()); + stream.flush(); + stream.close(); WalletGreen wallet(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); - wallet.load(stream, "pass"); + wallet.load(BOB_WALLET_PATH, "pass"); EXPECT_EQ(1, wallet.getAddressCount()); @@ -1300,8 +1462,7 @@ TEST_F(WalletApi, uninitializedObject) { WalletGreen bob(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); ASSERT_ANY_THROW(bob.changePassword("s", "p")); - std::stringstream stream; - ASSERT_ANY_THROW(bob.save(stream)); + ASSERT_ANY_THROW(bob.save()); ASSERT_ANY_THROW(bob.getAddressCount()); ASSERT_ANY_THROW(bob.getAddress(0)); ASSERT_ANY_THROW(bob.createAddress()); @@ -1403,7 +1564,7 @@ TEST_F(WalletApi, checkIncomingTransaction) { generateAndUnlockMoney(); CryptoNote::WalletGreen bob(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); - bob.initialize("pass2"); + bob.initialize(BOB_WALLET_PATH, "pass2"); std::string bobAddress = bob.createAddress(); sendMoney(bobAddress, SENT, FEE, 0, extra, 11); @@ -1434,11 +1595,11 @@ TEST_F(WalletApi, changePassword) { ASSERT_NO_THROW(alice.changePassword("pass", "pass2")); - std::stringstream data; - alice.save(data, false, false); + alice.save(WalletSaveLevel::SAVE_KEYS_ONLY); + boost::filesystem::copy(ALICE_WALLET_PATH, BOB_WALLET_PATH); CryptoNote::WalletGreen bob(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); - ASSERT_NO_THROW(bob.load(data, "pass2")); + ASSERT_NO_THROW(bob.load(BOB_WALLET_PATH, "pass2")); bob.shutdown(); wait(100); @@ -1454,7 +1615,7 @@ TEST_F(WalletApi, shutdownInit) { waitPendingBalanceUpdated(0); alice.shutdown(); - alice.initialize("p"); + alice.initialize(BOB_WALLET_PATH, "p"); EXPECT_EQ(0, alice.getAddressCount()); EXPECT_EQ(0, alice.getActualBalance()); @@ -1491,7 +1652,7 @@ TEST_F(WalletApi, incomingTxTransferWithChange) { generateAndUnlockMoney(); CryptoNote::WalletGreen bob(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); - bob.initialize("pass2"); + bob.initialize(BOB_WALLET_PATH, "pass2"); bob.createAddress(); bob.createAddress(); @@ -1520,7 +1681,7 @@ TEST_F(WalletApi, incomingTxTransferWithoutChange) { unlockMoney(); CryptoNote::WalletGreen bob(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); - bob.initialize("pass2"); + bob.initialize(BOB_WALLET_PATH, "pass2"); bob.createAddress(); sendMoney(bob.getAddress(0), SENT, FEE); @@ -1537,7 +1698,7 @@ TEST_F(WalletApi, walletSendsTransactionUpdatedEventAfterAddingTransfer) { generateAndUnlockMoney(); CryptoNote::WalletGreen bob(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); - bob.initialize("pass2"); + bob.initialize(BOB_WALLET_PATH, "pass2"); bob.createAddress(); bob.createAddress(); bob.createAddress(); @@ -1560,7 +1721,7 @@ TEST_F(WalletApi, walletCreatesTransferForEachTransactionFunding) { generateAndUnlockMoney(); CryptoNote::WalletGreen bob(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); - bob.initialize("pass2"); + bob.initialize(BOB_WALLET_PATH, "pass2"); bob.createAddress(); bob.createAddress(); @@ -1656,14 +1817,13 @@ TEST_F(WalletApi, doubleSpendJustSentOut) { } TEST_F(WalletApi, syncAfterLoad) { - std::stringstream data; - alice.save(data, true, true); + alice.save(); alice.shutdown(); generateBlockReward(); generator.generateEmptyBlocks(currency.minedMoneyUnlockWindow()); - alice.load(data, "pass"); + alice.load(ALICE_WALLET_PATH, "pass"); wait(300); @@ -1692,7 +1852,7 @@ TEST_F(WalletApi, DISABLED_loadTest) { INodeNoRelay noRelayNode(generator); CryptoNote::WalletGreen wallet(dispatcher, currency, noRelayNode, logger, TRANSACTION_SOFTLOCK_TIME); - wallet.initialize("pass"); + wallet.initialize(BOB_WALLET_PATH, "pass"); const size_t ADDRESSES_COUNT = 1000; @@ -1757,16 +1917,16 @@ TEST_F(WalletApi, initializeWithKeysSucceded) { CryptoNote::KeyPair viewKeys; Crypto::generate_keys(viewKeys.publicKey, viewKeys.secretKey); - ASSERT_NO_THROW(wallet.initializeWithViewKey(viewKeys.secretKey, "pass")); + ASSERT_NO_THROW(wallet.initializeWithViewKey(BOB_WALLET_PATH, "pass", viewKeys.secretKey)); wallet.shutdown(); } -TEST_F(WalletApi, initializeWithKeysThrowsIfAlreadInitialized) { +TEST_F(WalletApi, initializeWithKeysThrowsIfAlreadyInitialized) { CryptoNote::KeyPair viewKeys; Crypto::generate_keys(viewKeys.publicKey, viewKeys.secretKey); - ASSERT_ANY_THROW(alice.initializeWithViewKey(viewKeys.secretKey, "pass")); + ASSERT_ANY_THROW(alice.initializeWithViewKey(ALICE_WALLET_PATH, "pass", viewKeys.secretKey)); } TEST_F(WalletApi, initializeWithKeysThrowsIfStopped) { @@ -1775,7 +1935,7 @@ TEST_F(WalletApi, initializeWithKeysThrowsIfStopped) { CryptoNote::KeyPair viewKeys; Crypto::generate_keys(viewKeys.publicKey, viewKeys.secretKey); - ASSERT_ANY_THROW(wallet.initializeWithViewKey(viewKeys.secretKey, "pass")); + ASSERT_ANY_THROW(wallet.initializeWithViewKey(ALICE_WALLET_PATH, "pass", viewKeys.secretKey)); } TEST_F(WalletApi, getViewKeyReturnsProperKey) { @@ -1783,7 +1943,7 @@ TEST_F(WalletApi, getViewKeyReturnsProperKey) { CryptoNote::KeyPair viewKeys; Crypto::generate_keys(viewKeys.publicKey, viewKeys.secretKey); - wallet.initializeWithViewKey(viewKeys.secretKey, "pass"); + wallet.initializeWithViewKey(BOB_WALLET_PATH, "pass", viewKeys.secretKey); CryptoNote::KeyPair retrievedKeys = wallet.getViewKey(); ASSERT_EQ(viewKeys.publicKey, retrievedKeys.publicKey); @@ -1837,7 +1997,7 @@ Crypto::PublicKey generatePublicKey() { TEST_F(WalletApi, createTrackingKeyAddressSucceeded) { CryptoNote::WalletGreen wallet(dispatcher, currency, node, logger); - wallet.initialize("pass"); + wallet.initialize(BOB_WALLET_PATH, "pass"); Crypto::PublicKey publicKey = generatePublicKey(); @@ -1855,7 +2015,7 @@ TEST_F(WalletApi, createTrackingKeyThrowsIfNotInitialized) { TEST_F(WalletApi, createTrackingKeyThrowsIfStopped) { CryptoNote::WalletGreen wallet(dispatcher, currency, node, logger); - wallet.initialize("pass"); + wallet.initialize(BOB_WALLET_PATH, "pass"); wallet.stop(); Crypto::PublicKey publicKey = generatePublicKey(); @@ -1865,7 +2025,7 @@ TEST_F(WalletApi, createTrackingKeyThrowsIfStopped) { TEST_F(WalletApi, createTrackingKeyThrowsIfKeyExists) { CryptoNote::WalletGreen wallet(dispatcher, currency, node, logger); - wallet.initialize("pass"); + wallet.initialize(BOB_WALLET_PATH, "pass"); Crypto::PublicKey publicKey = generatePublicKey(); wallet.createAddress(publicKey); @@ -1880,7 +2040,7 @@ TEST_F(WalletApi, createTrackingKeyThrowsIfWalletHasNotTrackingKeys) { TEST_F(WalletApi, getAddressSpendKeyForTrackingKeyReturnsNullSecretKey) { CryptoNote::WalletGreen wallet(dispatcher, currency, node, logger); - wallet.initialize("pass"); + wallet.initialize(BOB_WALLET_PATH, "pass"); Crypto::PublicKey publicKey = generatePublicKey(); wallet.createAddress(publicKey); @@ -1895,7 +2055,7 @@ TEST_F(WalletApi, trackingAddressReceivesMoney) { generateAndUnlockMoney(); CryptoNote::WalletGreen bob(dispatcher, currency, node, logger); - bob.initialize("pass2"); + bob.initialize(BOB_WALLET_PATH, "pass2"); Crypto::PublicKey publicKey = generatePublicKey(); bob.createAddress(publicKey); @@ -1924,7 +2084,7 @@ TEST_F(WalletApi, trackingAddressUnlocksMoney) { generateAndUnlockMoney(); CryptoNote::WalletGreen bob(dispatcher, currency, node, logger); - bob.initialize("pass2"); + bob.initialize(BOB_WALLET_PATH, "pass2"); Crypto::PublicKey publicKey = generatePublicKey(); bob.createAddress(publicKey); @@ -1942,7 +2102,7 @@ TEST_F(WalletApi, transferFromTrackingKeyThrows) { generateAndUnlockMoney(); CryptoNote::WalletGreen bob(dispatcher, currency, node, logger); - bob.initialize("pass2"); + bob.initialize(BOB_WALLET_PATH, "pass2"); Crypto::PublicKey publicKey = generatePublicKey(); bob.createAddress(publicKey); @@ -1986,7 +2146,7 @@ struct CatchTransactionNodeStub : public INodeTrivialRefreshStub { TEST_F(WalletApi, createFusionTransactionCreatesValidFusionTransactionWithoutMixin) { CatchTransactionNodeStub catchNode(generator); CryptoNote::WalletGreen wallet(dispatcher, currency, catchNode, logger); - wallet.initialize("pass"); + wallet.initialize(BOB_WALLET_PATH, "pass"); wallet.createAddress(); generateFusionOutputsAndUnlock(wallet, catchNode, currency, FUSION_THRESHOLD); @@ -2001,7 +2161,7 @@ TEST_F(WalletApi, createFusionTransactionCreatesValidFusionTransactionWithoutMix TEST_F(WalletApi, createFusionTransactionCreatesValidFusionTransactionWithMixin) { CatchTransactionNodeStub catchNode(generator); CryptoNote::WalletGreen wallet(dispatcher, currency, catchNode, logger); - wallet.initialize("pass"); + wallet.initialize(BOB_WALLET_PATH, "pass"); wallet.createAddress(); generateFusionOutputsAndUnlock(wallet, catchNode, currency, FUSION_THRESHOLD); @@ -2037,7 +2197,7 @@ TEST_F(WalletApi, createFusionTransactionThrowsIfNotInitialized) { TEST_F(WalletApi, createFusionTransactionThrowsIfStopped) { CryptoNote::WalletGreen wallet(dispatcher, currency, node, logger); - wallet.initialize("pass"); + wallet.initialize(BOB_WALLET_PATH, "pass"); wallet.stop(); ASSERT_ANY_THROW(wallet.createFusionTransaction(FUSION_THRESHOLD, 0)); wallet.shutdown(); @@ -2049,7 +2209,7 @@ TEST_F(WalletApi, createFusionTransactionThrowsIfThresholdTooSmall) { TEST_F(WalletApi, createFusionTransactionThrowsIfNoAddresses) { CryptoNote::WalletGreen wallet(dispatcher, currency, node, logger); - wallet.initialize("pass"); + wallet.initialize(BOB_WALLET_PATH, "pass"); ASSERT_ANY_THROW(wallet.createFusionTransaction(FUSION_THRESHOLD, 0)); wallet.shutdown(); } @@ -2057,7 +2217,7 @@ TEST_F(WalletApi, createFusionTransactionThrowsIfNoAddresses) { TEST_F(WalletApi, createFusionTransactionThrowsIfTransactionSendError) { CatchTransactionNodeStub catchNode(generator); CryptoNote::WalletGreen wallet(dispatcher, currency, catchNode, logger); - wallet.initialize("pass"); + wallet.initialize(BOB_WALLET_PATH, "pass"); wallet.createAddress(); generateFusionOutputsAndUnlock(wallet, catchNode, currency, FUSION_THRESHOLD); @@ -2070,7 +2230,7 @@ TEST_F(WalletApi, createFusionTransactionThrowsIfTransactionSendError) { TEST_F(WalletApi, createFusionTransactionSpendsAllWalletsOutputsIfSourceAddressIsEmpty) { CatchTransactionNodeStub catchNode(generator); CryptoNote::WalletGreen wallet(dispatcher, currency, catchNode, logger); - wallet.initialize("pass"); + wallet.initialize(BOB_WALLET_PATH, "pass"); std::string address0 = wallet.createAddress(); std::string address1 = wallet.createAddress(); @@ -2098,7 +2258,7 @@ TEST_F(WalletApi, createFusionTransactionSpendsAllWalletsOutputsIfSourceAddressI TEST_F(WalletApi, createFusionTransactionTransfersAllMoneyToTheOnlySourceAddressIfDestinationIsEmpty) { CatchTransactionNodeStub catchNode(generator); CryptoNote::WalletGreen wallet(dispatcher, currency, catchNode, logger); - wallet.initialize("pass"); + wallet.initialize(BOB_WALLET_PATH, "pass"); std::string address0 = wallet.createAddress(); std::string address1 = wallet.createAddress(); @@ -2122,7 +2282,7 @@ TEST_F(WalletApi, createFusionTransactionTransfersAllMoneyToTheOnlySourceAddress TEST_F(WalletApi, createFusionTransactionThrowsIfSourceAddresIsNotAValidAddress) { CatchTransactionNodeStub catchNode(generator); CryptoNote::WalletGreen wallet(dispatcher, currency, catchNode, logger); - wallet.initialize("pass"); + wallet.initialize(BOB_WALLET_PATH, "pass"); std::string address0 = wallet.createAddress(); generateFusionOutputsAndUnlock(wallet, catchNode, currency, FUSION_THRESHOLD, 0); @@ -2140,7 +2300,7 @@ TEST_F(WalletApi, createFusionTransactionThrowsIfSourceAddresIsNotAValidAddress) TEST_F(WalletApi, createFusionTransactionThrowsIfSourceAddresDoesNotBelongToTheContainer) { CatchTransactionNodeStub catchNode(generator); CryptoNote::WalletGreen wallet(dispatcher, currency, catchNode, logger); - wallet.initialize("pass"); + wallet.initialize(BOB_WALLET_PATH, "pass"); std::string address0 = wallet.createAddress(); generateFusionOutputsAndUnlock(wallet, catchNode, currency, FUSION_THRESHOLD, 0); @@ -2162,7 +2322,7 @@ TEST_F(WalletApi, createFusionTransactionThrowsIfSourceAddresDoesNotBelongToTheC TEST_F(WalletApi, createFusionTransactionThrowsIfDestinationAddresIsNotAValidAddress) { CatchTransactionNodeStub catchNode(generator); CryptoNote::WalletGreen wallet(dispatcher, currency, catchNode, logger); - wallet.initialize("pass"); + wallet.initialize(BOB_WALLET_PATH, "pass"); std::string address0 = wallet.createAddress(); generateFusionOutputsAndUnlock(wallet, catchNode, currency, FUSION_THRESHOLD, 0); @@ -2180,7 +2340,7 @@ TEST_F(WalletApi, createFusionTransactionThrowsIfDestinationAddresIsNotAValidAdd TEST_F(WalletApi, createFusionTransactionThrowsIfContainerHasAFewWalletsAndSourceAddressesAndDestinationAddressIsEmpty) { CatchTransactionNodeStub catchNode(generator); CryptoNote::WalletGreen wallet(dispatcher, currency, catchNode, logger); - wallet.initialize("pass"); + wallet.initialize(BOB_WALLET_PATH, "pass"); std::string address0 = wallet.createAddress(); std::string address1 = wallet.createAddress(); @@ -2201,7 +2361,7 @@ TEST_F(WalletApi, createFusionTransactionThrowsIfContainerHasAFewWalletsAndSourc TEST_F(WalletApi, createFusionTransactionThrowsIfItHasAFewSourceAddressesButDestinationAddressIsEmpty) { CatchTransactionNodeStub catchNode(generator); CryptoNote::WalletGreen wallet(dispatcher, currency, catchNode, logger); - wallet.initialize("pass"); + wallet.initialize(BOB_WALLET_PATH, "pass"); std::string address0 = wallet.createAddress(); std::string address1 = wallet.createAddress(); @@ -2222,7 +2382,7 @@ TEST_F(WalletApi, createFusionTransactionThrowsIfItHasAFewSourceAddressesButDest TEST_F(WalletApi, createFusionTransactionSpendsOnlySourceAddressOutputs) { CatchTransactionNodeStub catchNode(generator); CryptoNote::WalletGreen wallet(dispatcher, currency, catchNode, logger); - wallet.initialize("pass"); + wallet.initialize(BOB_WALLET_PATH, "pass"); std::string address0 = wallet.createAddress(); std::string address1 = wallet.createAddress(); @@ -2256,7 +2416,7 @@ TEST_F(WalletApi, createFusionTransactionSpendsOnlySourceAddressOutputs) { TEST_F(WalletApi, createFusionTransactionTransfersAllMoneyToDestinationAddress) { CatchTransactionNodeStub catchNode(generator); CryptoNote::WalletGreen wallet(dispatcher, currency, catchNode, logger); - wallet.initialize("pass"); + wallet.initialize(BOB_WALLET_PATH, "pass"); std::string address0 = wallet.createAddress(); std::string address1 = wallet.createAddress(); @@ -2443,7 +2603,7 @@ TEST_F(WalletApi, fusionManagerIsFusionTransactionThrowsIfOutOfRange) { TEST_F(WalletApi, fusionManagerIsFusionTransactionSpent) { CryptoNote::WalletGreen wallet(dispatcher, currency, node, logger); - wallet.initialize("pass"); + wallet.initialize(BOB_WALLET_PATH, "pass"); wallet.createAddress(); generateFusionOutputsAndUnlock(alice, node, currency, FUSION_THRESHOLD); @@ -2537,7 +2697,7 @@ TEST_F(WalletApi, donationThrowsIfThresholdZero) { TEST_F(WalletApi, donationTransactionHaveCorrectFee) { CatchTransactionNodeStub catchNode(generator); CryptoNote::WalletGreen wallet(dispatcher, currency, catchNode, logger); - wallet.initialize("pass"); + wallet.initialize(BOB_WALLET_PATH, "pass"); wallet.createAddress(); const uint64_t DONATION_THRESHOLD = 1000000; @@ -2567,13 +2727,13 @@ TEST_F(WalletApi, donationSerialization) { sendMoneyWithDonation(RANDOM_ADDRESS, SENT, FEE, RANDOM_ADDRESS, DONATION_THRESHOLD); - std::stringstream data; - alice.save(data, true, true); + alice.save(); + boost::filesystem::copy(ALICE_WALLET_PATH, BOB_WALLET_PATH); WalletGreen bob(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); - bob.load(data, "pass"); + bob.load(BOB_WALLET_PATH, "pass"); - compareWalletsTransactionTransfers(alice, bob); + compareWalletsTransactionTransfers(alice, bob, true); bob.shutdown(); } @@ -3160,7 +3320,7 @@ TEST_F(WalletApi, transferDoesntAppearTwiceAfterIncludingToBlockchain) { unlockMoney(); CryptoNote::WalletGreen bob(dispatcher, currency, node, logger, 1); - bob.initialize("p"); + bob.initialize(BOB_WALLET_PATH, "p"); node.setNextTransactionToPool(); sendMoney(bob.createAddress(), SENT, FEE); @@ -3186,7 +3346,7 @@ TEST_F(WalletApi, incomingTransactionToTwoAddressesContainsTransfersForEachAddre unlockMoney(); CryptoNote::WalletGreen bob(dispatcher, currency, node, logger, 1); - bob.initialize("p"); + bob.initialize(BOB_WALLET_PATH, "p"); CryptoNote::TransactionParameters params; params.destinations = {{bob.createAddress(), SENT}, {bob.createAddress(), SENT + FEE}}; @@ -3348,7 +3508,7 @@ TEST_F(WalletApi, getTransactionsReturnsCorrectTransactionByBlockHash) { TEST_F(WalletApi, getTransactionsDoesntReturnUnconfirmedIncomingTransactions) { CryptoNote::WalletGreen bob(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); - bob.initialize("pass2"); + bob.initialize(BOB_WALLET_PATH, "pass2"); generateAndUnlockMoney(); @@ -3366,7 +3526,7 @@ TEST_F(WalletApi, getTransactionsDoesntReturnUnconfirmedIncomingTransactions) { TEST_F(WalletApi, getTransactionsReturnsConfirmedIncomingTransactions) { CryptoNote::WalletGreen bob(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); - bob.initialize("pass2"); + bob.initialize(BOB_WALLET_PATH, "pass2"); generateAndUnlockMoney(); @@ -3516,7 +3676,7 @@ TEST_F(WalletApi, getBlockHashesReturnsCorrectBlockHashesAfterDetach) { TEST_F(WalletApi, getBlockHashesReturnsOnlyGenesisBlockHashForWalletWithoutAddresses) { CryptoNote::WalletGreen bob(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); - bob.initialize("pass"); + bob.initialize(BOB_WALLET_PATH, "pass"); auto hashes = bob.getBlockHashes(0, 100); auto hash = hashes[0]; @@ -3547,11 +3707,11 @@ TEST_F(WalletApi, getBlockHashesReturnsCorrectHashesAfterLoad) { auto hashesBefore = alice.getBlockHashes(0, generator.getBlockchain().size()); - std::stringstream data; - alice.save(data, false, true); + alice.save(WalletSaveLevel::SAVE_ALL); + boost::filesystem::copy(ALICE_WALLET_PATH, BOB_WALLET_PATH); CryptoNote::WalletGreen bob(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); - bob.load(data, "pass"); + bob.load(BOB_WALLET_PATH, "pass"); auto hashesAfter = bob.getBlockHashes(0, generator.getBlockchain().size()); ASSERT_EQ(hashesBefore, hashesAfter); @@ -3571,7 +3731,7 @@ TEST_F(WalletApi, getBlockCountThrowIfNotStopped) { TEST_F(WalletApi, getBlockCountForWalletWithoutAddressesReturnsOne) { CryptoNote::WalletGreen bob(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); - bob.initialize("pass"); + bob.initialize(BOB_WALLET_PATH, "pass"); ASSERT_EQ(1, bob.getBlockCount()); bob.shutdown(); } @@ -3629,11 +3789,11 @@ TEST_F(WalletApi, getBlockCountReturnsCorrectBlockCountAfterLoad) { auto aliceBlockCount = alice.getBlockCount(); - std::stringstream data; - alice.save(data, false, true); + alice.save(WalletSaveLevel::SAVE_ALL); + boost::filesystem::copy(ALICE_WALLET_PATH, BOB_WALLET_PATH); CryptoNote::WalletGreen bob(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); - ASSERT_NO_THROW(bob.load(data, "pass")); + ASSERT_NO_THROW(bob.load(BOB_WALLET_PATH, "pass")); ASSERT_EQ(aliceBlockCount, bob.getBlockCount()); bob.shutdown(); @@ -3735,7 +3895,7 @@ TEST_F(WalletApi, getDelayedTransactionIdsThrowsIfStopped) { TEST_F(WalletApi, getDelayedTransactionIdsThrowsIfInTrackingMode) { CryptoNote::WalletGreen bob(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); - bob.initialize("p"); + bob.initialize(BOB_WALLET_PATH, "p"); Crypto::PublicKey pub; Crypto::SecretKey sec; @@ -3901,3 +4061,289 @@ TEST_F(WalletApi, checkBaseTransaction) { EXPECT_LT(0, transfer.amount); EXPECT_EQ(tx.totalAmount, transfer.amount); } + +TEST_F(WalletApi, walletResetsIfSavedCacheDoesNotContainAddedAddress) { + // Create address with money + ASSERT_EQ(1, alice.getAddressCount()); + auto address1 = alice.getAddress(0); + generateBlockReward(address1); + unlockMoney(alice, node); + waitForWalletEvent(alice, CryptoNote::SYNC_COMPLETED, std::chrono::seconds(5)); + auto aliceTransactions1 = exportWalletTransactions(alice); + + // Save wallet with one address and one transaction + alice.save(); + + // Create address, that transactions will not be saved + auto address2 = alice.createAddress(); + + // Send money, and unlock it + generateBlockReward(address2); + unlockMoney(alice, node); + waitForWalletEvent(alice, CryptoNote::SYNC_COMPLETED, std::chrono::seconds(5)); + + // Send money, but not unlock it + generateBlockReward(address2); + node.updateObservers(); + waitForWalletEvent(alice, CryptoNote::SYNC_COMPLETED, std::chrono::seconds(5)); + + ASSERT_NE(0, alice.getActualBalance()); + ASSERT_NE(0, alice.getPendingBalance()); + ASSERT_NE(0, alice.getTransactionCount()); + + boost::filesystem::copy(ALICE_WALLET_PATH, BOB_WALLET_PATH); + + WalletGreen bob(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); + bob.load(BOB_WALLET_PATH, "pass"); + + // Check wallet was reset, but has both addresses and first transactions + compareWalletsAddresses(alice, bob); + ASSERT_EQ(0, bob.getActualBalance()); + ASSERT_EQ(0, bob.getPendingBalance()); + compareWalletsTransactionTransfers(aliceTransactions1, bob, false); + + waitForWalletEvent(bob, CryptoNote::SYNC_COMPLETED, std::chrono::seconds(5)); + + // Check wallet restored all transactions and balance + compareWalletsAddresses(alice, bob); + compareWalletsActualBalance(alice, bob); + compareWalletsPendingBalance(alice, bob); + compareWalletsTransactionTransfers(alice, bob, true); + + bob.shutdown(); + + wait(100); +} + +TEST_F(WalletApi, walletRemovesTransactionsForAddressesDeletedAfterSaving) { + ASSERT_EQ(1, alice.getAddressCount()); + auto address1 = alice.getAddress(0); + generateBlockReward(address1); + unlockMoney(alice, node); + waitForWalletEvent(alice, CryptoNote::SYNC_COMPLETED, std::chrono::seconds(5)); + auto tx0 = alice.getTransactionCount() - 1; + ASSERT_EQ(0, tx0); + + // Create address, that will be deleted + auto address2 = alice.createAddress(); + + // Create incoming transaction to address2 + generateBlockReward(address2); + unlockMoney(alice, node); + auto tx1 = alice.getTransactionCount() - 1; + ASSERT_EQ(1, tx1); + + // Create transaction, that spend money only from address2 + // Spend all money in order to transaction doesn't have change + uint64_t address2Balance = alice.getActualBalance(address2); + uint64_t sendAmount = address2Balance - currency.minimumFee(); + auto tx2 = sendMoney(alice, { address2 }, RANDOM_ADDRESS, sendAmount, currency.minimumFee()); + generator.generateEmptyBlocks(1); + + // Create transaction, that transfers money from address1 to address2 + uint64_t address1Balance = alice.getActualBalance(address1); + sendAmount = (address1Balance - currency.minimumFee()) / 2; + auto tx3 = sendMoney(alice, { address1 }, address2, sendAmount, currency.minimumFee()); + generator.generateEmptyBlocks(1); + unlockMoney(alice, node); + waitForWalletEvent(alice, CryptoNote::SYNC_COMPLETED, std::chrono::seconds(5)); + + // Create transaction, that spends money from address1 and address2 and send change to address1 + address1Balance = alice.getActualBalance(address1); + address2Balance = alice.getActualBalance(address2); + sendAmount = address1Balance + address2Balance - currency.minimumFee() - 1; + auto tx4 = sendMoney(alice, { address1, address2 }, RANDOM_ADDRESS, sendAmount, currency.minimumFee()); + generator.generateEmptyBlocks(1); + + waitForWalletEvent(alice, CryptoNote::SYNC_COMPLETED, std::chrono::seconds(5)); + + // Save wallet with 2 addresses and 5 transactions + alice.save(); + + alice.deleteAddress(address2); + + ASSERT_EQ(0, alice.getActualBalance()); + ASSERT_EQ(1, alice.getPendingBalance()); + ASSERT_EQ(1, alice.getPendingBalance(address1)); + ASSERT_EQ(5, alice.getTransactionCount()); + ASSERT_EQ(WalletTransactionState::SUCCEEDED, alice.getTransaction(tx0).state); + ASSERT_EQ(WalletTransactionState::DELETED, alice.getTransaction(tx1).state); + ASSERT_EQ(WalletTransactionState::DELETED, alice.getTransaction(tx2).state); + ASSERT_EQ(WalletTransactionState::SUCCEEDED, alice.getTransaction(tx3).state); + ASSERT_EQ(WalletTransactionState::SUCCEEDED, alice.getTransaction(tx4).state); + + boost::filesystem::copy(ALICE_WALLET_PATH, BOB_WALLET_PATH); + + WalletGreen bob(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); + bob.load(BOB_WALLET_PATH, "pass"); + + compareWalletsAddresses(alice, bob); + compareWalletsActualBalance(alice, bob); + compareWalletsPendingBalance(alice, bob); + compareWalletsTransactionTransfers(alice, bob, true); + + bob.shutdown(); + + wait(100); +} + +TEST_F(WalletApi, walletResetsAndRemovesObsoleteTransactionsIfOneAddressDeletedAndOneAddressAddedAfterSaving) { + // Create address, that will be deleted + auto address1 = alice.createAddress(); + + // Create transaction for address2 + generateBlockReward(address1); + unlockMoney(alice, node); + waitForWalletEvent(alice, CryptoNote::SYNC_COMPLETED, std::chrono::seconds(30)); + auto tx1 = alice.getTransactionCount() - 1; + + alice.save(); + + // Create address, that transactions will not be saved + auto address2 = alice.createAddress(); + + // Create transaction for address3 + generateBlockReward(address2); + unlockMoney(alice, node); + waitForWalletEvent(alice, CryptoNote::SYNC_COMPLETED, std::chrono::seconds(30)); + + alice.deleteAddress(address1); + + boost::filesystem::copy(ALICE_WALLET_PATH, BOB_WALLET_PATH); + + WalletGreen bob(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); + bob.load(BOB_WALLET_PATH, "pass"); + + // Check wallet was reset, but has both addresses + compareWalletsAddresses(alice, bob); + ASSERT_EQ(0, bob.getActualBalance()); + ASSERT_EQ(0, bob.getPendingBalance()); + ASSERT_EQ(WalletTransactionState::DELETED, bob.getTransaction(tx1).state); + ASSERT_EQ(tx1 + 1, bob.getTransactionCount()); + + waitForWalletEvent(bob, CryptoNote::SYNC_COMPLETED, std::chrono::seconds(30)); + + // Check wallet restored all transactions and balance + compareWalletsAddresses(alice, bob); + compareWalletsActualBalance(alice, bob); + compareWalletsPendingBalance(alice, bob); + compareWalletsTransactionTransfers(alice, bob, true); + + bob.shutdown(); + wait(100); +} + +TEST_F(WalletApi, walletSavesAndLoadsExtra) { + std::string savedExtra = "some extra data"; + alice.save(WalletSaveLevel::SAVE_ALL, savedExtra); + + alice.shutdown(); + + std::string loadedExtra; + alice.load(ALICE_WALLET_PATH, "pass", loadedExtra); + + ASSERT_EQ(savedExtra, loadedExtra); +} + +TEST_F(WalletApi, walletHandlesResetAndSwitchingToAlternativeChain) { + // Create transaction 1, that will be preserved + generateBlockReward(aliceAddress); + unlockMoney(alice, node); + waitForWalletEvent(alice, CryptoNote::SYNC_COMPLETED, std::chrono::seconds(30)); + auto tx1 = alice.getTransactionCount() - 1; + + size_t detachHeight = generator.getBlockchain().size(); + + // Create transaction 2, that will be cancelled + generateBlockReward(aliceAddress); + unlockMoney(alice, node); + waitForWalletEvent(alice, CryptoNote::SYNC_COMPLETED, std::chrono::seconds(30)); + auto tx2 = alice.getTransactionCount() - 1; + + size_t alternativeChainSize = generator.getBlockchain().size() + 1; + + ASSERT_EQ(WalletTransactionState::SUCCEEDED, alice.getTransaction(tx1).state); + ASSERT_EQ(WalletTransactionState::SUCCEEDED, alice.getTransaction(tx2).state); + + // Don't save cache + alice.save(WalletSaveLevel::SAVE_KEYS_AND_TRANSACTIONS); + boost::filesystem::copy(ALICE_WALLET_PATH, BOB_WALLET_PATH); + + // Switch to alternative chain, that doesn't have transaction 2 + node.startAlternativeChain(detachHeight); + generator.generateEmptyBlocks(alternativeChainSize - detachHeight); + node.updateObservers(); + waitForWalletEvent(alice, CryptoNote::SYNC_COMPLETED, std::chrono::seconds(30)); + + // Make sure transaction 2 was cancelled + ASSERT_EQ(WalletTransactionState::SUCCEEDED, alice.getTransaction(tx1).state); + ASSERT_EQ(WalletTransactionState::CANCELLED, alice.getTransaction(tx2).state); + + WalletGreen bob(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); + bob.load(BOB_WALLET_PATH, "pass"); + + // Wallet was saved without cache, so all transactions should be canceled + ASSERT_EQ(WalletTransactionState::CANCELLED, bob.getTransaction(tx1).state); + ASSERT_EQ(WalletTransactionState::CANCELLED, bob.getTransaction(tx2).state); + + waitForWalletEvent(bob, CryptoNote::SYNC_COMPLETED, std::chrono::seconds(30)); + + // Check after synchronization wallets are equal + compareWalletsAddresses(alice, bob); + compareWalletsActualBalance(alice, bob); + compareWalletsPendingBalance(alice, bob); + compareWalletsTransactionTransfers(alice, bob, true); + + bob.shutdown(); + wait(100); +} + +TEST_F(WalletApi, exportedWalletIsFullCopyOfSourceWallet) { + fillWalletWithDetailsCache(); + + std::string savedExtra = "some extra data"; + alice.exportWallet(BOB_WALLET_PATH, true, WalletSaveLevel::SAVE_ALL, savedExtra); + + WalletGreen bob(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); + std::string loadedExtra; + bob.load(BOB_WALLET_PATH, "pass", loadedExtra); + + // Check wallets are equal + ASSERT_EQ(savedExtra, loadedExtra); + compareWalletsAddresses(alice, bob); + compareWalletsActualBalance(alice, bob); + compareWalletsPendingBalance(alice, bob); + compareWalletsTransactionTransfers(alice, bob, true); + + bob.shutdown(); + wait(100); +} + +TEST_F(WalletApi, walletExportedWithoutEncryptionCanBeOpenedWithEmptyPassword) { + std::string savedExtra = "some extra data"; + alice.exportWallet(BOB_WALLET_PATH, false, WalletSaveLevel::SAVE_ALL, savedExtra); + + WalletGreen bob(dispatcher, currency, node, logger, TRANSACTION_SOFTLOCK_TIME); + std::string loadedExtra; + ASSERT_NO_THROW(bob.load(BOB_WALLET_PATH, "", loadedExtra)); + ASSERT_EQ(savedExtra, loadedExtra); + + bob.shutdown(); + wait(100); +} + +TEST_F(WalletApi, walletExportFailedIfFileAlreadyExists) { + std::ofstream file(BOB_WALLET_PATH, std::ios_base::binary); + file.put('1'); + file.close(); + ASSERT_TRUE(boost::filesystem::exists(BOB_WALLET_PATH)); + ASSERT_EQ(1, boost::filesystem::file_size(BOB_WALLET_PATH)); + + try { + alice.exportWallet(BOB_WALLET_PATH, true, WalletSaveLevel::SAVE_ALL); + ASSERT_FALSE(true); + } catch (const std::system_error&) { + ASSERT_TRUE(boost::filesystem::exists(BOB_WALLET_PATH)); + ASSERT_EQ(1, boost::filesystem::file_size(BOB_WALLET_PATH)); + } +} diff --git a/tests/UnitTests/TestWalletService.cpp b/tests/UnitTests/TestWalletService.cpp index 05f105d4f9..357fcf306c 100644 --- a/tests/UnitTests/TestWalletService.cpp +++ b/tests/UnitTests/TestWalletService.cpp @@ -52,13 +52,15 @@ struct IWalletBaseStub : public CryptoNote::IWallet, public CryptoNote::IFusionM IWalletBaseStub(System::Dispatcher& dispatcher) : m_eventOccurred(dispatcher) {} virtual ~IWalletBaseStub() {} - virtual void initialize(const std::string& password) override { } - virtual void initializeWithViewKey(const Crypto::SecretKey& viewSecretKey, const std::string& password) override { } - virtual void load(std::istream& source, const std::string& password) override { } + virtual void initialize(const std::string& path, const std::string& password) override { } + virtual void initializeWithViewKey(const std::string& path, const std::string& password, const Crypto::SecretKey& viewSecretKey) override { } + virtual void load(const std::string& path, const std::string& password, std::string& extra) override { } + virtual void load(const std::string& path, const std::string& password) override { } virtual void shutdown() override { } virtual void changePassword(const std::string& oldPassword, const std::string& newPassword) override { } - virtual void save(std::ostream& destination, bool saveDetails = true, bool saveCache = true, bool encrypt = true) override { } + virtual void save(WalletSaveLevel saveLevel = WalletSaveLevel::SAVE_ALL, const std::string& extra = "") override { } + virtual void exportWallet(const std::string& path, bool encrypt = true, WalletSaveLevel saveLevel = WalletSaveLevel::SAVE_ALL, const std::string& extra = "") override { } virtual size_t getAddressCount() const override { return 0; } virtual std::string getAddress(size_t index) const override { return ""; }