Initial working base separated from top-level monero project

This commit is contained in:
Lee Clagett
2020-08-19 18:29:32 -04:00
commit a2ff89bc24
68 changed files with 11543 additions and 0 deletions

34
src/db/CMakeLists.txt Normal file
View File

@@ -0,0 +1,34 @@
# Copyright (c) 2018-2020, The Monero Project
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification, are
# permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this list of
# conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
# of conditions and the following disclaimer in the documentation and/or other
# materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors may be
# used to endorse or promote products derived from this software without specific
# prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
set(monero-lws-db_sources account.cpp data.cpp storage.cpp string.cpp)
set(monero-lws-db_headers account.h data.h fwd.h storage.h string.h)
add_library(monero-lws-db ${monero-lws-db_sources} ${monero-lws-db_headers})
target_include_directories(monero-lws-db PUBLIC "${LMDB_INCLUDE}")
target_link_libraries(monero-lws-db monero::libraries ${LMDB_LIB_PATH})

179
src/db/account.cpp Normal file
View File

@@ -0,0 +1,179 @@
// Copyright (c) 2018, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "account.h"
#include <algorithm>
#include <cstring>
#include "common/error.h"
#include "common/expect.h"
#include "db/data.h"
#include "db/string.h"
namespace lws
{
namespace
{
// update if `crypto::public_key` gets `operator<`
struct sort_pubs
{
bool operator()(crypto::public_key const& lhs, crypto::public_key const& rhs) const noexcept
{
return std::memcmp(std::addressof(lhs), std::addressof(rhs), sizeof(lhs)) < 0;
}
};
}
struct account::internal
{
explicit internal(db::account const& source)
: address(db::address_string(source.address)), id(source.id), pubs(source.address), view_key()
{
using inner_type =
std::remove_reference<decltype(tools::unwrap(view_key))>::type;
static_assert(std::is_standard_layout<db::view_key>(), "need standard layout source");
static_assert(std::is_pod<inner_type>(), "need pod target");
static_assert(sizeof(view_key) == sizeof(source.key), "different size keys");
std::memcpy(
std::addressof(tools::unwrap(view_key)),
std::addressof(source.key),
sizeof(source.key)
);
}
std::string address;
db::account_id id;
db::account_address pubs;
crypto::secret_key view_key;
};
account::account(std::shared_ptr<const internal> immutable, db::block_id height, std::vector<db::output_id> spendable, std::vector<crypto::public_key> pubs) noexcept
: immutable_(std::move(immutable))
, spendable_(std::move(spendable))
, pubs_(std::move(pubs))
, spends_()
, outputs_()
, height_(height)
{}
void account::null_check() const
{
if (!immutable_)
MONERO_THROW(::common_error::kInvalidArgument, "using moved from account");
}
account::account(db::account const& source, std::vector<db::output_id> spendable, std::vector<crypto::public_key> pubs)
: account(std::make_shared<internal>(source), source.scan_height, std::move(spendable), std::move(pubs))
{
std::sort(spendable_.begin(), spendable_.end());
std::sort(pubs_.begin(), pubs_.end(), sort_pubs{});
}
account::~account() noexcept
{}
account account::clone() const
{
account result{immutable_, height_, spendable_, pubs_};
result.outputs_ = outputs_;
result.spends_ = spends_;
return result;
}
void account::updated(db::block_id new_height) noexcept
{
height_ = new_height;
spends_.clear();
spends_.shrink_to_fit();
outputs_.clear();
outputs_.shrink_to_fit();
}
db::account_id account::id() const noexcept
{
if (immutable_)
return immutable_->id;
return db::account_id::invalid;
}
std::string const& account::address() const
{
null_check();
return immutable_->address;
}
db::account_address const& account::db_address() const
{
null_check();
return immutable_->pubs;
}
crypto::public_key const& account::view_public() const
{
null_check();
return immutable_->pubs.view_public;
}
crypto::public_key const& account::spend_public() const
{
null_check();
return immutable_->pubs.spend_public;
}
crypto::secret_key const& account::view_key() const
{
null_check();
return immutable_->view_key;
}
bool account::has_spendable(db::output_id const& id) const noexcept
{
return std::binary_search(spendable_.begin(), spendable_.end(), id);
}
bool account::add_out(db::output const& out)
{
auto existing_pub = std::lower_bound(pubs_.begin(), pubs_.end(), out.pub, sort_pubs{});
if (existing_pub != pubs_.end() && *existing_pub == out.pub)
return false;
pubs_.insert(existing_pub, out.pub);
spendable_.insert(
std::lower_bound(spendable_.begin(), spendable_.end(), out.spend_meta.id),
out.spend_meta.id
);
outputs_.push_back(out);
return true;
}
void account::add_spend(db::spend const& spend)
{
spends_.push_back(spend);
}
} // lws

