From 94e293fc18efcccc8e5ef72bb2a31f9cb8e47026 Mon Sep 17 00:00:00 2001 From: Anthony Fieroni Date: Mon, 15 Feb 2021 08:38:41 +0200 Subject: [PATCH 01/17] Split custom tx processing into parsing and applying Signed-off-by: Anthony Fieroni --- src/Makefile.am | 3 +- src/amount.h | 2 +- src/consensus/tx_check.cpp | 67 +- src/consensus/tx_check.h | 5 +- src/consensus/tx_verify.cpp | 3 +- src/masternodes/mn_checks.cpp | 1761 ++++++++----------- src/masternodes/mn_checks.h | 206 ++- src/masternodes/mn_rpc.cpp | 31 +- src/masternodes/mn_rpc.h | 1 + src/masternodes/poolpairs.cpp | 2 +- src/masternodes/poolpairs.h | 26 +- src/masternodes/res.h | 18 +- src/masternodes/rpc_accounts.cpp | 61 +- src/masternodes/rpc_customtx.cpp | 198 +++ src/masternodes/rpc_masternodes.cpp | 20 +- src/masternodes/rpc_poolpair.cpp | 60 +- src/masternodes/rpc_tokens.cpp | 118 +- src/masternodes/tokens.cpp | 9 +- src/masternodes/tokens.h | 4 +- src/miner.cpp | 5 +- src/rpc/rawtransaction_util.cpp | 13 +- src/test/applytx_tests.cpp | 8 +- src/test/liquidity_tests.cpp | 36 +- src/txmempool.cpp | 2 +- src/validation.cpp | 10 +- test/functional/feature_accounts_n_utxos.py | 3 +- test/functional/feature_autoauth.py | 4 +- test/functional/feature_token_fork.py | 4 +- test/functional/feature_tokens_minting.py | 2 +- test/functional/feature_tokens_multisig.py | 2 +- test/functional/rpc_getcustomtx.py | 2 +- 31 files changed, 1209 insertions(+), 1477 deletions(-) create mode 100644 src/masternodes/rpc_customtx.cpp diff --git a/src/Makefile.am b/src/Makefile.am index 3dedc3e176..b926fa8c9e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -374,8 +374,9 @@ libdefi_server_a_SOURCES = \ masternodes/masternodes.cpp \ masternodes/mn_checks.cpp \ masternodes/mn_rpc.cpp \ - masternodes/rpc_masternodes.cpp \ masternodes/rpc_accounts.cpp \ + masternodes/rpc_customtx.cpp \ + masternodes/rpc_masternodes.cpp \ masternodes/rpc_tokens.cpp \ masternodes/rpc_poolpair.cpp \ masternodes/tokens.cpp \ diff --git a/src/amount.h b/src/amount.h index 79a201ad93..6b37b45f13 100644 --- a/src/amount.h +++ b/src/amount.h @@ -125,7 +125,7 @@ struct CTokenAmount { // simple std::pair is less informative // add auto sumRes = SafeAdd(this->nValue, amount); if (!sumRes.ok) { - return sumRes.res(); + return std::move(sumRes); } this->nValue = *sumRes.val; return Res::Ok(); diff --git a/src/consensus/tx_check.cpp b/src/consensus/tx_check.cpp index ac13963584..9edfc0a8dc 100644 --- a/src/consensus/tx_check.cpp +++ b/src/consensus/tx_check.cpp @@ -65,50 +65,39 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state, bool fChe return true; } -bool IsCriminalProofTx(CTransaction const & tx, std::vector & metadata) +bool ParseScriptByMarker(CScript const & script, + const std::vector & marker, + std::vector & metadata) { - if (!tx.IsCoinBase() || tx.vout.size() != 1 || tx.vout[0].nValue != 0) { - return false; - } - CScript const & memo = tx.vout[0].scriptPubKey; - CScript::const_iterator pc = memo.begin(); opcodetype opcode; - if (!memo.GetOp(pc, opcode) || opcode != OP_RETURN) { + auto pc = script.begin(); + if (!script.GetOp(pc, opcode) || opcode != OP_RETURN) { return false; } - if (!memo.GetOp(pc, opcode, metadata) || - (opcode > OP_PUSHDATA1 && - opcode != OP_PUSHDATA2 && - opcode != OP_PUSHDATA4) || - metadata.size() < DfCriminalTxMarker.size() + 1 || - memcmp(&metadata[0], &DfCriminalTxMarker[0], DfCriminalTxMarker.size()) != 0) { + if (!script.GetOp(pc, opcode, metadata) + || (opcode > OP_PUSHDATA1 && opcode != OP_PUSHDATA2 && opcode != OP_PUSHDATA4) + || metadata.size() < marker.size() + 1 + || memcmp(&metadata[0], &marker[0], marker.size()) != 0) { return false; } - metadata.erase(metadata.begin(), metadata.begin() + DfCriminalTxMarker.size()); + metadata.erase(metadata.begin(), metadata.begin() + marker.size()); return true; } -bool IsAnchorRewardTx(CTransaction const & tx, std::vector & metadata) +bool IsCriminalProofTx(CTransaction const & tx, std::vector & metadata) { - if (!tx.IsCoinBase() || tx.vout.size() != 2 || tx.vout[0].nValue != 0) { - return false; - } - CScript const & memo = tx.vout[0].scriptPubKey; - CScript::const_iterator pc = memo.begin(); - opcodetype opcode; - if (!memo.GetOp(pc, opcode) || opcode != OP_RETURN) { + if (!tx.IsCoinBase() || tx.vout.size() != 1 || tx.vout[0].nValue != 0) { return false; } - if (!memo.GetOp(pc, opcode, metadata) || - (opcode > OP_PUSHDATA1 && - opcode != OP_PUSHDATA2 && - opcode != OP_PUSHDATA4) || - metadata.size() < DfAnchorFinalizeTxMarker.size() + 1 || - memcmp(&metadata[0], &DfAnchorFinalizeTxMarker[0], DfAnchorFinalizeTxMarker.size()) != 0) { + return ParseScriptByMarker(tx.vout[0].scriptPubKey, DfCriminalTxMarker, metadata); +} + +bool IsAnchorRewardTx(CTransaction const & tx, std::vector & metadata) +{ + if (!tx.IsCoinBase() || tx.vout.size() != 2 || tx.vout[0].nValue != 0) { return false; } - metadata.erase(metadata.begin(), metadata.begin() + DfAnchorFinalizeTxMarker.size()); - return true; + return ParseScriptByMarker(tx.vout[0].scriptPubKey, DfAnchorFinalizeTxMarker, metadata); } bool IsAnchorRewardTxPlus(CTransaction const & tx, std::vector & metadata) @@ -116,21 +105,5 @@ bool IsAnchorRewardTxPlus(CTransaction const & tx, std::vector & if (!tx.IsCoinBase() || tx.vout.size() != 2 || tx.vout[0].nValue != 0) { return false; } - CScript const & memo = tx.vout[0].scriptPubKey; - CScript::const_iterator pc = memo.begin(); - opcodetype opcode; - if (!memo.GetOp(pc, opcode) || opcode != OP_RETURN) { - return false; - } - if (!memo.GetOp(pc, opcode, metadata) || - (opcode > OP_PUSHDATA1 && - opcode != OP_PUSHDATA2 && - opcode != OP_PUSHDATA4) || - metadata.size() < DfAnchorFinalizeTxMarkerPlus.size() + 1 || - memcmp(&metadata[0], &DfAnchorFinalizeTxMarkerPlus[0], DfAnchorFinalizeTxMarkerPlus.size()) != 0) { - return false; - } - metadata.erase(metadata.begin(), metadata.begin() + DfAnchorFinalizeTxMarkerPlus.size()); - return true; + return ParseScriptByMarker(tx.vout[0].scriptPubKey, DfAnchorFinalizeTxMarkerPlus, metadata); } - diff --git a/src/consensus/tx_check.h b/src/consensus/tx_check.h index aa621ca437..d92aebe4ec 100644 --- a/src/consensus/tx_check.h +++ b/src/consensus/tx_check.h @@ -19,12 +19,15 @@ extern const std::vector DfCriminalTxMarker; extern const std::vector DfAnchorFinalizeTxMarker; extern const std::vector DfAnchorFinalizeTxMarkerPlus; +class CScript; class CTransaction; class CValidationState; bool CheckTransaction(const CTransaction& tx, CValidationState& state, bool fCheckDuplicateInputs=true); -/// moved here (!!) due to strange linker errors under mac/win builds +bool ParseScriptByMarker(CScript const & script, + const std::vector & marker, + std::vector & metadata); bool IsCriminalProofTx(CTransaction const & tx, std::vector & metadata); bool IsAnchorRewardTx(CTransaction const & tx, std::vector & metadata); bool IsAnchorRewardTxPlus(CTransaction const & tx, std::vector & metadata); diff --git a/src/consensus/tx_verify.cpp b/src/consensus/tx_verify.cpp index ba2329fc0b..646e167567 100644 --- a/src/consensus/tx_verify.cpp +++ b/src/consensus/tx_verify.cpp @@ -221,7 +221,8 @@ bool Consensus::CheckTxInputs(const CTransaction& tx, CValidationState& state, c const auto txType = GuessCustomTxType(tx, dummy); if (NotAllowedToFail(txType, nSpendHeight)) { - auto res = ApplyCustomTx(const_cast(*mnview), inputs, tx, chainparams.GetConsensus(), nSpendHeight, 0, uint64_t{0}, true); // note for 'isCheck == true' here; 'zero' for txn is dummy value + CCustomCSView discardCache(const_cast(*mnview)); + auto res = ApplyCustomTx(discardCache, inputs, tx, chainparams.GetConsensus(), nSpendHeight); if (!res.ok && (res.code & CustomTxErrCodes::Fatal)) { return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-customtx", res.msg); } diff --git a/src/masternodes/mn_checks.cpp b/src/masternodes/mn_checks.cpp index cc677909be..0c4c439e17 100644 --- a/src/masternodes/mn_checks.cpp +++ b/src/masternodes/mn_checks.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -18,21 +19,43 @@ #include #include -#include - #include #include #include using namespace std; +std::string ToString(CustomTxType type) { + switch (type) + { + case CustomTxType::CreateMasternode: return "CreateMasternode"; + case CustomTxType::ResignMasternode: return "ResignMasternode"; + case CustomTxType::CreateToken: return "CreateToken"; + case CustomTxType::UpdateToken: return "UpdateToken"; + case CustomTxType::UpdateTokenAny: return "UpdateTokenAny"; + case CustomTxType::MintToken: return "MintToken"; + case CustomTxType::CreatePoolPair: return "CreatePoolPair"; + case CustomTxType::UpdatePoolPair: return "UpdatePoolPair"; + case CustomTxType::PoolSwap: return "PoolSwap"; + case CustomTxType::AddPoolLiquidity: return "AddPoolLiquidity"; + case CustomTxType::RemovePoolLiquidity: return "RemovePoolLiquidity"; + case CustomTxType::UtxosToAccount: return "UtxosToAccount"; + case CustomTxType::AccountToUtxos: return "AccountToUtxos"; + case CustomTxType::AccountToAccount: return "AccountToAccount"; + case CustomTxType::AnyAccountsToAccounts: return "AnyAccountsToAccounts"; + case CustomTxType::SetGovVariable: return "SetGovVariable"; + case CustomTxType::AutoAuthPrep: return "AutoAuth"; + default: return "None"; + } +} + static ResVal BurntTokens(CTransaction const & tx) { - CBalances balances{}; + CBalances balances; for (const auto& out : tx.vout) { if (out.scriptPubKey.size() > 0 && out.scriptPubKey[0] == OP_RETURN) { auto res = balances.Add(out.TokenAmount()); if (!res.ok) { - return {res}; + return res; } } } @@ -40,11 +63,11 @@ static ResVal BurntTokens(CTransaction const & tx) { } static ResVal MintedTokens(CTransaction const & tx, uint32_t mintingOutputsStart) { - CBalances balances{}; + CBalances balances; for (uint32_t i = mintingOutputsStart; i < (uint32_t) tx.vout.size(); i++) { auto res = balances.Add(tx.vout[i].TokenAmount()); if (!res.ok) { - return {res}; + return res; } } return {balances, Res::Ok()}; @@ -52,1316 +75,912 @@ static ResVal MintedTokens(CTransaction const & tx, uint32_t mintingO CPubKey GetPubkeyFromScriptSig(CScript const & scriptSig) { - CScript::const_iterator pc = scriptSig.begin(); opcodetype opcode; std::vector data; + CScript::const_iterator pc = scriptSig.begin(); // Signature first, then pubkey. I think, that in all cases it will be OP_PUSHDATA1, but... - if (!scriptSig.GetOp(pc, opcode, data) || - (opcode > OP_PUSHDATA1 && - opcode != OP_PUSHDATA2 && - opcode != OP_PUSHDATA4)) - { - return CPubKey(); - } - if (!scriptSig.GetOp(pc, opcode, data) || - (opcode > OP_PUSHDATA1 && - opcode != OP_PUSHDATA2 && - opcode != OP_PUSHDATA4)) - { + if (!scriptSig.GetOp(pc, opcode, data) + || (opcode > OP_PUSHDATA1 && opcode != OP_PUSHDATA2 && opcode != OP_PUSHDATA4) + || !scriptSig.GetOp(pc, opcode, data) + || (opcode > OP_PUSHDATA1 && opcode != OP_PUSHDATA2 && opcode != OP_PUSHDATA4)) { return CPubKey(); } return CPubKey(data); } -// not used for now (cause works only with signed inputs) -bool HasAuth(CTransaction const & tx, CKeyID const & auth) -{ - for (auto input : tx.vin) +CCustomTxMessage customTypeToMessage(CustomTxType txType) { + switch (txType) { - if (input.scriptWitness.IsNull()) { - if (GetPubkeyFromScriptSig(input.scriptSig).GetID() == auth) - return true; - } - else { - auto test = CPubKey(input.scriptWitness.stack.back()); - if (test.GetID() == auth) - return true; - } + case CustomTxType::CreateMasternode: return CCreateMasterNodeMessage{}; + case CustomTxType::ResignMasternode: return CResignMasterNodeMessage{}; + case CustomTxType::CreateToken: return CCreateTokenMessage{}; + case CustomTxType::UpdateToken: return CUpdateTokenPreAMKMessage{}; + case CustomTxType::UpdateTokenAny: return CUpdateTokenMessage{}; + case CustomTxType::MintToken: return CMintTokensMessage{}; + case CustomTxType::CreatePoolPair: return CCreatePoolPairMessage{}; + case CustomTxType::UpdatePoolPair: return CUpdatePoolPairMessage{}; + case CustomTxType::PoolSwap: return CPoolSwapMessage{}; + case CustomTxType::AddPoolLiquidity: return CLiquidityMessage{}; + case CustomTxType::RemovePoolLiquidity: return CRemoveLiquidityMessage{}; + case CustomTxType::UtxosToAccount: return CUtxosToAccountMessage{}; + case CustomTxType::AccountToUtxos: return CAccountToUtxosMessage{}; + case CustomTxType::AccountToAccount: return CAccountToAccountMessage{}; + case CustomTxType::AnyAccountsToAccounts: return CAnyAccountsToAccountsMessage{}; + case CustomTxType::SetGovVariable: return CGovernanceMessage{}; + default: return CCustomTxMessageNone{}; } - return false; } -bool HasAuth(CTransaction const & tx, CCoinsViewCache const & coins, CScript const & auth) -{ - for (auto input : tx.vin) { - const Coin& coin = coins.AccessCoin(input.prevout); - if (!coin.IsSpent() && coin.out.scriptPubKey == auth) - return true; - } - return false; -} +extern std::string ScriptToString(CScript const& script); -bool HasCollateralAuth(CTransaction const & tx, CCoinsViewCache const & coins, uint256 const & collateralTx) +class CCustomMetadataParseVisitor : public boost::static_visitor { - const Coin& auth = coins.AccessCoin(COutPoint(collateralTx, 1)); // always n=1 output - return HasAuth(tx, coins, auth.out.scriptPubKey); -} + uint32_t height; + const Consensus::Params& consensus; + const std::vector& metadata; -bool HasFoundationAuth(CTransaction const & tx, CCoinsViewCache const & coins, Consensus::Params const & consensusParams) -{ - for (auto input : tx.vin) { - const Coin& coin = coins.AccessCoin(input.prevout); - if (!coin.IsSpent() && consensusParams.foundationMembers.find(coin.out.scriptPubKey) != consensusParams.foundationMembers.end()) - return true; + Res isPostAMKFork() const { + if(static_cast(height) < consensus.AMKHeight) { + return Res::Err("called before AMK height"); + } + return Res::Ok(); } - return false; -} -Res ApplyCustomTx(CCustomCSView & base_mnview, CCoinsViewCache const & coins, CTransaction const & tx, Consensus::Params const & consensusParams, uint32_t height, const uint64_t &time, uint32_t txn, bool isCheck, bool skipAuth) -{ - Res res = Res::Ok(); - - if ((tx.IsCoinBase() && height > 0) || tx.vout.empty()) { // genesis contains custom coinbase txs - return Res::Ok(); // not "custom" tx + Res isPostBayfrontFork() const { + if(static_cast(height) < consensus.BayfrontHeight) { + return Res::Err("called before Bayfront height"); + } + return Res::Ok(); } - CustomTxType guess; - std::vector metadata; - - try { - guess = GuessCustomTxType(tx, metadata); - } catch (std::exception& e) { - return Res::Err("GuessCustomTxType: %s", e.what()); - } catch (...) { - return Res::Err("GuessCustomTxType: unexpected error"); + Res isPostBayfrontGardensFork() const { + if(static_cast(height) < consensus.BayfrontGardensHeight) { + return Res::Err("called before Bayfront Gardens height"); + } + return Res::Ok(); } - CAccountsHistoryStorage mnview(base_mnview, height, txn, tx.GetHash(), (uint8_t)guess); - try { - // Check if it is custom tx with metadata - switch (guess) - { - case CustomTxType::CreateMasternode: - res = ApplyCreateMasternodeTx(mnview, tx, height, time, metadata); - break; - case CustomTxType::ResignMasternode: - res = ApplyResignMasternodeTx(mnview, coins, tx, height, metadata, skipAuth); - break; - case CustomTxType::CreateToken: - res = ApplyCreateTokenTx(mnview, coins, tx, height, metadata, consensusParams, skipAuth); - break; - case CustomTxType::UpdateToken: - res = ApplyUpdateTokenTx(mnview, coins, tx, height, metadata, consensusParams, skipAuth); - break; - case CustomTxType::UpdateTokenAny: - res = ApplyUpdateTokenAnyTx(mnview, coins, tx, height, metadata, consensusParams, skipAuth); - break; - case CustomTxType::MintToken: - res = ApplyMintTokenTx(mnview, coins, tx, height, metadata, consensusParams, skipAuth); - break; - case CustomTxType::CreatePoolPair: - res = ApplyCreatePoolPairTx(mnview, coins, tx, height, metadata, consensusParams, skipAuth); - break; - case CustomTxType::UpdatePoolPair: - res = ApplyUpdatePoolPairTx(mnview, coins, tx, height, metadata, consensusParams, skipAuth); - break; - case CustomTxType::PoolSwap: - res = ApplyPoolSwapTx(mnview, coins, tx, height, metadata, consensusParams, skipAuth); - break; - case CustomTxType::AddPoolLiquidity: - res = ApplyAddPoolLiquidityTx(mnview, coins, tx, height, metadata, consensusParams, skipAuth); - break; - case CustomTxType::RemovePoolLiquidity: - res = ApplyRemovePoolLiquidityTx(mnview, coins, tx, height, metadata, consensusParams, skipAuth); - break; - case CustomTxType::UtxosToAccount: - res = ApplyUtxosToAccountTx(mnview, tx, height, metadata, consensusParams); - break; - case CustomTxType::AccountToUtxos: - res = ApplyAccountToUtxosTx(mnview, coins, tx, height, metadata, consensusParams, skipAuth); - break; - case CustomTxType::AccountToAccount: - res = ApplyAccountToAccountTx(mnview, coins, tx, height, metadata, consensusParams, skipAuth); - break; - case CustomTxType::SetGovVariable: - res = ApplySetGovernanceTx(mnview, coins, tx, height, metadata, consensusParams, skipAuth); - break; - case CustomTxType::AnyAccountsToAccounts: - res = ApplyAnyAccountsToAccountsTx(mnview, coins, tx, height, metadata, consensusParams, skipAuth); - break; - default: - return Res::Ok(); // not "custom" tx - } - // list of transactions which aren't allowed to fail, - // post Dakota height all failed txs are marked as fatal - if (!res.ok && (NotAllowedToFail(guess, height) || height >= consensusParams.DakotaHeight)) { - res.code |= CustomTxErrCodes::Fatal; + template + Res serialize(T& obj) const { + CDataStream ss(metadata, SER_NETWORK, PROTOCOL_VERSION); + ss >> obj; + if (!ss.empty()) { + return Res::Err("deserialization failed: excess %d bytes", ss.size()); } - } catch (std::exception& e) { - res = Res::Err(e.what()); - } catch (...) { - res = Res::Err("unexpected error"); + return Res::Ok(); } - if (!res.ok || isCheck) { // 'isCheck' - don't create undo nor flush to the upper view - return res; - } +public: + CCustomMetadataParseVisitor(uint32_t height, + const Consensus::Params& consensus, + const std::vector& metadata) + : height(height), consensus(consensus), metadata(metadata) {} - // construct undo - auto& flushable = dynamic_cast(mnview.GetRaw()); - auto undo = CUndo::Construct(base_mnview.GetRaw(), flushable.GetRaw()); - // flush changes - mnview.Flush(); - // write undo - if (!undo.before.empty()) { - base_mnview.SetUndo(UndoKey{height, tx.GetHash()}, undo); + Res operator()(CCreateMasterNodeMessage& obj) const { + return serialize(obj); } - return res; -} - -/* - * Checks if given tx is 'txCreateMasternode'. Creates new MN if all checks are passed - * Issued by: any - */ -Res ApplyCreateMasternodeTx(CCustomCSView & mnview, CTransaction const & tx, uint32_t height, const uint64_t &time, std::vector const & metadata, UniValue *rpcInfo) -{ - // Check quick conditions first - if (tx.vout.size() < 2 || - tx.vout[0].nValue < GetMnCreationFee(height) || tx.vout[0].nTokenId != DCT_ID{0} || - tx.vout[1].nValue != GetMnCollateralAmount(height) || tx.vout[1].nTokenId != DCT_ID{0} - ) { - return Res::Err("%s: %s", __func__, "malformed tx vouts (wrong creation fee or collateral amount)"); + Res operator()(CResignMasterNodeMessage& obj) const { + if (metadata.size() != sizeof(obj)) { + return Res::Err("metadata must contain 32 bytes"); + } + return serialize(obj); } - CMasternode node; - CDataStream ss(metadata, SER_NETWORK, PROTOCOL_VERSION); - ss >> node.operatorType; - ss >> node.operatorAuthAddress; - if (!ss.empty()) { - return Res::Err("%s: deserialization failed: excess %d bytes", __func__, ss.size()); + Res operator()(CCreateTokenMessage& obj) const { + auto res = isPostAMKFork(); + return !res ? res : serialize(obj); } - CTxDestination dest; - if (ExtractDestination(tx.vout[1].scriptPubKey, dest)) { - if (dest.which() == PKHashType) { - node.ownerType = 1; - node.ownerAuthAddress = CKeyID(*boost::get(&dest)); + Res operator()(CUpdateTokenPreAMKMessage& obj) const { + auto res = isPostAMKFork(); + if (!res) { + return res; } - else if (dest.which() == WitV0KeyHashType) { - node.ownerType = 4; - node.ownerAuthAddress = CKeyID(*boost::get(&dest)); + if(isPostBayfrontFork()) { + return Res::Err("called post Bayfront height"); } + return serialize(obj); } - node.creationHeight = height; - // Return here to avoid addresses exit error - if (rpcInfo) { - rpcInfo->pushKV("collateralamount", ValueFromAmount(GetMnCollateralAmount(height))); - rpcInfo->pushKV("masternodeoperator", EncodeDestination(node.operatorType == 1 ? CTxDestination(PKHash(node.operatorAuthAddress)) : - CTxDestination(WitnessV0KeyHash(node.operatorAuthAddress)))); - return Res::Ok(); + Res operator()(CUpdateTokenMessage& obj) const { + auto res = isPostBayfrontFork(); + return !res ? res : serialize(obj); } - auto res = mnview.CreateMasternode(tx.GetHash(), node); - if (!res.ok) { - return Res::Err("%s: %s", __func__, res.msg); + Res operator()(CMintTokensMessage& obj) const { + auto res = isPostAMKFork(); + return !res ? res : serialize(obj); } - // Build coinage from the point of masternode creation - if (height >= static_cast(Params().GetConsensus().DakotaCrescentHeight)) - { - mnview.SetMasternodeLastBlockTime(node.operatorAuthAddress, static_cast(height), time); + Res operator()(CPoolSwapMessage& obj) const { + auto res = isPostBayfrontFork(); + return !res ? res : serialize(obj); } - return Res::Ok(); -} - -Res ApplyResignMasternodeTx(CCustomCSView & mnview, CCoinsViewCache const & coins, CTransaction const & tx, uint32_t height, const std::vector & metadata, bool skipAuth, UniValue *rpcInfo) -{ - if (metadata.size() != sizeof(uint256)) { - return Res::Err("%s: metadata must contain 32 bytes", __func__); - } - uint256 nodeId(metadata); - auto const node = mnview.GetMasternode(nodeId); - if (!node) { - return Res::Err("%s: node %s does not exist", __func__, nodeId.ToString()); - } - if (!skipAuth && !HasCollateralAuth(tx, coins, nodeId)) { - return Res::Err("%s %s: %s", __func__, nodeId.ToString(), "tx must have at least one input from masternode owner"); + Res operator()(CLiquidityMessage& obj) const { + auto res = isPostBayfrontFork(); + return !res ? res : serialize(obj); } - // Return here to avoid state is not ENABLED error - if (rpcInfo) { - rpcInfo->pushKV("id", nodeId.GetHex()); - return Res::Ok(); + Res operator()(CRemoveLiquidityMessage& obj) const { + auto res = isPostBayfrontFork(); + return !res ? res : serialize(obj); } - auto res = mnview.ResignMasternode(nodeId, tx.GetHash(), height); - if (!res.ok) { - return Res::Err("%s %s: %s", __func__, nodeId.ToString(), res.msg); + Res operator()(CUtxosToAccountMessage& obj) const { + auto res = isPostAMKFork(); + return !res ? res : serialize(obj); } - return Res::Ok(); -} -Res ApplyCreateTokenTx(CCustomCSView & mnview, CCoinsViewCache const & coins, CTransaction const & tx, uint32_t height, std::vector const & metadata, Consensus::Params const & consensusParams, bool skipAuth, UniValue *rpcInfo) -{ - if((int)height < consensusParams.AMKHeight) { return Res::Err("Token tx before AMK height (block %d)", consensusParams.AMKHeight); } - - // Check quick conditions first - if (tx.vout.size() < 2 || - tx.vout[0].nValue < GetTokenCreationFee(height) || tx.vout[0].nTokenId != DCT_ID{0} || - tx.vout[1].nValue != GetTokenCollateralAmount() || tx.vout[1].nTokenId != DCT_ID{0} - ) { - return Res::Err("%s: %s", __func__, "malformed tx vouts (wrong creation fee or collateral amount)"); + Res operator()(CAccountToUtxosMessage& obj) const { + auto res = isPostAMKFork(); + return !res ? res : serialize(obj); } - CTokenImplementation token; - CDataStream ss(metadata, SER_NETWORK, PROTOCOL_VERSION); - ss >> static_cast(token); - if (!ss.empty()) { - return Res::Err("%s: deserialization failed: excess %d bytes", __func__, ss.size()); + Res operator()(CAccountToAccountMessage& obj) const { + auto res = isPostAMKFork(); + return !res ? res : serialize(obj); } - token.symbol = trim_ws(token.symbol).substr(0, CToken::MAX_TOKEN_SYMBOL_LENGTH); - token.name = trim_ws(token.name).substr(0, CToken::MAX_TOKEN_NAME_LENGTH); - token.creationTx = tx.GetHash(); - token.creationHeight = height; - - //check foundation auth - if((token.IsDAT()) && !skipAuth && !HasFoundationAuth(tx, coins, consensusParams)) - {//no need to check Authority if we don't create isDAT - return Res::Err("%s: %s", __func__, "tx not from foundation member"); + Res operator()(CAnyAccountsToAccountsMessage& obj) const { + auto res = isPostBayfrontGardensFork(); + return !res ? res : serialize(obj); } - if ((int)height >= consensusParams.BayfrontHeight) { // formal compatibility if someone cheat and create LPS token on the pre-bayfront node - if(token.IsPoolShare()) { - return Res::Err("%s: %s", __func__, "Cant't manually create 'Liquidity Pool Share' token; use poolpair creation"); + Res operator()(CCreatePoolPairMessage& obj) const { + auto res = isPostBayfrontFork(); + if (!res) { + return res; } - } - // Return here to avoid already exist error - if (rpcInfo) { - rpcInfo->pushKV("creationTx", token.creationTx.GetHex()); - rpcInfo->pushKV("name", token.name); - rpcInfo->pushKV("symbol", token.symbol); - rpcInfo->pushKV("isDAT", token.IsDAT()); - rpcInfo->pushKV("mintable", token.IsMintable()); - rpcInfo->pushKV("tradeable", token.IsTradeable()); - rpcInfo->pushKV("finalized", token.IsFinalized()); + CDataStream ss(metadata, SER_NETWORK, PROTOCOL_VERSION); + ss >> obj.poolPair; + ss >> obj.pairSymbol; + // Read custom pool rewards + if (static_cast(height) >= consensus.ClarkeQuayHeight && !ss.empty()) { + ss >> obj.rewards; + } + if (!ss.empty()) { + return Res::Err("deserialization failed: excess %d bytes", ss.size()); + } return Res::Ok(); } - auto res = mnview.CreateToken(token, (int)height < consensusParams.BayfrontHeight); - if (!res.ok) { - return Res::Err("%s %s: %s", __func__, token.symbol, res.msg); - } - - return Res::Ok(); -} - -/// @deprecated version of updatetoken tx, prefer using UpdateTokenAny after "bayfront" fork -Res ApplyUpdateTokenTx(CCustomCSView & mnview, CCoinsViewCache const & coins, CTransaction const & tx, uint32_t height, std::vector const & metadata, Consensus::Params const & consensusParams, bool skipAuth, UniValue *rpcInfo) -{ - if((int)height < consensusParams.AMKHeight) { return Res::Err("Token tx before AMK height (block %d)", consensusParams.AMKHeight); } - - if ((int)height >= consensusParams.BayfrontHeight) { - return Res::Err("Old-style updatetoken tx forbidden after Bayfront height"); - } - - uint256 tokenTx; - bool isDAT; - CDataStream ss(metadata, SER_NETWORK, PROTOCOL_VERSION); - ss >> tokenTx; - ss >> isDAT; - if (!ss.empty()) { - return Res::Err("%s: deserialization failed: excess %d bytes", __func__, ss.size()); - } - - auto pair = mnview.GetTokenByCreationTx(tokenTx); - if (!pair) { - return Res::Err("%s: token with creationTx %s does not exist", __func__, tokenTx.ToString()); - } - CTokenImplementation const & token = pair->second; - - //check foundation auth - if (!skipAuth && !HasFoundationAuth(tx, coins, consensusParams)) { - return Res::Err("%s: %s", __func__, "Is not a foundation owner"); - } - - if(token.IsDAT() != isDAT && pair->first >= CTokensView::DCT_ID_START) - { - CToken newToken = static_cast(token); // keeps old and triggers only DAT! - newToken.flags ^= (uint8_t)CToken::TokenFlags::DAT; - - auto res = mnview.UpdateToken(token.creationTx, newToken, true); - if (!res.ok) { - return Res::Err("%s %s: %s", __func__, token.symbol, res.msg); + Res operator()(CUpdatePoolPairMessage& obj) const { + auto res = isPostBayfrontFork(); + if (!res) { + return res; } - } - // Only isDAT changed in deprecated ApplyUpdateTokenTx - if (rpcInfo) { - rpcInfo->pushKV("isDAT", token.IsDAT()); - } - - return Res::Ok(); -} + CDataStream ss(metadata, SER_NETWORK, PROTOCOL_VERSION); + ss >> obj.poolId; + ss >> obj.status; + ss >> obj.commission; + ss >> obj.ownerAddress; + // Read custom pool rewards + if (static_cast(height) >= consensus.ClarkeQuayHeight && !ss.empty()) { + ss >> obj.rewards; + } -Res ApplyUpdateTokenAnyTx(CCustomCSView & mnview, CCoinsViewCache const & coins, CTransaction const & tx, uint32_t height, std::vector const & metadata, Consensus::Params const & consensusParams, bool skipAuth, UniValue *rpcInfo) -{ - if ((int)height < consensusParams.BayfrontHeight) { - return Res::Err("Improved updatetoken tx before Bayfront height"); - } - - uint256 tokenTx; - CToken newToken; - CDataStream ss(metadata, SER_NETWORK, PROTOCOL_VERSION); - ss >> tokenTx; - ss >> newToken; - if (!ss.empty()) { - return Res::Err("%s: deserialization failed: excess %d bytes", __func__, ss.size()); - } - - auto pair = mnview.GetTokenByCreationTx(tokenTx); - if (!pair) { - return Res::Err("%s: token with creationTx %s does not exist", __func__, tokenTx.ToString()); - } - if (pair->first == DCT_ID{0}) { - return Res::Err("Can't alter DFI token!"); // may be redundant cause DFI is 'finalized' + if (!ss.empty()) { + return Res::Err("deserialization failed: excess %d bytes", ss.size()); + } + return Res::Ok(); } - CTokenImplementation const & token = pair->second; - - // need to check it exectly here cause lps has no collateral auth (that checked next) - if (token.IsPoolShare()) - return Res::Err("%s: token %s is the LPS token! Can't alter pool share's tokens!", __func__, tokenTx.ToString()); - - // check auth, depends from token's "origins" - const Coin& auth = coins.AccessCoin(COutPoint(token.creationTx, 1)); // always n=1 output - bool isFoundersToken = consensusParams.foundationMembers.find(auth.out.scriptPubKey) != consensusParams.foundationMembers.end(); - - if (!skipAuth) { - if (isFoundersToken && !HasFoundationAuth(tx, coins, consensusParams)) { - return Res::Err("%s: %s", __func__, "tx not from foundation member"); - } - else if (!HasCollateralAuth(tx, coins, token.creationTx)) { - return Res::Err("%s: %s", __func__, "tx must have at least one input from token owner"); + Res operator()(CGovernanceMessage& obj) const { + auto res = isPostBayfrontFork(); + if (!res) { + return res; } - - // Check for isDAT change in non-foundation token after set height - if (static_cast(height) >= consensusParams.BayfrontMarinaHeight) - { - //check foundation auth - if((newToken.IsDAT() != token.IsDAT()) && !HasFoundationAuth(tx, coins, consensusParams)) - {//no need to check Authority if we don't create isDAT - return Res::Err("%s: %s", __func__, "can't set isDAT to true, tx not from foundation member"); + std::string name; + CDataStream ss(metadata, SER_NETWORK, PROTOCOL_VERSION); + while(!ss.empty()) { + ss >> name; + auto var = GovVariable::Create(name); + if (!var) { + return Res::Err("'%s': variable does not registered", name); } + ss >> *var; + obj.govs.insert(std::move(var)); } + return Res::Ok(); } - auto res = mnview.UpdateToken(token.creationTx, newToken, false); - if (!res.ok) { - return Res::Err("%s %s: %s", __func__, token.symbol, res.msg); - } - - if (rpcInfo) { - rpcInfo->pushKV("name", newToken.name); - rpcInfo->pushKV("symbol", newToken.symbol); - rpcInfo->pushKV("isDAT", newToken.IsDAT()); - rpcInfo->pushKV("mintable", newToken.IsDAT()); - rpcInfo->pushKV("tradeable", newToken.IsDAT()); - rpcInfo->pushKV("finalized", newToken.IsDAT()); + Res operator()(CCustomTxMessageNone&) const { + return Res::Ok(); } +}; - return Res::Ok(); -} - -Res ApplyMintTokenTx(CCustomCSView & mnview, CCoinsViewCache const & coins, CTransaction const & tx, uint32_t height, std::vector const & metadata, Consensus::Params const & consensusParams, bool skipAuth, UniValue *rpcInfo) +class CCustomTxApplyVisitor : public boost::static_visitor { - if((int)height < consensusParams.AMKHeight) { return Res::Err("Token tx before AMK height (block %d)", consensusParams.AMKHeight); } - - CBalances minted; - CDataStream ss(metadata, SER_NETWORK, PROTOCOL_VERSION); - ss >> minted; - if (!ss.empty()) { - return Res::Err("%s: deserialization failed: excess %d bytes", __func__, ss.size()); + uint64_t time; + uint32_t height; + CCustomCSView& mnview; + const CTransaction& tx; + const CCoinsViewCache& coins; + const Consensus::Params& consensus; + + bool HasAuth(const CScript& auth) const { + for (const auto& input : tx.vin) { + const Coin& coin = coins.AccessCoin(input.prevout); + if (!coin.IsSpent() && coin.out.scriptPubKey == auth) { + return true; + } + } + return false; } - // check auth and increase balance of token's owner - for (auto const & kv : minted.balances) { - DCT_ID tokenId = kv.first; - - auto token = mnview.GetToken(kv.first); - if (!token) { - return Res::Err("%s: token %s does not exist!", tokenId.ToString()); // pre-bayfront throws but it affects only the message + Res HasCollateralAuth(const uint256& collateralTx) const { + const Coin& auth = coins.AccessCoin(COutPoint(collateralTx, 1)); // always n=1 output + if (!HasAuth(auth.out.scriptPubKey)) { + return Res::Err("tx must have at least one input from the owner"); } - auto tokenImpl = static_cast(*token); + return Res::Ok(); + } - if (tokenImpl.destructionTx != uint256{}) { - return Res::Err("%s: token %s already destroyed at height %i by tx %s", __func__, tokenImpl.symbol, // pre-bayfront throws but it affects only the message - tokenImpl.destructionHeight, tokenImpl.destructionTx.GetHex()); + Res HasFoundationAuth() const { + for (const auto& input : tx.vin) { + const Coin& coin = coins.AccessCoin(input.prevout); + if (!coin.IsSpent() && consensus.foundationMembers.count(coin.out.scriptPubKey) > 0) { + return Res::Ok(); + } } - const Coin& auth = coins.AccessCoin(COutPoint(tokenImpl.creationTx, 1)); // always n=1 output + return Res::Err("tx not from foundation member"); + } - // pre-bayfront logic: - if ((int)height < consensusParams.BayfrontHeight) { - if (tokenId < CTokensView::DCT_ID_START) - return Res::Err("%s: token %s is a 'stable coin', can't mint stable coin!", __func__, tokenId.ToString()); + Res eraseEmptyBalances(TAmounts& balances) const { + for (auto it = balances.begin(), next_it = it; it != balances.end(); it = next_it) { + ++next_it; - if (!skipAuth && !HasAuth(tx, coins, auth.out.scriptPubKey)) { - return Res::Err("%s: %s", __func__, "tx must have at least one input from token owner"); + auto token = mnview.GetToken(it->first); + if (!token) { + return Res::Err("reward token %d does not exist!", it->first.v); } - } - else { // post-bayfront logic (changed for minting DAT tokens to be able) - if (tokenId == DCT_ID{0}) - return Res::Err("can't mint default DFI coin!", __func__, tokenId.ToString()); - if (tokenImpl.IsPoolShare()) { - return Res::Err("can't mint LPS tokens!", __func__, tokenId.ToString()); - } - // may be different logic with LPS, so, dedicated check: - if (!rpcInfo && !tokenImpl.IsMintable()) { // Skip on rpcInfo as we cannot reliably check whether mintable has been toggled historically - return Res::Err("%s: token not mintable!", tokenId.ToString()); + if (it->second == 0) { + balances.erase(it); } + } + return Res::Ok(); + } - if (!skipAuth && !HasAuth(tx, coins, auth.out.scriptPubKey)) { // in the case of DAT, it's ok to do not check foundation auth cause exact DAT owner is foundation member himself - if (!tokenImpl.IsDAT()) { - return Res::Err("%s: %s", __func__, "tx must have at least one input from token owner"); - } else if (!HasFoundationAuth(tx, coins, consensusParams)) { // Is a DAT, check founders auth - return Res::Err("%s: %s", __func__, "token is DAT and tx not from foundation member"); + Res setShares(const CScript& owner, const TAmounts& balances) const { + for (const auto& balance : balances) { + auto token = mnview.GetToken(balance.first); + if (token && token->IsPoolShare()) { + const auto bal = mnview.GetBalance(owner, balance.first); + if (bal.nValue == balance.second) { + auto res = mnview.SetShare(balance.first, owner); + if (!res) { + return res; + } } } } - auto mint = mnview.AddMintedTokens(tokenImpl.creationTx, kv.second, rpcInfo); - if (!mint.ok) { - return Res::Err("%s %s: %s", __func__, tokenImpl.symbol, mint.msg); - } - const auto res = mnview.AddBalance(auth.out.scriptPubKey, CTokenAmount{kv.first,kv.second}); - if (!res.ok) { - return Res::Err("%s: %s", __func__, res.msg); - } - } - return Res::Ok(); -} - -Res ApplyAddPoolLiquidityTx(CCustomCSView & mnview, CCoinsViewCache const & coins, CTransaction const & tx, uint32_t height, std::vector const & metadata, Consensus::Params const & consensusParams, bool skipAuth, UniValue *rpcInfo) -{ - if ((int)height < consensusParams.BayfrontHeight) { - return Res::Err("LP tx before Bayfront height (block %d)", consensusParams.BayfrontHeight); + return Res::Ok(); } - // deserialize - CLiquidityMessage msg; - CDataStream ss(metadata, SER_NETWORK, PROTOCOL_VERSION); - ss >> msg; - if (!ss.empty()) { - return Res::Err("%s: deserialization failed: excess %d bytes", __func__, ss.size()); + Res delShares(const CScript& owner, const TAmounts& balances) const { + for (const auto& kv : balances) { + auto token = mnview.GetToken(kv.first); + if (token && token->IsPoolShare()) { + const auto balance = mnview.GetBalance(owner, kv.first); + if (balance.nValue == 0) { + auto res = mnview.DelShare(kv.first, owner); + if (!res) { + return res; + } + } + } + } + return Res::Ok(); } - CBalances sumTx = SumAllTransfers(msg.from); - if (sumTx.balances.size() != 2) { - return Res::Err("%s: the pool pair requires two tokens", __func__); + Res subBalanceDelShares(const CScript& owner, const CBalances& balance) const { + auto res = mnview.SubBalances(owner, balance); + if (!res) { + return Res::ErrCode(CustomTxErrCodes::NotEnoughBalance, res.msg); + } + return delShares(owner, balance.balances); } - std::pair amountA = *sumTx.balances.begin(); - std::pair amountB = *(std::next(sumTx.balances.begin(), 1)); - - // guaranteed by sumTx.balances.size() == 2 -// if (amountA.first == amountB.first) { - // return Res::Err("%s: tokens IDs are the same", __func__); -// } - - // checked internally too. remove here? - if (amountA.second <= 0 || amountB.second <= 0) { - return Res::Err("%s: amount cannot be less than or equal to zero", __func__); + Res addBalanceSetShares(const CScript& owner, const CBalances& balance) const { + auto res = mnview.AddBalances(owner, balance); + return !res ? res : setShares(owner, balance.balances); } - auto pair = mnview.GetPoolPair(amountA.first, amountB.first); - - if (!pair) { - return Res::Err("%s: there is no such pool pair", __func__); - } - - if (!skipAuth) { - for (const auto& kv : msg.from) { - if (!HasAuth(tx, coins, kv.first)) { - return Res::Err("%s: %s", __func__, "tx must have at least one input from account owner"); + Res addBalancesSetShares(const CAccounts& accounts) const { + for (const auto& account : accounts) { + auto res = addBalanceSetShares(account.first, account.second); + if (!res) { + return res; } } - } - - // Return here to avoid balance errors below. - if (rpcInfo) { - rpcInfo->pushKV(std::to_string(amountA.first.v), ValueFromAmount(amountA.second)); - rpcInfo->pushKV(std::to_string(amountB.first.v), ValueFromAmount(amountB.second)); - rpcInfo->pushKV("shareaddress", msg.shareAddress.GetHex()); - return Res::Ok(); } - for (const auto& kv : msg.from) { - const auto res = mnview.SubBalances(kv.first, kv.second); - if (!res.ok) { - return Res::Err("%s: %s", __func__, res.msg); + Res subBalancesDelShares(const CAccounts& accounts) const { + for (const auto& account : accounts) { + auto res = subBalanceDelShares(account.first, account.second); + if (!res) { + return res; + } } + return Res::Ok(); } - DCT_ID const & lpTokenID = pair->first; - CPoolPair & pool = pair->second; +public: + CCustomTxApplyVisitor(const CTransaction& tx, + uint32_t height, + const CCoinsViewCache& coins, + CCustomCSView& mnview, + const Consensus::Params& consensus, + uint64_t time) - // normalize A & B to correspond poolpair's tokens - if (amountA.first != pool.idTokenA) - std::swap(amountA, amountB); + : time(time), height(height), mnview(mnview), tx(tx), coins(coins), consensus(consensus) {} - bool slippageProtection = static_cast(height) >= consensusParams.BayfrontMarinaHeight; - const auto res = pool.AddLiquidity(amountA.second, amountB.second, msg.shareAddress, [&] /*onMint*/(CScript to, CAmount liqAmount) { - - auto add = mnview.AddBalance(to, { lpTokenID, liqAmount }); - if (!add.ok) { - return Res::Err("%s: %s", __func__, add.msg); + Res operator()(const CCreateMasterNodeMessage& obj) const { + if (tx.vout.size() < 2 + || tx.vout[0].nValue < GetMnCreationFee(height) || tx.vout[0].nTokenId != DCT_ID{0} + || tx.vout[1].nValue != GetMnCollateralAmount(height) || tx.vout[1].nTokenId != DCT_ID{0}) { + return Res::Err("malformed tx vouts (wrong creation fee or collateral amount)"); } - //insert update ByShare index - const auto setShare = mnview.SetShare(lpTokenID, to); - if (!setShare.ok) { - return Res::Err("%s: %s", __func__, setShare.msg); + CMasternode node; + CTxDestination dest; + if (ExtractDestination(tx.vout[1].scriptPubKey, dest)) { + if (dest.which() == PKHashType) { + node.ownerType = 1; + node.ownerAuthAddress = CKeyID(*boost::get(&dest)); + } else if (dest.which() == WitV0KeyHashType) { + node.ownerType = 4; + node.ownerAuthAddress = CKeyID(*boost::get(&dest)); + } } - - return Res::Ok(); - }, slippageProtection); - - if (!res.ok) { - return Res::Err("%s: %s", __func__, res.msg); - } - return mnview.SetPoolPair(lpTokenID, pool); -} - -Res ApplyRemovePoolLiquidityTx(CCustomCSView & mnview, CCoinsViewCache const & coins, CTransaction const & tx, uint32_t height, std::vector const & metadata, Consensus::Params const & consensusParams, bool skipAuth, UniValue *rpcInfo) -{ - if ((int)height < consensusParams.BayfrontHeight) { - return Res::Err("LP tx before Bayfront height (block %d)", consensusParams.BayfrontHeight); - } - - // deserialize - CRemoveLiquidityMessage msg; - CDataStream ss(metadata, SER_NETWORK, PROTOCOL_VERSION); - ss >> msg; - if (!ss.empty()) { - return Res::Err("%s: deserialization failed: excess %d bytes", __func__, ss.size()); - } - - CScript from = msg.from; - CTokenAmount amount = msg.amount; - - // checked internally too. remove here? - if (amount.nValue <= 0) { - return Res::Err("%s: amount cannot be less than or equal to zero", __func__); - } - - auto pair = mnview.GetPoolPair(amount.nTokenId); - - if (!pair) { - return Res::Err("%s: there is no such pool pair", __func__); - } - - if (!skipAuth && !HasAuth(tx, coins, from)) { - return Res::Err("%s: %s", __func__, "tx must have at least one input from account owner"); + node.creationHeight = height; + node.operatorType = obj.operatorType; + node.operatorAuthAddress = obj.operatorAuthAddress; + auto res = mnview.CreateMasternode(tx.GetHash(), node); + // Build coinage from the point of masternode creation + if (res && height >= static_cast(Params().GetConsensus().DakotaCrescentHeight)) { + mnview.SetMasternodeLastBlockTime(node.operatorAuthAddress, static_cast(height), time); + } + return res; } - // Return here to avoid balance errors below. - if (rpcInfo) { - rpcInfo->pushKV("from", msg.from.GetHex()); - rpcInfo->pushKV("amount", msg.amount.ToString()); - - return Res::Ok(); + Res operator()(const CResignMasterNodeMessage& obj) const { + auto res = HasCollateralAuth(obj); + return !res ? res : mnview.ResignMasternode(obj, tx.GetHash(), height); } - CPoolPair & pool = pair.get(); - - // subtract liq.balance BEFORE RemoveLiquidity call to check balance correctness - { - auto sub = mnview.SubBalance(from, amount); - if (!sub.ok) { - return Res::Err("%s: %s", __func__, sub.msg); - } - if (mnview.GetBalance(from, amount.nTokenId).nValue == 0) { - //delete ByShare index - const auto delShare = mnview.DelShare(amount.nTokenId, from); - if (!delShare.ok) { - return Res::Err("%s: %s", __func__, delShare.msg); - } + Res operator()(const CCreateTokenMessage& obj) const { + if (tx.vout.size() < 2 + || tx.vout[0].nValue < GetTokenCreationFee(height) || tx.vout[0].nTokenId != DCT_ID{0} + || tx.vout[1].nValue != GetTokenCollateralAmount() || tx.vout[1].nTokenId != DCT_ID{0}) { + return Res::Err("malformed tx vouts (wrong creation fee or collateral amount)"); } - } - const auto res = pool.RemoveLiquidity(from, amount.nValue, [&] (CScript to, CAmount amountA, CAmount amountB) { + CTokenImplementation token; + static_cast(token) = obj; - auto addA = mnview.AddBalance(to, { pool.idTokenA, amountA }); - if (!addA.ok) { - return Res::Err("%s: %s", __func__, addA.msg); - } + token.symbol = trim_ws(token.symbol).substr(0, CToken::MAX_TOKEN_SYMBOL_LENGTH); + token.name = trim_ws(token.name).substr(0, CToken::MAX_TOKEN_NAME_LENGTH); + token.creationTx = tx.GetHash(); + token.creationHeight = height; - auto addB = mnview.AddBalance(to, { pool.idTokenB, amountB }); - if (!addB.ok) { - return Res::Err("%s: %s", __func__, addB.msg); + //check foundation auth + if (token.IsDAT() && !HasFoundationAuth()) { + return Res::Err("tx not from foundation member"); } - return Res::Ok(); - }); + if (static_cast(height) >= consensus.BayfrontHeight) { // formal compatibility if someone cheat and create LPS token on the pre-bayfront node + if (token.IsPoolShare()) { + return Res::Err("Cant't manually create 'Liquidity Pool Share' token; use poolpair creation"); + } + } - if (!res.ok) { - return Res::Err("%s: %s", __func__, res.msg); + return mnview.CreateToken(token, static_cast(height) < consensus.BayfrontHeight); } - return mnview.SetPoolPair(amount.nTokenId, pool); -} - - -Res ApplyUtxosToAccountTx(CCustomCSView & mnview, CTransaction const & tx, uint32_t height, std::vector const & metadata, Consensus::Params const & consensusParams, UniValue *rpcInfo) -{ - if((int)height < consensusParams.AMKHeight) { return Res::Err("Token tx before AMK height (block %d)", consensusParams.AMKHeight); } + Res operator()(const CUpdateTokenPreAMKMessage& obj) const { + auto pair = mnview.GetTokenByCreationTx(obj.tokenTx); + if (!pair) { + return Res::Err("token with creationTx %s does not exist", obj.tokenTx.ToString()); + } + const auto& token = pair->second; - // deserialize - CUtxosToAccountMessage msg; - CDataStream ss(metadata, SER_NETWORK, PROTOCOL_VERSION); - ss >> msg; - if (!ss.empty()) { - return Res::Err("%s: deserialization failed: excess %d bytes", __func__, ss.size()); - } + //check foundation auth + auto res = HasFoundationAuth(); - // check enough tokens are "burnt" - const auto burnt = BurntTokens(tx); - CBalances mustBeBurnt = SumAllTransfers(msg.to); - if (!burnt.ok) { - return Res::Err("%s: %s", __func__, burnt.msg); - } - if (burnt.val->balances != mustBeBurnt.balances) { - return Res::Err("%s: transfer tokens mismatch burnt tokens: (%s) != (%s)", __func__, mustBeBurnt.ToString(), burnt.val->ToString()); - } + if (token.IsDAT() != obj.isDAT && pair->first >= CTokensView::DCT_ID_START) { + CToken newToken = static_cast(token); // keeps old and triggers only DAT! + newToken.flags ^= (uint8_t)CToken::TokenFlags::DAT; - // Create balances here and return to avoid balance errors below - if (rpcInfo) { - for (const auto& kv : msg.to) { - rpcInfo->pushKV(kv.first.GetHex(), kv.second.ToString()); + return !res ? res : mnview.UpdateToken(token.creationTx, newToken, true); } - - return Res::Ok(); + return res; } - // transfer - for (const auto& kv : msg.to) { - const auto res = mnview.AddBalances(kv.first, kv.second); - if (!res.ok) { - return Res::Err("%s: %s", __func__, res.msg); + Res operator()(const CUpdateTokenMessage& obj) const { + auto pair = mnview.GetTokenByCreationTx(obj.tokenTx); + if (!pair) { + return Res::Err("token with creationTx %s does not exist", obj.tokenTx.ToString()); } - for (const auto& balance : kv.second.balances) { - auto token = mnview.GetToken(balance.first); - if (token->IsPoolShare()) { - const auto bal = mnview.GetBalance(kv.first, balance.first); - if (bal.nValue == balance.second) { - const auto setShare = mnview.SetShare(balance.first, kv.first); - if (!setShare.ok) { - return Res::Err("%s: %s", __func__, setShare.msg); - } - } - } + if (pair->first == DCT_ID{0}) { + return Res::Err("Can't alter DFI token!"); // may be redundant cause DFI is 'finalized' } - } - return Res::Ok(); -} - -Res ApplyAccountToUtxosTx(CCustomCSView & mnview, CCoinsViewCache const & coins, CTransaction const & tx, uint32_t height, std::vector const & metadata, Consensus::Params const & consensusParams, bool skipAuth, UniValue *rpcInfo) -{ - if((int)height < consensusParams.AMKHeight) { return Res::Err("Token tx before AMK height (block %d)", consensusParams.AMKHeight); } - - // deserialize - CAccountToUtxosMessage msg; - CDataStream ss(metadata, SER_NETWORK, PROTOCOL_VERSION); - ss >> msg; - if (!ss.empty()) { - return Res::Err("%s: deserialization failed: excess %d bytes", __func__, ss.size()); - } - // check auth - if (!skipAuth && !HasAuth(tx, coins, msg.from)) { - return Res::Err("%s: %s", __func__, "tx must have at least one input from account owner"); - } - - // Return here to avoid balance errors below - if (rpcInfo) { - rpcInfo->pushKV("from", msg.from.GetHex()); + const auto& token = pair->second; - UniValue dest(UniValue::VOBJ); - for (uint32_t i = msg.mintingOutputsStart; i < static_cast(tx.vout.size()); i++) { - dest.pushKV(tx.vout[i].scriptPubKey.GetHex(), tx.vout[i].TokenAmount().ToString()); + // need to check it exectly here cause lps has no collateral auth (that checked next) + if (token.IsPoolShare()) { + return Res::Err("token %s is the LPS token! Can't alter pool share's tokens!", obj.tokenTx.ToString()); } - rpcInfo->pushKV("to", dest); - return Res::Ok(); - } - - // check that all tokens are minted, and no excess tokens are minted - const auto minted = MintedTokens(tx, msg.mintingOutputsStart); - if (!minted.ok) { - return Res::Err("%s: %s", __func__, minted.msg); - } - if (msg.balances != *minted.val) { - return Res::Err("%s: amount of minted tokens in UTXOs and metadata do not match: (%s) != (%s)", __func__, minted.val->ToString(), msg.balances.ToString()); - } + // check auth, depends from token's "origins" + const Coin& auth = coins.AccessCoin(COutPoint(token.creationTx, 1)); // always n=1 output + bool isFoundersToken = consensus.foundationMembers.count(auth.out.scriptPubKey) > 0; - // block for non-DFI transactions - for (auto const & kv : msg.balances.balances) { - const DCT_ID& tokenId = kv.first; - if (tokenId != DCT_ID{0}) { - return Res::Err("AccountToUtxos only available for DFI transactions"); + auto res = Res::Ok(); + if (isFoundersToken && !(res = HasFoundationAuth())) { + return res; + } else if (!(res = HasCollateralAuth(token.creationTx))) { + return res; } - } - - // transfer - const auto res = mnview.SubBalances(msg.from, msg.balances); - if (!res.ok) { - return Res::ErrCode(CustomTxErrCodes::NotEnoughBalance, "%s: %s", __func__, res.msg); - } - for (const auto& kv : msg.balances.balances) { - auto token = mnview.GetToken(kv.first); - if (token->IsPoolShare()) { - const auto balance = mnview.GetBalance(msg.from, kv.first); - if (balance.nValue == 0) { - const auto delShare = mnview.DelShare(kv.first, msg.from); - if (!delShare.ok) { - return Res::Err("%s: %s", __func__, delShare.msg); - } + // Check for isDAT change in non-foundation token after set height + if (static_cast(height) >= consensus.BayfrontMarinaHeight) { + //check foundation auth + if (obj.token.IsDAT() != token.IsDAT() && !HasFoundationAuth()) { //no need to check Authority if we don't create isDAT + return Res::Err("can't set isDAT to true, tx not from foundation member"); } } - } - return Res::Ok(); -} - -Res ApplyAccountToAccountTx(CCustomCSView & mnview, CCoinsViewCache const & coins, CTransaction const & tx, uint32_t height, std::vector const & metadata, Consensus::Params const & consensusParams, bool skipAuth, UniValue *rpcInfo) -{ - if((int)height < consensusParams.AMKHeight) { return Res::Err("Token tx before AMK height (block %d)", consensusParams.AMKHeight); } - // deserialize - CAccountToAccountMessage msg; - CDataStream ss(metadata, SER_NETWORK, PROTOCOL_VERSION); - ss >> msg; - if (!ss.empty()) { - return Res::Err("%s: deserialization failed: excess %d bytes", __func__, ss.size()); + return mnview.UpdateToken(token.creationTx, obj.token, false); } - // check auth - if (!skipAuth && !HasAuth(tx, coins, msg.from)) { - return Res::Err("%s: %s", __func__, "tx must have at least one input from account owner"); - } + Res operator()(const CMintTokensMessage& obj) const { + // check auth and increase balance of token's owner + for (const auto& kv : obj.balances) { + DCT_ID tokenId = kv.first; - // Return here to avoid balance errors below. - if (rpcInfo) { - rpcInfo->pushKV("from", msg.from.GetHex()); + auto token = mnview.GetToken(kv.first); + if (!token) { + return Res::Err("token %s does not exist!", tokenId.ToString()); // pre-bayfront throws but it affects only the message + } + auto tokenImpl = static_cast(*token); - UniValue dest(UniValue::VOBJ); - for (const auto& to : msg.to) { - dest.pushKV(to.first.GetHex(), to.second.ToString()); - } - rpcInfo->pushKV("to", dest); + if (tokenImpl.destructionTx != uint256{}) { + return Res::Err("token %s already destroyed at height %i by tx %s", tokenImpl.symbol, // pre-bayfront throws but it affects only the message + tokenImpl.destructionHeight, tokenImpl.destructionTx.GetHex()); + } + const Coin& auth = coins.AccessCoin(COutPoint(tokenImpl.creationTx, 1)); // always n=1 output - return Res::Ok(); - } + // pre-bayfront logic: + if (static_cast(height) < consensus.BayfrontHeight) { + if (tokenId < CTokensView::DCT_ID_START) { + return Res::Err("token %s is a 'stable coin', can't mint stable coin!", tokenId.ToString()); + } - // transfer - auto res = mnview.SubBalances(msg.from, SumAllTransfers(msg.to)); - if (!res.ok) { - return Res::ErrCode(CustomTxErrCodes::NotEnoughBalance, "%s: %s", __func__, res.msg); - } + if (!HasAuth(auth.out.scriptPubKey)) { + return Res::Err("tx must have at least one input from token owner"); + } + } else { // post-bayfront logic (changed for minting DAT tokens to be able) + if (tokenId == DCT_ID{0}) { + return Res::Err("can't mint default DFI coin!"); + } - for (const auto& kv : SumAllTransfers(msg.to).balances) { - const auto token = mnview.GetToken(kv.first); - if (token->IsPoolShare()) { - const auto balance = mnview.GetBalance(msg.from, kv.first); - if (balance.nValue == 0) { - const auto delShare = mnview.DelShare(kv.first, msg.from); - if (!delShare.ok) { - return Res::Err("%s: %s", __func__, delShare.msg); + if (tokenImpl.IsPoolShare()) { + return Res::Err("can't mint LPS token %s!", tokenId.ToString()); + } + // may be different logic with LPS, so, dedicated check: + if (!tokenImpl.IsMintable()) { + return Res::Err("token %s is not mintable!", tokenId.ToString()); } - } - } - } - for (const auto& kv : msg.to) { - const auto res = mnview.AddBalances(kv.first, kv.second); - if (!res.ok) { - return Res::Err("%s: %s", __func__, res.msg); - } - for (const auto& balance : kv.second.balances) { - auto token = mnview.GetToken(balance.first); - if (token->IsPoolShare()) { - const auto bal = mnview.GetBalance(kv.first, balance.first); - if (bal.nValue == balance.second) { - const auto setShare = mnview.SetShare(balance.first, kv.first); - if (!setShare.ok) { - return Res::Err("%s: %s", __func__, setShare.msg); + if (!HasAuth(auth.out.scriptPubKey)) { // in the case of DAT, it's ok to do not check foundation auth cause exact DAT owner is foundation member himself + if (!tokenImpl.IsDAT()) { + return Res::Err("tx must have at least one input from token owner"); + } else if (!HasFoundationAuth()) { // Is a DAT, check founders auth + return Res::Err("token is DAT and tx not from foundation member"); } } } + auto mint = mnview.AddMintedTokens(tokenImpl.creationTx, kv.second); + if (!mint) { + return mint; + } + auto res = mnview.AddBalance(auth.out.scriptPubKey, CTokenAmount{kv.first, kv.second}); + if (!res) { + return res; + } } + return Res::Ok(); } - return Res::Ok(); -} -Res ApplyAnyAccountsToAccountsTx(CCustomCSView & mnview, CCoinsViewCache const & coins, CTransaction const & tx, uint32_t height, std::vector const & metadata, Consensus::Params const & consensusParams, bool skipAuth, UniValue *rpcInfo) -{ - if((int)height < consensusParams.BayfrontGardensHeight) { return Res::Err("Token tx before BayfrontGardensHeight (block %d)", consensusParams.BayfrontGardensHeight ); } + Res operator()(const CCreatePoolPairMessage& obj) const { + //check foundation auth + if (!HasFoundationAuth()) { + return Res::Err("tx not from foundation member"); + } + if (obj.poolPair.commission < 0 || obj.poolPair.commission > COIN) { + return Res::Err("wrong commission"); + } - // deserialize - CAnyAccountsToAccountsMessage msg; - CDataStream ss(metadata, SER_NETWORK, PROTOCOL_VERSION); - ss >> msg; - if (!ss.empty()) { - return Res::Err("%s: deserialization failed: excess %d bytes", __func__, ss.size()); - } + /// @todo ownerAddress validity checked only in rpc. is it enough? + CPoolPair poolPair(obj.poolPair); + auto pairSymbol = obj.pairSymbol; + poolPair.creationTx = tx.GetHash(); + poolPair.creationHeight = height; - // check auth - if (!skipAuth) { - for (auto const & kv : msg.from) { - if (!HasAuth(tx, coins, kv.first)) { - return Res::Err("%s: %s", __func__, "tx must have at least one input from account owner"); - } + auto tokenA = mnview.GetToken(poolPair.idTokenA); + if (!tokenA) { + return Res::Err("token %s does not exist!", poolPair.idTokenA.ToString()); } - } - // Return here to avoid balance errors below. - if (rpcInfo) { - UniValue source(UniValue::VOBJ); - for (const auto& from : msg.from) { - source.pushKV(from.first.GetHex(), from.second.ToString()); + auto tokenB = mnview.GetToken(poolPair.idTokenB); + if (!tokenB) { + return Res::Err("token %s does not exist!", poolPair.idTokenB.ToString()); } - rpcInfo->pushKV("from", source); - UniValue dest(UniValue::VOBJ); - for (const auto& to : msg.to) { - dest.pushKV(to.first.GetHex(), to.second.ToString()); + if (pairSymbol.empty()) { + pairSymbol = trim_ws(tokenA->symbol + "-" + tokenB->symbol).substr(0, CToken::MAX_TOKEN_SYMBOL_LENGTH); + } else { + pairSymbol = trim_ws(pairSymbol).substr(0, CToken::MAX_TOKEN_SYMBOL_LENGTH); } - rpcInfo->pushKV("to", dest); - - return Res::Ok(); - } - // compare - auto const sumFrom = SumAllTransfers(msg.from); - auto const sumTo = SumAllTransfers(msg.to); + CTokenImplementation token; + token.flags = (uint8_t)CToken::TokenFlags::DAT | + (uint8_t)CToken::TokenFlags::LPS | + (uint8_t)CToken::TokenFlags::Tradeable | + (uint8_t)CToken::TokenFlags::Finalized; - if (sumFrom != sumTo) { - return Res::Err("%s: %s", __func__, "sum of inputs (from) != sum of outputs (to)"); - } + token.name = trim_ws(tokenA->name + "-" + tokenB->name).substr(0, CToken::MAX_TOKEN_NAME_LENGTH); + token.symbol = pairSymbol; + token.creationTx = tx.GetHash(); + token.creationHeight = height; - // transfer - // substraction - for (const auto& kv : msg.from) { - const auto res = mnview.SubBalances(kv.first, kv.second); - if (!res.ok) { - return Res::ErrCode(CustomTxErrCodes::NotEnoughBalance, "%s: %s", __func__, res.msg); + auto tokenId = mnview.CreateToken(token, false); + if (!tokenId) { + return std::move(tokenId); } - // track pool shares - for (const auto& balance : kv.second.balances) { - auto token = mnview.GetToken(balance.first); - if (token->IsPoolShare()) { - const auto bal = mnview.GetBalance(kv.first, balance.first); - if (bal.nValue == 0) { - const auto delShare = mnview.DelShare(balance.first, kv.first); - if (!delShare.ok) { - return Res::Err("%s: %s", __func__, delShare.msg); - } - } - } - } - } - // addition - for (const auto& kv : msg.to) { - const auto res = mnview.AddBalances(kv.first, kv.second); - if (!res.ok) { - return Res::Err("%s: %s", __func__, res.msg); + auto res = mnview.SetPoolPair(tokenId, poolPair); + if (!res) { + return res; } - // track pool shares - for (const auto& balance : kv.second.balances) { - auto token = mnview.GetToken(balance.first); - if (token->IsPoolShare()) { - const auto bal = mnview.GetBalance(kv.first, balance.first); - if (bal.nValue == balance.second) { - const auto setShare = mnview.SetShare(balance.first, kv.first); - if (!setShare.ok) { - return Res::Err("%s: %s", __func__, setShare.msg); - } - } - } - } - } - return Res::Ok(); -} -extern std::string ScriptToString(CScript const& script); - -Res ApplyCreatePoolPairTx(CCustomCSView &mnview, const CCoinsViewCache &coins, const CTransaction &tx, uint32_t height, const std::vector &metadata, Consensus::Params const & consensusParams, bool skipAuth, UniValue *rpcInfo) -{ - if ((int)height < consensusParams.BayfrontHeight) { - return Res::Err("LP tx before Bayfront height (block %d)", consensusParams.BayfrontHeight); + if (!obj.rewards.balances.empty()) { + auto rewards = obj.rewards; + // Check tokens exist and remove empty reward amounts + auto res = eraseEmptyBalances(rewards.balances); + // Will only fail if pool was not actually created in SetPoolPair + return !res ? res : mnview.SetPoolCustomReward(tokenId, rewards); + } + return Res::Ok(); } - CPoolPairMessage poolPairMsg; - std::string pairSymbol; - CBalances rewards; - CDataStream ss(metadata, SER_NETWORK, PROTOCOL_VERSION); - ss >> poolPairMsg; - ss >> pairSymbol; + Res operator()(const CUpdatePoolPairMessage& obj) const { + auto pool = mnview.GetPoolPair(obj.poolId); + if (!pool) { + return Res::Err("pool with poolId %s does not exist", obj.poolId.ToString()); + } - // Read custom pool rewards - if (static_cast(height) >= consensusParams.ClarkeQuayHeight && !ss.empty()) { - ss >> rewards; - } + //check foundation auth + if (!HasFoundationAuth()) { + return Res::Err("tx not from foundation member"); + } - if (!ss.empty()) { - return Res::Err("%s: deserialization failed: excess %d bytes", __func__, ss.size()); - } + auto res = mnview.UpdatePoolPair(obj.poolId, obj.status, obj.commission, obj.ownerAddress); + if (!res) { + return res; + } - //check foundation auth - if(!skipAuth && !HasFoundationAuth(tx, coins, consensusParams)) { - return Res::Err("%s: %s", __func__, "tx not from foundation member"); - } - if(poolPairMsg.commission < 0 || poolPairMsg.commission > COIN) { - return Res::Err("%s: %s", __func__, "wrong commission"); + if (!obj.rewards.balances.empty()) { + // Check for special case to wipe rewards + auto rewards = obj.rewards; + if (rewards.balances.size() == 1 && rewards.balances.cbegin()->first == DCT_ID{std::numeric_limits::max()} + && rewards.balances.cbegin()->second == std::numeric_limits::max()) { + rewards.balances.clear(); + } + // Check if tokens exist and remove empty reward amounts + auto res = eraseEmptyBalances(rewards.balances); + // Will only fail if pool was not actually created in SetPoolPair + return !res ? res : mnview.SetPoolCustomReward(obj.poolId, rewards); + } + return Res::Ok(); } - /// @todo ownerAddress validity checked only in rpc. is it enough? - CPoolPair poolPair(poolPairMsg); - poolPair.creationTx = tx.GetHash(); - poolPair.creationHeight = height; - - CTokenImplementation token{}; + Res operator()(const CPoolSwapMessage& obj) const { + // check auth + if (!HasAuth(obj.from)) { + return Res::Err("tx must have at least one input from account owner"); + } - auto tokenA = mnview.GetToken(poolPairMsg.idTokenA); - if (!tokenA) { - return Res::Err("%s: token %s does not exist!", __func__, poolPairMsg.idTokenA.ToString()); - } + auto poolPair = mnview.GetPoolPair(obj.idTokenFrom, obj.idTokenTo); + if (!poolPair) { + return Res::Err("can't find the poolpair!"); + } - auto tokenB = mnview.GetToken(poolPairMsg.idTokenB); - if (!tokenB) { - return Res::Err("%s: token %s does not exist!", __func__, poolPairMsg.idTokenB.ToString()); + CPoolPair& pp = poolPair->second; + return pp.Swap({obj.idTokenFrom, obj.amountFrom}, obj.maxPrice, [&] (const CTokenAmount &tokenAmount) { + auto res = mnview.SetPoolPair(poolPair->first, pp); + if (!res) { + return res; + } + res = mnview.SubBalance(obj.from, {obj.idTokenFrom, obj.amountFrom}); + return !res ? res : mnview.AddBalance(obj.to, tokenAmount); + }, static_cast(height)); } - if(pairSymbol.empty()) - pairSymbol = trim_ws(tokenA->symbol + "-" + tokenB->symbol).substr(0, CToken::MAX_TOKEN_SYMBOL_LENGTH); - else - pairSymbol = trim_ws(pairSymbol).substr(0, CToken::MAX_TOKEN_SYMBOL_LENGTH); + Res operator()(const CLiquidityMessage& obj) const { + CBalances sumTx = SumAllTransfers(obj.from); + if (sumTx.balances.size() != 2) { + return Res::Err("the pool pair requires two tokens"); + } - token.flags = (uint8_t)CToken::TokenFlags::DAT | - (uint8_t)CToken::TokenFlags::LPS | - (uint8_t)CToken::TokenFlags::Tradeable | - (uint8_t)CToken::TokenFlags::Finalized; - token.name = trim_ws(tokenA->name + "-" + tokenB->name).substr(0, CToken::MAX_TOKEN_NAME_LENGTH); - token.symbol = pairSymbol; - token.creationTx = tx.GetHash(); - token.creationHeight = height; + std::pair amountA = *sumTx.balances.begin(); + std::pair amountB = *(std::next(sumTx.balances.begin(), 1)); - // Return here to avoid token and poolpair exists errors below - if (rpcInfo) { - rpcInfo->pushKV("creationTx", tx.GetHash().GetHex()); - rpcInfo->pushKV("name", token.name); - rpcInfo->pushKV("symbol", pairSymbol); - rpcInfo->pushKV("tokenA", tokenA->name); - rpcInfo->pushKV("tokenB", tokenB->name); - rpcInfo->pushKV("commission", ValueFromAmount(poolPairMsg.commission)); - rpcInfo->pushKV("status", poolPairMsg.status); - rpcInfo->pushKV("ownerAddress", ScriptToString(poolPairMsg.ownerAddress)); - rpcInfo->pushKV("isDAT", token.IsDAT()); - rpcInfo->pushKV("mineable", token.IsMintable()); - rpcInfo->pushKV("tradeable", token.IsTradeable()); - rpcInfo->pushKV("finalized", token.IsFinalized()); + // checked internally too. remove here? + if (amountA.second <= 0 || amountB.second <= 0) { + return Res::Err("amount cannot be less than or equal to zero"); + } - if (!rewards.balances.empty()) { - UniValue rewardArr(UniValue::VARR); + auto pair = mnview.GetPoolPair(amountA.first, amountB.first); + if (!pair) { + return Res::Err("there is no such pool pair"); + } - for (const auto& reward : rewards.balances) { - if (reward.second > 0) { - rewardArr.push_back(CTokenAmount{reward.first, reward.second}.ToString()); - } + for (const auto& kv : obj.from) { + if (!HasAuth(kv.first)) { + return Res::Err("tx must have at least one input from account owner"); } + } - if (!rewardArr.empty()) { - rpcInfo->pushKV("customRewards", rewardArr); + for (const auto& kv : obj.from) { + auto res = mnview.SubBalances(kv.first, kv.second); + if (!res) { + return res; } } - return Res::Ok(); - } + const auto& lpTokenID = pair->first; + auto& pool = pair->second; - auto res = mnview.CreateToken(token, false); - if (!res.ok) { - return Res::Err("%s %s: %s", __func__, token.symbol, res.msg); - } + // normalize A & B to correspond poolpair's tokens + if (amountA.first != pool.idTokenA) { + std::swap(amountA, amountB); + } - //auto pairToken = mnview.GetToken(token.symbol); - auto pairToken = mnview.GetTokenByCreationTx(token.creationTx); - if (!pairToken) { - return Res::Err("%s: token %s does not exist!", __func__, token.symbol); - } + bool slippageProtection = static_cast(height) >= consensus.BayfrontMarinaHeight; + auto res = pool.AddLiquidity(amountA.second, amountB.second, [&] /*onMint*/(CAmount liqAmount) { - auto resPP = mnview.SetPoolPair(pairToken->first, poolPair); - if (!resPP.ok) { - return Res::Err("%s %s: %s", __func__, pairSymbol, resPP.msg); - } + CBalances balance{TAmounts{{lpTokenID, liqAmount}}}; + return addBalanceSetShares(obj.shareAddress, balance); + }, slippageProtection); - if (!rewards.balances.empty()) { - // Check tokens exist and remove empty reward amounts - for (auto it = rewards.balances.cbegin(), next_it = it; it != rewards.balances.cend(); it = next_it) { - ++next_it; + return !res ? res : mnview.SetPoolPair(lpTokenID, pool); + } - auto token = pcustomcsview->GetToken(it->first); - if (!token) { - return Res::Err("%s: reward token %d does not exist!", __func__, it->first.v); - } + Res operator()(const CRemoveLiquidityMessage& obj) const { + const auto& from = obj.from; + auto amount = obj.amount; - if (it->second == 0) { - rewards.balances.erase(it); - } + // checked internally too. remove here? + if (amount.nValue <= 0) { + return Res::Err("amount cannot be less than or equal to zero"); } - auto resCR = mnview.SetPoolCustomReward(pairToken->first, rewards); + auto pair = mnview.GetPoolPair(amount.nTokenId); + if (!pair) { + return Res::Err("there is no such pool pair"); + } - // Will only fail if pool was not actually created in SetPoolPair - if (!resCR.ok) { - return Res::Err("%s %s: %s", __func__, pairSymbol, resCR.msg); + if (!HasAuth(from)) { + return Res::Err("tx must have at least one input from account owner"); } - } - return Res::Ok(); -} + CPoolPair& pool = pair.get(); -Res ApplyUpdatePoolPairTx(CCustomCSView & mnview, CCoinsViewCache const & coins, CTransaction const & tx, uint32_t height, std::vector const & metadata, Consensus::Params const & consensusParams, bool skipAuth, UniValue *rpcInfo) -{ - if((int)height < consensusParams.BayfrontHeight) { - return Res::Err("LP tx before Bayfront height (block %d)", consensusParams.BayfrontHeight); - } + // subtract liq.balance BEFORE RemoveLiquidity call to check balance correctness + { + CBalances balance{TAmounts{{amount.nTokenId, amount.nValue}}}; + auto res = subBalanceDelShares(from, balance); + if (!res) { + return res; + } + } - DCT_ID poolId; - bool status; - CAmount commission; - CScript ownerAddress; - CBalances rewards; - CDataStream ss(metadata, SER_NETWORK, PROTOCOL_VERSION); - ss >> poolId; - ss >> status; - ss >> commission; - ss >> ownerAddress; + auto res = pool.RemoveLiquidity(from, amount.nValue, [&] (const CScript& to, CAmount amountA, CAmount amountB) { - // Read custom pool rewards - if (static_cast(height) >= consensusParams.ClarkeQuayHeight && !ss.empty()) { - ss >> rewards; - } + CBalances balances{TAmounts{{pool.idTokenA, amountA}, {pool.idTokenB, amountB}}}; + return mnview.AddBalances(to, balances); + }); - if (!ss.empty()) { - return Res::Err("%s: deserialization failed: excess %d bytes", __func__, ss.size()); + return !res ? res : mnview.SetPoolPair(amount.nTokenId, pool); } - auto pool = mnview.GetPoolPair(poolId); - if (!pool) { - return Res::Err("%s: pool with poolId %s does not exist", __func__, poolId.ToString()); - } + Res operator()(const CUtxosToAccountMessage& obj) const { + // check enough tokens are "burnt" + const auto burnt = BurntTokens(tx); + if (!burnt) { + return burnt; + } - //check foundation auth - if (!skipAuth && !HasFoundationAuth(tx, coins, consensusParams)) { - return Res::Err("%s: %s", __func__, "tx not from foundation member"); - } + const auto mustBeBurnt = SumAllTransfers(obj.to); + if (*burnt.val != mustBeBurnt) { + return Res::Err("transfer tokens mismatch burnt tokens: (%s) != (%s)", mustBeBurnt.ToString(), burnt.val->ToString()); + } - auto res = mnview.UpdatePoolPair(poolId, status, commission, ownerAddress); - if (!res.ok) { - return Res::Err("%s %s: %s", __func__, poolId.ToString(), res.msg); + // transfer + return addBalancesSetShares(obj.to); } - if (rpcInfo) { - rpcInfo->pushKV("commission", ValueFromAmount(commission)); - rpcInfo->pushKV("status", status); - rpcInfo->pushKV("ownerAddress", ScriptToString(ownerAddress)); + Res operator()(const CAccountToUtxosMessage& obj) const { + // check auth + if (!HasAuth(obj.from)) { + return Res::Err("tx must have at least one input from account owner"); + } - // Add rewards here before processing them below to avoid adding current rewards - if (!rewards.balances.empty()) { - UniValue rewardArr(UniValue::VARR); + // check that all tokens are minted, and no excess tokens are minted + auto minted = MintedTokens(tx, obj.mintingOutputsStart); + if (!minted) { + return std::move(minted); + } - // Check for special case to wipe rewards - if (rewards.balances.size() == 1 && rewards.balances.cbegin()->first == DCT_ID{std::numeric_limits::max()} - && rewards.balances.cbegin()->second == std::numeric_limits::max()) { - rpcInfo->pushKV("customRewards", rewardArr); - } else { - for (const auto& reward : rewards.balances) { - if (reward.second > 0) { - rewardArr.push_back(CTokenAmount{reward.first, reward.second}.ToString()); - } - } + if (obj.balances != *minted.val) { + return Res::Err("amount of minted tokens in UTXOs and metadata do not match: (%s) != (%s)", minted.val->ToString(), obj.balances.ToString()); + } - if (!rewardArr.empty()) { - rpcInfo->pushKV("customRewards", rewardArr); - } + // block for non-DFI transactions + for (const auto& kv : obj.balances.balances) { + const DCT_ID& tokenId = kv.first; + if (tokenId != DCT_ID{0}) { + return Res::Err("only available for DFI transactions"); } } + + // transfer + return subBalanceDelShares(obj.from, obj.balances); } - if (!rewards.balances.empty()) { - // Check for special case to wipe rewards - if (rewards.balances.size() == 1 && rewards.balances.cbegin()->first == DCT_ID{std::numeric_limits::max()} - && rewards.balances.cbegin()->second == std::numeric_limits::max()) { - rewards.balances.clear(); + Res operator()(const CAccountToAccountMessage& obj) const { + // check auth + if (!HasAuth(obj.from)) { + return Res::Err("tx must have at least one input from account owner"); } - // Check if tokens exist and remove empty reward amounts - for (auto it = rewards.balances.cbegin(), next_it = it; it != rewards.balances.cend(); it = next_it) { - ++next_it; - - auto token = pcustomcsview->GetToken(it->first); - if (!token) { - return Res::Err("%s: reward token %d does not exist!", __func__, it->first.v); - } + // transfer + auto res = subBalanceDelShares(obj.from, SumAllTransfers(obj.to)); + return !res ? res : addBalancesSetShares(obj.to); + } - if (it->second == 0) { - rewards.balances.erase(it); + Res operator()(const CAnyAccountsToAccountsMessage& obj) const { + // check auth + for (const auto& kv : obj.from) { + if (!HasAuth(kv.first)) { + return Res::Err("tx must have at least one input from account owner"); } } - auto resCR = mnview.SetPoolCustomReward(poolId, rewards); + // compare + const auto sumFrom = SumAllTransfers(obj.from); + const auto sumTo = SumAllTransfers(obj.to); - // Will only fail if pool was not actually created in SetPoolPair - if (!resCR.ok) { - return Res::Err("%s %s: %s", __func__, poolId.ToString(), resCR.msg); + if (sumFrom != sumTo) { + return Res::Err("sum of inputs (from) != sum of outputs (to)"); } - } - - return Res::Ok(); -} -Res ApplyPoolSwapTx(CCustomCSView &mnview, const CCoinsViewCache &coins, const CTransaction &tx, uint32_t height, const std::vector &metadata, Consensus::Params const & consensusParams, bool skipAuth, UniValue *rpcInfo) -{ - if ((int)height < consensusParams.BayfrontHeight) { - return Res::Err("LP tx before Bayfront height (block %d)", consensusParams.BayfrontHeight); + // transfer + // substraction + auto res = subBalancesDelShares(obj.from); + // addition + return !res ? res : addBalancesSetShares(obj.to); } - CPoolSwapMessage poolSwapMsg; - CDataStream ss(metadata, SER_NETWORK, PROTOCOL_VERSION); - ss >> poolSwapMsg; - if (!ss.empty()) { - return Res::Err("%s: deserialization failed: excess %d bytes", __func__, ss.size()); - } - - // check auth - if (!skipAuth && !HasAuth(tx, coins, poolSwapMsg.from)) { - return Res::Err("%s: %s", __func__, "tx must have at least one input from account owner"); - } - -// auto tokenFrom = mnview.GetToken(poolSwapMsg.idTokenFrom); -// if (!tokenFrom) { -// return Res::Err("%s: token %s does not exist!", __func__, poolSwapMsg.idTokenFrom.ToString()); -// } - -// auto tokenTo = mnview.GetToken(poolSwapMsg.idTokenTo); -// if (!tokenTo) { -// return Res::Err("%s: token %s does not exist!", __func__, poolSwapMsg.idTokenTo.ToString()); -// } - - auto poolPair = mnview.GetPoolPair(poolSwapMsg.idTokenFrom, poolSwapMsg.idTokenTo); - if (!poolPair) { - return Res::Err("%s: can't find the poolpair!", __func__); + Res operator()(const CGovernanceMessage& obj) const { + //check foundation auth + if (!HasFoundationAuth()) { + return Res::Err("tx not from foundation member"); + } + for(const auto& var : obj.govs) { + auto result = var->Validate(mnview); + if (!result) { + return Res::Err("%s: %s", var->GetName(), result.msg); + } + auto res = var->Apply(mnview); + if (!res) { + return Res::Err("%s: %s", var->GetName(), res.msg); + } + auto add = mnview.SetVariable(*var); + if (!add) { + return Res::Err("%s: %s", var->GetName(), add.msg); + } + } + return Res::Ok(); } - // Return here to avoid balance errors below - if (rpcInfo) { - rpcInfo->pushKV("fromAddress", poolSwapMsg.from.GetHex()); - rpcInfo->pushKV("fromToken", std::to_string(poolSwapMsg.idTokenFrom.v)); - rpcInfo->pushKV("fromAmount", ValueFromAmount(poolSwapMsg.amountFrom)); - rpcInfo->pushKV("toAddress", poolSwapMsg.to.GetHex()); - rpcInfo->pushKV("toToken", std::to_string(poolSwapMsg.idTokenTo.v)); - rpcInfo->pushKV("maxPrice", ValueFromAmount((poolSwapMsg.maxPrice.integer * COIN) + poolSwapMsg.maxPrice.fraction)); - + Res operator()(const CCustomTxMessageNone&) const { return Res::Ok(); } +}; - CPoolPair pp = poolPair->second; - const auto res = pp.Swap({poolSwapMsg.idTokenFrom, poolSwapMsg.amountFrom}, poolSwapMsg.maxPrice, [&] (const CTokenAmount &tokenAmount) { - auto resPP = mnview.SetPoolPair(poolPair->first, pp); - if (!resPP.ok) { - return Res::Err("%s: %s", __func__, resPP.msg); - } - - auto sub = mnview.SubBalance(poolSwapMsg.from, {poolSwapMsg.idTokenFrom, poolSwapMsg.amountFrom}); - if (!sub.ok) { - return Res::Err("%s: %s", __func__, sub.msg); - } - - auto add = mnview.AddBalance(poolSwapMsg.to, tokenAmount); - if (!add.ok) { - return Res::Err("%s: %s", __func__, add.msg); - } - - return Res::Ok(); - }, static_cast(height)); +Res CustomMetadataParse(uint32_t height, const Consensus::Params& consensus, const std::vector& metadata, CCustomTxMessage& txMessage) { + try { + return boost::apply_visitor(CCustomMetadataParseVisitor(height, consensus, metadata), txMessage); + } catch (const std::exception& e) { + return Res::Err(e.what()); + } catch (...) { + return Res::Err("unexpected error"); + } +} - if (!res.ok) { - return Res::Err("%s: %s", __func__, res.msg); +Res CustomTxVisit(CCustomCSView& mnview, const CCoinsViewCache& coins, const CTransaction& tx, uint32_t height, const Consensus::Params& consensus, const CCustomTxMessage& txMessage, uint64_t time) { + try { + return boost::apply_visitor(CCustomTxApplyVisitor(tx, height, coins, mnview, consensus, time), txMessage); + } catch (const std::exception& e) { + return Res::Err(e.what()); + } catch (...) { + return Res::Err("unexpected error"); } +} - return Res::Ok(); +bool ShouldReturnNonFatalError(const CTransaction& tx, uint32_t height) { + static const std::map skippedTx = { + { 471222, uint256S("0ab0b76352e2d865761f4c53037041f33e1200183d55cdf6b09500d6f16b7329") }, + }; + auto it = skippedTx.find(height); + return it != skippedTx.end() && it->second == tx.GetHash(); } -Res ApplySetGovernanceTx(CCustomCSView &mnview, const CCoinsViewCache &coins, const CTransaction &tx, uint32_t height, const std::vector &metadata, Consensus::Params const & consensusParams, bool skipAuth, UniValue* rpcInfo) -{ - if ((int)height < consensusParams.BayfrontHeight) { - return Res::Err("Governance tx before Bayfront height (block %d)", consensusParams.BayfrontHeight); +Res ApplyCustomTx(CCustomCSView& mnview, const CCoinsViewCache& coins, const CTransaction& tx, const Consensus::Params& consensus, uint32_t height, uint64_t time, uint32_t txn) { + auto res = Res::Ok(); + if (tx.IsCoinBase() && height > 0) { // genesis contains custom coinbase txs + return res; } - - //check foundation auth - if(!skipAuth && !HasFoundationAuth(tx, coins, consensusParams)) - { - return Res::Err("%s: %s", __func__, "tx not from foundation member"); + std::vector metadata; + auto txType = GuessCustomTxType(tx, metadata); + if (txType == CustomTxType::None) { + return res; } - - CDataStream ss(metadata, SER_NETWORK, PROTOCOL_VERSION); - std::set names; - while(!ss.empty()) - { - std::string name; - ss >> name; - names.insert(name); - auto var = GovVariable::Create(name); - if (!var) - return Res::Err("%s '%s': variable does not registered", __func__, name); - ss >> *var; - - Res result = var->Validate(mnview); - if(!result.ok) - return Res::Err("%s '%s': %s", __func__, name, result.msg); - - Res res = var->Apply(mnview); - if(!res.ok) - return Res::Err("%s '%s': %s", __func__, name, res.msg); - - auto add = mnview.SetVariable(*var); - if (!add.ok) - return Res::Err("%s '%s': %s", __func__, name, add.msg); + auto txMessage = customTypeToMessage(txType); + CAccountsHistoryStorage view(mnview, height, txn, tx.GetHash(), uint8_t(txType)); + if ((res = CustomMetadataParse(height, consensus, metadata, txMessage))) { + res = CustomTxVisit(view, coins, tx, height, consensus, txMessage, time); } + // list of transactions which aren't allowed to fail: + if (!res) { + res.msg = strprintf("%sTx: %s", ToString(txType), res.msg); - if (rpcInfo) { - for (const auto& name : names) { - auto var = mnview.GetVariable(name); - rpcInfo->pushKV(var->GetName(), var->Export()); + if (NotAllowedToFail(txType, height)) { + if (ShouldReturnNonFatalError(tx, height)) { + return res; + } + res.code |= CustomTxErrCodes::Fatal; + } + if (height >= consensus.DakotaHeight) { + res.code |= CustomTxErrCodes::Fatal; } + return res; } - //in this case it will throw (and cathced by outer method) -// if (!ss.empty()) { -// return Res::Err("%s: deserialization failed: excess %d bytes", __func__, ss.size()); -// } - return Res::Ok(); + // construct undo + auto& flushable = dynamic_cast(view.GetRaw()); + auto undo = CUndo::Construct(mnview.GetRaw(), flushable.GetRaw()); + // flush changes + view.Flush(); + // write undo + if (!undo.before.empty()) { + mnview.SetUndo(UndoKey{height, tx.GetHash()}, undo); + } + return res; } - ResVal ApplyAnchorRewardTx(CCustomCSView & mnview, CTransaction const & tx, int height, uint256 const & prevStakeModifier, std::vector const & metadata, Consensus::Params const & consensusParams) { if (height >= consensusParams.DakotaHeight) { @@ -1503,8 +1122,8 @@ ResVal ApplyAnchorRewardTxPlus(CCustomCSView & mnview, CTransaction con bool IsMempooledCustomTxCreate(const CTxMemPool & pool, const uint256 & txid) { CTransactionRef ptx = pool.get(txid); - std::vector dummy; if (ptx) { + std::vector dummy; CustomTxType txType = GuessCustomTxType(*ptx, dummy); return txType == CustomTxType::CreateMasternode || txType == CustomTxType::CreateToken; } diff --git a/src/masternodes/mn_checks.h b/src/masternodes/mn_checks.h index 06da90ad2b..55bd66502f 100644 --- a/src/masternodes/mn_checks.h +++ b/src/masternodes/mn_checks.h @@ -7,9 +7,12 @@ #include #include +#include #include #include +#include + class CBlock; class CTransaction; class CTxMemPool; @@ -59,36 +62,14 @@ enum class CustomTxType : unsigned char }; inline CustomTxType CustomTxCodeToType(unsigned char ch) { - char const txtypes[] = "CRTMNnpuslrUbBaGA"; - if (memchr(txtypes, ch, strlen(txtypes))) + constexpr const char txtypes[] = "CRTMNnpuslrUbBaGA"; + if (memchr(txtypes, ch, sizeof(txtypes) - 1)) return static_cast(ch); else return CustomTxType::None; } -inline std::string ToString(CustomTxType type) { - switch (type) - { - case CustomTxType::CreateMasternode: return "CreateMasternode"; - case CustomTxType::ResignMasternode: return "ResignMasternode"; - case CustomTxType::CreateToken: return "CreateToken"; - case CustomTxType::UpdateToken: return "UpdateToken"; - case CustomTxType::UpdateTokenAny: return "UpdateTokenAny"; - case CustomTxType::MintToken: return "MintToken"; - case CustomTxType::CreatePoolPair: return "CreatePoolPair"; - case CustomTxType::UpdatePoolPair: return "UpdatePoolPair"; - case CustomTxType::PoolSwap: return "PoolSwap"; - case CustomTxType::AddPoolLiquidity: return "AddPoolLiquidity"; - case CustomTxType::RemovePoolLiquidity: return "RemovePoolLiquidity"; - case CustomTxType::UtxosToAccount: return "UtxosToAccount"; - case CustomTxType::AccountToUtxos: return "AccountToUtxos"; - case CustomTxType::AccountToAccount: return "AccountToAccount"; - case CustomTxType::AnyAccountsToAccounts: return "AnyAccountsToAccounts"; - case CustomTxType::SetGovVariable: return "SetGovVariable"; - case CustomTxType::AutoAuthPrep: return "AutoAuth"; - default: return "None"; - } -} +std::string ToString(CustomTxType type); // it's disabled after Dakota height inline bool NotAllowedToFail(CustomTxType txType, int height) { @@ -97,8 +78,7 @@ inline bool NotAllowedToFail(CustomTxType txType, int height) { } template -inline void Serialize(Stream& s, CustomTxType txType) -{ +inline void Serialize(Stream& s, CustomTxType txType) { Serialize(s, static_cast(txType)); } @@ -110,72 +90,150 @@ inline void Unserialize(Stream& s, CustomTxType & txType) { txType = CustomTxCodeToType(ch); } -Res ApplyCustomTx(CCustomCSView & mnview, CCoinsViewCache const & coins, CTransaction const & tx, const Consensus::Params& consensusParams, uint32_t height, const uint64_t& time, uint32_t txn, bool isCheck = true, bool skipAuth = false); -//! Deep check (and write) -Res ApplyCreateMasternodeTx(CCustomCSView & mnview, CTransaction const & tx, uint32_t height, const uint64_t &time, std::vector const & metadata, UniValue* rpcInfo = nullptr); -Res ApplyResignMasternodeTx(CCustomCSView & mnview, CCoinsViewCache const & coins, CTransaction const & tx, uint32_t height, std::vector const & metadata, bool skipAuth = false, UniValue* rpcInfo = nullptr); +struct CAccountToUtxosMessage; +struct CAccountToAccountMessage; +struct CAnchorFinalizationMessage; +struct CAnyAccountsToAccountsMessage; +struct CBalances; +struct CLiquidityMessage; +struct CPoolSwapMessage; +struct CRemoveLiquidityMessage; +struct CUtxosToAccountMessage; + +struct CCreateMasterNodeMessage { + char operatorType; + CKeyID operatorAuthAddress; -Res ApplyCreateTokenTx(CCustomCSView & mnview, CCoinsViewCache const & coins, CTransaction const & tx, uint32_t height, std::vector const & metadata, Consensus::Params const & consensusParams, bool skipAuth = false, UniValue* rpcInfo = nullptr); -Res ApplyUpdateTokenTx(CCustomCSView & mnview, CCoinsViewCache const & coins, CTransaction const & tx, uint32_t height, std::vector const & metadata, Consensus::Params const & consensusParams, bool skipAuth = false, UniValue* rpcInfo = nullptr); -Res ApplyUpdateTokenAnyTx(CCustomCSView & mnview, CCoinsViewCache const & coins, CTransaction const & tx, uint32_t height, std::vector const & metadata, Consensus::Params const & consensusParams, bool skipAuth = false, UniValue* rpcInfo = nullptr); -Res ApplyMintTokenTx(CCustomCSView & mnview, CCoinsViewCache const & coins, CTransaction const & tx, uint32_t height, std::vector const & metadata, Consensus::Params const & consensusParams, bool skipAuth = false, UniValue* rpcInfo = nullptr); + ADD_SERIALIZE_METHODS; + template + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(operatorType); + READWRITE(operatorAuthAddress); + } +}; -Res ApplyCreatePoolPairTx(CCustomCSView & mnview, CCoinsViewCache const & coins, CTransaction const & tx, uint32_t height, std::vector const & metadata, Consensus::Params const & consensusParams, bool skipAuth = false, UniValue* rpcInfo = nullptr); -Res ApplyUpdatePoolPairTx(CCustomCSView & mnview, CCoinsViewCache const & coins, CTransaction const & tx, uint32_t height, std::vector const & metadata, Consensus::Params const & consensusParams, bool skipAuth = false, UniValue* rpcInfo = nullptr); -Res ApplyPoolSwapTx(CCustomCSView & mnview, CCoinsViewCache const & coins, CTransaction const & tx, uint32_t height, std::vector const & metadata, Consensus::Params const & consensusParams, bool skipAuth = false, UniValue* rpcInfo = nullptr); -Res ApplyAddPoolLiquidityTx(CCustomCSView & mnview, CCoinsViewCache const & coins, CTransaction const & tx, uint32_t height, std::vector const & metadata, Consensus::Params const & consensusParams, bool skipAuth = false, UniValue* rpcInfo = nullptr); -Res ApplyRemovePoolLiquidityTx(CCustomCSView & mnview, CCoinsViewCache const & coins, CTransaction const & tx, uint32_t height, std::vector const & metadata, Consensus::Params const & consensusParams, bool skipAuth = false, UniValue* rpcInfo = nullptr); +struct CResignMasterNodeMessage : public uint256 { + using uint256::uint256; -Res ApplyUtxosToAccountTx(CCustomCSView & mnview, CTransaction const & tx, uint32_t height, std::vector const & metadata, Consensus::Params const & consensusParams, UniValue* rpcInfo = nullptr); -Res ApplyAccountToUtxosTx(CCustomCSView & mnview, CCoinsViewCache const & coins, CTransaction const & tx, uint32_t height, std::vector const & metadata, Consensus::Params const & consensusParams, bool skipAuth = false, UniValue* rpcInfo = nullptr); -Res ApplyAccountToAccountTx(CCustomCSView & mnview, CCoinsViewCache const & coins, CTransaction const & tx, uint32_t height, std::vector const & metadata, Consensus::Params const & consensusParams, bool skipAuth = false, UniValue* rpcInfo = nullptr); -Res ApplyAnyAccountsToAccountsTx(CCustomCSView & mnview, CCoinsViewCache const & coins, CTransaction const & tx, uint32_t height, std::vector const & metadata, Consensus::Params const & consensusParams, bool skipAuth = false, UniValue* rpcInfo = nullptr); + ADD_SERIALIZE_METHODS; + template + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITEAS(uint256, *this); + } +}; -Res ApplySetGovernanceTx(CCustomCSView & mnview, CCoinsViewCache const & coins, CTransaction const & tx, uint32_t height, std::vector const & metadata, Consensus::Params const & consensusParams, bool skipAuth = false, UniValue* rpcInfo = nullptr); +struct CCreateTokenMessage : public CToken { + using CToken::CToken; -ResVal ApplyAnchorRewardTx(CCustomCSView & mnview, CTransaction const & tx, int height, uint256 const & prevStakeModifier, std::vector const & metadata, Consensus::Params const & consensusParams); -ResVal ApplyAnchorRewardTxPlus(CCustomCSView & mnview, CTransaction const & tx, int height, std::vector const & metadata, Consensus::Params const & consensusParams); + ADD_SERIALIZE_METHODS; + template + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITEAS(CToken, *this); + } +}; + +struct CUpdateTokenPreAMKMessage { + uint256 tokenTx; + bool isDAT; + + ADD_SERIALIZE_METHODS; + template + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(tokenTx); + READWRITE(isDAT); + } +}; + +struct CUpdateTokenMessage { + uint256 tokenTx; + CToken token; + + ADD_SERIALIZE_METHODS; + template + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(tokenTx); + READWRITE(token); + } +}; -bool IsMempooledCustomTxCreate(const CTxMemPool& pool, const uint256 & txid); +struct CMintTokensMessage : public CBalances { + using CBalances::CBalances; + + ADD_SERIALIZE_METHODS; + template + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITEAS(CBalances, *this); + } +}; + +struct CCreatePoolPairMessage { + CPoolPairMessage poolPair; + std::string pairSymbol; + CBalances rewards; +}; + +struct CUpdatePoolPairMessage { + DCT_ID poolId; + bool status; + CAmount commission; + CScript ownerAddress; + CBalances rewards; +}; + +struct CGovernanceMessage { + std::set> govs; +}; + +struct CCustomTxMessageNone {}; + +typedef boost::variant< + CCustomTxMessageNone, + CCreateMasterNodeMessage, + CResignMasterNodeMessage, + CCreateTokenMessage, + CUpdateTokenPreAMKMessage, + CUpdateTokenMessage, + CMintTokensMessage, + CCreatePoolPairMessage, + CUpdatePoolPairMessage, + CPoolSwapMessage, + CLiquidityMessage, + CRemoveLiquidityMessage, + CUtxosToAccountMessage, + CAccountToUtxosMessage, + CAccountToAccountMessage, + CAnyAccountsToAccountsMessage, + CGovernanceMessage +> CCustomTxMessage; + +CCustomTxMessage customTypeToMessage(CustomTxType txType); +bool IsMempooledCustomTxCreate(const CTxMemPool& pool, const uint256& txid); +Res RpcInfo(const CTransaction& tx, uint32_t height, CustomTxType& type, UniValue& results); +Res CustomMetadataParse(uint32_t height, const Consensus::Params& consensus, const std::vector& metadata, CCustomTxMessage& txMessage); +Res ApplyCustomTx(CCustomCSView& mnview, const CCoinsViewCache& coins, const CTransaction& tx, const Consensus::Params& consensus, uint32_t height, uint64_t time = 0, uint32_t txn = 0); +Res CustomTxVisit(CCustomCSView& mnview, const CCoinsViewCache& coins, const CTransaction& tx, uint32_t height, const Consensus::Params& consensus, const CCustomTxMessage& txMessage, uint64_t time = 0); +ResVal ApplyAnchorRewardTx(CCustomCSView& mnview, const CTransaction& tx, int height, const uint256& prevStakeModifier, const std::vector& metadata, const Consensus::Params& consensusParams); +ResVal ApplyAnchorRewardTxPlus(CCustomCSView& mnview, const CTransaction& tx, int height, const std::vector& metadata, const Consensus::Params& consensusParams); -// @todo refactor header functions /* * Checks if given tx is probably one of 'CustomTx', returns tx type and serialized metadata in 'data' */ -inline CustomTxType GuessCustomTxType(CTransaction const & tx, std::vector & metadata) -{ - if (tx.vout.size() == 0) - { +inline CustomTxType GuessCustomTxType(CTransaction const & tx, std::vector & metadata){ + if (tx.vout.empty()) { return CustomTxType::None; } - CScript const & memo = tx.vout[0].scriptPubKey; - CScript::const_iterator pc = memo.begin(); - opcodetype opcode; - if (!memo.GetOp(pc, opcode) || opcode != OP_RETURN) - { + if (!ParseScriptByMarker(tx.vout[0].scriptPubKey, DfTxMarker, metadata)) { return CustomTxType::None; } - if (!memo.GetOp(pc, opcode, metadata) || - (opcode > OP_PUSHDATA1 && - opcode != OP_PUSHDATA2 && - opcode != OP_PUSHDATA4) || - metadata.size() < DfTxMarker.size() + 1 || // i don't know how much exactly, but at least MnTxSignature + type prefix - memcmp(&metadata[0], &DfTxMarker[0], DfTxMarker.size()) != 0) - { - return CustomTxType::None; - } - auto txType = CustomTxCodeToType(metadata[DfTxMarker.size()]); - metadata.erase(metadata.begin(), metadata.begin() + DfTxMarker.size() + 1); + auto txType = CustomTxCodeToType(metadata[0]); + metadata.erase(metadata.begin()); return txType; } -inline boost::optional> GetMintTokenMetadata(const CTransaction & tx) +inline bool IsMintTokenTx(const CTransaction& tx) { std::vector metadata; - if (GuessCustomTxType(tx, metadata) == CustomTxType::MintToken) { - return metadata; - } - return {}; + return GuessCustomTxType(tx, metadata) == CustomTxType::MintToken; } inline boost::optional> GetAccountToUtxosMetadata(const CTransaction & tx) diff --git a/src/masternodes/mn_rpc.cpp b/src/masternodes/mn_rpc.cpp index 6658fd8b90..76ca161585 100644 --- a/src/masternodes/mn_rpc.cpp +++ b/src/masternodes/mn_rpc.cpp @@ -372,6 +372,26 @@ std::vector GetAuthInputsSmart(CWallet* const pwallet, int32_t txVersion, return result; } +void execTestTx(const CTransaction& tx, uint32_t height, const std::vector& metadata, CCustomTxMessage txMessage, const CCoinsViewCache& coins) { + auto res = CustomMetadataParse(height, Params().GetConsensus(), metadata, txMessage); + if (res) { + CCustomCSView view(*pcustomcsview); + res = CustomTxVisit(view, coins, tx, height, Params().GetConsensus(), txMessage); + } + if (!res) { + std::vector data; + auto txType = GuessCustomTxType(tx, data); + if (data != metadata) { + throw JSONRPCError(RPC_INVALID_REQUEST, "tx <-> metadata mismatch"); + } + if (res.code == CustomTxErrCodes::NotEnoughBalance) { + throw JSONRPCError(RPC_INVALID_REQUEST, + strprintf("Test %sTx execution failed: not enough balance on owner's account, call utxostoaccount to increase it.\n%s", ToString(txType), res.msg)); + } + throw JSONRPCError(RPC_INVALID_REQUEST, strprintf("Test %sTx execution failed:\n%s", ToString(txType), res.msg)); + } +} + CWallet* GetWallet(const JSONRPCRequest& request) { std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request); CWallet* const pwallet = wallet.get(); @@ -495,15 +515,10 @@ UniValue setgov(const JSONRPCRequest& request) { // check execution { LOCK(cs_main); - CCustomCSView mnview_dummy(*pcustomcsview); // don't write into actual DB - CCoinsViewCache coinview(&::ChainstateActive().CoinsTip()); + CCoinsViewCache coins(&::ChainstateActive().CoinsTip()); if (optAuthTx) - AddCoins(coinview, *optAuthTx, targetHeight); - const auto res = ApplySetGovernanceTx(mnview_dummy, coinview, CTransaction(rawTx), targetHeight, - ToByteVector(varStream), Params().GetConsensus()); - if (!res.ok) { - throw JSONRPCError(RPC_INVALID_REQUEST, "Execution test failed:\n" + res.msg); - } + AddCoins(coins, *optAuthTx, targetHeight); + execTestTx(CTransaction(rawTx), targetHeight, ToByteVector(varStream), CGovernanceMessage{}, coins); } return signsend(rawTx, pwallet, optAuthTx)->GetHash().GetHex(); } diff --git a/src/masternodes/mn_rpc.h b/src/masternodes/mn_rpc.h index 305d4416da..8fc974409e 100644 --- a/src/masternodes/mn_rpc.h +++ b/src/masternodes/mn_rpc.h @@ -73,6 +73,7 @@ std::vector GetAuthInputsSmart(CWallet* const pwallet, int32_t txVersion, std::string ScriptToString(CScript const& script); CAccounts GetAllMineAccounts(CWallet* const pwallet); CAccounts SelectAccountsByTargetBalances(const CAccounts& accounts, const CBalances& targetBalances, AccountSelectionMode selectionMode); +void execTestTx(const CTransaction& tx, uint32_t height, const std::vector& metadata, CCustomTxMessage txMessage, const CCoinsViewCache& coins = g_chainstate->CoinsTip()); CScript CreateScriptForHTLC(const JSONRPCRequest& request, uint32_t &blocks, std::vector& image); #endif // DEFI_MASTERNODES_MN_RPC_H diff --git a/src/masternodes/poolpairs.cpp b/src/masternodes/poolpairs.cpp index ef6a697fad..b13c7ececa 100644 --- a/src/masternodes/poolpairs.cpp +++ b/src/masternodes/poolpairs.cpp @@ -51,7 +51,7 @@ Res CPoolPairView::SetPoolPair(DCT_ID const & poolId, CPoolPair const & pool) return Res::Err("Error: Couldn't create/update pool pair."); } -Res CPoolPairView::UpdatePoolPair(DCT_ID const & poolId, bool & status, CAmount const & commission, CScript const & ownerAddress) +Res CPoolPairView::UpdatePoolPair(DCT_ID const & poolId, bool status, CAmount const & commission, CScript const & ownerAddress) { auto poolPair = GetPoolPair(poolId); if (!poolPair) { diff --git a/src/masternodes/poolpairs.h b/src/masternodes/poolpairs.h index e1c3bbacfe..8d5f8d41ec 100644 --- a/src/masternodes/poolpairs.h +++ b/src/masternodes/poolpairs.h @@ -113,7 +113,7 @@ class CPoolPair : public CPoolPairMessage // 'amountA' && 'amountB' should be normalized (correspond) to actual 'tokenA' and 'tokenB' ids in the pair!! // otherwise, 'AddLiquidity' should be () external to 'CPairPool' (i.e. CPoolPairView::AddLiquidity(TAmount a,b etc) with internal lookup of pool by TAmount a,b) - Res AddLiquidity(CAmount amountA, CAmount amountB, CScript const & shareAddress, std::function onMint, bool slippageProtection = false) { + Res AddLiquidity(CAmount amountA, CAmount amountB, std::function onMint, bool slippageProtection = false) { // instead of assertion due to tests if (amountA <= 0 || amountB <= 0) { return Res::Err("amounts should be positive"); @@ -159,7 +159,7 @@ class CPoolPair : public CPoolPairMessage return Res::Err("overflow when adding to reserves"); } - return onMint(shareAddress, liquidity); + return onMint(liquidity); } Res RemoveLiquidity(CScript const & address, CAmount const & liqAmount, std::function onReclaim) { @@ -250,7 +250,7 @@ class CPoolPairView : public virtual CStorageView { public: Res SetPoolPair(const DCT_ID &poolId, CPoolPair const & pool); - Res UpdatePoolPair(DCT_ID const & poolId, bool & status, CAmount const & commission, CScript const & ownerAddress); + Res UpdatePoolPair(DCT_ID const & poolId, bool status, CAmount const & commission, CScript const & ownerAddress); Res SetPoolCustomReward(const DCT_ID &poolId, CBalances &rewards); boost::optional GetPoolCustomReward(const DCT_ID &poolId); @@ -328,10 +328,10 @@ class CPoolPairView : public virtual CStorageView feeA = pool.blockCommissionA * liqWeight / PRECISION; feeB = pool.blockCommissionB * liqWeight / PRECISION; } - if (onTransfer(provider, CScript(), poolId, uint8_t(RewardType::Commission), {pool.idTokenA, feeA}).ok) { + if (onTransfer(provider, CScript(), poolId, uint8_t(RewardType::Commission), {pool.idTokenA, feeA})) { distributedFeeA += feeA; } - if (onTransfer(provider, CScript(), poolId, uint8_t(RewardType::Commission), {pool.idTokenB, feeB}).ok) { + if (onTransfer(provider, CScript(), poolId, uint8_t(RewardType::Commission), {pool.idTokenB, feeB})) { distributedFeeB += feeB; } } @@ -343,7 +343,7 @@ class CPoolPairView : public virtual CStorageView providerReward = poolReward * liqWeight / PRECISION; } if (providerReward) { - if (onTransfer(provider, CScript(), poolId, uint8_t(RewardType::Rewards), {DCT_ID{0}, providerReward}).ok) { + if (onTransfer(provider, CScript(), poolId, uint8_t(RewardType::Rewards), {DCT_ID{0}, providerReward})) { totalDistributed += providerReward; } } @@ -363,13 +363,15 @@ class CPoolPairView : public virtual CStorageView return true; }, PoolShareKey{poolId, CScript{}}); - pool.blockCommissionA -= distributedFeeA; - pool.blockCommissionB -= distributedFeeB; - pool.swapEvent = false; + if (pool.swapEvent) { + pool.blockCommissionA -= distributedFeeA; + pool.blockCommissionB -= distributedFeeB; + pool.swapEvent = false; - auto res = SetPoolPair(poolId, pool); - if (!res.ok) { - LogPrintf("Pool rewards: can't update pool (id=%s) state: %s\n", poolId.ToString(), res.msg); + auto res = SetPoolPair(poolId, pool); + if (!res.ok) { + LogPrintf("Pool rewards: can't update pool (id=%s) state: %s\n", poolId.ToString(), res.msg); + } } return true; }); diff --git a/src/masternodes/res.h b/src/masternodes/res.h index 647c3d2fe3..3ea6a5221e 100644 --- a/src/masternodes/res.h +++ b/src/masternodes/res.h @@ -14,6 +14,10 @@ struct Res Res() = delete; + operator bool() const { + return ok; + } + template static Res Err(std::string const & err, const Args&... args) { return Res{false, tfm::format(err, args...), 0, {} }; @@ -69,15 +73,19 @@ struct ResVal : public Res assert(this->ok); // if value if provided, then it's never an error } - Res res() const { - return *this; + operator bool() const { + return ok; + } + + operator T() const { + assert(ok); + return *val; } template - T ValOrException(F&& _throw) const { + T ValOrException(F&& func) const { if (!ok) { - _throw(code, msg); - throw std::logic_error{msg}; // shouldn't be reachable because of _throw + throw func(code, msg); } return *val; } diff --git a/src/masternodes/rpc_accounts.cpp b/src/masternodes/rpc_accounts.cpp index 415a610036..561c13c6ad 100644 --- a/src/masternodes/rpc_accounts.cpp +++ b/src/masternodes/rpc_accounts.cpp @@ -557,12 +557,8 @@ UniValue utxostoaccount(const JSONRPCRequest& request) { // check execution { LOCK(cs_main); - CCustomCSView mnview_dummy(*pcustomcsview); // don't write into actual DB - const auto res = ApplyUtxosToAccountTx(mnview_dummy, CTransaction(rawTx), targetHeight, - ToByteVector(CDataStream{SER_NETWORK, PROTOCOL_VERSION, msg}), Params().GetConsensus()); - if (!res.ok) { - throw JSONRPCError(RPC_INVALID_REQUEST, "Execution test failed:\n" + res.msg); - } + auto metadata = ToByteVector(CDataStream{SER_NETWORK, PROTOCOL_VERSION, msg}); + execTestTx(CTransaction(rawTx), targetHeight, metadata, CUtxosToAccountMessage{}); } return signsend(rawTx, pwallet, {})->GetHash().GetHex(); @@ -658,21 +654,11 @@ UniValue accounttoaccount(const JSONRPCRequest& request) { // check execution { LOCK(cs_main); - CCustomCSView mnview_dummy(*pcustomcsview); // don't write into actual DB - CCoinsViewCache coinview(&::ChainstateActive().CoinsTip()); + CCoinsViewCache coins(&::ChainstateActive().CoinsTip()); if (optAuthTx) - AddCoins(coinview, *optAuthTx, targetHeight); - const auto res = ApplyAccountToAccountTx(mnview_dummy, coinview, CTransaction(rawTx), targetHeight, - ToByteVector(CDataStream{SER_NETWORK, PROTOCOL_VERSION, msg}), Params().GetConsensus()); - if (!res.ok) { - /// @todo unlock - if (res.code == CustomTxErrCodes::NotEnoughBalance) { - throw JSONRPCError(RPC_INVALID_REQUEST, - "Execution test failed: not enough balance on owner's account, call utxostoaccount to increase it.\n" + - res.msg); - } - throw JSONRPCError(RPC_INVALID_REQUEST, "Execution test failed:\n" + res.msg); - } + AddCoins(coins, *optAuthTx, targetHeight); + auto metadata = ToByteVector(CDataStream{SER_NETWORK, PROTOCOL_VERSION, msg}); + execTestTx(CTransaction(rawTx), targetHeight, metadata, CAccountToAccountMessage{}, coins); } return signsend(rawTx, pwallet, optAuthTx)->GetHash().GetHex(); } @@ -786,20 +772,11 @@ UniValue accounttoutxos(const JSONRPCRequest& request) { // check execution { LOCK(cs_main); - CCustomCSView mnview_dummy(*pcustomcsview); // don't write into actual DB - CCoinsViewCache coinview(&::ChainstateActive().CoinsTip()); + CCoinsViewCache coins(&::ChainstateActive().CoinsTip()); if (optAuthTx) - AddCoins(coinview, *optAuthTx, targetHeight); - const auto res = ApplyAccountToUtxosTx(mnview_dummy, coinview, CTransaction(rawTx), targetHeight, - ToByteVector(CDataStream{SER_NETWORK, PROTOCOL_VERSION, msg}), Params().GetConsensus()); - if (!res.ok) { - if (res.code == CustomTxErrCodes::NotEnoughBalance) { - throw JSONRPCError(RPC_INVALID_REQUEST, - "Execution test failed: not enough balance on owner's account, call utxostoaccount to increase it.\n" + - res.msg); - } - throw JSONRPCError(RPC_INVALID_REQUEST, "Execution test failed:\n" + res.msg); - } + AddCoins(coins, *optAuthTx, targetHeight); + auto metadata = ToByteVector(CDataStream{SER_NETWORK, PROTOCOL_VERSION, msg}); + execTestTx(CTransaction(rawTx), targetHeight, metadata, CAccountToUtxosMessage{}, coins); } return signsend(rawTx, pwallet, optAuthTx)->GetHash().GetHex(); } @@ -1390,22 +1367,12 @@ UniValue sendtokenstoaddress(const JSONRPCRequest& request) { // check execution { LOCK(cs_main); - CCustomCSView mnview_dummy(*pcustomcsview); // don't write into actual DB - CCoinsViewCache coinview(&::ChainstateActive().CoinsTip()); + CCoinsViewCache coins(&::ChainstateActive().CoinsTip()); if (optAuthTx) - AddCoins(coinview, *optAuthTx, targetHeight); - const auto res = ApplyAnyAccountsToAccountsTx(mnview_dummy, coinview, CTransaction(rawTx), targetHeight, - ToByteVector(CDataStream{SER_NETWORK, PROTOCOL_VERSION, msg}), Params().GetConsensus()); - if (!res.ok) { - if (res.code == CustomTxErrCodes::NotEnoughBalance) { - throw JSONRPCError(RPC_INVALID_REQUEST, - "Execution test failed: not enough balance on owner's account, call utxostoaccount to increase it.\n" + - res.msg); - } - throw JSONRPCError(RPC_INVALID_REQUEST, "Execution test failed:\n" + res.msg); - } + AddCoins(coins, *optAuthTx, targetHeight); + auto metadata = ToByteVector(CDataStream{SER_NETWORK, PROTOCOL_VERSION, msg}); + execTestTx(CTransaction(rawTx), targetHeight, metadata, CAnyAccountsToAccountsMessage{}, coins); } - return signsend(rawTx, pwallet, optAuthTx)->GetHash().GetHex(); } diff --git a/src/masternodes/rpc_customtx.cpp b/src/masternodes/rpc_customtx.cpp new file mode 100644 index 0000000000..fa32c633a6 --- /dev/null +++ b/src/masternodes/rpc_customtx.cpp @@ -0,0 +1,198 @@ + +#include +#include +#include +#include +#include +#include + +#include +#include + +extern std::string ScriptToString(CScript const& script); + +class CCustomTxRpcVisitor : public boost::static_visitor +{ + uint32_t height; + UniValue& rpcInfo; + CCustomCSView& mnview; + const CTransaction& tx; + + void tokenInfo(const CToken& token) const { + rpcInfo.pushKV("name", token.name); + rpcInfo.pushKV("symbol", token.symbol); + rpcInfo.pushKV("isDAT", token.IsDAT()); + rpcInfo.pushKV("mintable", token.IsMintable()); + rpcInfo.pushKV("tradeable", token.IsTradeable()); + rpcInfo.pushKV("finalized", token.IsFinalized()); + } + + void customRewardsInfo(const CBalances& rewards) const { + UniValue rewardArr(UniValue::VARR); + for (const auto& reward : rewards.balances) { + if (reward.second > 0) { + rewardArr.push_back(CTokenAmount{reward.first, reward.second}.ToString()); + } + } + if (!rewardArr.empty()) { + rpcInfo.pushKV("customRewards", rewardArr); + } + } + + UniValue accountsInfo(const CAccounts& accounts) const { + UniValue info(UniValue::VOBJ); + for (const auto& account : accounts) { + info.pushKV(account.first.GetHex(), account.second.ToString()); + } + return info; + } + +public: + CCustomTxRpcVisitor(const CTransaction& tx, uint32_t height, CCustomCSView& mnview, UniValue& rpcInfo) + : height(height), rpcInfo(rpcInfo), mnview(mnview), tx(tx) { + } + + void operator()(const CCreateMasterNodeMessage& obj) const { + rpcInfo.pushKV("collateralamount", ValueFromAmount(GetMnCollateralAmount(height))); + rpcInfo.pushKV("masternodeoperator", EncodeDestination(obj.operatorType == 1 ? + CTxDestination(PKHash(obj.operatorAuthAddress)) : + CTxDestination(WitnessV0KeyHash(obj.operatorAuthAddress)))); + } + + void operator()(const CResignMasterNodeMessage& obj) const { + rpcInfo.pushKV("id", obj.GetHex()); + } + + void operator()(const CCreateTokenMessage& obj) const { + rpcInfo.pushKV("creationTx", tx.GetHash().GetHex()); + tokenInfo(obj); + } + + void operator()(const CUpdateTokenPreAMKMessage& obj) const { + rpcInfo.pushKV("isDAT", obj.isDAT); + } + + void operator()(const CUpdateTokenMessage& obj) const { + tokenInfo(obj.token); + } + + void operator()(const CMintTokensMessage& obj) const { + for (auto const & kv : obj.balances) { + if (auto token = mnview.GetToken(kv.first)) { + auto tokenImpl = static_cast(*token); + if (auto tokenPair = mnview.GetTokenByCreationTx(tokenImpl.creationTx)) { + rpcInfo.pushKV(tokenPair->first.ToString(), ValueFromAmount(kv.second)); + } + } + } + } + + void operator()(const CLiquidityMessage& obj) const { + CBalances sumTx = SumAllTransfers(obj.from); + if (sumTx.balances.size() == 2) { + auto amountA = *sumTx.balances.begin(); + auto amountB = *(std::next(sumTx.balances.begin(), 1)); + rpcInfo.pushKV(amountA.first.ToString(), ValueFromAmount(amountA.second)); + rpcInfo.pushKV(amountB.first.ToString(), ValueFromAmount(amountB.second)); + rpcInfo.pushKV("shareaddress", obj.shareAddress.GetHex()); + } + } + + void operator()(const CRemoveLiquidityMessage& obj) const { + rpcInfo.pushKV("from", obj.from.GetHex()); + rpcInfo.pushKV("amount", obj.amount.ToString()); + } + + void operator()(const CUtxosToAccountMessage& obj) const { + rpcInfo.pushKVs(accountsInfo(obj.to)); + } + + void operator()(const CAccountToUtxosMessage& obj) const { + rpcInfo.pushKV("from", obj.from.GetHex()); + + UniValue dest(UniValue::VOBJ); + for (uint32_t i = obj.mintingOutputsStart; i < static_cast(tx.vout.size()); i++) { + dest.pushKV(tx.vout[i].scriptPubKey.GetHex(), tx.vout[i].TokenAmount().ToString()); + } + rpcInfo.pushKV("to", dest); + } + + void operator()(CAccountToAccountMessage& obj) const { + rpcInfo.pushKV("from", obj.from.GetHex()); + rpcInfo.pushKV("to", accountsInfo(obj.to)); + } + + void operator()(const CAnyAccountsToAccountsMessage& obj) const { + rpcInfo.pushKV("from", accountsInfo(obj.from)); + rpcInfo.pushKV("to", accountsInfo(obj.to)); + } + + void operator()(const CCreatePoolPairMessage& obj) const { + auto tokenA = mnview.GetToken(obj.poolPair.idTokenA); + auto tokenB = mnview.GetToken(obj.poolPair.idTokenB); + auto tokenPair = mnview.GetTokenByCreationTx(tx.GetHash()); + if (!tokenA || !tokenB || !tokenPair) { + return; + } + rpcInfo.pushKV("creationTx", tx.GetHash().GetHex()); + tokenInfo(tokenPair->second); + rpcInfo.pushKV("tokenA", tokenA->name); + rpcInfo.pushKV("tokenB", tokenB->name); + rpcInfo.pushKV("commission", ValueFromAmount(obj.poolPair.commission)); + rpcInfo.pushKV("status", obj.poolPair.status); + rpcInfo.pushKV("ownerAddress", ScriptToString(obj.poolPair.ownerAddress)); + customRewardsInfo(obj.rewards); + } + + void operator()(const CUpdatePoolPairMessage& obj) const { + rpcInfo.pushKV("commission", ValueFromAmount(obj.commission)); + rpcInfo.pushKV("status", obj.status); + rpcInfo.pushKV("ownerAddress", ScriptToString(obj.ownerAddress)); + + // Add rewards here before processing them below to avoid adding current rewards + if (!obj.rewards.balances.empty()) { + UniValue rewardArr(UniValue::VARR); + auto& rewards = obj.rewards; + // Check for special case to wipe rewards + if (rewards.balances.size() == 1 && rewards.balances.cbegin()->first == DCT_ID{std::numeric_limits::max()} + && rewards.balances.cbegin()->second == std::numeric_limits::max()) { + rpcInfo.pushKV("customRewards", rewardArr); + } else { + customRewardsInfo(rewards); + } + } + } + + void operator()(const CPoolSwapMessage& obj) const { + rpcInfo.pushKV("fromAddress", obj.from.GetHex()); + rpcInfo.pushKV("fromToken", obj.idTokenFrom.ToString()); + rpcInfo.pushKV("fromAmount", ValueFromAmount(obj.amountFrom)); + rpcInfo.pushKV("toAddress", obj.to.GetHex()); + rpcInfo.pushKV("toToken", obj.idTokenTo.ToString()); + rpcInfo.pushKV("maxPrice", ValueFromAmount((obj.maxPrice.integer * COIN) + obj.maxPrice.fraction)); + } + + void operator()(const CGovernanceMessage& obj) const { + for (const auto& var : obj.govs) { + rpcInfo.pushKV(var->GetName(), var->Export()); + } + } + + void operator()(const CCustomTxMessageNone&) const { + } +}; + +Res RpcInfo(const CTransaction& tx, uint32_t height, CustomTxType& txType, UniValue& results) { + std::vector metadata; + txType = GuessCustomTxType(tx, metadata); + if (txType == CustomTxType::None) { + return Res::Ok(); + } + auto txMessage = customTypeToMessage(txType); + auto res = CustomMetadataParse(height, Params().GetConsensus(), metadata, txMessage); + if (res) { + CCustomCSView mnview(*pcustomcsview); + boost::apply_visitor(CCustomTxRpcVisitor(tx, height, mnview, results), txMessage); + } + return res; +} diff --git a/src/masternodes/rpc_masternodes.cpp b/src/masternodes/rpc_masternodes.cpp index e802e8cbb3..36dced54b0 100644 --- a/src/masternodes/rpc_masternodes.cpp +++ b/src/masternodes/rpc_masternodes.cpp @@ -127,12 +127,8 @@ UniValue createmasternode(const JSONRPCRequest& request) // check execution { LOCK(cs_main); - CCustomCSView mnview_dummy(*pcustomcsview); // don't write into actual DB - const auto res = ApplyCreateMasternodeTx(mnview_dummy, CTransaction(rawTx), targetHeight, uint64_t{0}, - ToByteVector(CDataStream{SER_NETWORK, PROTOCOL_VERSION, static_cast(operatorDest.which()), operatorAuthKey})); - if (!res.ok) { - throw JSONRPCError(RPC_INVALID_REQUEST, "Execution test failed:\n" + res.msg); - } + auto metadata = ToByteVector(CDataStream{SER_NETWORK, PROTOCOL_VERSION, static_cast(operatorDest.which()), operatorAuthKey}); + execTestTx(CTransaction(rawTx), targetHeight, metadata, CCreateMasterNodeMessage{}); } return signsend(rawTx, pwallet, {})->GetHash().GetHex(); } @@ -220,15 +216,11 @@ UniValue resignmasternode(const JSONRPCRequest& request) // check execution { LOCK(cs_main); - CCustomCSView mnview_dummy(*pcustomcsview); // don't write into actual DB - CCoinsViewCache coinview(&::ChainstateActive().CoinsTip()); + CCoinsViewCache coins(&::ChainstateActive().CoinsTip()); if (optAuthTx) - AddCoins(coinview, *optAuthTx, targetHeight); - const auto res = ApplyResignMasternodeTx(mnview_dummy, coinview, CTransaction(rawTx), targetHeight, - ToByteVector(CDataStream{SER_NETWORK, PROTOCOL_VERSION, nodeId})); - if (!res.ok) { - throw JSONRPCError(RPC_INVALID_REQUEST, "Execution test failed:\n" + res.msg); - } + AddCoins(coins, *optAuthTx, targetHeight); + auto metadata = ToByteVector(CDataStream{SER_NETWORK, PROTOCOL_VERSION, nodeId}); + execTestTx(CTransaction(rawTx), targetHeight, metadata, CResignMasterNodeMessage{}, coins); } return signsend(rawTx, pwallet, optAuthTx)->GetHash().GetHex(); } diff --git a/src/masternodes/rpc_poolpair.cpp b/src/masternodes/rpc_poolpair.cpp index bc79dbce32..3b92ba4740 100644 --- a/src/masternodes/rpc_poolpair.cpp +++ b/src/masternodes/rpc_poolpair.cpp @@ -348,15 +348,11 @@ UniValue addpoolliquidity(const JSONRPCRequest& request) { // check execution { LOCK(cs_main); - CCustomCSView mnview_dummy(*pcustomcsview); // don't write into actual DB - CCoinsViewCache coinview(&::ChainstateActive().CoinsTip()); + CCoinsViewCache coins(&::ChainstateActive().CoinsTip()); if (optAuthTx) - AddCoins(coinview, *optAuthTx, targetHeight); - const auto res = ApplyAddPoolLiquidityTx(mnview_dummy, coinview, CTransaction(rawTx), targetHeight, ToByteVector(CDataStream{SER_NETWORK, PROTOCOL_VERSION, msg}), Params().GetConsensus()); - - if (!res.ok) { - throw JSONRPCError(RPC_INVALID_REQUEST, "Execution test failed:\n" + res.msg); - } + AddCoins(coins, *optAuthTx, targetHeight); + auto metadata = ToByteVector(CDataStream{SER_NETWORK, PROTOCOL_VERSION, msg}); + execTestTx(CTransaction(rawTx), targetHeight, metadata, CLiquidityMessage{}, coins); } return signsend(rawTx, pwallet, optAuthTx)->GetHash().GetHex(); } @@ -441,15 +437,11 @@ UniValue removepoolliquidity(const JSONRPCRequest& request) { // check execution { LOCK(cs_main); - CCustomCSView mnview_dummy(*pcustomcsview); // don't write into actual DB - CCoinsViewCache coinview(&::ChainstateActive().CoinsTip()); + CCoinsViewCache coins(&::ChainstateActive().CoinsTip()); if (optAuthTx) - AddCoins(coinview, *optAuthTx, targetHeight); - const auto res = ApplyRemovePoolLiquidityTx(mnview_dummy, coinview, CTransaction(rawTx), targetHeight, ToByteVector(CDataStream{SER_NETWORK, PROTOCOL_VERSION, msg}), Params().GetConsensus()); - - if (!res.ok) { - throw JSONRPCError(RPC_INVALID_REQUEST, "Execution test failed:\n" + res.msg); - } + AddCoins(coins, *optAuthTx, targetHeight); + auto metadata = ToByteVector(CDataStream{SER_NETWORK, PROTOCOL_VERSION, msg}); + execTestTx(CTransaction(rawTx), targetHeight, metadata, CRemoveLiquidityMessage{}, coins); } return signsend(rawTx, pwallet, optAuthTx)->GetHash().GetHex(); } @@ -607,15 +599,11 @@ UniValue createpoolpair(const JSONRPCRequest& request) { // check execution { LOCK(cs_main); - CCustomCSView mnview_dummy(*pcustomcsview); // don't write into actual DB - CCoinsViewCache coinview(&::ChainstateActive().CoinsTip()); + CCoinsViewCache coins(&::ChainstateActive().CoinsTip()); if (optAuthTx) - AddCoins(coinview, *optAuthTx, targetHeight); - const auto res = ApplyCreatePoolPairTx(mnview_dummy, coinview, CTransaction(rawTx), targetHeight, - ToByteVector(CDataStream{SER_NETWORK, PROTOCOL_VERSION, poolPairMsg, pairSymbol}), Params().GetConsensus()); - if (!res.ok) { - throw JSONRPCError(RPC_INVALID_REQUEST, "Execution test failed:\n" + res.msg); - } + AddCoins(coins, *optAuthTx, targetHeight); + auto metadata = ToByteVector(CDataStream{SER_NETWORK, PROTOCOL_VERSION, poolPairMsg, pairSymbol}); + execTestTx(CTransaction(rawTx), targetHeight, metadata, CCreatePoolPairMessage{}, coins); } return signsend(rawTx, pwallet, optAuthTx)->GetHash().GetHex(); } @@ -750,15 +738,11 @@ UniValue updatepoolpair(const JSONRPCRequest& request) { // check execution { LOCK(cs_main); - CCustomCSView mnview_dummy(*pcustomcsview); // don't write into actual DB - CCoinsViewCache coinview(&::ChainstateActive().CoinsTip()); + CCoinsViewCache coins(&::ChainstateActive().CoinsTip()); if (optAuthTx) - AddCoins(coinview, *optAuthTx, targetHeight); - const auto res = ApplyUpdatePoolPairTx(mnview_dummy, coinview, CTransaction(rawTx), targetHeight, - ToByteVector(CDataStream{SER_NETWORK, PROTOCOL_VERSION, poolId, status, commission, ownerAddress}), Params().GetConsensus()); - if (!res.ok) { - throw JSONRPCError(RPC_INVALID_REQUEST, "Execution test failed:\n" + res.msg); - } + AddCoins(coins, *optAuthTx, targetHeight); + auto metadata = ToByteVector(CDataStream{SER_NETWORK, PROTOCOL_VERSION, poolId, status, commission, ownerAddress}); + execTestTx(CTransaction(rawTx), targetHeight, metadata, CUpdatePoolPairMessage{}, coins); } return signsend(rawTx, pwallet, optAuthTx)->GetHash().GetHex(); } @@ -862,15 +846,11 @@ UniValue poolswap(const JSONRPCRequest& request) { // check execution { LOCK(cs_main); - CCustomCSView mnview_dummy(*pcustomcsview); // don't write into actual DB - CCoinsViewCache coinview(&::ChainstateActive().CoinsTip()); + CCoinsViewCache coins(&::ChainstateActive().CoinsTip()); if (optAuthTx) - AddCoins(coinview, *optAuthTx, targetHeight); - const auto res = ApplyPoolSwapTx(mnview_dummy, coinview, CTransaction(rawTx), targetHeight, - ToByteVector(CDataStream{SER_NETWORK, PROTOCOL_VERSION, poolSwapMsg}), Params().GetConsensus()); - if (!res.ok) { - throw JSONRPCError(RPC_INVALID_REQUEST, "Execution test failed:\n" + res.msg); - } + AddCoins(coins, *optAuthTx, targetHeight); + auto metadata = ToByteVector(CDataStream{SER_NETWORK, PROTOCOL_VERSION, poolSwapMsg}); + execTestTx(CTransaction(rawTx), targetHeight, metadata, CPoolSwapMessage{}, coins); } return signsend(rawTx, pwallet, optAuthTx)->GetHash().GetHex(); } diff --git a/src/masternodes/rpc_tokens.cpp b/src/masternodes/rpc_tokens.cpp index 8a5368b8b4..96de076497 100644 --- a/src/masternodes/rpc_tokens.cpp +++ b/src/masternodes/rpc_tokens.cpp @@ -1,73 +1,6 @@ #include #include -static bool GetCustomTXInfo(const int nHeight, const CTransactionRef tx, CustomTxType& guess, Res& res, UniValue& txResults) -{ - std::vector metadata; - guess = GuessCustomTxType(*tx, metadata); - CCustomCSView mnview_dummy(*pcustomcsview); - - switch (guess) - { - case CustomTxType::CreateMasternode: - res = ApplyCreateMasternodeTx(mnview_dummy, *tx, nHeight, uint64_t{0}, metadata, &txResults); - break; - case CustomTxType::ResignMasternode: - res = ApplyResignMasternodeTx(mnview_dummy, ::ChainstateActive().CoinsTip(), *tx, nHeight, metadata, true, &txResults); - break; - case CustomTxType::CreateToken: - res = ApplyCreateTokenTx(mnview_dummy, ::ChainstateActive().CoinsTip(), *tx, nHeight, metadata, Params().GetConsensus(), true, &txResults); - break; - case CustomTxType::UpdateToken: - res = ApplyUpdateTokenTx(mnview_dummy, ::ChainstateActive().CoinsTip(), *tx, nHeight, metadata, Params().GetConsensus(), true, &txResults); - break; - case CustomTxType::UpdateTokenAny: - res = ApplyUpdateTokenAnyTx(mnview_dummy, ::ChainstateActive().CoinsTip(), *tx, nHeight, metadata, Params().GetConsensus(), true, &txResults); - break; - case CustomTxType::MintToken: - res = ApplyMintTokenTx(mnview_dummy, ::ChainstateActive().CoinsTip(), *tx, nHeight, metadata, Params().GetConsensus(), true, &txResults); - break; - case CustomTxType::CreatePoolPair: - res = ApplyCreatePoolPairTx(mnview_dummy, ::ChainstateActive().CoinsTip(), *tx, nHeight, metadata, Params().GetConsensus(), true, &txResults); - break; - case CustomTxType::UpdatePoolPair: - res = ApplyUpdatePoolPairTx(mnview_dummy, ::ChainstateActive().CoinsTip(), *tx, nHeight, metadata, Params().GetConsensus(), true, &txResults); - break; - case CustomTxType::PoolSwap: - res = ApplyPoolSwapTx(mnview_dummy, ::ChainstateActive().CoinsTip(), *tx, nHeight, metadata, Params().GetConsensus(), true, &txResults); - break; - case CustomTxType::AddPoolLiquidity: - res = ApplyAddPoolLiquidityTx(mnview_dummy, ::ChainstateActive().CoinsTip(), *tx, nHeight, metadata, Params().GetConsensus(), true, &txResults); - break; - case CustomTxType::RemovePoolLiquidity: - res = ApplyRemovePoolLiquidityTx(mnview_dummy, ::ChainstateActive().CoinsTip(), *tx, nHeight, metadata, Params().GetConsensus(), true, &txResults); - break; - case CustomTxType::UtxosToAccount: - res = ApplyUtxosToAccountTx(mnview_dummy, *tx, nHeight, metadata, Params().GetConsensus(), &txResults); - break; - case CustomTxType::AccountToUtxos: - res = ApplyAccountToUtxosTx(mnview_dummy, ::ChainstateActive().CoinsTip(), *tx, nHeight, metadata, Params().GetConsensus(), true, &txResults); - break; - case CustomTxType::AccountToAccount: - res = ApplyAccountToAccountTx(mnview_dummy, ::ChainstateActive().CoinsTip(), *tx, nHeight, metadata, Params().GetConsensus(), true, &txResults); - break; - case CustomTxType::SetGovVariable: - res = ApplySetGovernanceTx(mnview_dummy, ::ChainstateActive().CoinsTip(), *tx, nHeight, metadata, Params().GetConsensus(), true, &txResults); - break; - case CustomTxType::AnyAccountsToAccounts: - res = ApplyAnyAccountsToAccountsTx(mnview_dummy, ::ChainstateActive().CoinsTip(), *tx, nHeight, metadata, Params().GetConsensus(), true, &txResults); - break; - case CustomTxType::AutoAuthPrep: - res.ok = true; - res.msg = "AutoAuth"; - break; - default: - return false; - } - - return true; -} - UniValue createtoken(const JSONRPCRequest& request) { CWallet* const pwallet = GetWallet(request); @@ -198,15 +131,11 @@ UniValue createtoken(const JSONRPCRequest& request) { // check execution { LOCK(cs_main); - CCustomCSView mnview_dummy(*pcustomcsview); // don't write into actual DB - CCoinsViewCache coinview(&::ChainstateActive().CoinsTip()); + CCoinsViewCache coins(&::ChainstateActive().CoinsTip()); if (optAuthTx) - AddCoins(coinview, *optAuthTx, targetHeight); - const auto res = ApplyCreateTokenTx(mnview_dummy, coinview, CTransaction(rawTx), targetHeight, - ToByteVector(CDataStream{SER_NETWORK, PROTOCOL_VERSION, token}), Params().GetConsensus()); - if (!res.ok) { - throw JSONRPCError(RPC_INVALID_REQUEST, "Execution test failed:\n" + res.msg); - } + AddCoins(coins, *optAuthTx, targetHeight); + auto metadata = ToByteVector(CDataStream{SER_NETWORK, PROTOCOL_VERSION, token}); + execTestTx(CTransaction(rawTx), targetHeight, metadata, CCreateTokenMessage{}, coins); } return signsend(rawTx, pwallet, optAuthTx)->GetHash().GetHex(); } @@ -395,22 +324,16 @@ UniValue updatetoken(const JSONRPCRequest& request) { // check execution { LOCK(cs_main); - CCustomCSView mnview_dummy(*pcustomcsview); // don't write into actual DB - CCoinsViewCache coinview(&::ChainstateActive().CoinsTip()); + CCoinsViewCache coins(&::ChainstateActive().CoinsTip()); if (optAuthTx) - AddCoins(coinview, *optAuthTx, targetHeight); - Res res{}; + AddCoins(coins, *optAuthTx, targetHeight); + CCustomTxMessage txMessage = CUpdateTokenMessage{}; + auto metadata = ToByteVector(CDataStream{SER_NETWORK, PROTOCOL_VERSION, tokenImpl.creationTx, static_cast(tokenImpl)}); if (targetHeight < Params().GetConsensus().BayfrontHeight) { - res = ApplyUpdateTokenTx(mnview_dummy, coinview, CTransaction(rawTx), targetHeight, - ToByteVector(CDataStream{SER_NETWORK, PROTOCOL_VERSION, tokenImpl.creationTx, metaObj["isDAT"].getBool()}), Params().GetConsensus()); - } - else { - res = ApplyUpdateTokenAnyTx(mnview_dummy, coinview, CTransaction(rawTx), targetHeight, - ToByteVector(CDataStream{SER_NETWORK, PROTOCOL_VERSION, tokenImpl.creationTx, static_cast(tokenImpl)}), Params().GetConsensus()); - } - if (!res.ok) { - throw JSONRPCError(RPC_INVALID_REQUEST, "Execution test failed:\n" + res.msg); + txMessage = CUpdateTokenPreAMKMessage{}; + metadata = ToByteVector(CDataStream{SER_NETWORK, PROTOCOL_VERSION, tokenImpl.creationTx, metaObj["isDAT"].getBool()}); } + execTestTx(CTransaction(rawTx), targetHeight, metadata, txMessage, coins); } return signsend(rawTx, pwallet, optAuthTx)->GetHash().GetHex(); } @@ -655,7 +578,8 @@ UniValue getcustomtx(const JSONRPCRequest& request) return "Coinbase transaction. Not a custom transaction."; } - if (!GetCustomTXInfo(nHeight, tx, guess, res, txResults)) { + res = RpcInfo(*tx, nHeight, guess, txResults); + if (guess == CustomTxType::None) { return "Not a custom transaction"; } @@ -668,10 +592,10 @@ UniValue getcustomtx(const JSONRPCRequest& request) result.pushKV("type", ToString(guess)); if (!actualHeight) { - result.pushKV("valid", res.ok ? true : false); + result.pushKV("valid", res.ok); } else { auto undo = pcustomcsview->GetUndo(UndoKey{static_cast(nHeight), hash}); - result.pushKV("valid", undo || res.msg == "AutoAuth" ? true : false); + result.pushKV("valid", undo || guess == CustomTxType::AutoAuthPrep); } if (!res.ok) { @@ -802,15 +726,11 @@ UniValue minttokens(const JSONRPCRequest& request) { // check execution { LOCK(cs_main); - CCustomCSView mnview_dummy(*pcustomcsview); // don't write into actual DB - CCoinsViewCache view(&::ChainstateActive().CoinsTip()); + CCoinsViewCache coins(&::ChainstateActive().CoinsTip()); if (optAuthTx) - AddCoins(view, *optAuthTx, targetHeight); - const auto res = ApplyMintTokenTx(mnview_dummy, view, CTransaction(rawTx), targetHeight, - ToByteVector(CDataStream{SER_NETWORK, PROTOCOL_VERSION, minted }), Params().GetConsensus()); - if (!res.ok) { - throw JSONRPCError(RPC_INVALID_REQUEST, "Execution test failed:\n" + res.msg); - } + AddCoins(coins, *optAuthTx, targetHeight); + auto metadata = ToByteVector(CDataStream{SER_NETWORK, PROTOCOL_VERSION, minted }); + execTestTx(CTransaction(rawTx), targetHeight, metadata, CMintTokensMessage{}, coins); } return signsend(rawTx, pwallet, optAuthTx)->GetHash().GetHex(); } diff --git a/src/masternodes/tokens.cpp b/src/masternodes/tokens.cpp index ade8f14f20..07df272d0a 100644 --- a/src/masternodes/tokens.cpp +++ b/src/masternodes/tokens.cpp @@ -186,7 +186,7 @@ bool CTokensView::RevertCreateToken(const uint256 & txid) return true; } -Res CTokensView::UpdateToken(const uint256 &tokenTx, CToken & newToken, bool isPreBayfront) +Res CTokensView::UpdateToken(const uint256 &tokenTx, const CToken& newToken, bool isPreBayfront) { auto pair = GetTokenByCreationTx(tokenTx); if (!pair) { @@ -266,7 +266,7 @@ Res CTokensView::BayfrontFlagsCleanup() return Res::Ok(); } -Res CTokensView::AddMintedTokens(const uint256 &tokenTx, CAmount const & amount, UniValue* rpcInfo) +Res CTokensView::AddMintedTokens(const uint256 &tokenTx, CAmount const & amount) { auto pair = GetTokenByCreationTx(tokenTx); if (!pair) { @@ -280,11 +280,6 @@ Res CTokensView::AddMintedTokens(const uint256 &tokenTx, CAmount const & amount, } tokenImpl.minted = *resMinted.val; - // Transaction info for getcustomtx RPC call - if (rpcInfo) { - rpcInfo->pushKV(std::to_string(pair->first.v), ValueFromAmount(amount)); - } - WriteBy(WrapVarInt(pair->first.v), tokenImpl); return Res::Ok(); } diff --git a/src/masternodes/tokens.h b/src/masternodes/tokens.h index d3523677aa..86d97b7d2b 100644 --- a/src/masternodes/tokens.h +++ b/src/masternodes/tokens.h @@ -147,10 +147,10 @@ class CTokensView : public virtual CStorageView Res CreateDFIToken(); ResVal CreateToken(CTokenImpl const & token, bool isPreBayfront); bool RevertCreateToken(uint256 const & txid); /// @deprecated used only by tests. rewrite tests - Res UpdateToken(uint256 const & tokenTx, CToken & newToken, bool isPreBayfront); + Res UpdateToken(uint256 const & tokenTx, CToken const & newToken, bool isPreBayfront); Res BayfrontFlagsCleanup(); - Res AddMintedTokens(uint256 const & tokenTx, CAmount const & amount, UniValue *rpcInfo = nullptr); + Res AddMintedTokens(uint256 const & tokenTx, CAmount const & amount); // tags struct ID { static const unsigned char prefix; }; diff --git a/src/miner.cpp b/src/miner.cpp index 889ab09d65..4cb45b7744 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -460,6 +460,7 @@ void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpda // Copy of the view CCustomCSView view(*pcustomcsview); + CCoinsViewCache coins(&::ChainstateActive().CoinsTip()); while (mi != mempool.mapTx.get().end() || !mapModifiedTx.empty()) { @@ -569,12 +570,14 @@ void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpda continue; } + AddCoins(coins, tx, nHeight); + std::vector metadata; CustomTxType txType = GuessCustomTxType(tx, metadata); // Only check custom TXs if (txType != CustomTxType::None) { - auto res = ApplyCustomTx(view, ::ChainstateActive().CoinsTip(), tx, chainparams.GetConsensus(), nHeight, uint64_t{0}, 0, false, true); + auto res = ApplyCustomTx(view, coins, tx, chainparams.GetConsensus(), nHeight); // Not okay invalidate, undo and skip if (!res.ok) { diff --git a/src/rpc/rawtransaction_util.cpp b/src/rpc/rawtransaction_util.cpp index 0a6d222b81..30467cb458 100644 --- a/src/rpc/rawtransaction_util.cpp +++ b/src/rpc/rawtransaction_util.cpp @@ -25,13 +25,6 @@ #include -std::function JSONRPCErrorThrower(const std::string& prefix) { - return [=](int code, const std::string& msg) { - throw JSONRPCError(code, prefix + ": " + msg); - }; -} - - std::pair SplitAmount(std::string const & output) { const unsigned char TOKEN_SPLITTER = '@'; @@ -58,7 +51,7 @@ ResVal GuessTokenAmount(interfaces::Chain const & chain, std::stri { const auto parsed = ParseTokenAmount(tokenAmount); if (!parsed.ok) { - return {parsed.res()}; + return parsed; } DCT_ID tokenId; try { @@ -106,7 +99,9 @@ CTokenAmount DecodeAmount(interfaces::Chain const & chain, UniValue const& amoun } else { // only 1 amount strAmount = amountUni.get_str(); } - return GuessTokenAmount(chain, strAmount).ValOrException(JSONRPCErrorThrower(name)); + return GuessTokenAmount(chain, strAmount).ValOrException([name](int code, const std::string& msg) -> UniValue { + return JSONRPCError(code, name + ": " + msg); + }); } CBalances DecodeAmounts(interfaces::Chain const & chain, UniValue const& amountsUni, std::string const& name) diff --git a/src/test/applytx_tests.cpp b/src/test/applytx_tests.cpp index 1e31bde5f6..abf2c30328 100644 --- a/src/test/applytx_tests.cpp +++ b/src/test/applytx_tests.cpp @@ -116,7 +116,7 @@ BOOST_AUTO_TEST_CASE(apply_a2a_neg) rawTx.vout[0].scriptPubKey = CreateMetaA2A(msg); - res = ApplyCustomTx(mnview, coinview, CTransaction(rawTx), amkCheated, 1, 0, uint64_t{0}, false); + res = ApplyCustomTx(mnview, coinview, CTransaction(rawTx), amkCheated, 1); BOOST_CHECK(!res.ok); BOOST_CHECK_NE(res.msg.find("negative amount"), std::string::npos); // check that nothing changes: @@ -132,7 +132,7 @@ BOOST_AUTO_TEST_CASE(apply_a2a_neg) rawTx.vout[0].scriptPubKey = CreateMetaA2A(msg); - res = ApplyCustomTx(mnview, coinview, CTransaction(rawTx), amkCheated, 1, 0, uint64_t{0}, false); + res = ApplyCustomTx(mnview, coinview, CTransaction(rawTx), amkCheated, 1); BOOST_CHECK(!res.ok); BOOST_CHECK_EQUAL(res.code, (uint32_t) CustomTxErrCodes::NotEnoughBalance); // check that nothing changes: @@ -149,7 +149,7 @@ BOOST_AUTO_TEST_CASE(apply_a2a_neg) rawTx.vout[0].scriptPubKey = CreateMetaA2A(msg); - res = ApplyCustomTx(mnview, coinview, CTransaction(rawTx), amkCheated, 1, 0, uint64_t{0}, false); + res = ApplyCustomTx(mnview, coinview, CTransaction(rawTx), amkCheated, 1); BOOST_CHECK(!res.ok); BOOST_CHECK_NE(res.msg.find("negative amount"), std::string::npos); // check that nothing changes: @@ -166,7 +166,7 @@ BOOST_AUTO_TEST_CASE(apply_a2a_neg) rawTx.vout[0].scriptPubKey = CreateMetaA2A(msg); - res = ApplyCustomTx(mnview, coinview, CTransaction(rawTx), amkCheated, 1, 0, uint64_t{0}, false); + res = ApplyCustomTx(mnview, coinview, CTransaction(rawTx), amkCheated, 1); BOOST_CHECK(res.ok); // check result balances: auto const dfi90 = CTokenAmount{DFI, 90}; diff --git a/src/test/liquidity_tests.cpp b/src/test/liquidity_tests.cpp index 3229eda022..0eef8a5733 100644 --- a/src/test/liquidity_tests.cpp +++ b/src/test/liquidity_tests.cpp @@ -54,15 +54,15 @@ Res AddPoolLiquidity(CCustomCSView &mnview, DCT_ID idPool, CAmount amountA, CAmo auto optPool = mnview.GetPoolPair(idPool); BOOST_REQUIRE(optPool); - const auto res = optPool->AddLiquidity(amountA, amountB, shareAddress, [&] /*onMint*/(CScript const & to, CAmount liqAmount) -> Res { + const auto res = optPool->AddLiquidity(amountA, amountB, [&] /*onMint*/(CAmount liqAmount) -> Res { BOOST_CHECK(liqAmount > 0); - auto add = mnview.AddBalance(to, { idPool, liqAmount }); + auto add = mnview.AddBalance(shareAddress, { idPool, liqAmount }); if (!add.ok) { return add; } //insert update ByShare index - const auto setShare = mnview.SetShare(idPool, to); + const auto setShare = mnview.SetShare(idPool, shareAddress); if (!setShare.ok) { return setShare; } @@ -76,7 +76,7 @@ BOOST_FIXTURE_TEST_SUITE(liquidity_tests, TestingSetup) BOOST_AUTO_TEST_CASE(math_liquidity_and_trade) { - auto FAIL_onMint = [](const CScript &, CAmount)-> Res { BOOST_REQUIRE(false); return Res::Err("it should not happen"); }; + auto FAIL_onMint = [](CAmount)-> Res { BOOST_REQUIRE(false); return Res::Err("it should not happen"); }; auto FAIL_onSwap = [](const CTokenAmount &)-> Res { BOOST_REQUIRE(false); return Res::Err("it should not happen"); }; CCustomCSView mnview(*pcustomcsview); @@ -89,19 +89,19 @@ BOOST_AUTO_TEST_CASE(math_liquidity_and_trade) Res res{}; { // basic fails CPoolPair pool = *optPool; - res = pool.AddLiquidity(-1, 1000, {}, FAIL_onMint); + res = pool.AddLiquidity(-1, 1000, FAIL_onMint); BOOST_CHECK(!res.ok && res.msg == "amounts should be positive"); - res = pool.AddLiquidity(0, 1000, {}, FAIL_onMint); + res = pool.AddLiquidity(0, 1000, FAIL_onMint); BOOST_CHECK(!res.ok && res.msg == "amounts should be positive"); - res = pool.AddLiquidity(1, 1000, {}, FAIL_onMint); + res = pool.AddLiquidity(1, 1000, FAIL_onMint); BOOST_CHECK(!res.ok && res.msg == "liquidity too low"); - res = pool.AddLiquidity(10, 100000, {}, FAIL_onMint); + res = pool.AddLiquidity(10, 100000, FAIL_onMint); BOOST_CHECK(!res.ok && res.msg == "liquidity too low"); // median == MINIMUM_LIQUIDITY } { // amounts a bit larger MINIMUM_LIQUIDITY CPoolPair pool = *optPool; - res = pool.AddLiquidity(11, 100000, {}, [](const CScript &, CAmount liq)-> Res { + res = pool.AddLiquidity(11, 100000, [](CAmount liq)-> Res { BOOST_CHECK(liq == 48); // sqrt (11*100000) - MINIMUM_LIQUIDITY return Res::Ok(); }); @@ -112,7 +112,7 @@ BOOST_AUTO_TEST_CASE(math_liquidity_and_trade) { // one limit CPoolPair pool = *optPool; - res = pool.AddLiquidity(std::numeric_limits::max(), 1, {}, [](const CScript &, CAmount liq)-> Res { + res = pool.AddLiquidity(std::numeric_limits::max(), 1, [](CAmount liq)-> Res { BOOST_CHECK(liq == 3036999499); // == sqrt(limit)-MINIMUM_LIQUIDITY return Res::Ok(); }); @@ -122,7 +122,7 @@ BOOST_AUTO_TEST_CASE(math_liquidity_and_trade) BOOST_CHECK(pool.totalLiquidity == 3036999499 + CPoolPair::MINIMUM_LIQUIDITY); // plus 1 - res = pool.AddLiquidity(1, 1, {}, FAIL_onMint); + res = pool.AddLiquidity(1, 1, FAIL_onMint); BOOST_CHECK(!res.ok && res.msg == "amounts too low, zero liquidity"); // we can't swap forward even 1 satoshi @@ -138,7 +138,7 @@ BOOST_AUTO_TEST_CASE(math_liquidity_and_trade) { // two limits CPoolPair pool = *optPool; - res = pool.AddLiquidity(std::numeric_limits::max(), std::numeric_limits::max(), {}, [](const CScript &, CAmount liq)-> Res { + res = pool.AddLiquidity(std::numeric_limits::max(), std::numeric_limits::max(), [](CAmount liq)-> Res { BOOST_CHECK(liq == std::numeric_limits::max() - CPoolPair::MINIMUM_LIQUIDITY); return Res::Ok(); }); @@ -147,7 +147,7 @@ BOOST_AUTO_TEST_CASE(math_liquidity_and_trade) BOOST_CHECK(pool.reserveB == std::numeric_limits::max()); BOOST_CHECK(pool.totalLiquidity == std::numeric_limits::max()); - res = pool.AddLiquidity(1, 1, {}, FAIL_onMint); + res = pool.AddLiquidity(1, 1, FAIL_onMint); BOOST_CHECK(!res.ok && res.msg.find("can't add") != res.msg.npos); // in fact we got liquidity overflows // thats all, we can't do anything here until removing @@ -157,7 +157,7 @@ BOOST_AUTO_TEST_CASE(math_liquidity_and_trade) // it works extremely bad on low reserves, but it's okay, just bad trade. { CPoolPair pool = *optPool; - res = pool.AddLiquidity(1001, 1001, {}, [](const CScript &, CAmount liq)-> Res { + res = pool.AddLiquidity(1001, 1001, [](CAmount liq)-> Res { BOOST_CHECK(liq == 1001 - CPoolPair::MINIMUM_LIQUIDITY); return Res::Ok(); }); @@ -174,7 +174,7 @@ BOOST_AUTO_TEST_CASE(math_liquidity_and_trade) // trying to swap moooore than reserved (sliding), but on "resonable" reserves { CPoolPair pool = *optPool; - res = pool.AddLiquidity(COIN, COIN, {}, [](const CScript &, CAmount liq)-> Res { + res = pool.AddLiquidity(COIN, COIN, [](CAmount liq)-> Res { return Res::Ok(); }); res = pool.Swap(CTokenAmount{pool.idTokenA, 2*COIN}, PoolPrice{std::numeric_limits::max(), 0}, [&] (CTokenAmount const &ta) -> Res{ @@ -191,7 +191,7 @@ BOOST_AUTO_TEST_CASE(math_liquidity_and_trade) { // printf("2 COIN (1:1000)\n"); CPoolPair pool = *optPool; - res = pool.AddLiquidity(COIN, 1000*COIN, {}, [](const CScript &, CAmount liq)-> Res { + res = pool.AddLiquidity(COIN, 1000*COIN, [](CAmount liq)-> Res { return Res::Ok(); }); res = pool.Swap(CTokenAmount{pool.idTokenA, 2*COIN}, PoolPrice{std::numeric_limits::max(), 0}, [&] (CTokenAmount const &ta) -> Res{ @@ -206,7 +206,7 @@ BOOST_AUTO_TEST_CASE(math_liquidity_and_trade) { // printf("1 COIN (1:1000)\n"); CPoolPair pool = *optPool; - res = pool.AddLiquidity(COIN, 1000*COIN, {}, [](const CScript &, CAmount liq)-> Res { + res = pool.AddLiquidity(COIN, 1000*COIN, [](CAmount liq)-> Res { return Res::Ok(); }); res = pool.Swap(CTokenAmount{pool.idTokenA, COIN}, PoolPrice{std::numeric_limits::max(), 0}, [&] (CTokenAmount const &ta) -> Res{ @@ -221,7 +221,7 @@ BOOST_AUTO_TEST_CASE(math_liquidity_and_trade) { // printf("COIN/1000 (1:1000) (no slope due to commission)\n"); CPoolPair pool = *optPool; - res = pool.AddLiquidity(COIN, 1000*COIN, {}, [](const CScript &, CAmount liq)-> Res { + res = pool.AddLiquidity(COIN, 1000*COIN, [](CAmount liq)-> Res { return Res::Ok(); }); res = pool.Swap(CTokenAmount{pool.idTokenA, COIN/1000}, PoolPrice{std::numeric_limits::max(), 0}, [&] (CTokenAmount const &ta) -> Res{ diff --git a/src/txmempool.cpp b/src/txmempool.cpp index cbe467baac..9c5e198ad4 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -594,7 +594,7 @@ void CTxMemPool::removeForBlock(const std::vector& vtx, unsigne staged.insert(mapTx.project<0>(it)); continue; } - auto res = ApplyCustomTx(viewDuplicate, mempoolDuplicate, tx, Params().GetConsensus(), nBlockHeight, 0, uint64_t{0}, false); + auto res = ApplyCustomTx(viewDuplicate, mempoolDuplicate, tx, Params().GetConsensus(), nBlockHeight); if (!res.ok && (res.code & CustomTxErrCodes::Fatal)) { LogPrintf("%s: Remove conflicting custom TX: %s\n", __func__, tx.GetHash().GetHex()); staged.insert(mapTx.project<0>(it)); diff --git a/src/validation.cpp b/src/validation.cpp index 3fa7748778..27bc7da0f9 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -406,7 +406,7 @@ static void UpdateMempoolForReorg(DisconnectedBlockTransactions& disconnectpool, std::vector mintTokensToRemove; // not sure about tx refs safety while recursive deletion, so hashes for (const CTxMemPoolEntry& e : mempool.mapTx) { auto tx = e.GetTx(); - if (GetMintTokenMetadata(tx)) { + if (IsMintTokenTx(tx)) { auto values = tx.GetValuesOut(); for (auto const & pair : values) { if (pair.first == DCT_ID{0}) @@ -609,7 +609,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool // check for txs in mempool for (const auto& e : mempool.mapTx.get()) { const auto& tx = e.GetTx(); - auto res = ApplyCustomTx(mnview, view, tx, chainparams.GetConsensus(), height, uint64_t{0}, 0, false); + auto res = ApplyCustomTx(mnview, view, tx, chainparams.GetConsensus(), height); // we don't need contract anynore furthermore transition to new hardfork will broke it if (height < chainparams.GetConsensus().DakotaHeight) { assert(res.ok || !(res.code & CustomTxErrCodes::Fatal)); @@ -630,7 +630,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INVALID, "bad-txns-inputs-below-tx-fee"); } - auto res = ApplyCustomTx(mnview, view, tx, chainparams.GetConsensus(), height, 0, false); + auto res = ApplyCustomTx(mnview, view, tx, chainparams.GetConsensus(), height); if (!res.ok || (res.code & CustomTxErrCodes::Fatal)) { return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INVALID, res.msg); } @@ -2003,7 +2003,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl pcustomcsview->CreateDFIToken(); // init view|db with genesis here for (size_t i = 0; i < block.vtx.size(); ++i) { - const auto res = ApplyCustomTx(mnview, view, *block.vtx[i], chainparams.GetConsensus(), pindex->nHeight, pindex->GetBlockTime(), i, fJustCheck); + const auto res = ApplyCustomTx(mnview, view, *block.vtx[i], chainparams.GetConsensus(), pindex->nHeight, pindex->GetBlockTime(), i); if (!res.ok) { return error("%s: Genesis block ApplyCustomTx failed. TX: %s Error: %s", __func__, block.vtx[i]->GetHash().ToString(), res.msg); @@ -2254,7 +2254,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl tx.GetHash().ToString(), FormatStateMessage(state)); } - const auto res = ApplyCustomTx(mnview, view, tx, chainparams.GetConsensus(), pindex->nHeight, pindex->GetBlockTime(), i, fJustCheck); + const auto res = ApplyCustomTx(mnview, view, tx, chainparams.GetConsensus(), pindex->nHeight, pindex->GetBlockTime(), i); if (!res.ok && (res.code & CustomTxErrCodes::Fatal)) { // we will never fail, but skip, unless transaction mints UTXOs return error("ConnectBlock(): ApplyCustomTx on %s failed with %s", diff --git a/test/functional/feature_accounts_n_utxos.py b/test/functional/feature_accounts_n_utxos.py index 865a623c47..729c5d8292 100755 --- a/test/functional/feature_accounts_n_utxos.py +++ b/test/functional/feature_accounts_n_utxos.py @@ -187,8 +187,9 @@ def run_test(self): self.nodes[0].accounttoutxos(accountGold, {accountGold: "100@" + symbolGOLD}, []) self.nodes[0].generate(1) except JSONRPCException as e: + print(e) errorString = e.error['message'] - assert("AccountToUtxos only available for DFI transactions" in errorString) + assert("only available for DFI transactions" in errorString) assert_equal(self.nodes[0].getaccount(accountGold, {}, True)[idGold], initialGold - 100) assert_equal(self.nodes[0].getaccount(accountGold, {}, True)[idGold], self.nodes[1].getaccount(accountGold, {}, True)[idGold]) diff --git a/test/functional/feature_autoauth.py b/test/functional/feature_autoauth.py index f466f6032a..b6e53e325d 100755 --- a/test/functional/feature_autoauth.py +++ b/test/functional/feature_autoauth.py @@ -48,7 +48,7 @@ def run_test(self): assert (False) except JSONRPCException as e: errorString = e.error['message'] - assert("tx must have at least one input from masternode owner" in errorString) + assert("tx must have at least one input from the owner" in errorString) errorString = "" n0.resignmasternode(mnId) @@ -94,7 +94,7 @@ def run_test(self): assert (False) except JSONRPCException as e: errorString = e.error['message'] - assert("tx must have at least one input from token owner" in errorString) + assert("tx must have at least one input from the owner" in errorString) errorString = "" n0.updatetoken( diff --git a/test/functional/feature_token_fork.py b/test/functional/feature_token_fork.py index 16c48868cf..5ccb848bc6 100755 --- a/test/functional/feature_token_fork.py +++ b/test/functional/feature_token_fork.py @@ -44,7 +44,7 @@ def run_test(self): }, []) except JSONRPCException as e: errorString = e.error['message'] - assert("Token tx before AMK" in errorString) + assert("before AMK height" in errorString) self.nodes[0].generate(1) # Before fork, create should fail, so now only have default token @@ -119,7 +119,7 @@ def run_test(self): assert(False) except JSONRPCException as e: errorString = e.error['message'] - assert("Token tx before AMK" in errorString) + assert("before AMK height" in errorString) diff --git a/test/functional/feature_tokens_minting.py b/test/functional/feature_tokens_minting.py index 880180dff7..1f819b968a 100755 --- a/test/functional/feature_tokens_minting.py +++ b/test/functional/feature_tokens_minting.py @@ -118,7 +118,7 @@ def run_test(self): self.sync_blocks() except JSONRPCException as e: errorString = e.error['message'] - assert("AccountToUtxos only available for DFI transactions" in errorString) + assert("only available for DFI transactions" in errorString) if __name__ == '__main__': TokensMintingTest ().main () diff --git a/test/functional/feature_tokens_multisig.py b/test/functional/feature_tokens_multisig.py index a4ede9ba0d..6722e18a2a 100755 --- a/test/functional/feature_tokens_multisig.py +++ b/test/functional/feature_tokens_multisig.py @@ -103,7 +103,7 @@ def run_test(self): self.nodes[0].sendrawtransaction(signed_rawtx_1['hex']) except JSONRPCException as e: errorString = e.error['message'] - assert("tx must have at least one input from token owner" in errorString) + assert("tx must have at least one input from the owner" in errorString) # Test that multisig TXs can change names rawtx_1 = self.nodes[0].createrawtransaction([{"txid":txid_1,"vout":vout_1}], [{"data":name_change_1},{owner_1:0.9999}]) diff --git a/test/functional/rpc_getcustomtx.py b/test/functional/rpc_getcustomtx.py index bd8a0b6043..cb1ed6de90 100755 --- a/test/functional/rpc_getcustomtx.py +++ b/test/functional/rpc_getcustomtx.py @@ -247,7 +247,7 @@ def run_test(self): assert_equal(result['results']['status'], True) assert_equal(result['results']['ownerAddress'], pool_collateral) assert_equal(result['results']['isDAT'], True) - assert_equal(result['results']['mineable'], False) + assert_equal(result['results']['mintable'], False) assert_equal(result['results']['tradeable'], True) assert_equal(result['results']['finalized'], True) assert_equal(result['blockHeight'], blockheight) From c39011a0424f76cc27d3eb8a6675f505c4920a12 Mon Sep 17 00:00:00 2001 From: Anthony Fieroni Date: Tue, 23 Feb 2021 09:26:34 +0200 Subject: [PATCH 02/17] Prevent copies in serialize Signed-off-by: Anthony Fieroni --- src/flushablestorage.h | 25 ++++++++----------- .../govvariables/lp_daily_dfi_reward.h | 1 + src/masternodes/govvariables/lp_splits.h | 1 + src/masternodes/gv.h | 3 +++ src/serialize.h | 8 ++++++ 5 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/flushablestorage.h b/src/flushablestorage.h index cd0484a846..4ef63ec40c 100644 --- a/src/flushablestorage.h +++ b/src/flushablestorage.h @@ -18,15 +18,16 @@ using MapKV = std::map>; template static TBytes DbTypeToBytes(const T& value) { - CDataStream stream(SER_DISK, CLIENT_VERSION); + TBytes bytes; + CVectorWriter stream(SER_DISK, CLIENT_VERSION, bytes, 0); stream << value; - return TBytes(stream.begin(), stream.end()); + return bytes; } template static bool BytesToDbType(const TBytes& bytes, T& value) { try { - CDataStream stream(bytes, SER_DISK, CLIENT_VERSION); + VectorReader stream(SER_DISK, CLIENT_VERSION, bytes, 0); stream >> value; // assert(stream.size() == 0); // will fail with partial key matching } @@ -66,15 +67,13 @@ struct RawTBytes { std::reference_wrapper ref; template - void Serialize(Stream& os) const - { + void Serialize(Stream& os) const { auto& val = ref.get(); os.write((char*)val.data(), val.size()); } template - void Unserialize(Stream& is) - { + void Unserialize(Stream& is) { auto& val = ref.get(); val.resize(is.size()); is.read((char*)val.data(), is.size()); @@ -82,8 +81,7 @@ struct RawTBytes { }; template -inline RawTBytes refTBytes(T& val) -{ +inline RawTBytes refTBytes(T& val) { return RawTBytes{val}; } @@ -298,8 +296,7 @@ class CFlushableStorageKV : public CStorageKV { }; template -class CLazySerialize -{ +class CLazySerialize { Optional value; CStorageKVIterator& it; @@ -307,13 +304,11 @@ class CLazySerialize CLazySerialize(const CLazySerialize&) = default; explicit CLazySerialize(CStorageKVIterator& it) : it(it) {} - operator T() - { + operator T() { return get(); } - const T& get() - { + const T& get() { if (!value) { value = T{}; BytesToDbType(it.Value(), *value); diff --git a/src/masternodes/govvariables/lp_daily_dfi_reward.h b/src/masternodes/govvariables/lp_daily_dfi_reward.h index 6d9100aa4e..0d21f20d15 100644 --- a/src/masternodes/govvariables/lp_daily_dfi_reward.h +++ b/src/masternodes/govvariables/lp_daily_dfi_reward.h @@ -25,6 +25,7 @@ class LP_DAILY_DFI_REWARD : public GovVariable, public AutoRegistrator diff --git a/src/masternodes/govvariables/lp_splits.h b/src/masternodes/govvariables/lp_splits.h index 57046d8a42..19804b55a2 100644 --- a/src/masternodes/govvariables/lp_splits.h +++ b/src/masternodes/govvariables/lp_splits.h @@ -25,6 +25,7 @@ class LP_SPLITS : public GovVariable, public AutoRegistrator diff --git a/src/masternodes/gv.h b/src/masternodes/gv.h index c00b2b3a90..1b908c0dc7 100644 --- a/src/masternodes/gv.h +++ b/src/masternodes/gv.h @@ -28,6 +28,9 @@ class GovVariable virtual Res Validate(CCustomCSView const &mnview) const = 0; virtual Res Apply(CCustomCSView &mnview) = 0; + virtual void Serialize(CVectorWriter& s) const = 0; + virtual void Unserialize(VectorReader& s) = 0; + virtual void Serialize(CDataStream& s) const = 0; virtual void Unserialize(CDataStream& s) = 0; }; diff --git a/src/serialize.h b/src/serialize.h index 4f2057b4f0..40aa841f62 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -208,6 +208,14 @@ template const X& ReadWriteAsHelper(const X& x) { return x; } SerializationOp(s, CSerActionUnserialize()); \ } +#define ADD_OVERRIDE_VECTOR_SERIALIZE_METHODS \ + void Serialize(CVectorWriter& s) const override { \ + NCONST_PTR(this)->SerializationOp(s, CSerActionSerialize()); \ + } \ + void Unserialize(VectorReader& s) override { \ + SerializationOp(s, CSerActionUnserialize()); \ + } + #ifndef CHAR_EQUALS_INT8 template inline void Serialize(Stream& s, char a ) { ser_writedata8(s, a); } // TODO Get rid of bare char #endif From 6826652e5bf2c6f7d978da244c5b6a2d6002f736 Mon Sep 17 00:00:00 2001 From: Anthony Fieroni Date: Fri, 5 Mar 2021 17:01:54 +0200 Subject: [PATCH 03/17] Adopt pool tables to have historical records Per account rewards calculation Signed-off-by: Anthony Fieroni --- src/amount.h | 4 +- src/flushablestorage.h | 92 +++++- src/init.cpp | 8 +- src/masternodes/accounts.cpp | 22 +- src/masternodes/accounts.h | 7 +- src/masternodes/accountshistory.cpp | 60 +--- src/masternodes/accountshistory.h | 51 +--- src/masternodes/balances.h | 31 +- .../govvariables/lp_daily_dfi_reward.cpp | 6 +- .../govvariables/lp_daily_dfi_reward.h | 2 +- src/masternodes/govvariables/lp_splits.cpp | 6 +- src/masternodes/govvariables/lp_splits.h | 18 +- src/masternodes/gv.h | 2 +- src/masternodes/incentivefunding.cpp | 2 +- src/masternodes/incentivefunding.h | 2 +- src/masternodes/masternodes.cpp | 137 ++++----- src/masternodes/masternodes.h | 21 +- src/masternodes/mn_checks.cpp | 242 ++++++++++----- src/masternodes/mn_checks.h | 2 +- src/masternodes/mn_rpc.cpp | 12 +- src/masternodes/poolpairs.cpp | 202 +++++++++---- src/masternodes/poolpairs.h | 69 +++-- src/masternodes/rpc_accounts.cpp | 275 +++++++++--------- src/masternodes/rpc_poolpair.cpp | 4 +- src/masternodes/tokens.cpp | 75 ++--- src/masternodes/tokens.h | 6 +- src/masternodes/undo.h | 2 +- src/masternodes/undos.cpp | 2 +- src/masternodes/undos.h | 2 +- src/test/liquidity_tests.cpp | 152 +++++++++- src/test/storage_tests.cpp | 53 +--- src/validation.cpp | 4 +- 32 files changed, 930 insertions(+), 643 deletions(-) diff --git a/src/amount.h b/src/amount.h index 6b37b45f13..3e8695e4fb 100644 --- a/src/amount.h +++ b/src/amount.h @@ -70,7 +70,7 @@ struct DCT_ID { template inline void SerializationOp(Stream& s, Operation ser_action) { - READWRITE(v); + READWRITE(VARINT(v)); } }; @@ -162,7 +162,7 @@ struct CTokenAmount { // simple std::pair is less informative template inline void SerializationOp(Stream& s, Operation ser_action) { - READWRITE(VARINT(nTokenId.v)); + READWRITE(nTokenId); READWRITE(nValue); } diff --git a/src/flushablestorage.h b/src/flushablestorage.h index 4ef63ec40c..1d18f1bb50 100644 --- a/src/flushablestorage.h +++ b/src/flushablestorage.h @@ -298,25 +298,88 @@ class CFlushableStorageKV : public CStorageKV { template class CLazySerialize { Optional value; - CStorageKVIterator& it; + std::unique_ptr& it; public: CLazySerialize(const CLazySerialize&) = default; - explicit CLazySerialize(CStorageKVIterator& it) : it(it) {} + explicit CLazySerialize(std::unique_ptr& it) : it(it) {} - operator T() { + operator T() & { return get(); } - + operator T() && { + get(); + return std::move(*value); + } const T& get() { if (!value) { value = T{}; - BytesToDbType(it.Value(), *value); + BytesToDbType(it->Value(), *value); } return *value; } }; +template +class CStorageIteratorWrapper { + bool valid = false; + std::pair key; + std::unique_ptr it; + + void UpdateValidity() { + valid = it->Valid() && BytesToDbType(it->Key(), key) && key.first == By::prefix; + } + + struct Resolver { + std::unique_ptr& it; + + template + inline operator CLazySerialize() { + return CLazySerialize{it}; + } + template + inline T as() { + return CLazySerialize{it}; + } + template + inline operator T() { + return as(); + } + }; + +public: + CStorageIteratorWrapper(CStorageIteratorWrapper&&) = default; + CStorageIteratorWrapper(std::unique_ptr it) : it(std::move(it)) {} + + CStorageIteratorWrapper& operator=(CStorageIteratorWrapper&& other) { + valid = other.valid; + it = std::move(other.it); + key = std::move(other.key); + return *this; + } + bool Valid() { + return valid; + } + const KeyType& Key() { + assert(Valid()); + return key.second; + } + Resolver Value() { + assert(Valid()); + return Resolver{it}; + } + void Next() { + assert(Valid()); + it->Next(); + UpdateValidity(); + } + void Seek(const KeyType& newKey) { + key = std::make_pair(By::prefix, newKey); + it->Seek(DbTypeToBytes(key)); + UpdateValidity(); + } +}; + class CStorageView { public: CStorageView() = default; @@ -371,20 +434,21 @@ class CStorageView { return {result}; return {}; } - + template + CStorageIteratorWrapper LowerBound(KeyType const & key) { + CStorageIteratorWrapper it{DB().NewIterator()}; + it.Seek(key); + return it; + } template - bool ForEach(std::function)> callback, KeyType const & start = KeyType()) const { - auto& self = const_cast(*this); - auto key = std::make_pair(By::prefix, start); - - auto it = self.DB().NewIterator(); - for(it->Seek(DbTypeToBytes(key)); it->Valid() && BytesToDbType(it->Key(), key) && key.first == By::prefix; it->Next()) { + void ForEach(std::function)> callback, KeyType const & start = {}) { + for(auto it = LowerBound(start); it.Valid(); it.Next()) { boost::this_thread::interruption_point(); - if (!callback(key.second, CLazySerialize(*it))) + if (!callback(it.Key(), it.Value())) { break; + } } - return true; } bool Flush() { return DB().Flush(); } diff --git a/src/init.cpp b/src/init.cpp index 4182beba94..442e213951 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -418,7 +418,6 @@ void SetupServerArgs() #endif gArgs.AddArg("-txindex", strprintf("Maintain a full transaction index, used by the getrawtransaction rpc call (default: %u)", DEFAULT_TXINDEX), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); gArgs.AddArg("-acindex", strprintf("Maintain a full account history index, tracking all accounts balances changes. Used by the listaccounthistory and accounthistorycount rpc calls (default: %u)", false), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); - gArgs.AddArg("-acindex-mineonly", strprintf("Maintain a mine only account history index, tracking mine accounts balances changes. Used by the listaccounthistory and accounthistorycount rpc calls (default: %u)", false), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); gArgs.AddArg("-blockfilterindex=", strprintf("Maintain an index of compact filters by block (default: %s, values: %s).", DEFAULT_BLOCKFILTERINDEX, ListBlockFilterTypes()) + " If is not supplied or if = 1, indexes for all known types are enabled.", @@ -1506,11 +1505,6 @@ bool AppInitMain(InitInterfaces& interfaces) LogPrintf("* Using %.1f MiB for chain state database\n", nCoinDBCache * (1.0 / 1024 / 1024)); LogPrintf("* Using %.1f MiB for in-memory UTXO set (plus up to %.1f MiB of unused mempool space)\n", nCoinCacheUsage * (1.0 / 1024 / 1024), nMempoolSizeMax * (1.0 / 1024 / 1024)); - if (gArgs.GetBoolArg("-acindex", DEFAULT_ACINDEX) && gArgs.GetBoolArg("-acindex-mineonly", DEFAULT_ACINDEX_MINEONLY)) { - LogPrintf("Warning -acindex and -acindex-mineonly are set, -acindex will be used\n"); - gArgs.ForceSetArg("-acindex-mineonly", "0"); - } - bool fLoaded = false; while (!fLoaded && !ShutdownRequested()) { bool fReset = fReindex; @@ -1595,7 +1589,7 @@ bool AppInitMain(InitInterfaces& interfaces) pcustomcsDB = MakeUnique(GetDataDir() / "enhancedcs", nCustomCacheSize, false, fReset || fReindexChainState); pcustomcsview.reset(); pcustomcsview = MakeUnique(*pcustomcsDB.get()); - if (!fReset && (gArgs.GetBoolArg("-acindex", DEFAULT_ACINDEX) || gArgs.GetBoolArg("-acindex-mineonly", DEFAULT_ACINDEX_MINEONLY))) { + if (!fReset && gArgs.GetBoolArg("-acindex", DEFAULT_ACINDEX)) { if (shouldMigrateOldRewardHistory(*pcustomcsview)) { strLoadError = _("Account history needs rebuild").translated; break; diff --git a/src/masternodes/accounts.cpp b/src/masternodes/accounts.cpp index 3e95f470a8..1be589debd 100644 --- a/src/masternodes/accounts.cpp +++ b/src/masternodes/accounts.cpp @@ -6,8 +6,9 @@ /// @attention make sure that it does not overlap with those in masternodes.cpp/tokens.cpp/undos.cpp/accounts.cpp !!! const unsigned char CAccountsView::ByBalanceKey::prefix = 'a'; +const unsigned char CAccountsView::ByHeightKey::prefix = 'b'; -void CAccountsView::ForEachBalance(std::function callback, BalanceKey const & start) const +void CAccountsView::ForEachBalance(std::function callback, BalanceKey const & start) { ForEach([&callback] (BalanceKey const & key, CAmount val) { return callback(key.owner, CTokenAmount{key.tokenID, val}); @@ -82,3 +83,22 @@ Res CAccountsView::SubBalances(CScript const & owner, CBalances const & balances return Res::Ok(); } +void CAccountsView::ForEachAccount(std::function callback) +{ + ForEach([&callback] (CScript const & owner, CLazySerialize) { + return callback(owner); + }); +} + +Res CAccountsView::UpdateBalancesHeight(CScript const & owner, uint32_t height) +{ + WriteBy(owner, height); + return Res::Ok(); +} + +uint32_t CAccountsView::GetBalancesHeight(CScript const & owner) +{ + uint32_t height; + bool ok = ReadBy(owner, height); + return ok ? height : 0; +} diff --git a/src/masternodes/accounts.h b/src/masternodes/accounts.h index 8e216644e5..d04c62dbee 100644 --- a/src/masternodes/accounts.h +++ b/src/masternodes/accounts.h @@ -14,7 +14,8 @@ class CAccountsView : public virtual CStorageView { public: - void ForEachBalance(std::function callback, BalanceKey const & start = {}) const; + void ForEachAccount(std::function callback); + void ForEachBalance(std::function callback, BalanceKey const & start = {}); CTokenAmount GetBalance(CScript const & owner, DCT_ID tokenID) const; virtual Res AddBalance(CScript const & owner, CTokenAmount amount); @@ -23,8 +24,12 @@ class CAccountsView : public virtual CStorageView Res AddBalances(CScript const & owner, CBalances const & balances); Res SubBalances(CScript const & owner, CBalances const & balances); + uint32_t GetBalancesHeight(CScript const & owner); + Res UpdateBalancesHeight(CScript const & owner, uint32_t height); + // tags struct ByBalanceKey { static const unsigned char prefix; }; + struct ByHeightKey { static const unsigned char prefix; }; private: Res SetBalance(CScript const & owner, CTokenAmount amount); diff --git a/src/masternodes/accountshistory.cpp b/src/masternodes/accountshistory.cpp index 10dffb4e83..59a8de23c1 100644 --- a/src/masternodes/accountshistory.cpp +++ b/src/masternodes/accountshistory.cpp @@ -11,52 +11,16 @@ #include /// @attention make sure that it does not overlap with those in masternodes.cpp/tokens.cpp/undos.cpp/accounts.cpp !!! -const unsigned char CAccountsHistoryView::ByMineAccountHistoryKey::prefix = 'm'; -const unsigned char CAccountsHistoryView::ByAllAccountHistoryKey::prefix = 'h'; -const unsigned char CRewardsHistoryView::ByMineRewardHistoryKey::prefix = 'Q'; -const unsigned char CRewardsHistoryView::ByAllRewardHistoryKey::prefix = 'W'; +const unsigned char CAccountsHistoryView::ByAccountHistoryKey::prefix = 'h'; -void CAccountsHistoryView::ForEachMineAccountHistory(std::function)> callback, AccountHistoryKey const & start) const +void CAccountsHistoryView::ForEachAccountHistory(std::function)> callback, AccountHistoryKey const & start) { - ForEach(callback, start); + ForEach(callback, start); } -Res CAccountsHistoryView::SetMineAccountHistory(const AccountHistoryKey& key, const AccountHistoryValue& value) +Res CAccountsHistoryView::SetAccountHistory(const AccountHistoryKey& key, const AccountHistoryValue& value) { - WriteBy(key, value); - return Res::Ok(); -} - -void CAccountsHistoryView::ForEachAllAccountHistory(std::function)> callback, AccountHistoryKey const & start) const -{ - ForEach(callback, start); -} - -Res CAccountsHistoryView::SetAllAccountHistory(const AccountHistoryKey& key, const AccountHistoryValue& value) -{ - WriteBy(key, value); - return Res::Ok(); -} - -void CRewardsHistoryView::ForEachMineRewardHistory(std::function)> callback, RewardHistoryKey const & start) const -{ - ForEach(callback, start); -} - -Res CRewardsHistoryView::SetMineRewardHistory(const RewardHistoryKey& key, const RewardHistoryValue& value) -{ - WriteBy(key, value); - return Res::Ok(); -} - -void CRewardsHistoryView::ForEachAllRewardHistory(std::function)> callback, RewardHistoryKey const & start) const -{ - ForEach(callback, start); -} - -Res CRewardsHistoryView::SetAllRewardHistory(const RewardHistoryKey& key, const RewardHistoryValue& value) -{ - WriteBy(key, value); + WriteBy(key, value); return Res::Ok(); } @@ -70,20 +34,8 @@ bool shouldMigrateOldRewardHistory(CCustomCSView & view) if (it->Valid() && BytesToDbType(it->Key(), oldKey) && oldKey.first == prefix) { return true; } - prefix = CRewardsHistoryView::ByMineRewardHistoryKey::prefix; - auto newKey = std::make_pair(prefix, RewardHistoryKey{}); - it->Seek(DbTypeToBytes(newKey)); - if (it->Valid() && BytesToDbType(it->Key(), newKey) && newKey.first == prefix) { - return false; - } - prefix = CRewardsHistoryView::ByAllRewardHistoryKey::prefix; - newKey = std::make_pair(prefix, RewardHistoryKey{}); - it->Seek(DbTypeToBytes(newKey)); - if (it->Valid() && BytesToDbType(it->Key(), newKey) && newKey.first == prefix) { - return false; - } bool hasOldAccountHistory = false; - view.ForEachAllAccountHistory([&](AccountHistoryKey const & key, CLazySerialize) { + view.ForEachAccountHistory([&](AccountHistoryKey const & key, CLazySerialize) { if (key.txn == std::numeric_limits::max()) { hasOldAccountHistory = true; return false; diff --git a/src/masternodes/accountshistory.h b/src/masternodes/accountshistory.h index b486f1df9a..ab40475da3 100644 --- a/src/masternodes/accountshistory.h +++ b/src/masternodes/accountshistory.h @@ -52,60 +52,17 @@ struct AccountHistoryValue { } }; -struct RewardHistoryKey { - CScript owner; - uint32_t blockHeight; - uint8_t category; - - ADD_SERIALIZE_METHODS; - - template - inline void SerializationOp(Stream& s, Operation ser_action) { - READWRITE(owner); - - if (ser_action.ForRead()) { - READWRITE(WrapBigEndian(blockHeight)); - blockHeight = ~blockHeight; - } - else { - uint32_t blockHeight_ = ~blockHeight; - READWRITE(WrapBigEndian(blockHeight_)); - } - - READWRITE(category); - } -}; - -using RewardHistoryValue = std::map; - class CAccountsHistoryView : public virtual CStorageView { public: - Res SetMineAccountHistory(AccountHistoryKey const & key, AccountHistoryValue const & value); - Res SetAllAccountHistory(AccountHistoryKey const & key, AccountHistoryValue const & value); - void ForEachMineAccountHistory(std::function)> callback, AccountHistoryKey const & start = {}) const; - void ForEachAllAccountHistory(std::function)> callback, AccountHistoryKey const & start = {}) const; - - // tags - struct ByMineAccountHistoryKey { static const unsigned char prefix; }; - struct ByAllAccountHistoryKey { static const unsigned char prefix; }; -}; - -class CRewardsHistoryView : public virtual CStorageView -{ -public: - Res SetMineRewardHistory(RewardHistoryKey const & key, RewardHistoryValue const & value); - Res SetAllRewardHistory(RewardHistoryKey const & key, RewardHistoryValue const & value); - void ForEachMineRewardHistory(std::function)> callback, RewardHistoryKey const & start = {}) const; - void ForEachAllRewardHistory(std::function)> callback, RewardHistoryKey const & start = {}) const; + Res SetAccountHistory(AccountHistoryKey const & key, AccountHistoryValue const & value); + void ForEachAccountHistory(std::function)> callback, AccountHistoryKey const & start = {}); // tags - struct ByMineRewardHistoryKey { static const unsigned char prefix; }; - struct ByAllRewardHistoryKey { static const unsigned char prefix; }; + struct ByAccountHistoryKey { static const unsigned char prefix; }; }; -static constexpr bool DEFAULT_ACINDEX = false; -static constexpr bool DEFAULT_ACINDEX_MINEONLY = true; +static constexpr bool DEFAULT_ACINDEX = true; class CCustomCSView; bool shouldMigrateOldRewardHistory(CCustomCSView & view); diff --git a/src/masternodes/balances.h b/src/masternodes/balances.h index 9a93663ace..ee08f6a4af 100644 --- a/src/masternodes/balances.h +++ b/src/masternodes/balances.h @@ -135,27 +135,24 @@ struct CBalances template inline void SerializationOp(Stream& s, Operation ser_action) { - READWRITE(balances); + static_assert(std::is_same>::value, "Following code is invalid"); + std::map serializedBalances; if (ser_action.ForRead()) { + READWRITE(serializedBalances); + balances.clear(); // check that no zero values are written - const size_t sizeOriginal = balances.size(); - TrimZeros(); - const size_t sizeStripped = balances.size(); - if (sizeOriginal != sizeStripped) { - throw std::ios_base::failure("non-canonical balances (zero amount)"); + for (auto it = serializedBalances.begin(); it != serializedBalances.end(); /* advance */) { + if (it->second == 0) { + throw std::ios_base::failure("non-canonical balances (zero amount)"); + } + balances.emplace(DCT_ID{it->first}, it->second); + serializedBalances.erase(it++); } - } - } - -private: - // TrimZeros is private because balances cannot have zeros normally - void TrimZeros() { - for (auto it = balances.begin(); it != balances.end(); /* no advance */) { - if (it->second == 0) { - it = balances.erase(it); + } else { + for (const auto& it : balances) { + serializedBalances.emplace(it.first.v, it.second); } - else - it++; + READWRITE(serializedBalances); } } }; diff --git a/src/masternodes/govvariables/lp_daily_dfi_reward.cpp b/src/masternodes/govvariables/lp_daily_dfi_reward.cpp index 9b5f14f64e..cc382ad6fa 100644 --- a/src/masternodes/govvariables/lp_daily_dfi_reward.cpp +++ b/src/masternodes/govvariables/lp_daily_dfi_reward.cpp @@ -5,6 +5,7 @@ #include #include /// ValueFromAmount +#include /// CCustomCSView #include /// AmountFromValue @@ -23,8 +24,7 @@ Res LP_DAILY_DFI_REWARD::Validate(const CCustomCSView &) const return Res::Ok(); } -Res LP_DAILY_DFI_REWARD::Apply(CCustomCSView &) +Res LP_DAILY_DFI_REWARD::Apply(CCustomCSView & mnview, uint32_t height) { - // nothing to do - return Res::Ok(); + return mnview.SetDailyReward(height, dailyReward); } diff --git a/src/masternodes/govvariables/lp_daily_dfi_reward.h b/src/masternodes/govvariables/lp_daily_dfi_reward.h index 0d21f20d15..f2f388fefe 100644 --- a/src/masternodes/govvariables/lp_daily_dfi_reward.h +++ b/src/masternodes/govvariables/lp_daily_dfi_reward.h @@ -20,7 +20,7 @@ class LP_DAILY_DFI_REWARD : public GovVariable, public AutoRegistratorsecond; } - mnview.SetPoolPair(poolId, pool); + mnview.SetPoolPair(poolId, height, pool); return true; }); return Res::Ok(); diff --git a/src/masternodes/govvariables/lp_splits.h b/src/masternodes/govvariables/lp_splits.h index 19804b55a2..6ac727089f 100644 --- a/src/masternodes/govvariables/lp_splits.h +++ b/src/masternodes/govvariables/lp_splits.h @@ -20,7 +20,7 @@ class LP_SPLITS : public GovVariable, public AutoRegistrator inline void SerializationOp(Stream& s, Operation ser_action) { - READWRITE(splits); + static_assert(std::is_same>::value, "Following code is invalid"); + std::map serliazedSplits; + if (ser_action.ForRead()) { + READWRITE(serliazedSplits); + splits.clear(); + for (auto it = serliazedSplits.begin(); it != serliazedSplits.end(); /* advance */) { + splits.emplace(DCT_ID{it->first}, it->second); + serliazedSplits.erase(it++); + } + } else { + for (const auto& it : splits) { + serliazedSplits.emplace(it.first.v, it.second); + } + READWRITE(serliazedSplits); + } } std::map splits; diff --git a/src/masternodes/gv.h b/src/masternodes/gv.h index 1b908c0dc7..3edfcc745c 100644 --- a/src/masternodes/gv.h +++ b/src/masternodes/gv.h @@ -26,7 +26,7 @@ class GovVariable virtual UniValue Export() const = 0; /// @todo it looks like Validate+Apply may be redundant. refactor for one? virtual Res Validate(CCustomCSView const &mnview) const = 0; - virtual Res Apply(CCustomCSView &mnview) = 0; + virtual Res Apply(CCustomCSView &mnview, uint32_t height) = 0; virtual void Serialize(CVectorWriter& s) const = 0; virtual void Unserialize(VectorReader& s) = 0; diff --git a/src/masternodes/incentivefunding.cpp b/src/masternodes/incentivefunding.cpp index b55c1b7694..91de4d3e3e 100644 --- a/src/masternodes/incentivefunding.cpp +++ b/src/masternodes/incentivefunding.cpp @@ -28,7 +28,7 @@ Res CCommunityBalancesView::SetCommunityBalance(CommunityAccountType account, CA return Res::Ok(); } -void CCommunityBalancesView::ForEachCommunityBalance(std::function)> callback) const +void CCommunityBalancesView::ForEachCommunityBalance(std::function)> callback) { ForEach([&callback] (unsigned char const & key, CLazySerialize val) { return callback(CommunityAccountCodeToType(key), val); diff --git a/src/masternodes/incentivefunding.h b/src/masternodes/incentivefunding.h index 5d53157ba0..bf7378f275 100644 --- a/src/masternodes/incentivefunding.h +++ b/src/masternodes/incentivefunding.h @@ -35,7 +35,7 @@ class CCommunityBalancesView : public virtual CStorageView CAmount GetCommunityBalance(CommunityAccountType account) const; Res SetCommunityBalance(CommunityAccountType account, CAmount amount); - void ForEachCommunityBalance(std::function)> callback) const; + void ForEachCommunityBalance(std::function)> callback); Res AddCommunityBalance(CommunityAccountType account, CAmount amount); Res SubCommunityBalance(CommunityAccountType account, CAmount amount); diff --git a/src/masternodes/masternodes.cpp b/src/masternodes/masternodes.cpp index 02fd4cf7b5..931c73535d 100644 --- a/src/masternodes/masternodes.cpp +++ b/src/masternodes/masternodes.cpp @@ -416,25 +416,29 @@ void CMasternodesView::ForEachMinterNode(std::function(callback, start); } -//void CMasternodesView::UnCreateMasternode(const uint256 & nodeId) -//{ -// auto node = GetMasternode(nodeId); -// if (node) { -// EraseBy(nodeId); -// EraseBy(node->operatorAuthAddress); -// EraseBy(node->ownerAuthAddress); -// } -//} +Res CMasternodesView::UnCreateMasternode(const uint256 & nodeId) +{ + auto node = GetMasternode(nodeId); + if (node) { + EraseBy(nodeId); + EraseBy(node->operatorAuthAddress); + EraseBy(node->ownerAuthAddress); + return Res::Ok(); + } + return Res::Err("No such masternode %s", nodeId.GetHex()); +} -//void CMasternodesView::UnResignMasternode(const uint256 & nodeId, const uint256 & resignTx) -//{ -// auto node = GetMasternode(nodeId); -// if (node && node->resignTx == resignTx) { -// node->resignHeight = -1; -// node->resignTx = {}; -// WriteBy(nodeId, *node); -// } -//} +Res CMasternodesView::UnResignMasternode(const uint256 & nodeId, const uint256 & resignTx) +{ + auto node = GetMasternode(nodeId); + if (node && node->resignTx == resignTx) { + node->resignHeight = -1; + node->resignTx = {}; + WriteBy(nodeId, *node); + return Res::Ok(); + } + return Res::Err("No such masternode %s, resignTx: %s", nodeId.GetHex(), resignTx.GetHex()); +} /* * CLastHeightView @@ -721,17 +725,50 @@ bool CCustomCSView::CanSpend(const uint256 & txId, int height) const return !pair || pair->second.destructionTx != uint256{} || pair->second.IsPoolShare(); } -enum accountHistoryType { - None = 0, - MineOnly, - Full, -}; +bool CCustomCSView::CalculateOwnerRewards(CScript const & owner, uint32_t targetHeight) +{ + auto balanceHeight = GetBalancesHeight(owner); + if (balanceHeight >= targetHeight) { + return false; + } + ForEachPoolPair([&] (DCT_ID const & poolId, CLazySerialize) { + auto height = GetShare(poolId, owner); + if (!height || *height >= targetHeight) { + return true; // no share or target height is before a pool share' one + } + auto beginHeight = std::max(*height, balanceHeight); + CalculatePoolRewards(poolId, GetBalance(owner, poolId).nValue, beginHeight, targetHeight, + [&](CScript const & from, uint8_t type, CTokenAmount amount, uint32_t begin, uint32_t end) { + amount.nValue *= (end - begin); + if (!from.empty()) { + auto res = SubBalance(from, amount); + if (!res) { + LogPrintf("Custom pool rewards: can't subtract balance of %s: %s, height %ld\n", from.GetHex(), res.msg, targetHeight); + return; // no funds, no rewards + } + } else if (type != uint8_t(RewardType::Commission)) { + auto res = SubCommunityBalance(CommunityAccountType::IncentiveFunding, amount.nValue); + if (!res) { + LogPrintf("Pool rewards: can't subtract community balance: %s, height %ld\n", res.msg, targetHeight); + return; + } + } + auto res = AddBalance(owner, amount); + if (!res) { + LogPrintf("Pool rewards: can't update balance of %s: %s, height %ld\n", owner.GetHex(), res.msg, targetHeight); + } + } + ); + return true; + }); + + return UpdateBalancesHeight(owner, targetHeight); +} CAccountsHistoryStorage::CAccountsHistoryStorage(CCustomCSView & storage, uint32_t height, uint32_t txn, const uint256& txid, uint8_t type) : CStorageView(new CFlushableStorageKV(storage.GetRaw())), height(height), txn(txn), txid(txid), type(type) { - acindex = gArgs.GetBoolArg("-acindex-mineonly", DEFAULT_ACINDEX_MINEONLY) ? accountHistoryType::MineOnly : - gArgs.GetBoolArg("-acindex", DEFAULT_ACINDEX) ? accountHistoryType::Full : accountHistoryType::None; + acindex = gArgs.GetBoolArg("-acindex", DEFAULT_ACINDEX); } Res CAccountsHistoryStorage::AddBalance(CScript const & owner, CTokenAmount amount) @@ -755,56 +792,8 @@ Res CAccountsHistoryStorage::SubBalance(CScript const & owner, CTokenAmount amou bool CAccountsHistoryStorage::Flush() { if (acindex) { - auto wallets = GetWallets(); for (const auto& diff : diffs) { - bool isMine = false; - for (auto wallet : wallets) { - if ((isMine = IsMineCached(*wallet, diff.first) & ISMINE_ALL)) { - SetMineAccountHistory({diff.first, height, txn}, {txid, type, diff.second}); - break; - } - } - if (!isMine && acindex == accountHistoryType::Full) { - SetAllAccountHistory({diff.first, height, txn}, {txid, type, diff.second}); - } - } - } - return CCustomCSView::Flush(); -} - -CRewardsHistoryStorage::CRewardsHistoryStorage(CCustomCSView & storage, uint32_t height) - : CStorageView(new CFlushableStorageKV(storage.GetRaw())), height(height) -{ - acindex = gArgs.GetBoolArg("-acindex-mineonly", DEFAULT_ACINDEX_MINEONLY) ? accountHistoryType::MineOnly : - gArgs.GetBoolArg("-acindex", DEFAULT_ACINDEX) ? accountHistoryType::Full : accountHistoryType::None; -} - -Res CRewardsHistoryStorage::AddBalance(CScript const & owner, DCT_ID poolID, uint8_t type, CTokenAmount amount) -{ - auto res = CCustomCSView::AddBalance(owner, amount); - if (acindex && res.ok && amount.nValue > 0) { - auto& map = diffs[std::make_pair(owner, type)]; - map[poolID][amount.nTokenId] += amount.nValue; - } - return res; -} - -bool CRewardsHistoryStorage::Flush() -{ - if (acindex) { - auto wallets = GetWallets(); - for (const auto& diff : diffs) { - bool isMine = false; - const auto& pair = diff.first; - for (auto wallet : wallets) { - if ((isMine = IsMineCached(*wallet, pair.first) & ISMINE_ALL)) { - SetMineRewardHistory({pair.first, height, pair.second}, diff.second); - break; - } - } - if (!isMine && acindex == accountHistoryType::Full) { - SetAllRewardHistory({pair.first, height, pair.second}, diff.second); - } + SetAccountHistory({diff.first, height, txn}, {txid, type, diff.second}); } } return CCustomCSView::Flush(); diff --git a/src/masternodes/masternodes.h b/src/masternodes/masternodes.h index 4bc28adfdb..b3b30d862b 100644 --- a/src/masternodes/masternodes.h +++ b/src/masternodes/masternodes.h @@ -155,8 +155,8 @@ class CMasternodesView : public virtual CStorageView Res CreateMasternode(uint256 const & nodeId, CMasternode const & node); Res ResignMasternode(uint256 const & nodeId, uint256 const & txid, int height); -// void UnCreateMasternode(uint256 const & nodeId); -// void UnResignMasternode(uint256 const & nodeId, uint256 const & resignTx); + Res UnCreateMasternode(uint256 const & nodeId); + Res UnResignMasternode(uint256 const & nodeId, uint256 const & resignTx); void SetMasternodeLastBlockTime(const CKeyID & minter, const uint32_t &blockHeight, const int64_t &time); boost::optional GetMasternodeLastBlockTime(const CKeyID & minter); @@ -241,7 +241,6 @@ class CCustomCSView , public CTokensView , public CAccountsView , public CAccountsHistoryView - , public CRewardsHistoryView , public CCommunityBalancesView , public CUndosView , public CPoolPairView @@ -273,6 +272,8 @@ class CCustomCSView bool CanSpend(const uint256 & txId, int height) const; + bool CalculateOwnerRewards(CScript const & owner, uint32_t height); + CStorageKV& GetRaw() { return DB(); } @@ -280,7 +281,7 @@ class CCustomCSView class CAccountsHistoryStorage : public CCustomCSView { - int acindex; + bool acindex; const uint32_t height; const uint32_t txn; const uint256 txid; @@ -293,18 +294,6 @@ class CAccountsHistoryStorage : public CCustomCSView bool Flush(); }; -class CRewardsHistoryStorage : public CCustomCSView -{ - int acindex; - const uint32_t height; - std::map, std::map> diffs; - using CCustomCSView::AddBalance; -public: - CRewardsHistoryStorage(CCustomCSView & storage, uint32_t height); - Res AddBalance(CScript const & owner, DCT_ID poolID, uint8_t type, CTokenAmount amount); - bool Flush(); -}; - std::map AmISignerNow(CAnchorData::CTeam const & team); /** Global DB and view that holds enhanced chainstate data (should be protected by cs_main) */ diff --git a/src/masternodes/mn_checks.cpp b/src/masternodes/mn_checks.cpp index 0c4c439e17..231dda2861 100644 --- a/src/masternodes/mn_checks.cpp +++ b/src/masternodes/mn_checks.cpp @@ -295,15 +295,24 @@ class CCustomMetadataParseVisitor : public boost::static_visitor } }; -class CCustomTxApplyVisitor : public boost::static_visitor +class CCustomTxVisitor : public boost::static_visitor { - uint64_t time; +protected: uint32_t height; CCustomCSView& mnview; const CTransaction& tx; const CCoinsViewCache& coins; const Consensus::Params& consensus; +public: + CCustomTxVisitor(const CTransaction& tx, + uint32_t height, + const CCoinsViewCache& coins, + CCustomCSView& mnview, + const Consensus::Params& consensus) + + : height(height), mnview(mnview), tx(tx), coins(coins), consensus(consensus) {} + bool HasAuth(const CScript& auth) const { for (const auto& input : tx.vin) { const Coin& coin = coins.AccessCoin(input.prevout); @@ -332,6 +341,66 @@ class CCustomTxApplyVisitor : public boost::static_visitor return Res::Err("tx not from foundation member"); } + Res CheckMasternodeCreationTx() const { + if (tx.vout.size() < 2 + || tx.vout[0].nValue < GetMnCreationFee(height) || tx.vout[0].nTokenId != DCT_ID{0} + || tx.vout[1].nValue != GetMnCollateralAmount(height) || tx.vout[1].nTokenId != DCT_ID{0}) { + return Res::Err("malformed tx vouts (wrong creation fee or collateral amount)"); + } + return Res::Ok(); + } + + Res CheckTokenCreationTx() const { + if (tx.vout.size() < 2 + || tx.vout[0].nValue < GetTokenCreationFee(height) || tx.vout[0].nTokenId != DCT_ID{0} + || tx.vout[1].nValue != GetTokenCollateralAmount() || tx.vout[1].nTokenId != DCT_ID{0}) { + return Res::Err("malformed tx vouts (wrong creation fee or collateral amount)"); + } + return Res::Ok(); + } + + ResVal MintableToken(DCT_ID id, const CTokenImplementation& token) const { + if (token.destructionTx != uint256{}) { + return Res::Err("token %s already destroyed at height %i by tx %s", token.symbol, + token.destructionHeight, token.destructionTx.GetHex()); + } + const Coin& auth = coins.AccessCoin(COutPoint(token.creationTx, 1)); // always n=1 output + + // pre-bayfront logic: + if (static_cast(height) < consensus.BayfrontHeight) { + if (id < CTokensView::DCT_ID_START) { + return Res::Err("token %s is a 'stable coin', can't mint stable coin!", id.ToString()); + } + + if (!HasAuth(auth.out.scriptPubKey)) { + return Res::Err("tx must have at least one input from token owner"); + } + return {auth.out.scriptPubKey, Res::Ok()}; + } + + if (id == DCT_ID{0}) { + return Res::Err("can't mint default DFI coin!"); + } + + if (token.IsPoolShare()) { + return Res::Err("can't mint LPS token %s!", id.ToString()); + } + // may be different logic with LPS, so, dedicated check: + if (!token.IsMintable()) { + return Res::Err("token %s is not mintable!", id.ToString()); + } + + if (!HasAuth(auth.out.scriptPubKey)) { // in the case of DAT, it's ok to do not check foundation auth cause exact DAT owner is foundation member himself + if (!token.IsDAT()) { + return Res::Err("tx must have at least one input from token owner"); + } else if (!HasFoundationAuth()) { // Is a DAT, check founders auth + return Res::Err("token is DAT and tx not from foundation member"); + } + } + + return {auth.out.scriptPubKey, Res::Ok()}; + } + Res eraseEmptyBalances(TAmounts& balances) const { for (auto it = balances.begin(), next_it = it; it != balances.end(); it = next_it) { ++next_it; @@ -354,7 +423,7 @@ class CCustomTxApplyVisitor : public boost::static_visitor if (token && token->IsPoolShare()) { const auto bal = mnview.GetBalance(owner, balance.first); if (bal.nValue == balance.second) { - auto res = mnview.SetShare(balance.first, owner); + auto res = mnview.SetShare(balance.first, owner, height); if (!res) { return res; } @@ -381,6 +450,7 @@ class CCustomTxApplyVisitor : public boost::static_visitor } Res subBalanceDelShares(const CScript& owner, const CBalances& balance) const { + mnview.CalculateOwnerRewards(owner, height); auto res = mnview.SubBalances(owner, balance); if (!res) { return Res::ErrCode(CustomTxErrCodes::NotEnoughBalance, res.msg); @@ -389,6 +459,7 @@ class CCustomTxApplyVisitor : public boost::static_visitor } Res addBalanceSetShares(const CScript& owner, const CBalances& balance) const { + mnview.CalculateOwnerRewards(owner, height); auto res = mnview.AddBalances(owner, balance); return !res ? res : setShares(owner, balance.balances); } @@ -412,7 +483,11 @@ class CCustomTxApplyVisitor : public boost::static_visitor } return Res::Ok(); } +}; +class CCustomTxApplyVisitor : public CCustomTxVisitor +{ + uint64_t time; public: CCustomTxApplyVisitor(const CTransaction& tx, uint32_t height, @@ -421,13 +496,12 @@ class CCustomTxApplyVisitor : public boost::static_visitor const Consensus::Params& consensus, uint64_t time) - : time(time), height(height), mnview(mnview), tx(tx), coins(coins), consensus(consensus) {} + : CCustomTxVisitor(tx, height, coins, mnview, consensus), time(time) {} Res operator()(const CCreateMasterNodeMessage& obj) const { - if (tx.vout.size() < 2 - || tx.vout[0].nValue < GetMnCreationFee(height) || tx.vout[0].nTokenId != DCT_ID{0} - || tx.vout[1].nValue != GetMnCollateralAmount(height) || tx.vout[1].nTokenId != DCT_ID{0}) { - return Res::Err("malformed tx vouts (wrong creation fee or collateral amount)"); + auto res = CheckMasternodeCreationTx(); + if (!res) { + return res; } CMasternode node; @@ -444,7 +518,7 @@ class CCustomTxApplyVisitor : public boost::static_visitor node.creationHeight = height; node.operatorType = obj.operatorType; node.operatorAuthAddress = obj.operatorAuthAddress; - auto res = mnview.CreateMasternode(tx.GetHash(), node); + res = mnview.CreateMasternode(tx.GetHash(), node); // Build coinage from the point of masternode creation if (res && height >= static_cast(Params().GetConsensus().DakotaCrescentHeight)) { mnview.SetMasternodeLastBlockTime(node.operatorAuthAddress, static_cast(height), time); @@ -458,10 +532,9 @@ class CCustomTxApplyVisitor : public boost::static_visitor } Res operator()(const CCreateTokenMessage& obj) const { - if (tx.vout.size() < 2 - || tx.vout[0].nValue < GetTokenCreationFee(height) || tx.vout[0].nTokenId != DCT_ID{0} - || tx.vout[1].nValue != GetTokenCollateralAmount() || tx.vout[1].nTokenId != DCT_ID{0}) { - return Res::Err("malformed tx vouts (wrong creation fee or collateral amount)"); + auto res = CheckTokenCreationTx(); + if (!res) { + return res; } CTokenImplementation token; @@ -550,51 +623,20 @@ class CCustomTxApplyVisitor : public boost::static_visitor auto token = mnview.GetToken(kv.first); if (!token) { - return Res::Err("token %s does not exist!", tokenId.ToString()); // pre-bayfront throws but it affects only the message + return Res::Err("token %s does not exist!", tokenId.ToString()); } auto tokenImpl = static_cast(*token); - if (tokenImpl.destructionTx != uint256{}) { - return Res::Err("token %s already destroyed at height %i by tx %s", tokenImpl.symbol, // pre-bayfront throws but it affects only the message - tokenImpl.destructionHeight, tokenImpl.destructionTx.GetHex()); - } - const Coin& auth = coins.AccessCoin(COutPoint(tokenImpl.creationTx, 1)); // always n=1 output - - // pre-bayfront logic: - if (static_cast(height) < consensus.BayfrontHeight) { - if (tokenId < CTokensView::DCT_ID_START) { - return Res::Err("token %s is a 'stable coin', can't mint stable coin!", tokenId.ToString()); - } - - if (!HasAuth(auth.out.scriptPubKey)) { - return Res::Err("tx must have at least one input from token owner"); - } - } else { // post-bayfront logic (changed for minting DAT tokens to be able) - if (tokenId == DCT_ID{0}) { - return Res::Err("can't mint default DFI coin!"); - } - - if (tokenImpl.IsPoolShare()) { - return Res::Err("can't mint LPS token %s!", tokenId.ToString()); - } - // may be different logic with LPS, so, dedicated check: - if (!tokenImpl.IsMintable()) { - return Res::Err("token %s is not mintable!", tokenId.ToString()); - } - - if (!HasAuth(auth.out.scriptPubKey)) { // in the case of DAT, it's ok to do not check foundation auth cause exact DAT owner is foundation member himself - if (!tokenImpl.IsDAT()) { - return Res::Err("tx must have at least one input from token owner"); - } else if (!HasFoundationAuth()) { // Is a DAT, check founders auth - return Res::Err("token is DAT and tx not from foundation member"); - } - } + auto mintable = MintableToken(tokenId, tokenImpl); + if (!mintable) { + return std::move(mintable); } - auto mint = mnview.AddMintedTokens(tokenImpl.creationTx, kv.second); - if (!mint) { - return mint; + auto minted = mnview.AddMintedTokens(tokenImpl.creationTx, kv.second); + if (!minted) { + return minted; } - auto res = mnview.AddBalance(auth.out.scriptPubKey, CTokenAmount{kv.first, kv.second}); + mnview.CalculateOwnerRewards(*mintable.val, height); + auto res = mnview.AddBalance(*mintable.val, CTokenAmount{kv.first, kv.second}); if (!res) { return res; } @@ -649,7 +691,7 @@ class CCustomTxApplyVisitor : public boost::static_visitor return std::move(tokenId); } - auto res = mnview.SetPoolPair(tokenId, poolPair); + auto res = mnview.SetPoolPair(tokenId, height, poolPair); if (!res) { return res; } @@ -659,23 +701,18 @@ class CCustomTxApplyVisitor : public boost::static_visitor // Check tokens exist and remove empty reward amounts auto res = eraseEmptyBalances(rewards.balances); // Will only fail if pool was not actually created in SetPoolPair - return !res ? res : mnview.SetPoolCustomReward(tokenId, rewards); + return !res ? res : mnview.SetPoolCustomReward(tokenId, height, rewards); } return Res::Ok(); } Res operator()(const CUpdatePoolPairMessage& obj) const { - auto pool = mnview.GetPoolPair(obj.poolId); - if (!pool) { - return Res::Err("pool with poolId %s does not exist", obj.poolId.ToString()); - } - //check foundation auth if (!HasFoundationAuth()) { return Res::Err("tx not from foundation member"); } - auto res = mnview.UpdatePoolPair(obj.poolId, obj.status, obj.commission, obj.ownerAddress); + auto res = mnview.UpdatePoolPair(obj.poolId, height, obj.status, obj.commission, obj.ownerAddress); if (!res) { return res; } @@ -690,7 +727,7 @@ class CCustomTxApplyVisitor : public boost::static_visitor // Check if tokens exist and remove empty reward amounts auto res = eraseEmptyBalances(rewards.balances); // Will only fail if pool was not actually created in SetPoolPair - return !res ? res : mnview.SetPoolCustomReward(obj.poolId, rewards); + return !res ? res : mnview.SetPoolCustomReward(obj.poolId, height, rewards); } return Res::Ok(); } @@ -708,10 +745,12 @@ class CCustomTxApplyVisitor : public boost::static_visitor CPoolPair& pp = poolPair->second; return pp.Swap({obj.idTokenFrom, obj.amountFrom}, obj.maxPrice, [&] (const CTokenAmount &tokenAmount) { - auto res = mnview.SetPoolPair(poolPair->first, pp); + auto res = mnview.SetPoolPair(poolPair->first, height, pp); if (!res) { return res; } + mnview.CalculateOwnerRewards(obj.from, height); + mnview.CalculateOwnerRewards(obj.to, height); res = mnview.SubBalance(obj.from, {obj.idTokenFrom, obj.amountFrom}); return !res ? res : mnview.AddBalance(obj.to, tokenAmount); }, static_cast(height)); @@ -743,6 +782,7 @@ class CCustomTxApplyVisitor : public boost::static_visitor } for (const auto& kv : obj.from) { + mnview.CalculateOwnerRewards(kv.first, height); auto res = mnview.SubBalances(kv.first, kv.second); if (!res) { return res; @@ -764,7 +804,7 @@ class CCustomTxApplyVisitor : public boost::static_visitor return addBalanceSetShares(obj.shareAddress, balance); }, slippageProtection); - return !res ? res : mnview.SetPoolPair(lpTokenID, pool); + return !res ? res : mnview.SetPoolPair(lpTokenID, height, pool); } Res operator()(const CRemoveLiquidityMessage& obj) const { @@ -796,13 +836,14 @@ class CCustomTxApplyVisitor : public boost::static_visitor } } - auto res = pool.RemoveLiquidity(from, amount.nValue, [&] (const CScript& to, CAmount amountA, CAmount amountB) { + auto res = pool.RemoveLiquidity(amount.nValue, [&] (CAmount amountA, CAmount amountB) { + mnview.CalculateOwnerRewards(from, height); CBalances balances{TAmounts{{pool.idTokenA, amountA}, {pool.idTokenB, amountB}}}; - return mnview.AddBalances(to, balances); + return mnview.AddBalances(from, balances); }); - return !res ? res : mnview.SetPoolPair(amount.nTokenId, pool); + return !res ? res : mnview.SetPoolPair(amount.nTokenId, height, pool); } Res operator()(const CUtxosToAccountMessage& obj) const { @@ -893,7 +934,7 @@ class CCustomTxApplyVisitor : public boost::static_visitor if (!result) { return Res::Err("%s: %s", var->GetName(), result.msg); } - auto res = var->Apply(mnview); + auto res = var->Apply(mnview, height); if (!res) { return Res::Err("%s: %s", var->GetName(), res.msg); } @@ -910,6 +951,47 @@ class CCustomTxApplyVisitor : public boost::static_visitor } }; +class CCustomTxRevertVisitor : public CCustomTxVisitor +{ +public: + using CCustomTxVisitor::CCustomTxVisitor; + + template + Res operator()(const T&) const { + return Res::Ok(); + } + + Res operator()(const CCreateMasterNodeMessage& obj) const { + auto res = CheckMasternodeCreationTx(); + return !res ? res : mnview.UnCreateMasternode(tx.GetHash()); + } + + Res operator()(const CResignMasterNodeMessage& obj) const { + auto res = HasCollateralAuth(obj); + return !res ? res : mnview.UnResignMasternode(obj, tx.GetHash()); + } + + Res operator()(const CCreateTokenMessage& obj) const { + auto res = CheckTokenCreationTx(); + return !res ? res : mnview.RevertCreateToken(tx.GetHash()); + } + + Res operator()(const CCreatePoolPairMessage& obj) const { + //check foundation auth + if (!HasFoundationAuth()) { + return Res::Err("tx not from foundation member"); + } + auto pool = mnview.GetPoolPair(obj.poolPair.idTokenA, obj.poolPair.idTokenB); + if (!pool) { + return Res::Err("no such poolPair tokenA %s, tokenB %s", + obj.poolPair.idTokenA.ToString(), + obj.poolPair.idTokenB.ToString()); + } + + return mnview.RevertCreateToken(tx.GetHash()); + } +}; + Res CustomMetadataParse(uint32_t height, const Consensus::Params& consensus, const std::vector& metadata, CCustomTxMessage& txMessage) { try { return boost::apply_visitor(CCustomMetadataParseVisitor(height, consensus, metadata), txMessage); @@ -930,6 +1012,16 @@ Res CustomTxVisit(CCustomCSView& mnview, const CCoinsViewCache& coins, const CTr } } +Res CustomTxRevert(CCustomCSView& mnview, const CCoinsViewCache& coins, const CTransaction& tx, uint32_t height, const Consensus::Params& consensus, const CCustomTxMessage& txMessage) { + try { + return boost::apply_visitor(CCustomTxRevertVisitor(tx, height, coins, mnview, consensus), txMessage); + } catch (const std::exception& e) { + return Res::Err(e.what()); + } catch (...) { + return Res::Err("unexpected error"); + } +} + bool ShouldReturnNonFatalError(const CTransaction& tx, uint32_t height) { static const std::map skippedTx = { { 471222, uint256S("0ab0b76352e2d865761f4c53037041f33e1200183d55cdf6b09500d6f16b7329") }, @@ -938,6 +1030,20 @@ bool ShouldReturnNonFatalError(const CTransaction& tx, uint32_t height) { return it != skippedTx.end() && it->second == tx.GetHash(); } +Res RevertCustomTx(CCustomCSView& mnview, const CCoinsViewCache& coins, const CTransaction& tx, const Consensus::Params& consensus, uint32_t height) { + if (tx.IsCoinBase() && height > 0) { // genesis contains custom coinbase txs + return Res::Ok(); + } + std::vector metadata; + auto txType = GuessCustomTxType(tx, metadata); + if (txType == CustomTxType::None) { + return Res::Ok(); + } + auto txMessage = customTypeToMessage(txType); + auto res = CustomMetadataParse(height, consensus, metadata, txMessage); + return !res ? res : CustomTxRevert(mnview, coins, tx, height, consensus, txMessage); +} + Res ApplyCustomTx(CCustomCSView& mnview, const CCoinsViewCache& coins, const CTransaction& tx, const Consensus::Params& consensus, uint32_t height, uint64_t time, uint32_t txn) { auto res = Res::Ok(); if (tx.IsCoinBase() && height > 0) { // genesis contains custom coinbase txs diff --git a/src/masternodes/mn_checks.h b/src/masternodes/mn_checks.h index 55bd66502f..457299ddc7 100644 --- a/src/masternodes/mn_checks.h +++ b/src/masternodes/mn_checks.h @@ -94,7 +94,6 @@ struct CAccountToUtxosMessage; struct CAccountToAccountMessage; struct CAnchorFinalizationMessage; struct CAnyAccountsToAccountsMessage; -struct CBalances; struct CLiquidityMessage; struct CPoolSwapMessage; struct CRemoveLiquidityMessage; @@ -212,6 +211,7 @@ Res RpcInfo(const CTransaction& tx, uint32_t height, CustomTxType& type, UniValu Res CustomMetadataParse(uint32_t height, const Consensus::Params& consensus, const std::vector& metadata, CCustomTxMessage& txMessage); Res ApplyCustomTx(CCustomCSView& mnview, const CCoinsViewCache& coins, const CTransaction& tx, const Consensus::Params& consensus, uint32_t height, uint64_t time = 0, uint32_t txn = 0); Res CustomTxVisit(CCustomCSView& mnview, const CCoinsViewCache& coins, const CTransaction& tx, uint32_t height, const Consensus::Params& consensus, const CCustomTxMessage& txMessage, uint64_t time = 0); +Res RevertCustomTx(CCustomCSView& mnview, const CCoinsViewCache& coins, const CTransaction& tx, const Consensus::Params& consensus, uint32_t height); ResVal ApplyAnchorRewardTx(CCustomCSView& mnview, const CTransaction& tx, int height, const uint256& prevStakeModifier, const std::vector& metadata, const Consensus::Params& consensusParams); ResVal ApplyAnchorRewardTxPlus(CCustomCSView& mnview, const CTransaction& tx, int height, const std::vector& metadata, const Consensus::Params& consensusParams); diff --git a/src/masternodes/mn_rpc.cpp b/src/masternodes/mn_rpc.cpp index 76ca161585..d9ac99ceb6 100644 --- a/src/masternodes/mn_rpc.cpp +++ b/src/masternodes/mn_rpc.cpp @@ -14,9 +14,15 @@ CAccounts GetAllMineAccounts(CWallet * const pwallet) { CAccounts walletAccounts; - pcustomcsview->ForEachBalance([&](CScript const & owner, CTokenAmount const & balance) { - if (IsMineCached(*pwallet, owner) == ISMINE_SPENDABLE) { - walletAccounts[owner].Add(balance); + CCustomCSView mnview(*pcustomcsview); + auto targetHeight = chainHeight(*pwallet->chain().lock()) + 1; + + mnview.ForEachAccount([&](CScript const & account) { + if (IsMineCached(*pwallet, account) == ISMINE_SPENDABLE) { + mnview.CalculateOwnerRewards(account, targetHeight); + mnview.ForEachBalance([&](CScript const & owner, CTokenAmount balance) { + return account == owner && walletAccounts[owner].Add(balance); + }, {account, DCT_ID{}}); } return true; }); diff --git a/src/masternodes/poolpairs.cpp b/src/masternodes/poolpairs.cpp index b13c7ececa..2e6b035a0c 100644 --- a/src/masternodes/poolpairs.cpp +++ b/src/masternodes/poolpairs.cpp @@ -7,57 +7,67 @@ #include #include -const unsigned char CPoolPairView::ByID ::prefix = 'i'; -const unsigned char CPoolPairView::ByPair ::prefix = 'j'; -const unsigned char CPoolPairView::ByShare ::prefix = 'k'; -const unsigned char CPoolPairView::Reward ::prefix = 'I'; - -Res CPoolPairView::SetPoolPair(DCT_ID const & poolId, CPoolPair const & pool) +const unsigned char CPoolPairView::ByID ::prefix = 'i'; +const unsigned char CPoolPairView::ByPair ::prefix = 'j'; +const unsigned char CPoolPairView::ByShare ::prefix = 'k'; +const unsigned char CPoolPairView::Reward ::prefix = 'I'; +const unsigned char CPoolPairView::ByDailyReward ::prefix = 'C'; +const unsigned char CPoolPairView::ByPoolHeight ::prefix = 'P'; + +Res CPoolPairView::SetPoolPair(DCT_ID const & poolId, uint32_t height, CPoolPair const & pool) { - DCT_ID poolID = poolId; - if(pool.idTokenA == pool.idTokenB) + if (pool.idTokenA == pool.idTokenB) { return Res::Err("Error: tokens IDs are the same."); + } - auto poolPairByID = GetPoolPair(poolID); + auto poolPairByID = GetPoolPair(poolId); auto poolPairByTokens = GetPoolPair(pool.idTokenA, pool.idTokenB); - if(!poolPairByID && poolPairByTokens) - { + if (!poolPairByID && poolPairByTokens) { return Res::Err("Error, there is already a poolpairwith same tokens, but different poolId"); } - //create new - if(!poolPairByID && !poolPairByTokens) - {//no ByID and no ByTokens - WriteBy(WrapVarInt(poolID.v), pool); - WriteBy(ByPairKey{pool.idTokenA, pool.idTokenB}, WrapVarInt(poolID.v)); - WriteBy(ByPairKey{pool.idTokenB, pool.idTokenA}, WrapVarInt(poolID.v)); + + // create new + if (!poolPairByID && !poolPairByTokens) { + WriteBy(poolId, pool); + WriteBy(ByPairKey{pool.idTokenA, pool.idTokenB}, poolId); + WriteBy(ByPairKey{pool.idTokenB, pool.idTokenA}, poolId); + if (height < UINT_MAX) { + WriteBy(PoolRewardKey{poolId, height}, pool); + } return Res::Ok(); } - //update - if(poolPairByTokens && poolID == poolPairByTokens->first && poolPairByTokens->second.idTokenA == pool.idTokenA && poolPairByTokens->second.idTokenB == pool.idTokenB) - {//if pool exists and parameters are the same -> update - WriteBy(WrapVarInt(poolID.v), pool); + + // update + if(poolPairByTokens && poolId == poolPairByTokens->first + && poolPairByTokens->second.idTokenA == pool.idTokenA + && poolPairByTokens->second.idTokenB == pool.idTokenB) { + WriteBy(poolId, pool); + if (height < UINT_MAX) { + WriteBy(PoolRewardKey{poolId, height}, pool); + } return Res::Ok(); } - else if (poolPairByTokens && poolID != poolPairByTokens->first) - { + + // errors + if (poolPairByTokens && poolId != poolPairByTokens->first) { return Res::Err("Error, PoolID is incorrect"); } - else if (poolPairByTokens && (poolPairByTokens->second.idTokenA != pool.idTokenA || poolPairByTokens->second.idTokenB == pool.idTokenB)) - { - throw std::runtime_error("Error, idTokenA or idTokenB is incorrect."); + if (poolPairByTokens && (poolPairByTokens->second.idTokenA != pool.idTokenA + || poolPairByTokens->second.idTokenB == pool.idTokenB)) { + return Res::Err("Error, idTokenA or idTokenB is incorrect."); } - return Res::Err("Error: Couldn't create/update pool pair."); } -Res CPoolPairView::UpdatePoolPair(DCT_ID const & poolId, bool status, CAmount const & commission, CScript const & ownerAddress) +Res CPoolPairView::UpdatePoolPair(DCT_ID const & poolId, uint32_t height, bool status, CAmount const & commission, CScript const & ownerAddress) { auto poolPair = GetPoolPair(poolId); if (!poolPair) { return Res::Err("Pool with poolId %s does not exist", poolId.ToString()); } + uint32_t usedHeight = UINT_MAX; CPoolPair & pool = poolPair.get(); if (pool.status != status) { @@ -70,10 +80,13 @@ Res CPoolPairView::UpdatePoolPair(DCT_ID const & poolId, bool status, CAmount co pool.commission = commission; } if (!ownerAddress.empty()) { + if (pool.ownerAddress != ownerAddress) { + usedHeight = height; + } pool.ownerAddress = ownerAddress; } - auto res = SetPoolPair(poolId, pool); + auto res = SetPoolPair(poolId, usedHeight, pool); if (!res.ok) { return Res::Err("Update poolpair: %s" , res.msg); } @@ -82,45 +95,124 @@ Res CPoolPairView::UpdatePoolPair(DCT_ID const & poolId, bool status, CAmount co boost::optional CPoolPairView::GetPoolPair(const DCT_ID &poolId) const { - DCT_ID poolID = poolId; - return ReadBy(WrapVarInt(poolID.v)); + return ReadBy(poolId); } boost::optional > CPoolPairView::GetPoolPair(const DCT_ID &tokenA, const DCT_ID &tokenB) const { DCT_ID poolId; - auto varint = WrapVarInt(poolId.v); ByPairKey key {tokenA, tokenB}; - if(ReadBy(key, varint)) { - auto poolPair = ReadBy(varint); - if(poolPair) - return { std::make_pair(poolId, std::move(*poolPair)) }; + if(ReadBy(key, poolId)) { + if (auto poolPair = GetPoolPair(poolId)) { + return std::make_pair(poolId, std::move(*poolPair)); + } } return {}; } -Res CPoolPairView::SetPoolCustomReward(const DCT_ID &poolId, CBalances& rewards) +Res CPoolPairView::SetPoolCustomReward(const DCT_ID &poolId, uint32_t height, const CBalances& rewards) { - DCT_ID poolID = poolId; - if (!GetPoolPair(poolID)) - { - return Res::Err("Error %s: poolID %s does not exist", __func__, poolID.ToString()); + if (!GetPoolPair(poolId)) { + return Res::Err("Error %s: poolID %s does not exist", __func__, poolId.ToString()); } - WriteBy(WrapVarInt(poolID.v), rewards); + WriteBy(PoolRewardKey{poolId, height}, rewards); return Res::Ok(); } boost::optional CPoolPairView::GetPoolCustomReward(const DCT_ID &poolId) { - DCT_ID poolID = poolId; - return ReadBy(WrapVarInt(poolID.v)); + auto it = LowerBound(PoolRewardKey{poolId, UINT_MAX}); + if (!it.Valid() || it.Key().poolID != poolId) { + return {}; + } + return it.Value().as(); } -Res CPoolPair::Swap(CTokenAmount in, PoolPrice const & maxPrice, std::function onTransfer, int height) { - if (in.nTokenId != idTokenA && in.nTokenId != idTokenB) { - throw std::runtime_error("Error, input token ID (" + in.nTokenId.ToString() + ") doesn't match pool tokens (" + idTokenA.ToString() + "," + idTokenB.ToString() + ")"); +void CPoolPairView::CalculatePoolRewards(DCT_ID const & poolId, CAmount liquidity, uint32_t begin, uint32_t end, std::function onReward) { + if (begin >= end || liquidity == 0) { + return; } + constexpr const uint32_t PRECISION = 10000; + const auto newCalcHeight = uint32_t(Params().GetConsensus().BayfrontGardensHeight); + const auto blocksPerDay = (60 * 60 * 24 / Params().GetConsensus().pos.nTargetSpacing); + const auto beginCustomRewards = std::max(begin, uint32_t(Params().GetConsensus().ClarkeQuayHeight)); + + auto itPct = LowerBound(PoolRewardKey{{}, end - 1}); + auto itPool = LowerBound(PoolRewardKey{poolId, end - 1}); + auto itReward = LowerBound(PoolRewardKey{poolId, end - 1}); + + while (itPool.Valid() && itPool.Key().poolID == poolId) { + // rewards starting in same block as pool + const auto poolHeight = itPool.Key().height; + const auto pool = itPool.Value().as(); + // daily rewards + for (auto endHeight = end; itPct.Valid(); itPct.Next()) { + // we have desc order so we select higher height + auto beginHeight = std::max(begin, std::max(poolHeight, itPct.Key().height)); + auto poolReward = itPct.Value().as() / blocksPerDay * pool.rewardPct / COIN; + if (poolReward != 0 && pool.totalLiquidity != 0) { + auto beginCalcHeight = beginHeight; + auto endCalcHeight = std::min(endHeight, newCalcHeight); + if (endCalcHeight > beginHeight) { // old calculation + uint32_t liqWeight = liquidity * PRECISION / pool.totalLiquidity; + auto providerReward = poolReward * liqWeight / PRECISION; + onReward({}, uint8_t(RewardType::Rewards), {DCT_ID{0}, providerReward}, beginHeight, endCalcHeight); + beginCalcHeight = endCalcHeight; + } + if (endHeight > beginCalcHeight) { // new calculation + auto providerReward = static_cast((arith_uint256(poolReward) * arith_uint256(liquidity) / arith_uint256(pool.totalLiquidity)).GetLow64()); + onReward({}, uint8_t(RewardType::Rewards), {DCT_ID{0}, providerReward}, beginCalcHeight, endHeight); + } + } + if (beginHeight == begin || beginHeight == poolHeight) { + break; + } + endHeight = beginHeight; + } + // commissions + if (pool.swapEvent && pool.totalLiquidity != 0) { + CAmount feeA, feeB; + if (poolHeight < newCalcHeight) { + uint32_t liqWeight = liquidity * PRECISION / pool.totalLiquidity; + feeA = pool.blockCommissionA * liqWeight / PRECISION; + feeB = pool.blockCommissionB * liqWeight / PRECISION; + } else { + feeA = static_cast((arith_uint256(pool.blockCommissionA) * arith_uint256(liquidity) / arith_uint256(pool.totalLiquidity)).GetLow64()); + feeB = static_cast((arith_uint256(pool.blockCommissionB) * arith_uint256(liquidity) / arith_uint256(pool.totalLiquidity)).GetLow64()); + } + onReward({}, uint8_t(RewardType::Commission), {pool.idTokenA, feeA}, poolHeight, poolHeight + 1); + onReward({}, uint8_t(RewardType::Commission), {pool.idTokenB, feeB}, poolHeight, poolHeight + 1); + } + // custom rewards + if (end > beginCustomRewards) { + for (auto endHeight = end; itReward.Valid() && itReward.Key().poolID == poolId; itReward.Next()) { + auto beginHeight = std::max(beginCustomRewards, std::max(poolHeight, itReward.Key().height)); + if (endHeight > beginHeight && pool.totalLiquidity != 0) { + for (const auto& reward : itReward.Value().as().balances) { + if (auto providerReward = static_cast((arith_uint256(reward.second) * arith_uint256(liquidity) / arith_uint256(pool.totalLiquidity)).GetLow64())) { + onReward(pool.ownerAddress, uint8_t(RewardType::Rewards), {reward.first, providerReward}, beginHeight, endHeight); + } + } + } + if (beginHeight == beginCustomRewards || beginHeight == poolHeight) { + break; + } + endHeight = beginHeight; + } + } + if (begin >= poolHeight) { + break; + } + itPool.Next(); + end = poolHeight; + } +} + +Res CPoolPair::Swap(CTokenAmount in, PoolPrice const & maxPrice, std::function onTransfer, int height) { + if (in.nTokenId != idTokenA && in.nTokenId != idTokenB) + return Res::Err("Error, input token ID (" + in.nTokenId.ToString() + ") doesn't match pool tokens (" + idTokenA.ToString() + "," + idTokenB.ToString() + ")"); + if (in.nValue <= 0) return Res::Err("Input amount should be positive!"); @@ -198,18 +290,14 @@ CAmount CPoolPair::slopeSwap(CAmount unswapped, CAmount &poolFrom, CAmount &pool return swapped.GetLow64(); } -void CPoolPairView::ForEachPoolPair(std::function)> callback, DCT_ID const & start) { - DCT_ID poolId = start; - auto hint = WrapVarInt(poolId.v); - - ForEach, CPoolPair>([&poolId, &callback] (CVarInt const &, CLazySerialize pool) { - return callback(poolId, pool); - }, hint); +void CPoolPairView::ForEachPoolPair(std::function)> callback, DCT_ID const & start) +{ + ForEach(callback, start); } -void CPoolPairView::ForEachPoolShare(std::function callback, const PoolShareKey &startKey) const +void CPoolPairView::ForEachPoolShare(std::function callback, const PoolShareKey &startKey) { - ForEach([&callback] (PoolShareKey const & poolShareKey, CLazySerialize) { - return callback(poolShareKey.poolID, poolShareKey.owner); + ForEach([&callback] (PoolShareKey const & poolShareKey, uint32_t height) { + return callback(poolShareKey.poolID, poolShareKey.owner, height); }, startKey); } diff --git a/src/masternodes/poolpairs.h b/src/masternodes/poolpairs.h index 8d5f8d41ec..a6705a3833 100644 --- a/src/masternodes/poolpairs.h +++ b/src/masternodes/poolpairs.h @@ -24,8 +24,8 @@ struct ByPairKey { template inline void SerializationOp(Stream& s, Operation ser_action) { - READWRITE(VARINT(idTokenA.v)); - READWRITE(VARINT(idTokenB.v)); + READWRITE(idTokenA); + READWRITE(idTokenB); } }; @@ -56,10 +56,10 @@ struct CPoolSwapMessage { template inline void SerializationOp(Stream& s, Operation ser_action) { READWRITE(from); - READWRITE(VARINT(idTokenFrom.v)); + READWRITE(idTokenFrom); READWRITE(amountFrom); READWRITE(to); - READWRITE(VARINT(idTokenTo.v)); + READWRITE(idTokenTo); READWRITE(maxPrice); } }; @@ -74,8 +74,8 @@ struct CPoolPairMessage { template inline void SerializationOp(Stream& s, Operation ser_action) { - READWRITE(VARINT(idTokenA.v)); - READWRITE(VARINT(idTokenB.v)); + READWRITE(idTokenA); + READWRITE(idTokenB); READWRITE(commission); READWRITE(ownerAddress); READWRITE(status); @@ -162,7 +162,7 @@ class CPoolPair : public CPoolPairMessage return onMint(liquidity); } - Res RemoveLiquidity(CScript const & address, CAmount const & liqAmount, std::function onReclaim) { + Res RemoveLiquidity(CAmount const & liqAmount, std::function onReclaim) { // instead of assertion due to tests // IRL it can't be more than "total-1000", and was checked indirectly by balances before. but for tests and incapsulation: if (liqAmount <= 0 || liqAmount >= totalLiquidity) { @@ -177,7 +177,7 @@ class CPoolPair : public CPoolPairMessage reserveB -= resAmountB; totalLiquidity -= liqAmount; - return onReclaim(address, resAmountA, resAmountB); + return onReclaim(resAmountA, resAmountB); } Res Swap(CTokenAmount in, PoolPrice const & maxPrice, std::function onTransfer, int height = INT_MAX); @@ -226,11 +226,31 @@ struct PoolShareKey { template inline void SerializationOp(Stream& s, Operation ser_action) { - READWRITE(WrapBigEndian(poolID.v)); + READWRITE(poolID); READWRITE(owner); } }; +struct PoolRewardKey { + DCT_ID poolID; + uint32_t height; + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(poolID); + + if (ser_action.ForRead()) { + READWRITE(WrapBigEndian(height)); + height = ~height; + } else { + uint32_t height_ = ~height; + READWRITE(WrapBigEndian(height_)); + } + } +}; + enum class RewardType : uint8_t { Commission = 128, @@ -249,27 +269,39 @@ inline std::string RewardToString(RewardType type) class CPoolPairView : public virtual CStorageView { public: - Res SetPoolPair(const DCT_ID &poolId, CPoolPair const & pool); - Res UpdatePoolPair(DCT_ID const & poolId, bool status, CAmount const & commission, CScript const & ownerAddress); + Res SetPoolPair(const DCT_ID &poolId, uint32_t height, CPoolPair const & pool); + Res UpdatePoolPair(DCT_ID const & poolId, uint32_t height, bool status, CAmount const & commission, CScript const & ownerAddress); - Res SetPoolCustomReward(const DCT_ID &poolId, CBalances &rewards); + Res SetPoolCustomReward(const DCT_ID &poolId, uint32_t height, const CBalances &rewards); boost::optional GetPoolCustomReward(const DCT_ID &poolId); boost::optional GetPoolPair(const DCT_ID &poolId) const; boost::optional > GetPoolPair(DCT_ID const & tokenA, DCT_ID const & tokenB) const; void ForEachPoolPair(std::function)> callback, DCT_ID const & start = DCT_ID{0}); - void ForEachPoolShare(std::function callback, PoolShareKey const &startKey = {}) const; + void ForEachPoolShare(std::function callback, PoolShareKey const &startKey = {}); - Res SetShare(DCT_ID const & poolId, CScript const & provider) { - WriteBy(PoolShareKey{poolId, provider}, '\0'); + Res SetShare(DCT_ID const & poolId, CScript const & provider, uint32_t height) { + WriteBy(PoolShareKey{poolId, provider}, height); return Res::Ok(); } + Res DelShare(DCT_ID const & poolId, CScript const & provider) { EraseBy(PoolShareKey{poolId, provider}); return Res::Ok(); } + boost::optional GetShare(DCT_ID const & poolId, CScript const & provider) { + return ReadBy(PoolShareKey{poolId, provider}); + } + + void CalculatePoolRewards(DCT_ID const & poolId, CAmount liquidity, uint32_t begin, uint32_t end, std::function onReward); + + Res SetDailyReward(uint32_t height, CAmount reward) { + WriteBy(PoolRewardKey{{}, height}, reward); + return Res::Ok(); + } + CAmount DistributeRewards(CAmount yieldFarming, std::function onGetBalance, std::function onTransfer, int nHeight = 0) { bool newRewardCalc = nHeight >= Params().GetConsensus().BayfrontGardensHeight; @@ -309,7 +341,7 @@ class CPoolPairView : public virtual CStorageView return true; // no events, skip to the next pool } - ForEachPoolShare([&] (DCT_ID const & currentId, CScript const & provider) { + ForEachPoolShare([&] (DCT_ID const & currentId, CScript const & provider, uint32_t height) { if (currentId != poolId) { return false; // stop } @@ -368,7 +400,7 @@ class CPoolPairView : public virtual CStorageView pool.blockCommissionB -= distributedFeeB; pool.swapEvent = false; - auto res = SetPoolPair(poolId, pool); + auto res = SetPoolPair(poolId, UINT_MAX, pool); if (!res.ok) { LogPrintf("Pool rewards: can't update pool (id=%s) state: %s\n", poolId.ToString(), res.msg); } @@ -378,12 +410,13 @@ class CPoolPairView : public virtual CStorageView return totalDistributed; } - // tags struct ByID { static const unsigned char prefix; }; // lsTokenID -> СPoolPair struct ByPair { static const unsigned char prefix; }; // tokenA+tokenB -> lsTokenID struct ByShare { static const unsigned char prefix; }; // lsTokenID+accountID -> {} struct Reward { static const unsigned char prefix; }; // lsTokenID -> CBalances + struct ByDailyReward { static const unsigned char prefix; }; + struct ByPoolHeight { static const unsigned char prefix; }; }; struct CLiquidityMessage { diff --git a/src/masternodes/rpc_accounts.cpp b/src/masternodes/rpc_accounts.cpp index 561c13c6ad..f884eae196 100644 --- a/src/masternodes/rpc_accounts.cpp +++ b/src/masternodes/rpc_accounts.cpp @@ -2,17 +2,15 @@ std::string tokenAmountString(CTokenAmount const& amount) { const auto token = pcustomcsview->GetToken(amount.nTokenId); - const auto valueString = strprintf("%d.%08d", amount.nValue / COIN, amount.nValue % COIN); - return valueString + "@" + token->symbol + (token->IsDAT() ? "" : "#" + amount.nTokenId.ToString()); + const auto valueString = ValueFromAmount(amount.nValue).getValStr(); + return valueString + "@" + token->CreateSymbolKey(amount.nTokenId); } UniValue AmountsToJSON(TAmounts const & diffs) { UniValue obj(UniValue::VARR); for (auto const & diff : diffs) { - auto token = pcustomcsview->GetToken(diff.first); - auto const tokenIdStr = token->CreateSymbolKey(diff.first); - obj.push_back(ValueFromAmount(diff.second).getValStr() + "@" + tokenIdStr); + obj.push_back(tokenAmountString({diff.first, diff.second})); } return obj; } @@ -62,17 +60,17 @@ UniValue accounthistoryToJSON(AccountHistoryKey const & key, AccountHistoryValue return obj; } -UniValue rewardhistoryToJSON(RewardHistoryKey const & key, std::pair const & value) { +UniValue rewardhistoryToJSON(CScript const & owner, uint32_t height, DCT_ID const & poolId, uint8_t type, CTokenAmount amount) { UniValue obj(UniValue::VOBJ); - obj.pushKV("owner", ScriptToString(key.owner)); - obj.pushKV("blockHeight", (uint64_t) key.blockHeight); - if (auto block = ::ChainActive()[key.blockHeight]) { + obj.pushKV("owner", ScriptToString(owner)); + obj.pushKV("blockHeight", (uint64_t) height); + if (auto block = ::ChainActive()[height]) { obj.pushKV("blockHash", block->GetBlockHash().GetHex()); obj.pushKV("blockTime", block->GetBlockTime()); } - obj.pushKV("type", RewardToString(RewardType(key.category))); - obj.pushKV("poolID", value.first.ToString()); - obj.pushKV("amounts", AmountsToJSON(value.second)); + obj.pushKV("type", RewardToString(RewardType(type))); + obj.pushKV("poolID", poolId.ToString()); + obj.pushKV("amounts", tokenAmountString(amount)); return obj; } @@ -92,10 +90,28 @@ UniValue outputEntryToJSON(COutputEntry const & entry, CBlockIndex const * index } obj.pushKV("txn", (uint64_t) entry.vout); obj.pushKV("txid", pwtx->GetHash().ToString()); - obj.pushKV("amounts", AmountsToJSON({{DCT_ID{0}, entry.amount}})); + obj.pushKV("amounts", tokenAmountString({DCT_ID{0}, entry.amount})); return obj; } +static void onPoolRewards(CCustomCSView & view, CScript const & owner, uint32_t begin, uint32_t end, std::function onReward) { + view.ForEachPoolPair([&] (DCT_ID const & poolId, CLazySerialize) { + auto height = view.GetShare(poolId, owner); + if (!height || *height >= end) { + return true; // no share or target height is before a pool share' one + } + auto beginHeight = std::max(*height, begin); + view.CalculatePoolRewards(poolId, view.GetBalance(owner, poolId).nValue, beginHeight, end, + [&](CScript const &, uint8_t type, CTokenAmount amount, uint32_t begin, uint32_t end) { + for (auto height = end - 1; height >= begin; height--) { + onReward(height, poolId, type, amount); + } + } + ); + return true; + }); +} + static void searchInWallet(CWallet const * pwallet, CScript const & account, isminetype filter, @@ -282,24 +298,41 @@ UniValue listaccounts(const JSONRPCRequest& request) { UniValue ret(UniValue::VARR); LOCK(cs_main); - pcustomcsview->ForEachBalance([&](CScript const & owner, CTokenAmount const & balance) { + CCustomCSView mnview(*pcustomcsview); + auto targetHeight = chainHeight(*pwallet->chain().lock()) + 1; + + mnview.ForEachAccount([&](CScript const & account) { + if (!start.owner.empty() && start.owner != account) { + return false; + } + if (isMineOnly) { - if (IsMineCached(*pwallet, owner) == ISMINE_SPENDABLE) { - ret.push_back(accountToJSON(owner, balance, verbose, indexed_amounts)); - limit--; + if (IsMineCached(*pwallet, account) == ISMINE_SPENDABLE) { + mnview.CalculateOwnerRewards(account, targetHeight); + } else { + return true; } } else { - ret.push_back(accountToJSON(owner, balance, verbose, indexed_amounts)); - limit--; + mnview.CalculateOwnerRewards(account, targetHeight); } + mnview.ForEachBalance([&](CScript const & owner, CTokenAmount balance) { + if (account != owner) { + return false; + } + ret.push_back(accountToJSON(owner, balance, verbose, indexed_amounts)); + return --limit != 0; + }, {account, start.tokenID}); + return limit != 0; - }, start); + }); return ret; } UniValue getaccount(const JSONRPCRequest& request) { + CWallet* const pwallet = GetWallet(request); + RPCHelpMan{"getaccount", "\nReturns information about account.\n", { @@ -366,7 +399,12 @@ UniValue getaccount(const JSONRPCRequest& request) { } LOCK(cs_main); - pcustomcsview->ForEachBalance([&](CScript const & owner, CTokenAmount const & balance) { + CCustomCSView mnview(*pcustomcsview); + auto targetHeight = chainHeight(*pwallet->chain().lock()) + 1; + + mnview.CalculateOwnerRewards(reqOwner, targetHeight); + + mnview.ForEachBalance([&](CScript const & owner, CTokenAmount balance) { if (owner != reqOwner) { return false; } @@ -453,9 +491,15 @@ UniValue gettokenbalances(const JSONRPCRequest& request) { LOCK(cs_main); CBalances totalBalances; - pcustomcsview->ForEachBalance([&](CScript const & owner, CTokenAmount const & balance) { - if (IsMineCached(*pwallet, owner) == ISMINE_SPENDABLE) { - totalBalances.Add(balance); + CCustomCSView mnview(*pcustomcsview); + auto targetHeight = chainHeight(*pwallet->chain().lock()) + 1; + + mnview.ForEachAccount([&](CScript const & account) { + if (IsMineCached(*pwallet, account) == ISMINE_SPENDABLE) { + mnview.CalculateOwnerRewards(account, targetHeight); + mnview.ForEachBalance([&](CScript const & owner, CTokenAmount balance) { + return account == owner && totalBalances.Add(balance); + }, {account, DCT_ID{}}); } return true; }); @@ -464,7 +508,7 @@ UniValue gettokenbalances(const JSONRPCRequest& request) { CTokenAmount bal = CTokenAmount{(*it).first, (*it).second}; std::string tokenIdStr = bal.nTokenId.ToString(); if (symbol_lookup) { - auto token = pcustomcsview->GetToken(bal.nTokenId); + auto token = mnview.GetToken(bal.nTokenId); tokenIdStr = token->CreateSymbolKey(bal.nTokenId); } if (indexed_amounts) @@ -781,6 +825,20 @@ UniValue accounttoutxos(const JSONRPCRequest& request) { return signsend(rawTx, pwallet, optAuthTx)->GetHash().GetHex(); } +class CScopeTxReverter { + CCustomCSView & view; + uint256 const & txid; + uint32_t height; + +public: + CScopeTxReverter(CCustomCSView & view, uint256 const & txid, uint32_t height) + : view(view), txid(txid), height(height) {} + + ~CScopeTxReverter() { + view.OnUndoTx(txid, height); + } +}; + UniValue listaccounthistory(const JSONRPCRequest& request) { CWallet* const pwallet = GetWallet(request); RPCHelpMan{"listaccounthistory", @@ -819,11 +877,10 @@ UniValue listaccounthistory(const JSONRPCRequest& request) { accounts = request.params[0].getValStr(); } - const auto acFull = gArgs.GetBoolArg("-acindex", DEFAULT_ACINDEX); - const auto acMineOnly = gArgs.GetBoolArg("-acindex-mineonly", DEFAULT_ACINDEX_MINEONLY); + const auto acindex = gArgs.GetBoolArg("-acindex", DEFAULT_ACINDEX); - if (!acMineOnly && !acFull) { - throw JSONRPCError(RPC_INVALID_REQUEST, "-acindex or -acindex-mineonly is need for account history"); + if (!acindex) { + throw JSONRPCError(RPC_INVALID_REQUEST, "-acindex is needed for account history"); } uint32_t maxBlockHeight = std::numeric_limits::max(); @@ -898,9 +955,6 @@ UniValue listaccounthistory(const JSONRPCRequest& request) { } else if (accounts != "all") { account = DecodeScript(accounts); isMine = IsMineCached(*pwallet, account) & ISMINE_ALL; - if (acMineOnly && !isMine) { - throw JSONRPCError(RPC_INVALID_REQUEST, "account " + accounts + " is not mine, it's needed -acindex to find it"); - } isMatchOwner = [&account](CScript const & owner) { return owner == account; }; @@ -921,23 +975,31 @@ UniValue listaccounthistory(const JSONRPCRequest& request) { }; LOCK(cs_main); + CCustomCSView view(*pcustomcsview); + CCoinsViewCache coins(&::ChainstateActive().CoinsTip()); std::map> ret; auto count = limit; + auto lastHeight = maxBlockHeight; auto shouldContinueToNextAccountHistory = [&](AccountHistoryKey const & key, CLazySerialize valueLazy) -> bool { if (!isMatchOwner(key.owner)) { return false; } - if (shouldSkipBlock(key.blockHeight)) { - return true; + std::unique_ptr reverter; + if (!noRewards) { + reverter = MakeUnique(view, valueLazy.get().txid, key.blockHeight); } if (isMine && !(IsMineCached(*pwallet, key.owner) & filter)) { return true; } + if (shouldSkipBlock(key.blockHeight)) { + return true; + } + const auto & value = valueLazy.get(); if (CustomTxType::None != txType && value.category != uint8_t(txType)) { @@ -953,17 +1015,25 @@ UniValue listaccounthistory(const JSONRPCRequest& request) { if (shouldSearchInWallet) { txs.insert(value.txid); } - return --count != 0; - }; - AccountHistoryKey startKey{account, maxBlockHeight, std::numeric_limits::max()}; + --count; - pcustomcsview->ForEachMineAccountHistory(shouldContinueToNextAccountHistory, startKey); + if (!noRewards && count) { + onPoolRewards(view, key.owner, key.blockHeight, lastHeight, + [&](int32_t height, DCT_ID poolId, uint8_t type, CTokenAmount amount) { + auto& array = ret.emplace(height, UniValue::VARR).first->second; + array.push_back(rewardhistoryToJSON(key.owner, height, poolId, type, amount)); + count ? --count : 0; + } + ); + lastHeight = key.blockHeight; + } - if (!isMine) { - count = limit; - pcustomcsview->ForEachAllAccountHistory(shouldContinueToNextAccountHistory, startKey); - } + return count != 0; + }; + + AccountHistoryKey startKey{account, maxBlockHeight, std::numeric_limits::max()}; + view.ForEachAccountHistory(shouldContinueToNextAccountHistory, startKey); if (shouldSearchInWallet) { count = limit; @@ -979,53 +1049,6 @@ UniValue listaccounthistory(const JSONRPCRequest& request) { ); } - if (!noRewards) { - auto shouldContinueToNextReward = [&](RewardHistoryKey const & key, CLazySerialize valueLazy) -> bool { - if (!isMatchOwner(key.owner)) { - return false; - } - - if (shouldSkipBlock(key.blockHeight)) { - return true; - } - - if (isMine && !(IsMineCached(*pwallet, key.owner) & filter)) { - return true; - } - - if(!tokenFilter.empty()) { - bool tokenFound = false; - for (auto& value : valueLazy.get()) { - if ((tokenFound = hasToken(value.second))) { - break; - } - } - if (!tokenFound) { - return true; - } - } - - auto& array = ret.emplace(key.blockHeight, UniValue::VARR).first->second; - for (const auto & value : valueLazy.get()) { - array.push_back(rewardhistoryToJSON(key, value)); - if (--count == 0) { - break; - } - } - return count != 0; - }; - - RewardHistoryKey startKey{account, maxBlockHeight, 0}; - - count = limit; - pcustomcsview->ForEachMineRewardHistory(shouldContinueToNextReward, startKey); - - if (!isMine) { - count = limit; - pcustomcsview->ForEachAllRewardHistory(shouldContinueToNextReward, startKey); - } - } - UniValue slice(UniValue::VARR); for (auto it = ret.cbegin(); limit != 0 && it != ret.cend(); ++it) { const auto& array = it->second.get_array(); @@ -1068,11 +1091,10 @@ UniValue accounthistorycount(const JSONRPCRequest& request) { accounts = request.params[0].getValStr(); } - const auto acFull = gArgs.GetBoolArg("-acindex", DEFAULT_ACINDEX); - const auto acMineOnly = gArgs.GetBoolArg("-acindex-mineonly", DEFAULT_ACINDEX_MINEONLY); + const auto acindex = gArgs.GetBoolArg("-acindex", DEFAULT_ACINDEX); - if (!acMineOnly && !acFull) { - throw JSONRPCError(RPC_INVALID_REQUEST, "-acindex or -acindex-mineonly is need for account history"); + if (!acindex) { + throw JSONRPCError(RPC_INVALID_REQUEST, "-acindex is need for account history"); } bool noRewards = false; @@ -1112,9 +1134,6 @@ UniValue accounthistorycount(const JSONRPCRequest& request) { } else if (accounts != "all") { owner = DecodeScript(accounts); isMine = IsMineCached(*pwallet, owner) & ISMINE_ALL; - if (acMineOnly && !isMine) { - throw JSONRPCError(RPC_INVALID_REQUEST, "account " + accounts + " is not mine, it's needed -acindex to find it"); - } } std::set txs; @@ -1132,16 +1151,23 @@ UniValue accounthistorycount(const JSONRPCRequest& request) { }; LOCK(cs_main); - UniValue ret(UniValue::VARR); + CCustomCSView view(*pcustomcsview); + CCoinsViewCache coins(&::ChainstateActive().CoinsTip()); uint64_t count = 0; - const auto currentHeight = uint32_t(::ChainActive().Height()); + auto lastHeight = uint32_t(::ChainActive().Height()); + const auto currentHeight = lastHeight; auto shouldContinueToNextAccountHistory = [&](AccountHistoryKey const & key, CLazySerialize valueLazy) -> bool { if (!owner.empty() && owner != key.owner) { return false; } + std::unique_ptr reverter; + if (!noRewards) { + reverter = MakeUnique(view, valueLazy.get().txid, key.blockHeight); + } + if (isMine && !(IsMineCached(*pwallet, key.owner) & filter)) { return true; } @@ -1159,17 +1185,22 @@ UniValue accounthistorycount(const JSONRPCRequest& request) { if (shouldSearchInWallet) { txs.insert(value.txid); } + + if (!noRewards) { + onPoolRewards(view, key.owner, key.blockHeight, lastHeight, + [&](int32_t, DCT_ID, uint8_t, CTokenAmount) { + ++count; + } + ); + lastHeight = key.blockHeight; + } + ++count; return true; }; AccountHistoryKey startAccountKey{owner, currentHeight, std::numeric_limits::max()}; - - pcustomcsview->ForEachMineAccountHistory(shouldContinueToNextAccountHistory, startAccountKey); - - if (!isMine) { - pcustomcsview->ForEachAllAccountHistory(shouldContinueToNextAccountHistory, startAccountKey); - } + view.ForEachAccountHistory(shouldContinueToNextAccountHistory, startAccountKey); if (shouldSearchInWallet) { searchInWallet(pwallet, owner, filter, @@ -1183,42 +1214,6 @@ UniValue accounthistorycount(const JSONRPCRequest& request) { ); } - if (noRewards) { - return count; - } - - auto shouldContinueToNextReward = [&](RewardHistoryKey const & key, CLazySerialize valueLazy) -> bool { - if (!owner.empty() && owner != key.owner) { - return false; - } - - if (isMine && !(IsMineCached(*pwallet, key.owner) & filter)) { - return true; - } - - if(!tokenFilter.empty()) { - bool tokenFound = false; - for (const auto & value : valueLazy.get()) { - if ((tokenFound = hasToken(value.second))) { - break; - } - } - if (!tokenFound) { - return true; - } - } - ++count; - return true; - }; - - RewardHistoryKey startHistoryKey{owner, currentHeight, 0}; - - pcustomcsview->ForEachMineRewardHistory(shouldContinueToNextReward, startHistoryKey); - - if (!isMine) { - pcustomcsview->ForEachAllRewardHistory(shouldContinueToNextReward, startHistoryKey); - } - return count; } diff --git a/src/masternodes/rpc_poolpair.cpp b/src/masternodes/rpc_poolpair.cpp index 3b92ba4740..a658dddebe 100644 --- a/src/masternodes/rpc_poolpair.cpp +++ b/src/masternodes/rpc_poolpair.cpp @@ -917,7 +917,7 @@ UniValue testpoolswap(const JSONRPCRequest& request) { CPoolPair pp = poolPair->second; res = pp.Swap({poolSwapMsg.idTokenFrom, poolSwapMsg.amountFrom}, poolSwapMsg.maxPrice, [&] (const CTokenAmount &tokenAmount) { - auto resPP = mnview_dummy.SetPoolPair(poolPair->first, pp); + auto resPP = mnview_dummy.SetPoolPair(poolPair->first, targetHeight, pp); if (!resPP.ok) { return Res::Err("%s: %s", base, resPP.msg); } @@ -1004,7 +1004,7 @@ UniValue listpoolshares(const JSONRPCRequest& request) { // startKey.owner = CScript(0); UniValue ret(UniValue::VOBJ); - pcustomcsview->ForEachPoolShare([&](DCT_ID const & poolId, CScript const & provider) { + pcustomcsview->ForEachPoolShare([&](DCT_ID const & poolId, CScript const & provider, uint32_t) { const CTokenAmount tokenAmount = pcustomcsview->GetBalance(provider, poolId); if(tokenAmount.nValue) { const auto poolPair = pcustomcsview->GetPoolPair(poolId); diff --git a/src/masternodes/tokens.cpp b/src/masternodes/tokens.cpp index 07df272d0a..f9e95b8653 100644 --- a/src/masternodes/tokens.cpp +++ b/src/masternodes/tokens.cpp @@ -34,9 +34,9 @@ std::string trim_ws(std::string const & str) std::unique_ptr CTokensView::GetToken(DCT_ID id) const { - auto tokenImpl = ReadBy(WrapVarInt(id.v)); // @todo change serialization of DCT_ID to VarInt by default? - if (tokenImpl) + if (auto tokenImpl = ReadBy(id)) { return MakeUnique(*tokenImpl); + } return {}; } @@ -44,10 +44,7 @@ std::unique_ptr CTokensView::GetToken(DCT_ID id) const boost::optional > > CTokensView::GetToken(const std::string & symbolKey) const { DCT_ID id; - auto varint = WrapVarInt(id.v); - - if (ReadBy(symbolKey, varint)) { -// assert(id >= DCT_ID_START);// ? not needed anymore? + if (ReadBy(symbolKey, id)) { return std::make_pair(id, GetToken(id)); } return {}; @@ -56,11 +53,10 @@ boost::optional > > CTokensView::GetTo boost::optional > CTokensView::GetTokenByCreationTx(const uint256 & txid) const { DCT_ID id; - auto varint = WrapVarInt(id.v); - if (ReadBy(txid, varint)) { - auto tokenImpl = ReadBy(varint); - if (tokenImpl) - return { std::make_pair(id, std::move(*tokenImpl))}; + if (ReadBy(txid, id)) { + if (auto tokenImpl = ReadBy(id)) { + return std::make_pair(id, std::move(*tokenImpl)); + } } return {}; } @@ -71,7 +67,7 @@ std::unique_ptr CTokensView::GetTokenGuessId(const std::string & str, DC if (key.empty()) { id = DCT_ID{0}; - return GetToken(DCT_ID{0}); + return GetToken(id); } if (ParseUInt32(key, &id.v)) return GetToken(id); @@ -83,8 +79,7 @@ std::unique_ptr CTokensView::GetTokenGuessId(const std::string & str, DC id = pair->first; return MakeUnique(pair->second); } - } - else { + } else { auto pair = GetToken(key); if (pair) { id = pair->first; @@ -96,13 +91,7 @@ std::unique_ptr CTokensView::GetTokenGuessId(const std::string & str, DC void CTokensView::ForEachToken(std::function)> callback, DCT_ID const & start) { - DCT_ID tokenId = start; - auto hint = WrapVarInt(tokenId.v); - - ForEach, CTokenImpl>([&tokenId, &callback] (CVarInt const &, CLazySerialize tokenImpl) { - return callback(tokenId, tokenImpl); - }, hint); - + ForEach(callback, start); } Res CTokensView::CreateDFIToken() @@ -118,9 +107,9 @@ Res CTokensView::CreateDFIToken() token.flags |= (uint8_t)CToken::TokenFlags::Finalized; DCT_ID id{0}; - WriteBy(WrapVarInt(id.v), token); - WriteBy(token.symbol, WrapVarInt(id.v)); - WriteBy(token.creationTx, WrapVarInt(id.v)); + WriteBy(id, token); + WriteBy(token.symbol, id); + WriteBy(token.creationTx, id); return Res::Ok(); } @@ -152,38 +141,35 @@ ResVal CTokensView::CreateToken(const CTokensView::CTokenImpl & token, b id = IncrementLastDctId(); LogPrintf("Warning! Range (WrapVarInt(id.v), token); - WriteBy(symbolKey, WrapVarInt(id.v)); - WriteBy(token.creationTx, WrapVarInt(id.v)); + WriteBy(id, token); + WriteBy(symbolKey, id); + WriteBy(token.creationTx, id); return {id, Res::Ok()}; } -/// @deprecated used only by tests. rewrite tests -bool CTokensView::RevertCreateToken(const uint256 & txid) +Res CTokensView::RevertCreateToken(const uint256 & txid) { auto pair = GetTokenByCreationTx(txid); if (!pair) { - LogPrintf("Token creation revert error: token with creation tx %s does not exist!\n", txid.ToString()); - return false; + return Res::Err("Token creation revert error: token with creation tx %s does not exist!\n", txid.ToString()); } DCT_ID id = pair->first; auto lastId = ReadLastDctId(); if (!lastId || (*lastId) != id) { - LogPrintf("Token creation revert error: revert sequence broken! (txid = %s, id = %s, LastDctId = %s)\n", txid.ToString(), id.ToString(), (lastId ? lastId->ToString() : DCT_ID{0}.ToString())); - return false; + return Res::Err("Token creation revert error: revert sequence broken! (txid = %s, id = %s, LastDctId = %s)\n", txid.ToString(), id.ToString(), (lastId ? lastId->ToString() : DCT_ID{0}.ToString())); } auto const & token = pair->second; - EraseBy(WrapVarInt(id.v)); + EraseBy(id); EraseBy(token.symbol); EraseBy(token.creationTx); DecrementLastDctId(); - return true; + return Res::Ok(); } Res CTokensView::UpdateToken(const uint256 &tokenTx, const CToken& newToken, bool isPreBayfront) @@ -217,7 +203,7 @@ Res CTokensView::UpdateToken(const uint256 &tokenTx, const CToken& newToken, boo return Res::Err("token with key '%s' already exists!", newSymbolKey); } EraseBy(oldSymbolKey); - WriteBy(newSymbolKey, WrapVarInt(id.v)); + WriteBy(newSymbolKey, id); } // apply DAT flag and symbol only AFTER dealing with symbol indexes: @@ -235,7 +221,7 @@ Res CTokensView::UpdateToken(const uint256 &tokenTx, const CToken& newToken, boo if (!oldToken.IsFinalized() && newToken.IsFinalized()) // IsFinalized() itself was checked upthere (with Err) oldToken.flags |= (uint8_t)CToken::TokenFlags::Finalized; - WriteBy(WrapVarInt(id.v), oldToken); + WriteBy(id, oldToken); return Res::Ok(); } @@ -258,8 +244,7 @@ Res CTokensView::BayfrontFlagsCleanup() changed = true; } if (changed) { - DCT_ID dummy = id; - WriteBy(WrapVarInt(dummy.v), token); + WriteBy(id, token); } return true; }, DCT_ID{1}); // start from non-DFI @@ -280,7 +265,7 @@ Res CTokensView::AddMintedTokens(const uint256 &tokenTx, CAmount const & amount) } tokenImpl.minted = *resMinted.val; - WriteBy(WrapVarInt(pair->first.v), tokenImpl); + WriteBy(pair->first, tokenImpl); return Res::Ok(); } @@ -295,14 +280,12 @@ DCT_ID CTokensView::IncrementLastDctId() return result; } -/// @deprecated used only by "revert*". rewrite tests DCT_ID CTokensView::DecrementLastDctId() { auto lastDctId = ReadLastDctId(); if (lastDctId && *lastDctId >= DCT_ID_START) { --(lastDctId->v); - } - else { + } else { LogPrintf("Critical fault: trying to decrement nonexistent DCT_ID or it is lower than DCT_ID_START\n"); assert (false); } @@ -318,5 +301,3 @@ boost::optional CTokensView::ReadLastDctId() const } return {}; } - - diff --git a/src/masternodes/tokens.h b/src/masternodes/tokens.h index 86d97b7d2b..540fcaa447 100644 --- a/src/masternodes/tokens.h +++ b/src/masternodes/tokens.h @@ -146,7 +146,7 @@ class CTokensView : public virtual CStorageView Res CreateDFIToken(); ResVal CreateToken(CTokenImpl const & token, bool isPreBayfront); - bool RevertCreateToken(uint256 const & txid); /// @deprecated used only by tests. rewrite tests + Res RevertCreateToken(uint256 const & txid); Res UpdateToken(uint256 const & tokenTx, CToken const & newToken, bool isPreBayfront); Res BayfrontFlagsCleanup(); @@ -161,10 +161,8 @@ class CTokensView : public virtual CStorageView private: // have to incapsulate "last token id" related methods here DCT_ID IncrementLastDctId(); - DCT_ID DecrementLastDctId(); /// @deprecated used only by "revert*". rewrite tests + DCT_ID DecrementLastDctId(); boost::optional ReadLastDctId() const; }; - - #endif // DEFI_MASTERNODES_TOKENS_H diff --git a/src/masternodes/undo.h b/src/masternodes/undo.h index 52132073d0..b9f6e1e967 100644 --- a/src/masternodes/undo.h +++ b/src/masternodes/undo.h @@ -26,7 +26,7 @@ struct UndoKey { }; struct CUndo { - std::map> before; + MapKV before; static CUndo Construct(CStorageKV const & before, MapKV const & diff) { CUndo result; diff --git a/src/masternodes/undos.cpp b/src/masternodes/undos.cpp index 413c926e60..ba9f452e2a 100644 --- a/src/masternodes/undos.cpp +++ b/src/masternodes/undos.cpp @@ -7,7 +7,7 @@ /// @attention make sure that it does not overlap with those in masternodes.cpp/tokens.cpp/undos.cpp/accounts.cpp !!! const unsigned char CUndosView::ByUndoKey::prefix = 'u'; -void CUndosView::ForEachUndo(std::function)> callback, UndoKey const & start) const +void CUndosView::ForEachUndo(std::function)> callback, UndoKey const & start) { ForEach(callback, start); } diff --git a/src/masternodes/undos.h b/src/masternodes/undos.h index 8694435929..76b29908b9 100644 --- a/src/masternodes/undos.h +++ b/src/masternodes/undos.h @@ -11,7 +11,7 @@ class CUndosView : public virtual CStorageView { public: - void ForEachUndo(std::function)> callback, UndoKey const & start = {}) const; + void ForEachUndo(std::function)> callback, UndoKey const & start = {}); boost::optional GetUndo(UndoKey key) const; Res SetUndo(UndoKey key, CUndo const & undo); diff --git a/src/test/liquidity_tests.cpp b/src/test/liquidity_tests.cpp index 0eef8a5733..3037fccba6 100644 --- a/src/test/liquidity_tests.cpp +++ b/src/test/liquidity_tests.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -44,7 +45,7 @@ std::tuple CreatePoolNTokens(CCustomCSView &mnview, std: pool.commission = 1000000; // 1% // poolMsg.ownerAddress = CScript(); pool.status = true; - BOOST_REQUIRE(mnview.SetPoolPair(idPool, pool).ok); + BOOST_REQUIRE(mnview.SetPoolPair(idPool, 1, pool).ok); } return std::tuple(idA, idB, idPool); // ! simple initialization list (as "{a,b,c}") doesn't work here under ubuntu 16.04 - due to older gcc? } @@ -62,14 +63,14 @@ Res AddPoolLiquidity(CCustomCSView &mnview, DCT_ID idPool, CAmount amountA, CAmo return add; } //insert update ByShare index - const auto setShare = mnview.SetShare(idPool, shareAddress); + const auto setShare = mnview.SetShare(idPool, shareAddress, 1); if (!setShare.ok) { return setShare; } return Res::Ok(); }); BOOST_REQUIRE(res.ok); - return mnview.SetPoolPair(idPool, *optPool); + return mnview.SetPoolPair(idPool, 1, *optPool); } BOOST_FIXTURE_TEST_SUITE(liquidity_tests, TestingSetup) @@ -244,7 +245,7 @@ void SetPoolRewardPct(CCustomCSView & mnview, DCT_ID idPool, CAmount pct) auto optPool = mnview.GetPoolPair(idPool); BOOST_REQUIRE(optPool); optPool->rewardPct = pct; - mnview.SetPoolPair(idPool, *optPool); + mnview.SetPoolPair(idPool, 1, *optPool); } void SetPoolTradeFees(CCustomCSView & mnview, DCT_ID idPool, CAmount A, CAmount B) @@ -254,7 +255,7 @@ void SetPoolTradeFees(CCustomCSView & mnview, DCT_ID idPool, CAmount A, CAmount optPool->blockCommissionA = A; optPool->blockCommissionB = B; optPool->swapEvent = true; - mnview.SetPoolPair(idPool, *optPool); + mnview.SetPoolPair(idPool, 1, *optPool); } BOOST_AUTO_TEST_CASE(math_rewards) @@ -328,7 +329,7 @@ BOOST_AUTO_TEST_CASE(math_rewards) // check it auto rwd25 = 25 * COIN / ProvidersCount; auto rwd50 = 50 * COIN / ProvidersCount; - cache.ForEachPoolShare([&] (DCT_ID const & id, CScript const & owner) { + cache.ForEachPoolShare([&] (DCT_ID const & id, CScript const & owner, uint32_t) { // if (id == RWD25/* || id == RWD50*/) // printf("owner = %s: %s,\n", owner.GetHex().c_str(), cache.GetBalance(owner, DCT_ID{0}).ToString().c_str()); @@ -352,7 +353,7 @@ BOOST_AUTO_TEST_CASE(math_rewards) { DCT_ID idPool = DCT_ID{1}; auto optPool = cache.GetPoolPair(idPool); - cache.ForEachPoolShare([&] (DCT_ID const & id, CScript const & owner) { + cache.ForEachPoolShare([&] (DCT_ID const & id, CScript const & owner, uint32_t) { if (id != idPool) return false; @@ -368,5 +369,142 @@ BOOST_AUTO_TEST_CASE(math_rewards) } } +BOOST_AUTO_TEST_CASE(owner_rewards) +{ + CCustomCSView mnview(*pcustomcsview); + + constexpr const int PoolCount = 10; + CScript shareAddress[PoolCount]; + + // create pools + for (int i = 0; i < PoolCount; ++i) { + DCT_ID idA, idB, idPool; + std::tie(idA, idB, idPool) = CreatePoolNTokens(mnview, "A"+std::to_string(i), "B"+std::to_string(i)); + shareAddress[i] = CScript(idPool.v * PoolCount + i); + auto optPool = mnview.GetPoolPair(idPool); + BOOST_REQUIRE(optPool); + } + + // create shares + mnview.ForEachPoolPair([&] (DCT_ID const & idPool, CLazySerialize) { + for (int i = 0; i < PoolCount; ++i) { + Res res = AddPoolLiquidity(mnview, idPool, idPool.v*COIN, idPool.v*COIN, shareAddress[i]); + BOOST_CHECK(res.ok); + } + return true; + }); + + mnview.ForEachPoolPair([&] (DCT_ID const & idPool, CPoolPair pool) { + pool.rewardPct = COIN/(idPool.v + 1); + pool.blockCommissionA = idPool.v * COIN; + pool.blockCommissionB = idPool.v * COIN * 2; + pool.swapEvent = true; + pool.ownerAddress = shareAddress[0]; + mnview.SetPoolPair(idPool, 1, pool); + mnview.SetPoolCustomReward(idPool, 1, {TAmounts{{idPool, COIN}}}); + return true; + }); + + mnview.SetDailyReward(3, COIN); + + auto oldRewardCalculation = [](CAmount liquidity, const CPoolPair& pool) -> CAmount { + constexpr const uint32_t PRECISION = 10000; + int32_t liqWeight = liquidity * PRECISION / pool.totalLiquidity; + return COIN / 2880 * pool.rewardPct / COIN * liqWeight / PRECISION; + }; + + auto oldCommissionCalculation = [](CAmount liquidity, const CPoolPair& pool) -> std::pair { + constexpr const uint32_t PRECISION = 10000; + int32_t liqWeight = liquidity * PRECISION / pool.totalLiquidity; + auto feeA = pool.blockCommissionA * liqWeight / PRECISION; + auto feeB = pool.blockCommissionB * liqWeight / PRECISION; + return std::make_pair(feeA, feeB); + }; + + mnview.ForEachPoolPair([&] (DCT_ID const & idPool, CPoolPair pool) { + auto liquidity = mnview.GetBalance(shareAddress[0], idPool).nValue; + mnview.CalculatePoolRewards(idPool, liquidity, 1, 10, + [&](CScript const &, uint8_t type, CTokenAmount amount, uint32_t begin, uint32_t end) { + switch(type) { + case int(RewardType::Rewards): + BOOST_CHECK_EQUAL(begin, 3); + BOOST_CHECK_EQUAL(end, 10); + BOOST_CHECK_EQUAL(amount.nValue, oldRewardCalculation(liquidity, pool)); + break; + case int(RewardType::Commission): + BOOST_CHECK_EQUAL(begin, 1); + BOOST_CHECK_EQUAL(end, 2); + if (amount.nTokenId == pool.idTokenA) { + BOOST_CHECK_EQUAL(amount.nValue, oldCommissionCalculation(liquidity, pool).first); + } else { + BOOST_CHECK_EQUAL(amount.nValue, oldCommissionCalculation(liquidity, pool).second); + } + break; + } + } + ); + return true; + }); + + // new calculation + const_cast(Params().GetConsensus().BayfrontGardensHeight) = 6; + // custom rewards + const_cast(Params().GetConsensus().ClarkeQuayHeight) = 7; + + auto newRewardCalculation = [](CAmount liquidity, const CPoolPair& pool) -> CAmount { + return COIN / 2880 * pool.rewardPct / COIN * liquidity / pool.totalLiquidity; + }; + + auto newCommissionCalculation = [](CAmount liquidity, const CPoolPair& pool) -> std::pair { + auto feeA = pool.blockCommissionA * liquidity / pool.totalLiquidity; + auto feeB = pool.blockCommissionB * liquidity / pool.totalLiquidity; + return std::make_pair(feeA, feeB); + }; + + mnview.ForEachPoolPair([&] (DCT_ID const & idPool, CPoolPair pool) { + pool.swapEvent = true; + pool.ownerAddress = shareAddress[1]; + mnview.SetPoolPair(idPool, 8, pool); + return false; + }); + + mnview.ForEachPoolPair([&] (DCT_ID const & idPool, CPoolPair pool) { + auto liquidity = mnview.GetBalance(shareAddress[0], idPool).nValue; + mnview.CalculatePoolRewards(idPool, liquidity, 1, 10, + [&](CScript const & from, uint8_t type, CTokenAmount amount, uint32_t begin, uint32_t end) { + if (begin >= Params().GetConsensus().BayfrontGardensHeight) { + if (!from.empty()) { + if (begin >= 8) { + BOOST_CHECK(from == shareAddress[1]); + } else { + BOOST_CHECK(from == shareAddress[0]); + } + auto rewards = mnview.GetPoolCustomReward(idPool); + BOOST_REQUIRE(rewards); + for (const auto& reward : rewards->balances) { + auto providerReward = static_cast((arith_uint256(reward.second) * arith_uint256(liquidity) / arith_uint256(pool.totalLiquidity)).GetLow64()); + BOOST_CHECK_EQUAL(amount.nValue, providerReward); + } + } else if (type == int(RewardType::Rewards)) { + BOOST_CHECK_EQUAL(amount.nValue, newRewardCalculation(liquidity, pool)); + } else if (amount.nTokenId == pool.idTokenA) { + BOOST_CHECK_EQUAL(amount.nValue, newCommissionCalculation(liquidity, pool).first); + } else { + BOOST_CHECK_EQUAL(amount.nValue, newCommissionCalculation(liquidity, pool).second); + } + } else { + if (type == int(RewardType::Rewards)) { + BOOST_CHECK_EQUAL(amount.nValue, oldRewardCalculation(liquidity, pool)); + } else if (amount.nTokenId == pool.idTokenA) { + BOOST_CHECK_EQUAL(amount.nValue, oldCommissionCalculation(liquidity, pool).first); + } else { + BOOST_CHECK_EQUAL(amount.nValue, oldCommissionCalculation(liquidity, pool).second); + } + } + } + ); + return false; + }); +} BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/storage_tests.cpp b/src/test/storage_tests.cpp index 227a91fd07..ae9451c030 100644 --- a/src/test/storage_tests.cpp +++ b/src/test/storage_tests.cpp @@ -334,61 +334,22 @@ BOOST_AUTO_TEST_CASE(for_each_order) } } -BOOST_AUTO_TEST_CASE(RewardMigrationTests) -{ - { - CCustomCSView view(*pcustomcsview); - - // Nothing to migrate - BOOST_CHECK(!shouldMigrateOldRewardHistory(view)); - - view.Write(std::make_pair(oldRewardHistoryPrefix, oldRewardHistoryKey{}), RewardHistoryValue{}); - - // we have old prefix and key, should migrate - BOOST_CHECK(shouldMigrateOldRewardHistory(view)); - } - - { - CCustomCSView view(*pcustomcsview); - - // Nothing to migrate - BOOST_CHECK(!shouldMigrateOldRewardHistory(view)); - - view.SetAllRewardHistory(RewardHistoryKey{}, RewardHistoryValue{}); - - // we have new prefix and key, should not migrate - BOOST_CHECK(!shouldMigrateOldRewardHistory(view)); - } - - { - CCustomCSView view(*pcustomcsview); - - // Nothing to migrate - BOOST_CHECK(!shouldMigrateOldRewardHistory(view)); - - view.SetAllAccountHistory({ {}, 0, std::numeric_limits::max() }, {}); - - // we have old accounthistory, should migrate - BOOST_CHECK(shouldMigrateOldRewardHistory(view)); - } -} - BOOST_AUTO_TEST_CASE(AccountHistoryDescOrderTest) { CCustomCSView view(*pcustomcsview); - view.SetAllAccountHistory({ {}, 5021, 0 }, {}); - view.SetAllAccountHistory({ {}, 5022, 1 }, {}); - view.SetAllAccountHistory({ {}, 5023, 2 }, {}); - view.SetAllAccountHistory({ {}, 5024, 3 }, {}); - view.SetAllAccountHistory({ {}, 5025, 4 }, {}); - view.SetAllAccountHistory({ {}, 5026, 5 }, {}); + view.SetAccountHistory({ {}, 5021, 0 }, {}); + view.SetAccountHistory({ {}, 5022, 1 }, {}); + view.SetAccountHistory({ {}, 5023, 2 }, {}); + view.SetAccountHistory({ {}, 5024, 3 }, {}); + view.SetAccountHistory({ {}, 5025, 4 }, {}); + view.SetAccountHistory({ {}, 5026, 5 }, {}); uint32_t startBlock = 5021; // exclude uint32_t maxBlockHeight = 5025; auto blockCount = maxBlockHeight; - view.ForEachAllAccountHistory([&](AccountHistoryKey const & key, AccountHistoryValue) { + view.ForEachAccountHistory([&](AccountHistoryKey const & key, AccountHistoryValue) { if (startBlock > key.blockHeight || key.blockHeight > maxBlockHeight) { return true; } diff --git a/src/validation.cpp b/src/validation.cpp index 27bc7da0f9..38bfae2a6a 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2359,7 +2359,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl { // old data pruning and other (some processing made for the whole block) // make all changes to the new cache/snapshot to make it possible to take a diff later: - CRewardsHistoryStorage cache(mnview, static_cast(pindex->nHeight)); + CCustomCSView cache(mnview); // cache.CallYourInterblockProcessingsHere(); @@ -2383,7 +2383,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl return res; // no funds, no rewards } } - auto res = cache.AddBalance(to, poolID, type, amount); + auto res = cache.AddBalance(to, amount); if (!res.ok) { LogPrintf("Pool rewards: can't update balance of %s: %s, Block %ld (%s)\n", to.GetHex(), res.msg, block.height, block.GetHash().ToString()); } From e9cf3d2d473cb9a9340db065419779b4c23f59ed Mon Sep 17 00:00:00 2001 From: Anthony Fieroni Date: Wed, 10 Mar 2021 16:49:38 +0200 Subject: [PATCH 04/17] Do not redistribute rewards per block Signed-off-by: Anthony Fieroni --- src/masternodes/poolpairs.h | 102 +++++++---------------------------- src/test/liquidity_tests.cpp | 23 ++------ src/validation.cpp | 40 ++------------ 3 files changed, 29 insertions(+), 136 deletions(-) diff --git a/src/masternodes/poolpairs.h b/src/masternodes/poolpairs.h index a6705a3833..c98750a7bd 100644 --- a/src/masternodes/poolpairs.h +++ b/src/masternodes/poolpairs.h @@ -302,112 +302,50 @@ class CPoolPairView : public virtual CStorageView return Res::Ok(); } - CAmount DistributeRewards(CAmount yieldFarming, std::function onGetBalance, std::function onTransfer, int nHeight = 0) { + void UpdatePoolCommissions(std::function onGetBalance, int nHeight = 0) { bool newRewardCalc = nHeight >= Params().GetConsensus().BayfrontGardensHeight; - uint32_t const PRECISION = 10000; // (== 100%) just searching the way to avoid arith256 inflating - CAmount totalDistributed = 0; + constexpr uint32_t const PRECISION = 10000; // (== 100%) just searching the way to avoid arith256 inflating ForEachPoolPair([&] (DCT_ID const & poolId, CPoolPair pool) { - // yield farming counters - CAmount const poolReward = yieldFarming * pool.rewardPct / COIN; // 'rewardPct' should be defined by 'setgov "LP_SPLITS"', also, it is assumed that it was totally validated and normalized to 100% - CAmount distributedFeeA = 0; - CAmount distributedFeeB = 0; - - auto rewards = GetPoolCustomReward(poolId); - bool payCustomRewards{false}; - - if (nHeight >= Params().GetConsensus().ClarkeQuayHeight && rewards && !rewards->balances.empty()) { - for (auto it = rewards->balances.cbegin(), next_it = it; it != rewards->balances.cend(); it = next_it) { - ++next_it; - - // Get token balance - const auto balance = onGetBalance(pool.ownerAddress, it->first).nValue; - - // Make there's enough to pay reward otherwise remove it - if (balance < it->second) { - rewards->balances.erase(it); - } - } - - if (!rewards->balances.empty()) { - payCustomRewards = true; - } - } - - if (pool.totalLiquidity == 0 || (!pool.swapEvent && poolReward == 0 && !payCustomRewards)) { + if (!pool.swapEvent) { return true; // no events, skip to the next pool } + CAmount distributedFeeA = 0; + CAmount distributedFeeB = 0; + ForEachPoolShare([&] (DCT_ID const & currentId, CScript const & provider, uint32_t height) { if (currentId != poolId) { return false; // stop } CAmount const liquidity = onGetBalance(provider, poolId).nValue; - uint32_t const liqWeight = liquidity * PRECISION / pool.totalLiquidity; - assert (liqWeight < PRECISION); - // distribute trading fees - if (pool.swapEvent) { - CAmount feeA, feeB; - if (newRewardCalc) { - feeA = static_cast((arith_uint256(pool.blockCommissionA) * arith_uint256(liquidity) / arith_uint256(pool.totalLiquidity)).GetLow64()); - feeB = static_cast((arith_uint256(pool.blockCommissionB) * arith_uint256(liquidity) / arith_uint256(pool.totalLiquidity)).GetLow64()); - } else { - feeA = pool.blockCommissionA * liqWeight / PRECISION; - feeB = pool.blockCommissionB * liqWeight / PRECISION; - } - if (onTransfer(provider, CScript(), poolId, uint8_t(RewardType::Commission), {pool.idTokenA, feeA})) { - distributedFeeA += feeA; - } - if (onTransfer(provider, CScript(), poolId, uint8_t(RewardType::Commission), {pool.idTokenB, feeB})) { - distributedFeeB += feeB; - } - } - - // distribute yield farming - if (poolReward) { - CAmount providerReward = static_cast((arith_uint256(poolReward) * arith_uint256(liquidity) / arith_uint256(pool.totalLiquidity)).GetLow64()); - if (!newRewardCalc) { - providerReward = poolReward * liqWeight / PRECISION; - } - if (providerReward) { - if (onTransfer(provider, CScript(), poolId, uint8_t(RewardType::Rewards), {DCT_ID{0}, providerReward})) { - totalDistributed += providerReward; - } - } + if (newRewardCalc) { + distributedFeeA += static_cast((arith_uint256(pool.blockCommissionA) * arith_uint256(liquidity) / arith_uint256(pool.totalLiquidity)).GetLow64()); + distributedFeeB += static_cast((arith_uint256(pool.blockCommissionB) * arith_uint256(liquidity) / arith_uint256(pool.totalLiquidity)).GetLow64()); + } else { + uint32_t const liqWeight = liquidity * PRECISION / pool.totalLiquidity; + assert (liqWeight < PRECISION); + distributedFeeA += pool.blockCommissionA * liqWeight / PRECISION; + distributedFeeB += pool.blockCommissionB * liqWeight / PRECISION; } - - // Distribute custom rewards - if (payCustomRewards) { - for (const auto& reward : rewards->balances) { - CAmount providerReward = static_cast((arith_uint256(reward.second) * arith_uint256(liquidity) / arith_uint256(pool.totalLiquidity)).GetLow64()); - - if (providerReward) { - onTransfer(provider, pool.ownerAddress, poolId, uint8_t(RewardType::Rewards), {reward.first, providerReward}); - } - } - } - return true; }, PoolShareKey{poolId, CScript{}}); - if (pool.swapEvent) { - pool.blockCommissionA -= distributedFeeA; - pool.blockCommissionB -= distributedFeeB; - pool.swapEvent = false; + pool.blockCommissionA -= distributedFeeA; + pool.blockCommissionB -= distributedFeeB; + pool.swapEvent = false; - auto res = SetPoolPair(poolId, UINT_MAX, pool); - if (!res.ok) { - LogPrintf("Pool rewards: can't update pool (id=%s) state: %s\n", poolId.ToString(), res.msg); - } + auto res = SetPoolPair(poolId, UINT_MAX, pool); + if (!res.ok) { + LogPrintf("Pool rewards: can't update pool (id=%s) state: %s\n", poolId.ToString(), res.msg); } return true; }); - return totalDistributed; } // tags diff --git a/src/test/liquidity_tests.cpp b/src/test/liquidity_tests.cpp index 3037fccba6..b30a646eb6 100644 --- a/src/test/liquidity_tests.cpp +++ b/src/test/liquidity_tests.cpp @@ -309,30 +309,17 @@ BOOST_AUTO_TEST_CASE(math_rewards) }); // distribute 100 coins - CAmount totalRwd = 100*COIN; - -// int64_t nTimeBegin = GetTimeMicros(); - CAmount distributed = cache.DistributeRewards(totalRwd, - [&cache] (CScript const & owner, DCT_ID tokenID) { - return cache.GetBalance(owner, tokenID); - }, - [&cache] (CScript const & to, CScript const & from, DCT_ID poolID, uint8_t type, CTokenAmount amount) { - return cache.AddBalance(to, amount); - } - ); -// int64_t nTimeEnd = GetTimeMicros(); auto nTimeRwd = nTimeEnd - nTimeBegin; -// printf("Rewarded %d pools with %d shares each: %.2fms \n", PoolCount, ProvidersCount, 0.001 * (nTimeRwd)); - -// printf("Distributed: = %ld\n", distributed); - BOOST_CHECK(distributed == 9999000000); // always slightly less due to MINIMUM_LIQUIDITY & rounding + CAmount totalRwd = 100*COIN*2880; + cache.SetDailyReward(1, totalRwd); + // fund community + cache.AddCommunityBalance(CommunityAccountType::IncentiveFunding, totalRwd); // check it auto rwd25 = 25 * COIN / ProvidersCount; auto rwd50 = 50 * COIN / ProvidersCount; cache.ForEachPoolShare([&] (DCT_ID const & id, CScript const & owner, uint32_t) { -// if (id == RWD25/* || id == RWD50*/) -// printf("owner = %s: %s,\n", owner.GetHex().c_str(), cache.GetBalance(owner, DCT_ID{0}).ToString().c_str()); + cache.CalculateOwnerRewards(owner, 2); // one block // check only first couple of pools and the last (zero) if (id == RWD25 && owner != CScript(id.v * ProvidersCount)) { // first got slightly less due to MINIMUM_LIQUIDITY CAmount rwd = cache.GetBalance(owner, DCT_ID{0}).nValue; diff --git a/src/validation.cpp b/src/validation.cpp index 38bfae2a6a..fb4ea957fe 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2361,42 +2361,10 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl // make all changes to the new cache/snapshot to make it possible to take a diff later: CCustomCSView cache(mnview); -// cache.CallYourInterblockProcessingsHere(); - - // distribute pool incentive rewards and trading fees: - { - std::shared_ptr var = std::dynamic_pointer_cast(cache.GetVariable(LP_DAILY_DFI_REWARD::TypeName())); - CAmount poolsBlockReward = std::min( - cache.GetCommunityBalance(CommunityAccountType::IncentiveFunding), - var->dailyReward / (60*60*24/chainparams.GetConsensus().pos.nTargetSpacing) // 2880 - ); - - CAmount distributed = cache.DistributeRewards(poolsBlockReward, - [&cache] (CScript const & owner, DCT_ID tokenID) { - return cache.GetBalance(owner, tokenID); - }, - [&cache, &block] (CScript const & to, CScript const & from, DCT_ID poolID, uint8_t type, CTokenAmount amount) { - if (from != CScript()) { - auto res = cache.SubBalance(from, amount); - if (!res.ok) { - LogPrintf("Custom pool rewards: can't update balance of %s: %s, Block %ld (%s)\n", to.GetHex(), res.msg, block.height, block.GetHash().ToString()); - return res; // no funds, no rewards - } - } - auto res = cache.AddBalance(to, amount); - if (!res.ok) { - LogPrintf("Pool rewards: can't update balance of %s: %s, Block %ld (%s)\n", to.GetHex(), res.msg, block.height, block.GetHash().ToString()); - } - return res; - }, - pindex->nHeight - ); - - auto res = cache.SubCommunityBalance(CommunityAccountType::IncentiveFunding, distributed); - if (!res.ok) { - LogPrintf("Pool rewards: can't update community balance: %s. Block %ld (%s)\n", res.msg, block.height, block.GetHash().ToString()); - } - } + // hardfork commissions update + cache.UpdatePoolCommissions([&cache](CScript const & owner, DCT_ID tokenID) { + return cache.GetBalance(owner, tokenID); + }, pindex->nHeight); // Remove `Finalized` and/or `LPS` flags _possibly_set_ by bytecoded (cheated) txs before bayfront fork if (pindex->nHeight == chainparams.GetConsensus().BayfrontHeight - 1) { // call at block _before_ fork cache.BayfrontFlagsCleanup(); From 6b93f7364b0ee42da07b4b3aa166e59b6d65aa6e Mon Sep 17 00:00:00 2001 From: Anthony Fieroni Date: Wed, 10 Mar 2021 17:20:08 +0200 Subject: [PATCH 05/17] Fix lint circular dependency Signed-off-by: Anthony Fieroni --- test/lint/lint-circular-dependencies.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/lint/lint-circular-dependencies.sh b/test/lint/lint-circular-dependencies.sh index 90c0639afb..057020b8ec 100755 --- a/test/lint/lint-circular-dependencies.sh +++ b/test/lint/lint-circular-dependencies.sh @@ -31,8 +31,8 @@ EXPECTED_CIRCULAR_DEPENDENCIES=( "masternodes/criminals -> masternodes/masternodes -> masternodes/criminals" "masternodes/criminals -> masternodes/masternodes -> validation -> masternodes/criminals" "masternodes/govvariables/lp_daily_dfi_reward -> masternodes/gv -> masternodes/govvariables/lp_daily_dfi_reward" + "masternodes/govvariables/lp_daily_dfi_reward -> masternodes/masternodes -> validation -> masternodes/govvariables/lp_daily_dfi_reward" "masternodes/govvariables/lp_splits -> masternodes/gv -> masternodes/govvariables/lp_splits" - "masternodes/govvariables/lp_daily_dfi_reward -> rpc/util -> node/transaction -> validation -> masternodes/govvariables/lp_daily_dfi_reward" "masternodes/masternodes -> masternodes/mn_checks -> masternodes/masternodes" "masternodes/masternodes -> validation -> pos -> masternodes/masternodes" "masternodes/masternodes -> validation -> masternodes/masternodes" From 2329de8372cb92639a65ecd07cde7188b7d5e0e6 Mon Sep 17 00:00:00 2001 From: Anthony Fieroni Date: Thu, 11 Mar 2021 09:37:12 +0200 Subject: [PATCH 06/17] Introduce db version check at runtime Signed-off-by: Anthony Fieroni --- src/Makefile.am | 1 - src/flushablestorage.h | 3 +++ src/init.cpp | 9 ++++--- src/masternodes/accountshistory.cpp | 25 ------------------- src/masternodes/accountshistory.h | 3 --- src/masternodes/masternodes.cpp | 14 +++++++++++ src/masternodes/masternodes.h | 7 ++++++ src/masternodes/rewardhistoryold.h | 38 ----------------------------- src/test/storage_tests.cpp | 1 - 9 files changed, 30 insertions(+), 71 deletions(-) delete mode 100644 src/masternodes/rewardhistoryold.h diff --git a/src/Makefile.am b/src/Makefile.am index b926fa8c9e..492097361b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -166,7 +166,6 @@ DEFI_CORE_H = \ masternodes/mn_checks.h \ masternodes/mn_rpc.h \ masternodes/res.h \ - masternodes/rewardhistoryold.h \ masternodes/tokens.h \ masternodes/poolpairs.h \ masternodes/undo.h \ diff --git a/src/flushablestorage.h b/src/flushablestorage.h index 1d18f1bb50..7edb32255f 100644 --- a/src/flushablestorage.h +++ b/src/flushablestorage.h @@ -146,6 +146,9 @@ class CStorageLevelDB : public CStorageKV { std::unique_ptr NewIterator() override { return MakeUnique(std::unique_ptr(db.NewIterator())); } + bool IsEmpty() { + return db.IsEmpty(); + } private: CDBWrapper db; diff --git a/src/init.cpp b/src/init.cpp index 442e213951..8383fca3a8 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1589,13 +1589,16 @@ bool AppInitMain(InitInterfaces& interfaces) pcustomcsDB = MakeUnique(GetDataDir() / "enhancedcs", nCustomCacheSize, false, fReset || fReindexChainState); pcustomcsview.reset(); pcustomcsview = MakeUnique(*pcustomcsDB.get()); - if (!fReset && gArgs.GetBoolArg("-acindex", DEFAULT_ACINDEX)) { - if (shouldMigrateOldRewardHistory(*pcustomcsview)) { - strLoadError = _("Account history needs rebuild").translated; + if (!fReset && !fReindexChainState) { + if (!pcustomcsDB->IsEmpty() && pcustomcsview->GetDbVersion() != CCustomCSView::DbVersion) { + strLoadError = _("Account database is unsuitable").translated; break; } } + // Ensure we are on latest DB version + pcustomcsview->SetDbVersion(CCustomCSView::DbVersion); + // If necessary, upgrade from older database format. // This is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate if (!::ChainstateActive().CoinsDB().Upgrade()) { diff --git a/src/masternodes/accountshistory.cpp b/src/masternodes/accountshistory.cpp index 59a8de23c1..3880f2e44c 100644 --- a/src/masternodes/accountshistory.cpp +++ b/src/masternodes/accountshistory.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include @@ -23,27 +22,3 @@ Res CAccountsHistoryView::SetAccountHistory(const AccountHistoryKey& key, const WriteBy(key, value); return Res::Ok(); } - -bool shouldMigrateOldRewardHistory(CCustomCSView & view) -{ - auto it = view.GetRaw().NewIterator(); - try { - auto prefix = oldRewardHistoryPrefix; - auto oldKey = std::make_pair(prefix, oldRewardHistoryKey{}); - it->Seek(DbTypeToBytes(oldKey)); - if (it->Valid() && BytesToDbType(it->Key(), oldKey) && oldKey.first == prefix) { - return true; - } - bool hasOldAccountHistory = false; - view.ForEachAccountHistory([&](AccountHistoryKey const & key, CLazySerialize) { - if (key.txn == std::numeric_limits::max()) { - hasOldAccountHistory = true; - return false; - } - return true; - }, { {}, 0, std::numeric_limits::max() }); - return hasOldAccountHistory; - } catch(...) { - return true; - } -} diff --git a/src/masternodes/accountshistory.h b/src/masternodes/accountshistory.h index ab40475da3..13005cb7c4 100644 --- a/src/masternodes/accountshistory.h +++ b/src/masternodes/accountshistory.h @@ -64,7 +64,4 @@ class CAccountsHistoryView : public virtual CStorageView static constexpr bool DEFAULT_ACINDEX = true; -class CCustomCSView; -bool shouldMigrateOldRewardHistory(CCustomCSView & view); - #endif //DEFI_MASTERNODES_ACCOUNTSHISTORY_H diff --git a/src/masternodes/masternodes.cpp b/src/masternodes/masternodes.cpp index 931c73535d..0dfdd19f36 100644 --- a/src/masternodes/masternodes.cpp +++ b/src/masternodes/masternodes.cpp @@ -27,6 +27,7 @@ const unsigned char DB_MN_OPERATORS = 'o'; // masternodes' operators index const unsigned char DB_MN_OWNERS = 'w'; // masternodes' owners index const unsigned char DB_MN_STAKER = 'X'; // masternodes' last staked block time const unsigned char DB_MN_HEIGHT = 'H'; // single record with last processed chain height +const unsigned char DB_MN_VERSION = 'D'; const unsigned char DB_MN_ANCHOR_REWARD = 'r'; const unsigned char DB_MN_ANCHOR_CONFIRM = 'x'; const unsigned char DB_MN_CURRENT_TEAM = 't'; @@ -587,6 +588,19 @@ std::vector CAnchorConfirmsView::GetAnchorConfirmData() /* * CCustomCSView */ +int CCustomCSView::GetDbVersion() const +{ + int version; + if (Read(DB_MN_VERSION, version)) + return version; + return 0; +} + +void CCustomCSView::SetDbVersion(int version) +{ + Write(DB_MN_VERSION, version); +} + CTeamView::CTeam CCustomCSView::CalcNextTeam(const uint256 & stakeModifier) { if (stakeModifier == uint256()) diff --git a/src/masternodes/masternodes.h b/src/masternodes/masternodes.h index b3b30d862b..62f63d6ce6 100644 --- a/src/masternodes/masternodes.h +++ b/src/masternodes/masternodes.h @@ -248,6 +248,9 @@ class CCustomCSView , public CAnchorConfirmsView { public: + // Increase version when underlaying tables are changed + static constexpr const int DbVersion = 1; + CCustomCSView() = default; CCustomCSView(CStorageKV & st) @@ -274,6 +277,10 @@ class CCustomCSView bool CalculateOwnerRewards(CScript const & owner, uint32_t height); + void SetDbVersion(int version); + + int GetDbVersion() const; + CStorageKV& GetRaw() { return DB(); } diff --git a/src/masternodes/rewardhistoryold.h b/src/masternodes/rewardhistoryold.h deleted file mode 100644 index 42dfe5461c..0000000000 --- a/src/masternodes/rewardhistoryold.h +++ /dev/null @@ -1,38 +0,0 @@ -// 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_REWARDHISTORYOLD_H -#define DEFI_MASTERNODES_REWARDHISTORYOLD_H - -#include -#include -#include