From 81715e04f32657b98dcd4f3abb228a900c2e4510 Mon Sep 17 00:00:00 2001 From: Anthony Fieroni Date: Thu, 11 Mar 2021 10:10:03 +0200 Subject: [PATCH 1/4] Oracle development Signed-off-by: Anthony Fieroni --- configure.ac | 2 +- src/Makefile.am | 3 + src/Makefile.test.include | 1 + src/amount.h | 6 +- src/masternodes/masternodes.h | 2 + src/masternodes/mn_checks.cpp | 103 ++- src/masternodes/mn_checks.h | 85 ++- src/masternodes/oracles.cpp | 130 ++++ src/masternodes/oracles.h | 128 ++++ src/masternodes/rpc_accounts.cpp | 4 +- src/masternodes/rpc_customtx.cpp | 48 ++ src/masternodes/rpc_oracles.cpp | 952 ++++++++++++++++++++++++++++ src/rpc/client.cpp | 4 + src/rpc/register.h | 3 + src/test/oracles_tests.cpp | 141 ++++ test/functional/feature_oracles.py | 359 +++++++++++ test/functional/rpc_help.py | 2 +- test/functional/test_runner.py | 1 + test/lint/check-rpc-mappings.py | 1 + test/lint/lint-locale-dependence.sh | 2 + 20 files changed, 1934 insertions(+), 43 deletions(-) create mode 100644 src/masternodes/oracles.cpp create mode 100644 src/masternodes/oracles.h create mode 100644 src/masternodes/rpc_oracles.cpp create mode 100644 src/test/oracles_tests.cpp create mode 100755 test/functional/feature_oracles.py diff --git a/configure.ac b/configure.ac index d302ec0fcc..aedbd1f130 100644 --- a/configure.ac +++ b/configure.ac @@ -1031,7 +1031,7 @@ AX_BOOST_CHRONO dnl Boost 1.56 through 1.62 allow using std::atomic instead of its own atomic dnl counter implementations. In 1.63 and later the std::atomic approach is default. m4_pattern_allow(DBOOST_AC_USE_STD_ATOMIC) dnl otherwise it's treated like a macro -BOOST_CPPFLAGS="-DBOOST_SP_USE_STD_ATOMIC -DBOOST_AC_USE_STD_ATOMIC $BOOST_CPPFLAGS" +BOOST_CPPFLAGS="-DBOOST_MPL_CFG_NO_PREPROCESSED_HEADERS -DBOOST_MPL_LIMIT_LIST_SIZE=50 -DBOOST_SP_USE_STD_ATOMIC -DBOOST_AC_USE_STD_ATOMIC $BOOST_CPPFLAGS" if test x$use_reduce_exports = xyes; then AC_MSG_CHECKING([for working boost reduced exports]) diff --git a/src/Makefile.am b/src/Makefile.am index 492097361b..ce9316eced 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -170,6 +170,7 @@ DEFI_CORE_H = \ masternodes/poolpairs.h \ masternodes/undo.h \ masternodes/undos.h \ + masternodes/oracles.h \ memusage.h \ merkleblock.h \ miner.h \ @@ -366,6 +367,7 @@ libdefi_server_a_SOURCES = \ masternodes/accountshistory.cpp \ masternodes/anchors.cpp \ masternodes/criminals.cpp \ + masternodes/oracles.cpp \ masternodes/govvariables/lp_daily_dfi_reward.cpp \ masternodes/govvariables/lp_splits.cpp \ masternodes/gv.cpp \ @@ -378,6 +380,7 @@ libdefi_server_a_SOURCES = \ masternodes/rpc_masternodes.cpp \ masternodes/rpc_tokens.cpp \ masternodes/rpc_poolpair.cpp \ + masternodes/rpc_oracles.cpp \ masternodes/tokens.cpp \ masternodes/poolpairs.cpp \ masternodes/undos.cpp \ diff --git a/src/Makefile.test.include b/src/Makefile.test.include index f924d6fe5e..632dfac89e 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -125,6 +125,7 @@ DEFI_TESTS =\ test/multisig_tests.cpp \ test/net_tests.cpp \ test/netbase_tests.cpp \ + test/oracles_tests.cpp \ test/pmt_tests.cpp \ test/policyestimator_tests.cpp \ test/pow_tests.cpp \ diff --git a/src/amount.h b/src/amount.h index 3e8695e4fb..5d2f45880c 100644 --- a/src/amount.h +++ b/src/amount.h @@ -110,11 +110,7 @@ struct CTokenAmount { // simple std::pair is less informative CAmount nValue; std::string ToString() const { - const bool sign = nValue < 0; - const int64_t n_abs = (sign ? -nValue : nValue); - const int64_t quotient = n_abs / COIN; - const int64_t remainder = n_abs % COIN; - return strprintf("%s%d.%08d@%d", sign ? "-" : "", quotient, remainder, nTokenId.v); + return strprintf("%s@%d", GetDecimaleString(nValue), nTokenId.v); } Res Add(CAmount amount) { diff --git a/src/masternodes/masternodes.h b/src/masternodes/masternodes.h index 0616a4423e..373964b972 100644 --- a/src/masternodes/masternodes.h +++ b/src/masternodes/masternodes.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -244,6 +245,7 @@ class CCustomCSView , public CPoolPairView , public CGovView , public CAnchorConfirmsView + , public COracleView { public: // Increase version when underlaying tables are changed diff --git a/src/masternodes/mn_checks.cpp b/src/masternodes/mn_checks.cpp index 692a36b5b3..004d4cd4f4 100644 --- a/src/masternodes/mn_checks.cpp +++ b/src/masternodes/mn_checks.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -21,8 +22,6 @@ #include #include -#include -#include using namespace std; @@ -45,9 +44,14 @@ std::string ToString(CustomTxType type) { case CustomTxType::AccountToAccount: return "AccountToAccount"; case CustomTxType::AnyAccountsToAccounts: return "AnyAccountsToAccounts"; case CustomTxType::SetGovVariable: return "SetGovVariable"; + case CustomTxType::AppointOracle: return "AppointOracle"; + case CustomTxType::RemoveOracleAppoint: return "RemoveOracleAppoint"; + case CustomTxType::UpdateOracleAppoint: return "UpdateOracleAppoint"; + case CustomTxType::SetOracleData: return "SetOracleData"; case CustomTxType::AutoAuthPrep: return "AutoAuth"; - default: return "None"; + case CustomTxType::None: return "None"; } + return "None"; } static ResVal BurntTokens(CTransaction const & tx) { @@ -108,8 +112,14 @@ CCustomTxMessage customTypeToMessage(CustomTxType txType) { case CustomTxType::AccountToAccount: return CAccountToAccountMessage{}; case CustomTxType::AnyAccountsToAccounts: return CAnyAccountsToAccountsMessage{}; case CustomTxType::SetGovVariable: return CGovernanceMessage{}; - default: return CCustomTxMessageNone{}; - } + case CustomTxType::AppointOracle: return CAppointOracleMessage{}; + case CustomTxType::RemoveOracleAppoint: return CRemoveOracleAppointMessage{}; + case CustomTxType::UpdateOracleAppoint: return CUpdateOracleAppointMessage{}; + case CustomTxType::SetOracleData: return CSetOracleDataMessage{}; + case CustomTxType::AutoAuthPrep: return CCustomTxMessageNone{}; + case CustomTxType::None: return CCustomTxMessageNone{}; + } + return CCustomTxMessageNone{}; } extern std::string ScriptToString(CScript const& script); @@ -141,6 +151,13 @@ class CCustomMetadataParseVisitor : public boost::static_visitor return Res::Ok(); } + Res isPostEunosFork() const { + if(static_cast(height) < consensus.EunosHeight) { + return Res::Err("called before Eunos height"); + } + return Res::Ok(); + } + template Res serialize(T& obj) const { CDataStream ss(metadata, SER_NETWORK, PROTOCOL_VERSION); @@ -292,6 +309,26 @@ class CCustomMetadataParseVisitor : public boost::static_visitor return Res::Ok(); } + Res operator()(CAppointOracleMessage& obj) const { + auto res = isPostEunosFork(); + return !res ? res : serialize(obj); + } + + Res operator()(CRemoveOracleAppointMessage& obj) const { + auto res = isPostEunosFork(); + return !res ? res : serialize(obj); + } + + Res operator()(CUpdateOracleAppointMessage& obj) const { + auto res = isPostEunosFork(); + return !res ? res : serialize(obj); + } + + Res operator()(CSetOracleDataMessage& obj) const { + auto res = isPostEunosFork(); + return !res ? res : serialize(obj); + } + Res operator()(CCustomTxMessageNone&) const { return Res::Ok(); } @@ -485,6 +522,20 @@ class CCustomTxVisitor : public boost::static_visitor } return Res::Ok(); } + + Res normalizeTokenCurrencyPair(std::set& tokenCurrency) const { + std::set trimmed; + for (const auto& pair : tokenCurrency) { + auto token = trim_ws(pair.first).substr(0, CToken::MAX_TOKEN_SYMBOL_LENGTH); + auto currency = trim_ws(pair.second).substr(0, CToken::MAX_TOKEN_SYMBOL_LENGTH); + if (token.empty() || currency.empty()) { + return Res::Err("empty token / currency"); + } + trimmed.emplace(token, currency); + } + tokenCurrency = std::move(trimmed); + return Res::Ok(); + } }; class CCustomTxApplyVisitor : public CCustomTxVisitor @@ -941,6 +992,43 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor return Res::Ok(); } + Res operator()(const CAppointOracleMessage& obj) const { + if (!HasFoundationAuth()) { + return Res::Err("tx not from foundation member"); + } + auto msg = obj; + auto res = normalizeTokenCurrencyPair(msg.availablePairs); + return !res ? res : mnview.AppointOracle(tx.GetHash(), COracle(msg)); + } + + Res operator()(const CUpdateOracleAppointMessage& obj) const { + if (!HasFoundationAuth()) { + return Res::Err("tx not from foundation member"); + } + auto msg = obj.newOracleAppoint; + auto res = normalizeTokenCurrencyPair(msg.availablePairs); + return !res ? res : mnview.UpdateOracle(obj.oracleId, COracle(msg)); + } + + Res operator()(const CRemoveOracleAppointMessage& obj) const { + if (!HasFoundationAuth()) { + return Res::Err("tx not from foundation member"); + } + return mnview.RemoveOracle(obj.oracleId); + } + + Res operator()(const CSetOracleDataMessage& obj) const { + + auto oracle = mnview.GetOracleData(obj.oracleId); + if (!oracle) { + return Res::Err("failed to retrieve oracle <%s> from database", obj.oracleId.GetHex()); + } + if (!HasAuth(oracle.val->oracleAddress)) { + return Res::Err("tx must have at least one input from account owner"); + } + return mnview.SetOracleData(obj.oracleId, obj.timestamp, obj.tokenPrices); + } + Res operator()(const CCustomTxMessageNone&) const { return Res::Ok(); } @@ -1235,7 +1323,6 @@ ResVal ApplyAnchorRewardTx(CCustomCSView & mnview, CTransaction const & return { finMsg.btcTxHash, Res::Ok() }; } - ResVal ApplyAnchorRewardTxPlus(CCustomCSView & mnview, CTransaction const & tx, int height, std::vector const & metadata, Consensus::Params const & consensusParams) { if (height < consensusParams.DakotaHeight) { @@ -1304,9 +1391,7 @@ ResVal ApplyAnchorRewardTxPlus(CCustomCSView & mnview, CTransaction con return { finMsg.btcTxHash, Res::Ok() }; } - -bool IsMempooledCustomTxCreate(const CTxMemPool & pool, const uint256 & txid) -{ +bool IsMempooledCustomTxCreate(const CTxMemPool &pool, const uint256 &txid) { CTransactionRef ptx = pool.get(txid); if (ptx) { std::vector dummy; diff --git a/src/masternodes/mn_checks.h b/src/masternodes/mn_checks.h index 84257fbd71..e710489e87 100644 --- a/src/masternodes/mn_checks.h +++ b/src/masternodes/mn_checks.h @@ -30,44 +30,71 @@ enum CustomTxErrCodes : uint32_t { Fatal = uint32_t(1) << 31 // not allowed to fail }; -enum class CustomTxType : unsigned char +enum class CustomTxType : uint8_t { None = 0, // masternodes: - CreateMasternode = 'C', - ResignMasternode = 'R', + CreateMasternode = 'C', + ResignMasternode = 'R', // custom tokens: - CreateToken = 'T', - MintToken = 'M', - UpdateToken = 'N', // previous type, only DAT flag triggers - UpdateTokenAny = 'n', // new type of token's update with any flags/fields possible + CreateToken = 'T', + MintToken = 'M', + UpdateToken = 'N', // previous type, only DAT flag triggers + UpdateTokenAny = 'n', // new type of token's update with any flags/fields possible // dex orders - just not to overlap in future // CreateOrder = 'O', // DestroyOrder = 'E', // MatchOrders = 'A', //poolpair - CreatePoolPair = 'p', - UpdatePoolPair = 'u', - PoolSwap = 's', - AddPoolLiquidity = 'l', - RemovePoolLiquidity = 'r', + CreatePoolPair = 'p', + UpdatePoolPair = 'u', + PoolSwap = 's', + AddPoolLiquidity = 'l', + RemovePoolLiquidity = 'r', // accounts - UtxosToAccount = 'U', - AccountToUtxos = 'b', - AccountToAccount = 'B', - AnyAccountsToAccounts = 'a', + UtxosToAccount = 'U', + AccountToUtxos = 'b', + AccountToAccount = 'B', + AnyAccountsToAccounts = 'a', //set governance variable - SetGovVariable = 'G', + SetGovVariable = 'G', // Auto auth TX - AutoAuthPrep = 'A', + AutoAuthPrep = 'A', + // oracles + AppointOracle = 'o', + RemoveOracleAppoint = 'h', + UpdateOracleAppoint = 't', + SetOracleData = 'y', }; -inline CustomTxType CustomTxCodeToType(unsigned char ch) { - constexpr const char txtypes[] = "CRTMNnpuslrUbBaGA"; - if (memchr(txtypes, ch, sizeof(txtypes) - 1)) - return static_cast(ch); - else - return CustomTxType::None; +inline CustomTxType CustomTxCodeToType(uint8_t ch) { + auto type = static_cast(ch); + switch(type) { + case CustomTxType::CreateMasternode: + case CustomTxType::ResignMasternode: + case CustomTxType::CreateToken: + case CustomTxType::MintToken: + case CustomTxType::UpdateToken: + case CustomTxType::UpdateTokenAny: + case CustomTxType::CreatePoolPair: + case CustomTxType::UpdatePoolPair: + case CustomTxType::PoolSwap: + case CustomTxType::AddPoolLiquidity: + case CustomTxType::RemovePoolLiquidity: + case CustomTxType::UtxosToAccount: + case CustomTxType::AccountToUtxos: + case CustomTxType::AccountToAccount: + case CustomTxType::AnyAccountsToAccounts: + case CustomTxType::SetGovVariable: + case CustomTxType::AutoAuthPrep: + case CustomTxType::AppointOracle: + case CustomTxType::RemoveOracleAppoint: + case CustomTxType::UpdateOracleAppoint: + case CustomTxType::SetOracleData: + case CustomTxType::None: + return type; + } + return CustomTxType::None; } std::string ToString(CustomTxType type); @@ -99,6 +126,10 @@ struct CLiquidityMessage; struct CPoolSwapMessage; struct CRemoveLiquidityMessage; struct CUtxosToAccountMessage; +struct CAppointOracleMessage; +struct CRemoveOracleAppointMessage; +struct CUpdateOracleAppointMessage; +struct CSetOracleDataMessage; struct CCreateMasterNodeMessage { char operatorType; @@ -203,7 +234,11 @@ typedef boost::variant< CAccountToUtxosMessage, CAccountToAccountMessage, CAnyAccountsToAccountsMessage, - CGovernanceMessage + CGovernanceMessage, + CAppointOracleMessage, + CRemoveOracleAppointMessage, + CUpdateOracleAppointMessage, + CSetOracleDataMessage > CCustomTxMessage; CCustomTxMessage customTypeToMessage(CustomTxType txType); diff --git a/src/masternodes/oracles.cpp b/src/masternodes/oracles.cpp new file mode 100644 index 0000000000..e11747ec43 --- /dev/null +++ b/src/masternodes/oracles.cpp @@ -0,0 +1,130 @@ +// Copyright (c) DeFi Blockchain Developers +// Distributed under the MIT software license, see the accompanying +// file LICENSE or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include +#include + +const unsigned char COracleView::ByName::prefix = 'O'; // the big O for Oracles + +COracle::COracle(CAppointOracleMessage const& msg) : CAppointOracleMessage(msg) +{ +} + +bool COracle::SupportsPair(const std::string& token, const std::string& currency) const +{ + return availablePairs.count(std::make_pair(token, currency)) > 0; +} + +Res COracle::SetTokenPrice(const std::string& token, const std::string& currency, CAmount amount, int64_t timestamp) +{ + if (!SupportsPair(token, currency)) { + return Res::Err("token <%s> - currency <%s> is not allowed", token, currency); + } + + tokenPrices[token][currency] = std::make_pair(amount, timestamp); + + return Res::Ok(); +} + +Res COracleView::AppointOracle(const COracleId& oracleId, const COracle& oracle) +{ + if (!WriteBy(oracleId, oracle)) { + return Res::Err("failed to appoint the new oracle <%s>", oracleId.GetHex()); + } + + return Res::Ok(); +} + +Res COracleView::UpdateOracle(const COracleId& oracleId, const COracle& newOracle) +{ + COracle oracle; + if (!ReadBy(oracleId, oracle)) { + return Res::Err("oracle <%s> not found", oracleId.GetHex()); + } + + if (!newOracle.tokenPrices.empty()) { + return Res::Err("oracle <%s> has token prices on update", oracleId.GetHex()); + } + + oracle.weightage = newOracle.weightage; + oracle.oracleAddress = std::move(newOracle.oracleAddress); + + CTokenPricePoints allowedPrices; + for (const auto& tokenPrice : oracle.tokenPrices) { + const auto& token = tokenPrice.first; + for (const auto& price : tokenPrice.second) { + const auto& currency = price.first; + if (!newOracle.SupportsPair(token, currency)) { + continue; + } + allowedPrices[token][currency] = price.second; + } + } + + oracle.tokenPrices = std::move(allowedPrices); + oracle.availablePairs = std::move(newOracle.availablePairs); + + // no need to update oracles list + if (!WriteBy(oracleId, oracle)) { + return Res::Err("failed to save oracle <%s>", oracleId.GetHex()); + } + + return Res::Ok(); +} + +Res COracleView::RemoveOracle(const COracleId& oracleId) +{ + if (!ExistsBy(oracleId)) { + return Res::Err("oracle <%s> not found", oracleId.GetHex()); + } + + // remove oracle + if (!EraseBy(oracleId)) { + return Res::Err("failed to remove oracle <%s>", oracleId.GetHex()); + } + + return Res::Ok(); +} + +Res COracleView::SetOracleData(const COracleId& oracleId, int64_t timestamp, const CTokenPrices& tokenPrices) +{ + COracle oracle; + if (!ReadBy(oracleId, oracle)) { + return Res::Err("failed to read oracle %s from database", oracleId.GetHex()); + } + + for (const auto& tokenPrice : tokenPrices) { + const auto& token = tokenPrice.first; + for (const auto& price : tokenPrice.second) { + const auto& currency = price.first; + auto res = oracle.SetTokenPrice(token, currency, price.second, timestamp); + if (!res.ok) { + return res; + } + } + } + + if (!WriteBy(oracleId, oracle)) { + return Res::Err("failed to store oracle %s to database", oracleId.GetHex()); + } + + return Res::Ok(); +} + +ResVal COracleView::GetOracleData(const COracleId& oracleId) const +{ + COracle oracle; + if (!ReadBy(oracleId, oracle)) { + return Res::Err("oracle <%s> not found", oracleId.GetHex()); + } + + return ResVal(oracle, Res::Ok()); +} + +void COracleView::ForEachOracle(std::function)> callback, const COracleId& start) +{ + ForEach(callback, start); +} diff --git a/src/masternodes/oracles.h b/src/masternodes/oracles.h new file mode 100644 index 0000000000..50ac47e0ae --- /dev/null +++ b/src/masternodes/oracles.h @@ -0,0 +1,128 @@ +// Copyright (c) DeFi Blockchain Developers +// Distributed under the MIT software license, see the accompanying +// file LICENSE or http://www.opensource.org/licenses/mit-license.php. + +#ifndef DEFI_MASTERNODES_ORACLES_H +#define DEFI_MASTERNODES_ORACLES_H + +#include +#include +#include +#include +#include