114
src/db/account.h Normal file
View File

@@ -0,0 +1,114 @@
// Copyright (c) 2018, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
#include "crypto/crypto.h"
#include "fwd.h"
#include "db/fwd.h"
namespace lws
{
//! Tracks a subset of DB account info for scanning/updating.
class account
{
struct internal;
std::shared_ptr<const internal> immutable_;
std::vector<db::output_id> spendable_;
std::vector<crypto::public_key> pubs_;
std::vector<db::spend> spends_;
std::vector<db::output> outputs_;
db::block_id height_;
explicit account(std::shared_ptr<const internal> immutable, db::block_id height, std::vector<db::output_id> spendable, std::vector<crypto::public_key> pubs) noexcept;
void null_check() const;
public:
//! Construct an account from `source` and current `spendable` outputs.
explicit account(db::account const& source, std::vector<db::output_id> spendable, std::vector<crypto::public_key> pubs);
/*!
\return False if this is a "moved-from" account (i.e. the internal memory
has been moved to another object).
*/
explicit operator bool() const noexcept { return immutable_ != nullptr; }
account(const account&) = delete;
account(account&&) = default;
~account() noexcept;
account& operator=(const account&) = delete;
account& operator=(account&&) = default;
//! \return A copy of `this`.
account clone() const;
//! \return A copy of `this` with a new height and `outputs().empty()`.
void updated(db::block_id new_height) noexcept;
//! \return Unique ID from the account database, possibly `db::account_id::kInvalid`.
db::account_id id() const noexcept;
//! \return Monero base58 string for account.
std::string const& address() const;
//! \return Object used for lookup in LMDB.
db::account_address const& db_address() const;
//! \return Extracted view public key from `address()`
crypto::public_key const& view_public() const;
//! \return Extracted spend public key from `address()`.
crypto::public_key const& spend_public() const;
//! \return Secret view key for the account.
crypto::secret_key const& view_key() const;
//! \return Current scan height of `this`.
db::block_id scan_height() const noexcept { return height_; }
//! \return True iff `id` is spendable by `this`.
bool has_spendable(db::output_id const& id) const noexcept;
//! \return Outputs matched during the latest scan.
std::vector<db::output> const& outputs() const noexcept { return outputs_; }
//! \return Spends matched during the latest scan.
std::vector<db::spend> const& spends() const noexcept { return spends_; }
//! Track a newly received `out`, \return `false` if `out.pub` is duplicated.
bool add_out(db::output const& out);
//! Track a possible `spend`.
void add_spend(db::spend const& spend);
};
} // lws

225
src/db/data.cpp Normal file
View File

