5ba22c2ec9
1. rewrite of the Haven variation of the Pricing Record class to support: - versioning of the PR format - nested supply_data and asset_data structs - verification of the signature using a variable-length string (not R+S) 2. calculation of the slippage tallies for a block in add_block(), so that we can work out the yield that is due to be paid out for the block. Loads of little fixes and cleanups.
315 lines
9.5 KiB
C++
315 lines
9.5 KiB
C++
// Copyright (c) 2019, Haven Protocol
|
|
// 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.
|
|
//
|
|
// Portions of this code based upon code Copyright (c) 2019, The Monero Project
|
|
|
|
#include <boost/multiprecision/cpp_int.hpp>
|
|
#include "pricing_record.h"
|
|
|
|
#include "serialization/keyvalue_serialization.h"
|
|
#include "storages/portable_storage.h"
|
|
|
|
#include "string_tools.h"
|
|
namespace oracle
|
|
{
|
|
|
|
namespace
|
|
{
|
|
struct asset_data_serialized
|
|
{
|
|
std::string asset_type;
|
|
uint64_t spot_price;
|
|
uint64_t ma_price;
|
|
|
|
BEGIN_KV_SERIALIZE_MAP()
|
|
KV_SERIALIZE(asset_type)
|
|
KV_SERIALIZE(spot_price)
|
|
KV_SERIALIZE(ma_price)
|
|
END_KV_SERIALIZE_MAP()
|
|
};
|
|
|
|
struct supply_data_serialized
|
|
{
|
|
uint64_t FULM;
|
|
uint64_t FUSD;
|
|
|
|
BEGIN_KV_SERIALIZE_MAP()
|
|
KV_SERIALIZE(FULM)
|
|
KV_SERIALIZE(FUSD)
|
|
END_KV_SERIALIZE_MAP()
|
|
};
|
|
|
|
struct pr_serialized
|
|
{
|
|
uint64_t pr_version;
|
|
uint64_t height;
|
|
supply_data supply;
|
|
std::vector<asset_data> assets;
|
|
uint64_t timestamp;
|
|
std::string signature;
|
|
|
|
BEGIN_KV_SERIALIZE_MAP()
|
|
KV_SERIALIZE(pr_version)
|
|
KV_SERIALIZE(height)
|
|
KV_SERIALIZE(supply)
|
|
KV_SERIALIZE(assets)
|
|
KV_SERIALIZE(timestamp)
|
|
KV_SERIALIZE(signature)
|
|
END_KV_SERIALIZE_MAP()
|
|
};
|
|
}
|
|
|
|
pricing_record::pricing_record() noexcept
|
|
: pr_version(0)
|
|
, assets()
|
|
, timestamp(0)
|
|
, signature()
|
|
{
|
|
}
|
|
|
|
bool supply_data::_load(epee::serialization::portable_storage& src, epee::serialization::section* hparent)
|
|
{
|
|
supply_data_serialized in{};
|
|
if (in._load(src, hparent))
|
|
{
|
|
// Copy everything into the local instance
|
|
fulm = in.FULM;
|
|
fusd = in.FUSD;
|
|
return true;
|
|
}
|
|
// Report error here?
|
|
return false;
|
|
}
|
|
|
|
bool supply_data::store(epee::serialization::portable_storage& dest, epee::serialization::section* hparent) const
|
|
{
|
|
assert(false);
|
|
return true;
|
|
}
|
|
|
|
bool asset_data::_load(epee::serialization::portable_storage& src, epee::serialization::section* hparent)
|
|
{
|
|
asset_data_serialized in{};
|
|
if (in._load(src, hparent))
|
|
{
|
|
// Copy everything into the local instance
|
|
asset_type = in.asset_type;
|
|
spot_price = in.spot_price;
|
|
ma_price = in.ma_price;
|
|
return true;
|
|
}
|
|
// Report error here?
|
|
return false;
|
|
}
|
|
|
|
bool asset_data::store(epee::serialization::portable_storage& dest, epee::serialization::section* hparent) const
|
|
{
|
|
assert(false);
|
|
return true;
|
|
}
|
|
|
|
bool pricing_record::_load(epee::serialization::portable_storage& src, epee::serialization::section* hparent)
|
|
{
|
|
pr_serialized in{};
|
|
if (in._load(src, hparent))
|
|
{
|
|
// Copy everything into the local instance
|
|
pr_version = in.pr_version;
|
|
height = in.height;
|
|
supply = in.supply;
|
|
assets = in.assets;
|
|
timestamp = in.timestamp;
|
|
|
|
// Signature arrives in HEX format, but needs to be used in BINARY format - convert it here
|
|
signature.resize(in.signature.length() >> 1);
|
|
for (unsigned int i = 0; i < in.signature.length(); i += 2) {
|
|
std::string byteString = in.signature.substr(i, 2);
|
|
signature[i>>1] = (char) strtol(byteString.c_str(), NULL, 16);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Report error here?
|
|
return false;
|
|
}
|
|
|
|
bool pricing_record::store(epee::serialization::portable_storage& dest, epee::serialization::section* hparent) const
|
|
{
|
|
std::string sig_hex;
|
|
for (unsigned int i=0; i<64; i++) {
|
|
std::stringstream ss;
|
|
ss << std::hex << std::setw(2) << std::setfill('0') << (0xff & signature[i]);
|
|
sig_hex += ss.str();
|
|
}
|
|
const pr_serialized out{pr_version, height, supply, assets, timestamp, sig_hex};
|
|
return out.store(dest, hparent);
|
|
}
|
|
|
|
pricing_record::pricing_record(const pricing_record& orig) noexcept
|
|
: pr_version(orig.pr_version)
|
|
, height(orig.height)
|
|
, supply(orig.supply)
|
|
, assets(orig.assets)
|
|
, timestamp(orig.timestamp)
|
|
, signature(orig.signature)
|
|
{
|
|
}
|
|
|
|
pricing_record& pricing_record::operator=(const pricing_record& orig) noexcept
|
|
{
|
|
pr_version = orig.pr_version;
|
|
height = orig.height;
|
|
supply = orig.supply;
|
|
assets = orig.assets;
|
|
timestamp = orig.timestamp;
|
|
signature = orig.signature;
|
|
return *this;
|
|
}
|
|
|
|
uint64_t pricing_record::operator[](const std::string& asset_type) const
|
|
{
|
|
for (const auto& asset: assets) {
|
|
if (asset.asset_type != asset_type) continue;
|
|
if (asset_type == "FULM") return asset.spot_price;
|
|
if (asset_type == "FUSD") {
|
|
boost::multiprecision::uint128_t exchange_128 = COIN;
|
|
exchange_128 *= COIN;
|
|
exchange_128 /= asset.spot_price;
|
|
return exchange_128.convert_to<uint64_t>();
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool pricing_record::equal(const pricing_record& other) const noexcept
|
|
{
|
|
return ((pr_version == other.pr_version) &&
|
|
(height == other.height) &&
|
|
(supply == other.supply) &&
|
|
(assets == other.assets) &&
|
|
(timestamp == other.timestamp) &&
|
|
(signature == other.signature));
|
|
}
|
|
|
|
bool pricing_record::empty() const noexcept
|
|
{
|
|
const pricing_record empty_pr = oracle::pricing_record();
|
|
return (*this).equal(empty_pr);
|
|
}
|
|
|
|
bool pricing_record::verifySignature(const std::string& public_key) const
|
|
{
|
|
CHECK_AND_ASSERT_THROW_MES(!public_key.empty(), "Pricing record verification failed. NULL public key. PK Size: " << public_key.size());
|
|
|
|
// extract the key
|
|
EVP_PKEY* pubkey;
|
|
BIO* bio = BIO_new_mem_buf(public_key.c_str(), public_key.size());
|
|
if (!bio) {
|
|
return false;
|
|
}
|
|
pubkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
|
|
BIO_free(bio);
|
|
CHECK_AND_ASSERT_THROW_MES(pubkey != NULL, "Pricing record verification failed. NULL public key.");
|
|
|
|
// Build the JSON string, so that we can verify the signature
|
|
std::ostringstream oss;
|
|
oss << "{\"pr_version\":" << pr_version;
|
|
oss << ",\"height\":" << height;
|
|
oss << ",\"supply\":{\"FULM\":" << supply.fulm <<",\"FUSD\":" << supply.fusd << "}";
|
|
oss << ",\"assets\":[";
|
|
bool first = true;
|
|
for (const auto& asset: assets) {
|
|
if (first)
|
|
first=false;
|
|
else
|
|
oss << ",";
|
|
oss << "{\"asset_type\":\"" << asset.asset_type << "\",\"spot_price\":" << asset.spot_price << ",\"ma_price\":" << asset.ma_price << "}";
|
|
}
|
|
oss << "]";
|
|
oss << ",\"timestamp\":" << timestamp;
|
|
oss << "}";
|
|
std::string message = oss.str();
|
|
|
|
// Create a verify digest from the message
|
|
EVP_MD_CTX *ctx = EVP_MD_CTX_create();
|
|
int ret = 0;
|
|
if (ctx) {
|
|
ret = EVP_DigestVerifyInit(ctx, NULL, EVP_sha256(), NULL, pubkey);
|
|
if (ret == 1) {
|
|
ret = EVP_DigestVerifyUpdate(ctx, message.data(), message.length());
|
|
if (ret == 1) {
|
|
ret = EVP_DigestVerifyFinal(ctx, (const unsigned char *)signature.data(), signature.size());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Cleanup the context we created
|
|
EVP_MD_CTX_destroy(ctx);
|
|
// Cleanup the openssl stuff
|
|
EVP_PKEY_free(pubkey);
|
|
|
|
if (ret == 1)
|
|
return true;
|
|
|
|
// Get the errors from OpenSSL
|
|
ERR_print_errors_fp (stderr);
|
|
|
|
return false;
|
|
}
|
|
|
|
// overload for pr validation for block
|
|
bool pricing_record::valid(cryptonote::network_type nettype, uint32_t hf_version, uint64_t bl_timestamp, uint64_t last_bl_timestamp) const
|
|
{
|
|
if (hf_version < HF_VERSION_SLIPPAGE_YIELD) {
|
|
if (!this->empty())
|
|
return false;
|
|
}
|
|
|
|
if (this->empty())
|
|
return true;
|
|
|
|
if (!verifySignature(get_config(nettype).ORACLE_PUBLIC_KEY)) {
|
|
LOG_ERROR("Invalid pricing record signature.");
|
|
return false;
|
|
}
|
|
|
|
// validate the timestmap
|
|
if (this->timestamp > bl_timestamp + PRICING_RECORD_VALID_TIME_DIFF_FROM_BLOCK) {
|
|
LOG_ERROR("Pricing record timestamp is too far in the future.");
|
|
return false;
|
|
}
|
|
|
|
if (this->timestamp <= last_bl_timestamp) {
|
|
LOG_ERROR("Pricing record timestamp: " << this->timestamp << ", block timestamp: " << bl_timestamp);
|
|
LOG_ERROR("Pricing record timestamp is too old.");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|