@@ -0,0 +1,225 @@
// Copyright (c) 2018, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "data.h"
#include <cstring>
#include <memory>
#include "wire/crypto.h"
#include "wire.h"
namespace lws
{
namespace db
{
namespace
{
template<typename F, typename T>
void map_output_id(F& format, T& self)
{
wire::object(format, WIRE_FIELD(high), WIRE_FIELD(low));
}
}
WIRE_DEFINE_OBJECT(output_id, map_output_id);
namespace
{
constexpr const char* map_account_status[] = {"active", "inactive", "hidden"};
constexpr const char* map_request[] = {"create", "import"};
}
WIRE_DEFINE_ENUM(account_status, map_account_status);
WIRE_DEFINE_ENUM(request, map_request);
namespace
{
template<typename F, typename T>
void map_account_address(F& format, T& self)
{
wire::object(format, WIRE_FIELD(spend_public), WIRE_FIELD(view_public));
}
}
WIRE_DEFINE_OBJECT(account_address, map_account_address);
void write_bytes(wire::writer& dest, const account& self, const bool show_key)
{
view_key const* const key =
show_key ? std::addressof(self.key) : nullptr;
const bool admin = (self.flags & admin_account);
const bool generated_locally = (self.flags & account_generated_locally);
wire::object(dest,
WIRE_FIELD(id),
wire::field("access_time", self.access),
WIRE_FIELD(address),
wire::optional_field("view_key", key),
WIRE_FIELD(scan_height),
WIRE_FIELD(start_height),
wire::field("creation_time", self.creation),
wire::field("admin", admin),
wire::field("generated_locally", generated_locally)
);
}
namespace
{
template<typename F, typename T>
void map_block_info(F& format, T& self)
{
wire::object(format, WIRE_FIELD(id), WIRE_FIELD(hash));
}
}
WIRE_DEFINE_OBJECT(block_info, map_block_info);
namespace
{
template<typename F, typename T>
void map_transaction_link(F& format, T& self)
{
wire::object(format, WIRE_FIELD(height), WIRE_FIELD(tx_hash));
}
}
WIRE_DEFINE_OBJECT(transaction_link, map_transaction_link);
void write_bytes(wire::writer& dest, const output& self)
{
const std::pair<db::extra, std::uint8_t> unpacked =
db::unpack(self.extra);
const bool coinbase = (unpacked.first & lws::db::coinbase_output);
const bool rct = (unpacked.first & lws::db::ringct_output);
const auto rct_mask = rct ? std::addressof(self.ringct_mask) : nullptr;
epee::span<const std::uint8_t> payment_bytes{};
if (unpacked.second == 32)
payment_bytes = epee::as_byte_span(self.payment_id.long_);
else if (unpacked.second == 8)
payment_bytes = epee::as_byte_span(self.payment_id.short_);
const auto payment_id = payment_bytes.empty() ?
nullptr : std::addressof(payment_bytes);
wire::object(dest,
wire::field("id", std::cref(self.spend_meta.id)),
wire::field("block", self.link.height),
wire::field("index", self.spend_meta.index),
wire::field("amount", self.spend_meta.amount),
wire::field("timestamp", self.timestamp),
wire::field("tx_hash", std::cref(self.link.tx_hash)),
wire::field("tx_prefix_hash", std::cref(self.tx_prefix_hash)),
wire::field("tx_public", std::cref(self.spend_meta.tx_public)),
wire::optional_field("rct_mask", rct_mask),
wire::optional_field("payment_id", payment_id),
wire::field("unlock_time", self.unlock_time),
wire::field("mixin_count", self.spend_meta.mixin_count),
wire::field("coinbase", coinbase)
);
}
namespace
{
template<typename F, typename T1, typename T2>
void map_spend(F& format, T1& self, T2& payment_id)
{
wire::object(format,
wire::field("height", self.link.height),
wire::field("tx_hash", std::ref(self.link.tx_hash)),
WIRE_FIELD(image),
WIRE_FIELD(source),
WIRE_FIELD(timestamp),
WIRE_FIELD(unlock_time),
WIRE_FIELD(mixin_count),
wire::optional_field("payment_id", payment_id)
);
}
}
void read_bytes(wire::reader& source, spend& dest)
{
boost::optional<crypto::hash> payment_id;
map_spend(source, dest, payment_id);
if (payment_id)
{
dest.length = sizeof(dest.payment_id);
dest.payment_id = std::move(*payment_id);
}
else
dest.length = 0;
}
void write_bytes(wire::writer& dest, const spend& source)
{
crypto::hash const* const payment_id =
(source.length == sizeof(source.payment_id) ?
std::addressof(source.payment_id) : nullptr);
return map_spend(dest, source, payment_id);
}
namespace
{
template<typename F, typename T>
void map_key_image(F& format, T& self)
{
wire::object(format,
wire::field("key_image", std::ref(self.value)),
wire::field("tx_hash", std::ref(self.link.tx_hash)),
wire::field("height", self.link.height)
);
}
}
WIRE_DEFINE_OBJECT(key_image, map_key_image);
void write_bytes(wire::writer& dest, const request_info& self, const bool show_key)
{
db::view_key const* const key =
show_key ? std::addressof(self.key) : nullptr;
const bool generated = (self.creation_flags & lws::db::account_generated_locally);
wire::object(dest,
WIRE_FIELD(address),
wire::optional_field("view_key", key),
WIRE_FIELD(start_height),
wire::field("generated_locally", generated)
);
}
/*! TODO consider making an `operator<` for `crypto::tx_hash`. Not known to be
needed elsewhere yet. */
bool operator<(transaction_link const& left, transaction_link const& right) noexcept
{
return left.height == right.height ?
std::memcmp(std::addressof(left.tx_hash), std::addressof(right.tx_hash), sizeof(left.tx_hash)) < 0 :
left.height < right.height;
}
bool operator<=(transaction_link const& left, transaction_link const& right) noexcept
{
return right.height == left.height ?
std::memcmp(std::addressof(left.tx_hash), std::addressof(right.tx_hash), sizeof(left.tx_hash)) <= 0 :
left.height < right.height;
}
} // db
} // lws

275
src/db/data.h Normal file
View File

@@ -0,0 +1,275 @@
// Copyright (c) 2018-2020, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <cassert>
#include <cstdint>
#include <iosfwd>
#include <utility>
#include "crypto/crypto.h"
#include "lmdb/util.h"
#include "ringct/rctTypes.h" //! \TODO brings in lots of includes, try to remove
#include "wire/fwd.h"
#include "wire/traits.h"
namespace lws
{
namespace db
{
/*
Enum classes are used because they generate identical code to native integer
types, but are not implicitly convertible to each other or any integer types.
They also have comparison but not arithmetic operators defined.
*/
//! References an account stored in the database, faster than by address
enum class account_id : std::uint32_t
{
invalid = std::uint32_t(-1) //!< Always represents _not an_ account id.
};
WIRE_AS_INTEGER(account_id);
//! Number of seconds since UNIX epoch.
enum class account_time : std::uint32_t {};
WIRE_AS_INTEGER(account_time);
//! References a block height
enum class block_id : std::uint64_t {};
WIRE_AS_INTEGER(block_id);
//! References a global output number, as determined by the public chain
struct output_id
{
std::uint64_t high; //!< Amount on public chain; rct outputs are `0`
std::uint64_t low; //!< Offset within `amount` on the public chain
};
WIRE_DECLARE_OBJECT(output_id);
enum class account_status : std::uint8_t
{
active = 0, //!< Actively being scanned and reported by API
inactive, //!< Not being scanned, but still reported by API
hidden //!< Not being scanned or reported by API
};
WIRE_DECLARE_ENUM(account_status);
enum account_flags : std::uint8_t
{
default_account = 0,
admin_account = 1, //!< Not currently used, for future extensions
account_generated_locally = 2 //!< Flag sent by client on initial login request
};
enum class request : std::uint8_t
{
create = 0, //!< Add a new account
import_scan //!< Set account start and scan height to zero.
};
WIRE_DECLARE_ENUM(request);
/*!
DB does not use `crypto::secret_key` because it is not POD (UB to copy over
entire struct). LMDB is keeping a copy in process memory anyway (row
encryption not currently used). The roadmap recommends process isolation
per-connection by default as a defense against viewkey leaks due to bug. */
struct view_key : crypto::ec_scalar {};
// wire::is_blob trait below
//! The public keys of a monero address
struct account_address
{
crypto::public_key spend_public; //!< Must be first for LMDB optimizations.
crypto::public_key view_public;
};
static_assert(sizeof(account_address) == 64, "padding in account_address");
WIRE_DECLARE_OBJECT(account_address);
struct account
{
account_id id; //!< Must be first for LMDB optimizations
account_time access; //!< Last time `get_address_info` was called.
account_address address;
view_key key; //!< Doubles as authorization handle for REST API.
block_id scan_height; //!< Last block scanned; check-ins are always by block
block_id start_height; //!< Account started scanning at this block height
account_time creation; //!< Time account first appeared in database.
account_flags flags; //!< Additional account info bitmask.
char reserved[3];
};
static_assert(sizeof(account) == (4 * 2) + 64 + 32 + (8 * 2) + (4 * 2), "padding in account");
void write_bytes(wire::writer&, const account&, bool show_key = false);
struct block_info
{
block_id id; //!< Must be first for LMDB optimizations
crypto::hash hash;
};
static_assert(sizeof(block_info) == 8 + 32, "padding in block_info");
WIRE_DECLARE_OBJECT(block_info);
//! `output`s and `spend`s are sorted by these fields to make merging easier.
struct transaction_link
{
block_id height; //!< Block height containing transaction
crypto::hash tx_hash; //!< Hash of the transaction
};
//! Additional flags stored in `output`s.
enum extra : std::uint8_t
{
coinbase_output = 1,
ringct_output = 2
};
//! Packed information stored in `output`s.
enum class extra_and_length : std::uint8_t {};
//! \return `val` and `length` packed into a single byte.
inline extra_and_length pack(extra val, std::uint8_t length) noexcept
{
assert(length <= 32);
return extra_and_length((std::uint8_t(val) << 6) | (length & 0x3f));
}
//! \return `extra` and length unpacked from a single byte.
inline std::pair<extra, std::uint8_t> unpack(extra_and_length val) noexcept
{
const std::uint8_t real_val = std::uint8_t(val);
return {extra(real_val >> 6), std::uint8_t(real_val & 0x3f)};
}
//! Information for an output that has been received by an `account`.
struct output
{
transaction_link link; //! Orders and links `output` to `spend`s.
//! Data that a linked `spend` needs in some REST endpoints.
struct spend_meta_
{
output_id id; //!< Unique id for output within monero
// `link` and `id` must be in this order for LMDB optimizations
std::uint64_t amount;
std::uint32_t mixin_count;//!< Ring-size of TX
std::uint32_t index; //!< Offset within a tx
crypto::public_key tx_public;
} spend_meta;
std::uint64_t timestamp;
std::uint64_t unlock_time; //!< Not always a timestamp; mirrors chain value.
crypto::hash tx_prefix_hash;
crypto::public_key pub; //!< One-time spendable public key.
rct::key ringct_mask; //!< Unencrypted CT mask
char reserved[7];
extra_and_length extra; //!< Extra info + length of payment id
union payment_id_
{
crypto::hash8 short_; //!< Decrypted short payment id
crypto::hash long_; //!< Long version of payment id (always decrypted)
} payment_id;
};
static_assert(
sizeof(output) == 8 + 32 + (8 * 3) + (4 * 2) + 32 + (8 * 2) + (32 * 3) + 7 + 1 + 32,
"padding in output"
);
void write_bytes(wire::writer&, const output&);
//! Information about a possible spend of a received `output`.
struct spend
{
transaction_link link; //!< Orders and links `spend` to `output`.
crypto::key_image image; //!< Unique ID for the spend
// `link` and `image` must in this order for LMDB optimizations
output_id source; //!< The output being spent
std::uint64_t timestamp; //!< Timestamp of spend
std::uint64_t unlock_time;//!< Unlock time of spend
std::uint32_t mixin_count;//!< Ring-size of TX output
char reserved[3];
std::uint8_t length; //!< Length of `payment_id` field (0..32).
crypto::hash payment_id; //!< Unencrypted only, can't decrypt spend
};
static_assert(sizeof(spend) == 8 + 32 * 2 + 8 * 4 + 4 + 3 + 1 + 32, "padding in spend");
WIRE_DECLARE_OBJECT(spend);
//! Key image and info needed to retrieve primary `spend` data.
struct key_image
{
crypto::key_image value; //!< Actual key image value
// The above field needs to be first for LMDB optimizations
transaction_link link; //!< Link to `spend` and `output`.
};
WIRE_DECLARE_OBJECT(key_image);
struct request_info
{
account_address address;//!< Must be first for LMDB optimizations
view_key key;
block_id start_height;
account_time creation; //!< Time the request was created.
account_flags creation_flags; //!< Generated locally?
char reserved[3];
};
static_assert(sizeof(request_info) == 64 + 32 + 8 + (4 * 2), "padding in request_info");
void write_bytes(wire::writer& dest, const request_info& self, bool show_key = false);
inline constexpr bool operator==(output_id left, output_id right) noexcept
{
return left.high == right.high && left.low == right.low;
}
inline constexpr bool operator!=(output_id left, output_id right) noexcept
{
return left.high != right.high || left.low != right.low;
}
inline constexpr bool operator<(output_id left, output_id right) noexcept
{
return left.high == right.high ?
left.low < right.low : left.high < right.high;
}
inline constexpr bool operator<=(output_id left, output_id right) noexcept
{
return left.high == right.high ?
left.low <= right.low : left.high < right.high;
}
bool operator<(transaction_link const& left, transaction_link const& right) noexcept;
bool operator<=(transaction_link const& left, transaction_link const& right) noexcept;
/*!
Write `address` to `out` in base58 format using `lws::config::network` to
determine tag. */
std::ostream& operator<<(std::ostream& out, account_address const& address);
} // db
} // lws
namespace wire
{
template<>
struct is_blob<lws::db::view_key>
: std::true_type
{};
}

56
src/db/fwd.h Normal file
View File

@@ -0,0 +1,56 @@
// Copyright (c) 2018-2020, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <cstdint>
namespace lws
{
namespace db
{
enum account_flags : std::uint8_t;
enum class account_id : std::uint32_t;
enum class account_status : std::uint8_t;
enum class block_id : std::uint64_t;
enum extra : std::uint8_t;
enum class extra_and_length : std::uint8_t;
enum class request : std::uint8_t;
struct account;
struct account_address;
struct block_info;
struct key_image;
struct output;
struct output_id;
struct request_info;
struct spend;
class storage;
struct transaction_link;
struct view_key;
} // db
} // lws

1797
src/db/storage.cpp Normal file

File diff suppressed because it is too large Load Diff

239
src/db/storage.h Normal file
View File

@@ -0,0 +1,239 @@
// Copyright (c) 2018, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <iosfwd>
#include <list>
#include <memory>
#include <utility>
#include <vector>
#include "common/expect.h"
#include "crypto/crypto.h"
#include "db/account.h"
#include "db/data.h"
#include "fwd.h"
#include "lmdb/transaction.h"
#include "lmdb/key_stream.h"
#include "lmdb/value_stream.h"
namespace lws
{
namespace db
{
namespace cursor
{
MONERO_CURSOR(accounts);
MONERO_CURSOR(outputs);
MONERO_CURSOR(spends);
MONERO_CURSOR(images);
MONERO_CURSOR(requests);
MONERO_CURSOR(blocks);
MONERO_CURSOR(accounts_by_address);
MONERO_CURSOR(accounts_by_height);
}
struct storage_internal;
struct reader_internal
{
cursor::blocks blocks_cur;
cursor::accounts_by_address accounts_ba_cur;
cursor::accounts_by_height accounts_bh_cur;
};
//! Wrapper for LMDB read access to on-disk storage of light-weight server data.
class storage_reader
{
std::shared_ptr<storage_internal> db;
lmdb::read_txn txn;
reader_internal curs;
public:
storage_reader(std::shared_ptr<storage_internal> db, lmdb::read_txn txn) noexcept
: db(std::move(db)), txn(std::move(txn)), curs{}
{}
storage_reader(storage_reader&&) = default;
storage_reader(storage_reader const&) = delete;
~storage_reader() noexcept;
storage_reader& operator=(storage_reader&&) = default;
storage_reader& operator=(storage_reader const&) = delete;
//! \return Last known block.
expect<block_info> get_last_block() noexcept;
//! \return List for `GetHashesFast` to sync blockchain with daemon.
expect<std::list<crypto::hash>> get_chain_sync();
//! \return All registered `account`s.
expect<lmdb::key_stream<account_status, account, cursor::close_accounts>>
get_accounts(cursor::accounts cur = nullptr) noexcept;
//! \return All `account`s currently in `status` or `lmdb::error(MDB_NOT_FOUND)`.
expect<lmdb::value_stream<account, cursor::close_accounts>>
get_accounts(account_status status, cursor::accounts cur = nullptr) noexcept;
//! \return Info related to `address` or `lmdb::error(MDB_NOT_FOUND)`.
expect<std::pair<account_status, account>>
get_account(account_address const& address, cursor::accounts& cur) noexcept;
expect<std::pair<account_status, account>>
get_account(account_address const& address) noexcept
{
cursor::accounts cur;
return get_account(address, cur);
}
//! \return All outputs received by `id`.
expect<lmdb::value_stream<output, cursor::close_outputs>>
get_outputs(account_id id, cursor::outputs cur = nullptr) noexcept;
//! \return All potential spends by `id`.
expect<lmdb::value_stream<spend, cursor::close_spends>>
get_spends(account_id id, cursor::spends cur = nullptr) noexcept;
//! \return All key images associated with `id`.
expect<lmdb::value_stream<db::key_image, cursor::close_images>>
get_images(output_id id, cursor::images cur = nullptr) noexcept;
//! \return All `request_info`s.
expect<lmdb::key_stream<request, request_info, cursor::close_requests>>
get_requests(cursor::requests cur = nullptr) noexcept;
//! \return A specific request from `address` of `type`.
expect<request_info>
get_request(request type, account_address const& address, cursor::requests cur = nullptr) noexcept;
//! Dump the contents of the database in JSON format to `out`.
expect<void> json_debug(std::ostream& out, bool show_keys);
//! \return Read txn that can be re-used via `storage::start_read`.
lmdb::suspended_txn finish_read() noexcept;
};
//! Wrapper for LMDB on-disk storage of light-weight server data.
class storage
{
std::shared_ptr<storage_internal> db;
storage(std::shared_ptr<storage_internal> db) noexcept
: db(std::move(db))
{}
public:
/*!
Open a light_wallet_server LDMB database.
\param path Directory for LMDB storage
\param create_queue_max Maximum number of create account requests allowed.
\throw std::system_error on any LMDB error (all treated as fatal).
\throw std::bad_alloc If `std::shared_ptr` fails to allocate.
\return A ready light-wallet server database.
*/
static storage open(const char* path, unsigned create_queue_max);
storage(storage&&) = default;
storage(storage const&) = delete;
~storage() noexcept;
storage& operator=(storage&&) = default;
storage& operator=(storage const&) = delete;
//! \return A copy of the LMDB environment, but not reusable txn/cursors.
storage clone() const noexcept;
//! Rollback chain and accounts to `height`.
expect<void> rollback(block_id height);
/*!
Sync the local blockchain with a remote version. Pops user txes if reorg
detected.
\param height The height of the element in `hashes`
\param hashes List of blockchain hashes starting at `height`.
\return True if the local blockchain is correctly synced.
*/
expect<void> sync_chain(block_id height, epee::span<const crypto::hash> hashes);
//! Bump the last access time of `address` to the current time.
expect<void> update_access_time(account_address const& address) noexcept;
//! Change state of `address` to `status`. \return Updated `addresses`.
expect<std::vector<account_address>>
change_status(account_status status, epee::span<const account_address> addresses);
//! Add an account, for immediate inclusion in the active list.
expect<void> add_account(account_address const& address, crypto::secret_key const& key) noexcept;
//! Reset `addresses` to `height` for scanning.
expect<std::vector<account_address>>
rescan(block_id height, epee::span<const account_address> addresses);
//! Add an account for later approval. For use with the login endpoint.
expect<void> creation_request(account_address const& address, crypto::secret_key const& key, account_flags flags) noexcept;
/*!
Request lock height of an existing account. No effect if the `start_height`
is already older.
*/
expect<void> import_request(account_address const& address, block_id height) noexcept;
//! Accept requests by `addresses` of type `req`. \return Accepted addresses.
expect<std::vector<account_address>>
accept_requests(request req, epee::span<const account_address> addresses);
//! Reject requests by `addresses` of type `req`. \return Rejected addresses.
expect<std::vector<account_address>>
reject_requests(request req, epee::span<const account_address> addresses);
/*!
Updates the status of user accounts, even if inactive or hidden. Duplicate
receives or spends provided in `accts` are silently ignored. If a gap in
`height` vs the stored account record is detected, the entire update will
fail.
\param height The first hash in `chain` is at this height.
\param chain List of block hashes that `accts` were scanned against.
\param accts Updated to `height + chain.size()` scan height.
\return True iff LMDB successfully committed the update.
*/
expect<std::size_t> update(block_id height, epee::span<const crypto::hash> chain, epee::span<const lws::account> accts);
//! `txn` must have come from a previous call on the same thread.
expect<storage_reader> start_read(lmdb::suspended_txn txn = nullptr) const;
};
} // db
} // lws

62
src/db/string.cpp Normal file
View File

@@ -0,0 +1,62 @@
// Copyright (c) 2018, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "string.h"
#include "config.h"
#include "cryptonote_basic/cryptonote_basic_impl.h"
#include "db/data.h"
#include "error.h"
namespace lws
{
namespace db
{
std::string address_string_::operator()(account_address const& address) const
{
const cryptonote::account_public_address address_{
address.spend_public, address.view_public
};
return cryptonote::get_account_address_as_str(
lws::config::network, false, address_
);
}
expect<account_address>
address_string_::operator()(boost::string_ref address) const noexcept
{
cryptonote::address_parse_info info{};
if (!cryptonote::get_account_address_from_str(info, lws::config::network, std::string{address}))
return {lws::error::bad_address};
if (info.is_subaddress || info.has_payment_id)
return {lws::error::bad_address};
return account_address{
info.address.m_spend_public_key, info.address.m_view_public_key
};
}
} // db
} // lws

55
src/db/string.h Normal file
View File

@@ -0,0 +1,55 @@
// Copyright (c) 2018, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <boost/utility/string_ref.hpp>
#include <string>
#include "common/expect.h"
#include "db/fwd.h"
namespace lws
{
namespace db
{
//! Callable for converting `account_address` to/from monero base58 public address.
struct address_string_
{
/*!
\return `address` as a monero base58 public address, using
`lws::config::network` for the tag.
*/
std::string operator()(account_address const& address) const;
/*!
\return `address`, as base58 public address, using `lws::config::network`
for the tag.
*/
expect<account_address> operator()(boost::string_ref address) const noexcept;
};
constexpr const address_string_ address_string{};
} // db
} // lws