From 0789fc8cc96993a1d1e737adb1b04954dc08927e Mon Sep 17 00:00:00 2001 From: blondfrogs Date: Thu, 16 Aug 2018 19:05:23 -0600 Subject: [PATCH 001/105] Fix database, fix unit tests --- src/assets/assets.cpp | 45 ++++++++++++++++++------------ src/consensus/tx_verify.cpp | 22 +++++++++------ src/consensus/tx_verify.h | 2 +- src/test/assets/asset_tx_tests.cpp | 13 ++++----- 4 files changed, 47 insertions(+), 35 deletions(-) diff --git a/src/assets/assets.cpp b/src/assets/assets.cpp index 44f4ee51b3..202e80611d 100644 --- a/src/assets/assets.cpp +++ b/src/assets/assets.cpp @@ -1226,23 +1226,6 @@ bool CAssetsCache::Flush(bool fSoftCopy, bool fFlushDB) bool dirty = false; std::string message; - // Save the assets that have been spent by erasing the quantity in the database - for (auto spentAsset : vSpentAssets) { - auto pair = make_pair(spentAsset.assetName, spentAsset.address); - if (mapAssetsAddressAmount.count(pair)) { - if (mapAssetsAddressAmount.at(make_pair(spentAsset.assetName, spentAsset.address)) == 0) { - if (!passetsdb->EraseAssetAddressQuantity(spentAsset.assetName, spentAsset.address)) { - dirty = true; - message = "_Failed Erasing a Spent Asset, from database"; - } - - if (dirty) { - return error("%s : %s", __func__, message); - } - } - } - } - // Remove new assets from the database for (auto newAsset : setNewAssetsToRemove) { passetsCache->Erase(newAsset.asset.strName); @@ -1379,7 +1362,7 @@ bool CAssetsCache::Flush(bool fSoftCopy, bool fFlushDB) if (mapAssetsAddressAmount.count(pair)) { if (!passetsdb->WriteAssetAddressQuantity(pair.first, pair.second, - mapAssetsAddressAmount[pair])) { + mapAssetsAddressAmount.at(pair))) { dirty = true; message = "_Failed Writing reissue asset quantity to the address quantity database"; } @@ -1452,6 +1435,32 @@ bool CAssetsCache::Flush(bool fSoftCopy, bool fFlushDB) } } + // Save the assets that have been spent by erasing the quantity in the database + for (auto spentAsset : vSpentAssets) { + auto pair = make_pair(spentAsset.assetName, spentAsset.address); + if (mapAssetsAddressAmount.count(pair)) { + if (mapAssetsAddressAmount.at(make_pair(spentAsset.assetName, spentAsset.address)) == 0) { + if (!passetsdb->EraseAssetAddressQuantity(spentAsset.assetName, spentAsset.address)) { + dirty = true; + message = "_Failed Erasing a Spent Asset, from database"; + } + + if (dirty) { + return error("%s : %s", __func__, message); + } + } else { + if (!passetsdb->WriteAssetAddressQuantity(spentAsset.assetName, spentAsset.address, mapAssetsAddressAmount.at(pair))) { + dirty = true; + message = "_Failed Erasing a Spent Asset, from database"; + } + + if (dirty) { + return error("%s : %s", __func__, message); + } + } + } + } + ClearDirtyCache(); } diff --git a/src/consensus/tx_verify.cpp b/src/consensus/tx_verify.cpp index b01dc1c31e..9a7cddd390 100644 --- a/src/consensus/tx_verify.cpp +++ b/src/consensus/tx_verify.cpp @@ -323,7 +323,7 @@ bool Consensus::CheckTxInputs(const CTransaction& tx, CValidationState& state, c } //! Check to make sure that the inputs and outputs CAmount match exactly. -bool Consensus::CheckTxAssets(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs) +bool Consensus::CheckTxAssets(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, const bool fRunningUnitTests) { // are the actual inputs available? if (!inputs.HaveInputs(tx)) { @@ -370,10 +370,11 @@ bool Consensus::CheckTxAssets(const CTransaction& tx, CValidationState& state, c else totalOutputs.insert(make_pair(transfer.strName, transfer.nAmount)); - if (IsAssetNameAnOwner(transfer.strName)) { - if (transfer.nAmount != OWNER_ASSET_AMOUNT) - return state.DoS(100, false, REJECT_INVALID, "bad-txns-transfer-owner-amount-was-not-1"); - } else { + if (!fRunningUnitTests) { + if (IsAssetNameAnOwner(transfer.strName)) { + if (transfer.nAmount != OWNER_ASSET_AMOUNT) + return state.DoS(100, false, REJECT_INVALID, "bad-txns-transfer-owner-amount-was-not-1"); + } else { // For all other types of assets, make sure they are sending the right type of units CNewAsset asset; if (!passets->GetAssetIfExists(transfer.strName, asset)) @@ -385,6 +386,7 @@ bool Consensus::CheckTxAssets(const CTransaction& tx, CValidationState& state, c if (transfer.nAmount % int64_t(pow(10, (MAX_UNIT - asset.units))) != 0) return state.DoS(100, false, REJECT_INVALID, "bad-txns-transfer-asset-amount-not-match-units"); + } } } else if (txout.scriptPubKey.IsReissueAsset()) { CReissueAsset reissue; @@ -392,10 +394,12 @@ bool Consensus::CheckTxAssets(const CTransaction& tx, CValidationState& state, c if (!ReissueAssetFromScript(txout.scriptPubKey, reissue, address)) return state.DoS(100, false, REJECT_INVALID, "bad-tx-asset-reissue-bad-deserialize"); - std::string strError; - if (!reissue.IsValid(strError, *passets)) { - return state.DoS(100, false, REJECT_INVALID, - "bad-txns" + strError); + if (!fRunningUnitTests) { + std::string strError; + if (!reissue.IsValid(strError, *passets)) { + return state.DoS(100, false, REJECT_INVALID, + "bad-txns" + strError); + } } } diff --git a/src/consensus/tx_verify.h b/src/consensus/tx_verify.h index c80a9c78f2..2430525666 100644 --- a/src/consensus/tx_verify.h +++ b/src/consensus/tx_verify.h @@ -32,7 +32,7 @@ namespace Consensus { bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee); /** RVN START */ -bool CheckTxAssets(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs); +bool CheckTxAssets(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, const bool fRunningUnitTests = false); /** RVN END */ } // namespace Consensus diff --git a/src/test/assets/asset_tx_tests.cpp b/src/test/assets/asset_tx_tests.cpp index bc462ca54a..ee9c18f051 100644 --- a/src/test/assets/asset_tx_tests.cpp +++ b/src/test/assets/asset_tx_tests.cpp @@ -16,7 +16,6 @@ BOOST_FIXTURE_TEST_SUITE(asset_tx_tests, BasicTestingSetup) - BOOST_AUTO_TEST_CASE(asset_tx_valid) { SelectParams(CBaseChainParams::MAIN); @@ -57,7 +56,7 @@ BOOST_FIXTURE_TEST_SUITE(asset_tx_tests, BasicTestingSetup) // The inputs are spending 1000 Assets // The outputs are assigning a destination to 1000 Assets // This test should pass because all assets are assigned a destination - BOOST_CHECK_MESSAGE(Consensus::CheckTxAssets(tx, state, coins), "CheckTxAssets Failed"); + BOOST_CHECK_MESSAGE(Consensus::CheckTxAssets(tx, state, coins, true), "CheckTxAssets Failed"); } BOOST_AUTO_TEST_CASE(asset_tx_not_valid) { @@ -110,7 +109,7 @@ BOOST_FIXTURE_TEST_SUITE(asset_tx_tests, BasicTestingSetup) // The inputs of this transaction are spending 1000 Assets // The outputs are assigning a destination to only 100 Assets // This should fail because 900 Assets aren't being assigned a destination (Trying to burn 900 Assets) - BOOST_CHECK_MESSAGE(!Consensus::CheckTxAssets(tx, state, coins), "CheckTxAssets should of failed"); + BOOST_CHECK_MESSAGE(!Consensus::CheckTxAssets(tx, state, coins, true), "CheckTxAssets should of failed"); } BOOST_AUTO_TEST_CASE(asset_tx_valid_multiple_outs) { @@ -166,7 +165,7 @@ BOOST_FIXTURE_TEST_SUITE(asset_tx_tests, BasicTestingSetup) // The inputs are spending 1000 Assets // The outputs are assigned 100 Assets to 10 destinations (10 * 100) = 1000 // This test should pass all assets that are being spent are assigned to a destination - BOOST_CHECK_MESSAGE(Consensus::CheckTxAssets(tx, state, coins), "CheckTxAssets failed"); + BOOST_CHECK_MESSAGE(Consensus::CheckTxAssets(tx, state, coins, true), "CheckTxAssets failed"); } BOOST_AUTO_TEST_CASE(asset_tx_multiple_outs_invalid) { @@ -222,7 +221,7 @@ BOOST_FIXTURE_TEST_SUITE(asset_tx_tests, BasicTestingSetup) // The inputs are spending 1000 Assets // The outputs are assigning 100 Assets to 12 destinations (12 * 100 = 1200) // This test should fail because the Outputs are greater than the inputs - BOOST_CHECK_MESSAGE(!Consensus::CheckTxAssets(tx, state, coins), "CheckTxAssets passed when it should of failed"); + BOOST_CHECK_MESSAGE(!Consensus::CheckTxAssets(tx, state, coins, true), "CheckTxAssets passed when it should of failed"); } BOOST_AUTO_TEST_CASE(asset_tx_multiple_assets) { @@ -337,7 +336,7 @@ BOOST_FIXTURE_TEST_SUITE(asset_tx_tests, BasicTestingSetup) // The inputs are spending 3000 Assets (1000 of each RAVEN, RAVENTEST, RAVENTESTTEST) // The outputs are spending 100 Assets to 10 destinations (10 * 100 = 1000) (of each RAVEN, RAVENTEST, RAVENTESTTEST) // This test should pass because for each asset that is spent. It is assigned a destination - BOOST_CHECK_MESSAGE(Consensus::CheckTxAssets(tx, state, coins), "CheckTxAssets Failed"); + BOOST_CHECK_MESSAGE(Consensus::CheckTxAssets(tx, state, coins, true), "CheckTxAssets Failed"); // Try it not but only spend 900 of each asset instead of 1000 @@ -389,7 +388,7 @@ BOOST_FIXTURE_TEST_SUITE(asset_tx_tests, BasicTestingSetup) // Check the transaction that contains inputs that are spending 1000 Assets for 3 different assets // While only outputs only contain 900 Assets being sent to a destination // This should fail because 100 of each Asset isn't being sent to a destination (Trying to burn 100 Assets each) - BOOST_CHECK_MESSAGE(!Consensus::CheckTxAssets(tx2, state, coins), "CheckTxAssets should of failed"); + BOOST_CHECK_MESSAGE(!Consensus::CheckTxAssets(tx2, state, coins, true), "CheckTxAssets should of failed"); } BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file From 9843103a0ad0d38e47ca373273770b24797749eb Mon Sep 17 00:00:00 2001 From: blondfrogs Date: Mon, 20 Aug 2018 15:51:42 -0600 Subject: [PATCH 002/105] Allow Asset Tx Chaining (#237) * Add ability to chain asset transaction just like you can chain RVN transactions --- src/assets/assets.cpp | 169 +++++---- src/assets/assets.h | 7 +- src/chainparams.cpp | 2 +- src/consensus/tx_verify.cpp | 45 ++- src/primitives/transaction.h | 4 + src/qt/assetsdialog.cpp | 138 -------- src/test/assets/asset_tests.cpp | 3 + src/validation.cpp | 6 + src/wallet/wallet.cpp | 610 ++++++++++++++++++++------------ src/wallet/wallet.h | 33 +- test/functional/assets.py | 42 +++ 11 files changed, 605 insertions(+), 454 deletions(-) diff --git a/src/assets/assets.cpp b/src/assets/assets.cpp index 202e80611d..806e128e6b 100644 --- a/src/assets/assets.cpp +++ b/src/assets/assets.cpp @@ -26,6 +26,7 @@ #include "protocol.h" #include "wallet/coincontrol.h" #include "utilmoneystr.h" +#include "coins.h" // excluding owner tag ('!') static const auto MAX_NAME_LENGTH = 30; @@ -509,6 +510,16 @@ bool ReissueAssetFromScript(const CScript& scriptPubKey, CReissueAsset& reissue, } bool CTransaction::IsNewAsset() const +{ + // Check for the assets data CTxOut. This will always be the last output in the transaction + if (!CheckIssueDataTx(vout[vout.size() - 1])) + return false; + + return true; +} + +//! To be called on CTransactions where IsNewAsset returns true +bool CTransaction::VerifyNewAsset() const { // Issuing an Asset must contain at least 3 CTxOut( Raven Burn Tx, Any Number of other Outputs ..., Owner Asset Change Tx, Reissue Tx) if (vout.size() < 3) @@ -531,6 +542,13 @@ bool CTransaction::IsNewAsset() const AssetType assetType; IsAssetNameValid(asset.strName, assetType); + std::string strOwnerName; + if (!OwnerAssetFromScript(vout[vout.size() - 2].scriptPubKey, strOwnerName, address)) + return false; + + if (strOwnerName != asset.strName + OWNER_TAG) + return false; + // Check for the Burn CTxOut in one of the vouts ( This is needed because the change CTxOut is places in a random position in the CWalletTx for (auto out : vout) if (CheckIssueBurnTx(out, assetType)) @@ -540,6 +558,16 @@ bool CTransaction::IsNewAsset() const } bool CTransaction::IsReissueAsset() const +{ + // Check for the reissue asset data CTxOut. This will always be the last output in the transaction + if (!CheckReissueDataTx(vout[vout.size() - 1])) + return false; + + return true; +} + +//! To be called on CTransactions where IsReissueAsset returns true +bool CTransaction::VerifyReissueAsset(CCoinsViewCache& view) const { // Reissuing an Asset must contain at least 3 CTxOut ( Raven Burn Tx, Any Number of other Outputs ..., Reissue Asset Tx, Owner Asset Change Tx) if (vout.size() < 3) @@ -550,14 +578,45 @@ bool CTransaction::IsReissueAsset() const return false; // Check that there is an asset transfer, this will be the owner asset change - bool ownerFound = false; - for (auto out : vout) + bool fOwnerOutFound = false; + for (auto out : vout) { if (CheckTransferOwnerTx(out)) { - ownerFound = true; + fOwnerOutFound = true; break; } + } + + if (!fOwnerOutFound) + return false; + + CReissueAsset reissue; + std::string address; + if (!ReissueAssetFromScript(vout[vout.size() - 1].scriptPubKey, reissue, address)) + return false; + + bool fFoundCorrectInput = false; + for (unsigned int i = 0; i < vin.size(); ++i) { + const COutPoint &prevout = vin[i].prevout; + const Coin& coin = view.AccessCoin(prevout); + assert(!coin.IsSpent()); + + int nType = -1; + bool fOwner = false; + if (coin.out.scriptPubKey.IsAssetScript(nType, fOwner)) { + std::string strAssetName; + CAmount nAssetAmount; + if (!GetAssetInfoFromCoin(coin, strAssetName, nAssetAmount)) + continue; + if (IsAssetNameAnOwner(strAssetName)) { + if (strAssetName == reissue.strName + OWNER_TAG) { + fFoundCorrectInput = true; + break; + } + } + } + } - if (!ownerFound) + if (!fFoundCorrectInput) return false; // Check for the Burn CTxOut in one of the vouts ( This is needed because the change CTxOut is placed in a random position in the CWalletTx @@ -776,10 +835,12 @@ bool CAssetsCache::TrySpendCoin(const COutPoint& out, const CTxOut& txOut) if (address != "" && assetName != "" && nAmount > 0) { CAssetCacheSpendAsset spend(assetName, address, nAmount); if (GetBestAssetAddressAmount(*this, assetName, address)) { - assert(mapAssetsAddressAmount[make_pair(assetName, address)] >= nAmount); - mapAssetsAddressAmount[make_pair(assetName, address)] -= nAmount; + auto pair = make_pair(assetName, address); + mapAssetsAddressAmount.at(pair) -= nAmount; - if (mapAssetsAddressAmount[make_pair(assetName, address)] == 0 && + if (mapAssetsAddressAmount.at(pair) < 0) + mapAssetsAddressAmount.at(pair) = 0; + if (mapAssetsAddressAmount.at(pair) == 0 && mapAssetsAddresses.count(assetName)) mapAssetsAddresses.at(assetName).erase(address); @@ -1785,11 +1846,11 @@ bool CAssetsCache::GetAssetIfExists(const std::string& name, CNewAsset& asset) return false; } -bool GetAssetFromCoin(const Coin& coin, std::string& strName, CAmount& nAmount) -{ +bool GetAssetInfoFromScript(const CScript& scriptPubKey, std::string& strName, CAmount& nAmount) { + int nType = 0; bool fIsOwner = false; - if (!coin.out.scriptPubKey.IsAssetScript(nType, fIsOwner)) { + if (!scriptPubKey.IsAssetScript(nType, fIsOwner)) { return false; } @@ -1799,7 +1860,7 @@ bool GetAssetFromCoin(const Coin& coin, std::string& strName, CAmount& nAmount) if (type == TX_NEW_ASSET && !fIsOwner) { CNewAsset asset; std::string address; - if (!AssetFromScript(coin.out.scriptPubKey, asset, address)) + if (!AssetFromScript(scriptPubKey, asset, address)) return false; strName = asset.strName; nAmount = asset.nAmount; @@ -1807,7 +1868,7 @@ bool GetAssetFromCoin(const Coin& coin, std::string& strName, CAmount& nAmount) } else if (type == TX_TRANSFER_ASSET) { CAssetTransfer asset; std::string address; - if (!TransferAssetFromScript(coin.out.scriptPubKey, asset, address)) + if (!TransferAssetFromScript(scriptPubKey, asset, address)) return false; strName = asset.strName; nAmount = asset.nAmount; @@ -1815,7 +1876,7 @@ bool GetAssetFromCoin(const Coin& coin, std::string& strName, CAmount& nAmount) } else if (type == TX_NEW_ASSET && fIsOwner) { std::string name; std::string address; - if (!OwnerAssetFromScript(coin.out.scriptPubKey, name, address)) + if (!OwnerAssetFromScript(scriptPubKey, name, address)) return false; strName = name; nAmount = OWNER_ASSET_AMOUNT; @@ -1823,7 +1884,7 @@ bool GetAssetFromCoin(const Coin& coin, std::string& strName, CAmount& nAmount) } else if (type == TX_REISSUE_ASSET) { CReissueAsset reissue; std::string address; - if (!ReissueAssetFromScript(coin.out.scriptPubKey, reissue, address)) + if (!ReissueAssetFromScript(scriptPubKey, reissue, address)) return false; strName = reissue.strName; nAmount = reissue.nAmount; @@ -1833,6 +1894,11 @@ bool GetAssetFromCoin(const Coin& coin, std::string& strName, CAmount& nAmount) return false; } +bool GetAssetInfoFromCoin(const Coin& coin, std::string& strName, CAmount& nAmount) +{ + return GetAssetInfoFromScript(coin.out.scriptPubKey, strName, nAmount); +} + void GetAssetData(const CScript& script, CAssetOutputEntry& data) { // Placeholder strings that will get set if you successfully get the transfer or asset from the script @@ -1886,15 +1952,6 @@ void GetAssetData(const CScript& script, CAssetOutputEntry& data) } } -bool CheckAssetOwner(const std::string& assetName) -{ - if (passets->mapMyUnspentAssets.count(assetName + OWNER_TAG)) { - return true; - } - - return false; -} - void GetAllOwnedAssets(std::vector& names) { for (auto owned : passets->mapMyUnspentAssets) { @@ -2169,15 +2226,14 @@ bool CreateAssetTransaction(CWallet* pwallet, const CNewAsset& asset, const std: } // Get the owner outpoints if this is a subasset - std::set myAssetOutPoints; if (assetType == AssetType::SUB) { // Verify that this wallet is the owner for the asset, and get the owner asset outpoint - if (!VerifyAssetOwner(GetParentName(asset.strName), myAssetOutPoints, error)) { + if (!VerifyWalletHasAsset(GetParentName(asset.strName) + OWNER_TAG, error)) { return false; } } - if (!pwallet->CreateTransactionWithAsset(vecSend, wtxNew, reservekey, nFeeRequired, nChangePosRet, strTxError, coin_control, asset, DecodeDestination(address), myAssetOutPoints, assetType)) { + if (!pwallet->CreateTransactionWithAsset(vecSend, wtxNew, reservekey, nFeeRequired, nChangePosRet, strTxError, coin_control, asset, DecodeDestination(address), assetType)) { if (!fSubtractFeeFromAmount && burnAmount + nFeeRequired > curBalance) strTxError = strprintf("Error: This transaction requires a transaction fee of at least %s", FormatMoney(nFeeRequired)); error = std::make_pair(RPC_WALLET_ERROR, strTxError); @@ -2255,8 +2311,7 @@ bool CreateReissueAssetTransaction(CWallet* pwallet, const CReissueAsset& reissu } // Verify that this wallet is the owner for the asset, and get the owner asset outpoint - std::set myAssetOutPoints; - if (!VerifyAssetOwner(asset_name, myAssetOutPoints, error)) { + if (!VerifyWalletHasAsset(asset_name, error)) { return false; } @@ -2298,7 +2353,7 @@ bool CreateReissueAssetTransaction(CWallet* pwallet, const CReissueAsset& reissu CRecipient recipient2 = {scriptTransferOwnerAsset, 0, fSubtractFeeFromAmount}; vecSend.push_back(recipient); vecSend.push_back(recipient2); - if (!pwallet->CreateTransactionWithReissueAsset(vecSend, wtxNew, reservekey, nFeeRequired, nChangePosRet, strTxError, coin_control, reissueAsset, DecodeDestination(address), myAssetOutPoints)) { + if (!pwallet->CreateTransactionWithReissueAsset(vecSend, wtxNew, reservekey, nFeeRequired, nChangePosRet, strTxError, coin_control, reissueAsset, DecodeDestination(address))) { if (!fSubtractFeeFromAmount && burnAmount + nFeeRequired > curBalance) strTxError = strprintf("Error: This transaction requires a transaction fee of at least %s", FormatMoney(nFeeRequired)); error = std::make_pair(RPC_WALLET_ERROR, strTxError); @@ -2328,7 +2383,6 @@ bool CreateTransferAssetTransaction(CWallet* pwallet, const std::vector< std::pa return false; } - std::set myAssetOutPoints; // Loop through all transfers and create scriptpubkeys for them for (auto transfer : vTransfers) { std::string address = transfer.second; @@ -2345,27 +2399,17 @@ bool CreateTransferAssetTransaction(CWallet* pwallet, const std::vector< std::pa return false; } - std::set myTempAssetOutPoints; - if (!passets->GetAssetsOutPoints(asset_name, myTempAssetOutPoints)) { - error = std::make_pair(RPC_INVALID_PARAMS, std::string("This wallet doesn't own any assets with the name: ") + asset_name); + if (!VerifyWalletHasAsset(asset_name, error)) // Sets error if it fails return false; - } - - if (myTempAssetOutPoints.size() == 0) { - error = std::make_pair(RPC_INVALID_PARAMS, std::string("This wallet doesn't own any assets with the name: ") + asset_name); - return false; - } - - // Put the outpoints into our master set - myAssetOutPoints.insert(myTempAssetOutPoints.begin(), myTempAssetOutPoints.end()); // If it is an ownership transfer, make a quick check to make sure the amount is 1 - if (IsAssetNameAnOwner(asset_name)) - if (nAmount != COIN * 1) { + if (IsAssetNameAnOwner(asset_name)) { + if (nAmount != OWNER_ASSET_AMOUNT) { error = std::make_pair(RPC_INVALID_PARAMS, std::string( "When transfer an 'Ownership Asset' the amount must always be 1. Please try again with the amount of 1")); return false; } + } // Get the script for the burn address CScript scriptPubKey = GetScriptForDestination(DecodeDestination(address)); @@ -2381,7 +2425,7 @@ bool CreateTransferAssetTransaction(CWallet* pwallet, const std::vector< std::pa CCoinControl coin_control; // Create and send the transaction - if (!pwallet->CreateTransactionWithTransferAsset(vecSend, wtxNew, reservekey, nFeeRequired, nChangePosRet, strTxError, coin_control, myAssetOutPoints)) { + if (!pwallet->CreateTransactionWithTransferAsset(vecSend, wtxNew, reservekey, nFeeRequired, nChangePosRet, strTxError, coin_control)) { if (!fSubtractFeeFromAmount && nFeeRequired > curBalance) { error = std::make_pair(RPC_WALLET_ERROR, strprintf("Error: This transaction requires a transaction fee of at least %s", FormatMoney(nFeeRequired))); return false; @@ -2399,35 +2443,28 @@ bool SendAssetTransaction(CWallet* pwallet, CWalletTx& transaction, CReserveKey& error = std::make_pair(RPC_WALLET_ERROR, strprintf("Error: The transaction was rejected! Reason given: %s", state.GetRejectReason())); return false; } + txid = transaction.GetHash().GetHex(); return true; } -bool VerifyAssetOwner(const std::string& asset_name, std::set& myOwnerOutPoints, std::pair& error) +bool VerifyWalletHasAsset(const std::string& asset_name, std::pair& pairError) { - // Check to make sure this wallet is the owner of the asset - if(!CheckAssetOwner(asset_name)) { - error = std::make_pair(RPC_INVALID_PARAMS, - std::string("This wallet is not the owner of the asset: ") + asset_name); - return false; - } - - // Get the outpoint that belongs to the Owner Asset - if (!passets->GetAssetsOutPoints(asset_name + OWNER_TAG, myOwnerOutPoints)) { - error = std::make_pair(RPC_INVALID_PARAMS, std::string("This wallet can't find the owner token information for: ") + asset_name); + CWallet* pwallet; + if (vpwallets.size() > 0) + pwallet = vpwallets[0]; + else { + pairError = std::make_pair(RPC_WALLET_ERROR, strprintf("Wallet not found. Can't verify if it contains: %s", asset_name)); return false; } - // Check to make sure we have the right amount of outpoints - if (myOwnerOutPoints.size() == 0) { - error = std::make_pair(RPC_INVALID_PARAMS, std::string("This wallet doesn't own any assets with the name: ") + asset_name + OWNER_TAG); - return false; - } + std::vector vCoins; + std::map > mapAssetCoins; + pwallet->AvailableAssets(mapAssetCoins); - if (myOwnerOutPoints.size() != 1) { - error = std::make_pair(RPC_INVALID_PARAMS, "Found multiple Owner Assets. Database is out of sync. You might have to run the wallet with -reindex"); - return false; - } + if (mapAssetCoins.count(asset_name)) + return true; - return true; + pairError = std::make_pair(RPC_INVALID_REQUEST, strprintf("Wallet doesn't have asset: %s", asset_name)); + return false; } \ No newline at end of file diff --git a/src/assets/assets.h b/src/assets/assets.h index a67ea46839..3e3041d253 100644 --- a/src/assets/assets.h +++ b/src/assets/assets.h @@ -338,13 +338,13 @@ bool IsScriptTransferAsset(const CScript& scriptPubKey); bool IsNewOwnerTxValid(const CTransaction& tx, const std::string& assetName, const std::string& address, std::string& errorMsg); -bool CheckAssetOwner(const std::string& assetName); void GetAllOwnedAssets(std::vector& names); void GetAllMyAssets(std::vector& names); void UpdatePossibleAssets(); -bool GetAssetFromCoin(const Coin& coin, std::string& strName, CAmount& nAmount); +bool GetAssetInfoFromCoin(const Coin& coin, std::string& strName, CAmount& nAmount); +bool GetAssetInfoFromScript(const CScript& scriptPubKey, std::string& strName, CAmount& nAmount); void GetAssetData(const CScript& script, CAssetOutputEntry& data); @@ -356,7 +356,8 @@ bool GetMyAssetBalance(CAssetsCache& cache, const std::string& assetName, CAmoun bool GetMyAssetBalances(CAssetsCache& cache, const std::vector& assetNames, std::map& balances); bool GetMyAssetBalances(CAssetsCache& cache, std::map& balances); -bool VerifyAssetOwner(const std::string& asset_name, std::set& myOwnerOutPoints, std::pair& error); +/** Verifies that this wallet owns the give asset */ +bool VerifyWalletHasAsset(const std::string& asset_name, std::pair& pairError); std::string DecodeIPFS(std::string encoded); std::string EncodeIPFS(std::string decoded); diff --git a/src/chainparams.cpp b/src/chainparams.cpp index a145a85adc..4d72456ebd 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -412,7 +412,7 @@ class CRegTestParams : public CChainParams { consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].bit = 28; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nStartTime = 0; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nTimeout = 999999999999ULL; - consensus.vDeployments[Consensus::DEPLOYMENT_ASSETS].bit = 5; + consensus.vDeployments[Consensus::DEPLOYMENT_ASSETS].bit = 4; consensus.vDeployments[Consensus::DEPLOYMENT_ASSETS].nStartTime = 0; consensus.vDeployments[Consensus::DEPLOYMENT_ASSETS].nTimeout = 999999999999ULL; diff --git a/src/consensus/tx_verify.cpp b/src/consensus/tx_verify.cpp index 9a7cddd390..076fab3039 100644 --- a/src/consensus/tx_verify.cpp +++ b/src/consensus/tx_verify.cpp @@ -186,14 +186,24 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state, CAssetsCa return state.DoS(100, false, REJECT_INVALID, "bad-txns-txouttotal-toolarge"); /** RVN START */ - if (!AreAssetsDeployed() && txout.scriptPubKey.IsAssetScript() && !fReindex) + bool isAsset = false; + int nType; + bool fIsOwner; + if (txout.scriptPubKey.IsAssetScript(nType, fIsOwner)) + isAsset = true; + + // Make sure that all asset tx have a nValue of zero RVN + if (isAsset && txout.nValue != 0) + return state.DoS(100, false, REJECT_INVALID, "bad-txns-asset-tx-amount-isn't-zero"); + + if (!AreAssetsDeployed() && isAsset && !fReindex) return state.DoS(100, false, REJECT_INVALID, "bad-txns-is-asset-and-asset-not-active"); // Check for transfers that don't meet the assets units only if the assetCache is not null - if (AreAssetsDeployed()) { + if (AreAssetsDeployed() && isAsset) { if (assetCache) { // Get the transfer transaction data from the scriptPubKey - if (txout.scriptPubKey.IsTransferAsset()) { + if ((txnouttype) nType == TX_TRANSFER_ASSET) { CAssetTransfer transfer; std::string address; if (!TransferAssetFromScript(txout.scriptPubKey, transfer, address)) @@ -237,6 +247,9 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state, CAssetsCa if (assetCache) { // Get the new asset from the transaction if (tx.IsNewAsset()) { + if(!tx.VerifyNewAsset()) + return state.DoS(100, false, REJECT_INVALID, "bad-txns-verifying-issue-asset"); + CNewAsset asset; std::string strAddress; if (!AssetFromTransaction(tx, asset, strAddress)) @@ -251,6 +264,7 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state, CAssetsCa return state.DoS(100, false, REJECT_INVALID, "bad-txns-" + strError); } else if (tx.IsReissueAsset()) { + CReissueAsset reissue; std::string strAddress; if (!ReissueAssetFromTransaction(tx, reissue, strAddress)) @@ -343,7 +357,7 @@ bool Consensus::CheckTxAssets(const CTransaction& tx, CValidationState& state, c std::string strName; CAmount nAmount; - if (!GetAssetFromCoin(coin, strName, nAmount)) + if (!GetAssetInfoFromCoin(coin, strName, nAmount)) return state.DoS(100, false, REJECT_INVALID, "bad-txns-failed-to-get-asset-from-script"); // Add to the total value of assets in the inputs @@ -401,22 +415,27 @@ bool Consensus::CheckTxAssets(const CTransaction& tx, CValidationState& state, c "bad-txns" + strError); } } + } + } + for (const auto& outValue : totalOutputs) { + if (!totalInputs.count(outValue.first)) { + std::string errorMsg; + errorMsg = strprintf("Bad Transaction - Trying to create outpoint for asset that you don't have: %s", outValue.first); + return state.DoS(100, false, REJECT_INVALID, "bad-tx-inputs-outputs-mismatch " + errorMsg); + } + + if (totalInputs.at(outValue.first) != outValue.second) { + std::string errorMsg; + errorMsg = strprintf("Bad Transaction - Assets would be burnt %s", outValue.first); + return state.DoS(100, false, REJECT_INVALID, "bad-tx-inputs-outputs-mismatch " + errorMsg); } } - // Check the input values and the output values + // Check the input size and the output size if (totalOutputs.size() != totalInputs.size()) { return state.DoS(100, false, REJECT_INVALID, "bad-tx-asset-inputs-size-does-not-match-outputs-size"); } - for (const auto& outValue : totalOutputs) { - if (!totalInputs.count(outValue.first)) - return state.DoS(100, false, REJECT_INVALID, "bad-tx-asset-inputs-does-not-have-asset-that-is-in-outputs"); - - if (totalInputs.at(outValue.first) != outValue.second) - return state.DoS(100, false, REJECT_INVALID, "bad-tx-asset-inputs-amount-mismatch-with-outputs-amount"); - } - return true; } diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index a6b194b5f9..c3cf095ee7 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -15,6 +15,8 @@ static const int SERIALIZE_TRANSACTION_NO_WITNESS = 0x40000000; +class CCoinsViewCache; + /** An outpoint - a combination of a transaction hash and an index n into its vout */ class COutPoint { @@ -326,7 +328,9 @@ class CTransaction /** RVN START */ bool IsNewAsset() const; + bool VerifyNewAsset() const; bool IsReissueAsset() const; + bool VerifyReissueAsset(CCoinsViewCache& view) const; /** RVN END */ /** diff --git a/src/qt/assetsdialog.cpp b/src/qt/assetsdialog.cpp index f881b094cd..056d498dd3 100644 --- a/src/qt/assetsdialog.cpp +++ b/src/qt/assetsdialog.cpp @@ -383,144 +383,6 @@ void AssetsDialog::on_sendButton_clicked() coinControlUpdateLabels(); } fNewRecipientAllowed = true; - - - - std::string txid; - if (!SendAssetTransaction(model->getWallet(), tx, reservekey, error, txid)) { - QMessageBox msgBox; - msgBox.setText(QString::fromStdString(error.second)); - msgBox.exec(); - return; - } else { - QMessageBox msgBox; - msgBox.setText("Transaction sent to network"); - msgBox.setInformativeText("Transaction Hash: " + QString::fromStdString(txid)); - msgBox.exec(); - accept(); - } - fNewRecipientAllowed = true; - -// // prepare transaction for getting txFee earlier -// WalletModelTransaction currentTransaction(recipients); -// WalletModel::SendCoinsReturn prepareStatus; -// -// // Always use a CCoinControl instance, use the CoinControlDialog instance if CoinControl has been enabled -// CCoinControl ctrl; -// if (model->getOptionsModel()->getCoinControlFeatures()) -// ctrl = *CoinControlDialog::coinControl; -// -// updateCoinControlState(ctrl); -// -// prepareStatus = model->prepareTransaction(currentTransaction, ctrl); -// -// // process prepareStatus and on error generate message shown to user -// processSendCoinsReturn(prepareStatus, -// RavenUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), currentTransaction.getTransactionFee())); -// -// if(prepareStatus.status != WalletModel::OK) { -// fNewRecipientAllowed = true; -// return; -// } -// -// CAmount txFee = currentTransaction.getTransactionFee(); -// -// // Format confirmation message -// QStringList formatted; -// for (const SendCoinsRecipient &rcp : currentTransaction.getRecipients()) -// { -// // generate bold amount string -// QString amount = "" + RavenUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount); -// amount.append(""); -// // generate monospace address string -// QString address = "" + rcp.address; -// address.append(""); -// -// QString recipientElement; -// -// if (!rcp.paymentRequest.IsInitialized()) // normal payment -// { -// if(rcp.label.length() > 0) // label with address -// { -// recipientElement = tr("%1 to %2").arg(amount, GUIUtil::HtmlEscape(rcp.label)); -// recipientElement.append(QString(" (%1)").arg(address)); -// } -// else // just address -// { -// recipientElement = tr("%1 to %2").arg(amount, address); -// } -// } -// else if(!rcp.authenticatedMerchant.isEmpty()) // authenticated payment request -// { -// recipientElement = tr("%1 to %2").arg(amount, GUIUtil::HtmlEscape(rcp.authenticatedMerchant)); -// } -// else // unauthenticated payment request -// { -// recipientElement = tr("%1 to %2").arg(amount, address); -// } -// -// formatted.append(recipientElement); -// } -// -// QString questionString = tr("Are you sure you want to send?"); -// questionString.append("

%1"); -// -// if(txFee > 0) -// { -// // append fee string if a fee is required -// questionString.append("
"); -// questionString.append(RavenUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), txFee)); -// questionString.append(" "); -// questionString.append(tr("added as transaction fee")); -// -// // append transaction size -// questionString.append(" (" + QString::number((double)currentTransaction.getTransactionSize() / 1000) + " kB)"); -// } -// -// // add total amount in all subdivision units -// questionString.append("
"); -// CAmount totalAmount = currentTransaction.getTotalTransactionAmount() + txFee; -// QStringList alternativeUnits; -// for (RavenUnits::Unit u : RavenUnits::availableUnits()) -// { -// if(u != model->getOptionsModel()->getDisplayUnit()) -// alternativeUnits.append(RavenUnits::formatHtmlWithUnit(u, totalAmount)); -// } -// questionString.append(tr("Total Amount %1") -// .arg(RavenUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), totalAmount))); -// questionString.append(QString("
(=%2)
") -// .arg(alternativeUnits.join(" " + tr("or") + "
"))); -// -// if (ui->optInRBF->isChecked()) -// { -// questionString.append("
"); -// questionString.append(tr("This transaction signals replaceability (optin-RBF).")); -// questionString.append(""); -// } -// -// SendConfirmationDialog confirmationDialog(tr("Confirm send coins"), -// questionString.arg(formatted.join("
")), SEND_CONFIRM_DELAY, this); -// confirmationDialog.exec(); -// QMessageBox::StandardButton retval = (QMessageBox::StandardButton)confirmationDialog.result(); -// -// if(retval != QMessageBox::Yes) -// { -// fNewRecipientAllowed = true; -// return; -// } -// -// // now send the prepared transaction -// WalletModel::SendCoinsReturn sendStatus = model->sendCoins(currentTransaction); -// // process sendStatus and on error generate message shown to user -// processSendCoinsReturn(sendStatus); -// -// if (sendStatus.status == WalletModel::OK) -// { -// accept(); -// CoinControlDialog::coinControl->UnSelectAll(); -// coinControlUpdateLabels(); -// } -// fNewRecipientAllowed = true; } void AssetsDialog::clear() diff --git a/src/test/assets/asset_tests.cpp b/src/test/assets/asset_tests.cpp index 0c581ab4c9..b704a4838e 100644 --- a/src/test/assets/asset_tests.cpp +++ b/src/test/assets/asset_tests.cpp @@ -239,6 +239,9 @@ BOOST_FIXTURE_TEST_SUITE(asset_tests, BasicTestingSetup) amount = 1; BOOST_CHECK(ValueFromAmountString(amount, 8) == "0.00000001"); + amount = 40000000; + BOOST_CHECK(ValueFromAmountString(amount, 8) == "0.40000000"); + } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/validation.cpp b/src/validation.cpp index a103d7b8a6..52310e2bc4 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2191,6 +2191,9 @@ static bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockInd if (!AreAssetsDeployed()) return state.DoS(100, false, REJECT_INVALID, "bad-txns-new-asset-when-assets-is-not-active"); + if (!tx.VerifyNewAsset()) + return state.DoS(100, false, REJECT_INVALID, "bad-txns-issue-asset-failed-verify"); + CNewAsset asset; std::string strAddress; if (!AssetFromTransaction(tx, asset, strAddress)) @@ -2206,6 +2209,9 @@ static bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockInd } else if (tx.IsReissueAsset()) { if (!AreAssetsDeployed()) return state.DoS(100, false, REJECT_INVALID, "bad-txns-reissue-asset-when-assets-is-not-active"); + + if (!tx.VerifyReissueAsset(view)) + return state.DoS(100, false, REJECT_INVALID, "bad-txns-reissue-asset-failed-verify"); CReissueAsset reissue; std::string strAddress; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 72e8048a57..7a1d431de5 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -81,6 +81,15 @@ struct CompareValueOnly } }; +struct CompareAssetValueOnly +{ + bool operator()(const std::pair& t1, + const std::pair& t2) const + { + return t1.second < t2.second; + } +}; + std::string COutput::ToString() const { return strprintf("COutput(%s, %d, %d) [%s]", tx->GetHash().ToString(), i, nDepth, FormatMoney(tx->tx->vout[i].nValue)); @@ -1691,8 +1700,8 @@ bool CWalletTx::RelayWalletTransaction(CConnman* connman) assert(pwallet->GetBroadcastTransactions()); if (!IsCoinBase() && !isAbandoned() && GetDepthInMainChain() == 0) { - CValidationState state; /* GetDepthInMainChain already catches known conflicts. */ + CValidationState state; if (InMempool() || AcceptToMemoryPool(maxTxFee, state)) { LogPrintf("Relaying wtx %s\n", GetHash().ToString()); if (connman) { @@ -2137,23 +2146,31 @@ CAmount CWallet::GetAvailableBalance(const CCoinControl* coinControl) const void CWallet::AvailableCoins(std::vector &vCoins, bool fOnlySafe, const CCoinControl *coinControl, const CAmount &nMinimumAmount, const CAmount &nMaximumAmount, const CAmount &nMinimumSumAmount, const uint64_t &nMaximumCount, const int &nMinDepth, const int &nMaxDepth) const { std::map > mapAssetCoins; - std::set setAssetOutPoint; - AvailableCoinsAll(vCoins, mapAssetCoins, setAssetOutPoint, false, fOnlySafe, coinControl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount, nMinDepth, nMaxDepth); + AvailableCoinsAll(vCoins, mapAssetCoins, true, false, fOnlySafe, coinControl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount, nMinDepth, nMaxDepth); } -void CWallet::AvailableCoinsWithAssets(std::vector& vCoins, std::map >& mapAssetCoins, const std::set& setAssetOutPoint, bool fWithAssets, bool fOnlySafe, const CCoinControl *coinControl, const CAmount& nMinimumAmount, const CAmount& nMaximumAmount, const CAmount& nMinimumSumAmount, const uint64_t& nMaximumCount, const int& nMinDepth, const int& nMaxDepth) const +void CWallet::AvailableAssets(std::map > &mapAssetCoins, bool fOnlySafe, + const CCoinControl *coinControl, const CAmount &nMinimumAmount, + const CAmount &nMaximumAmount, const CAmount &nMinimumSumAmount, + const uint64_t &nMaximumCount, const int &nMinDepth, const int &nMaxDepth) const { if (!AreAssetsDeployed()) return; - if (!fWithAssets) - return; + std::vector vCoins; - AvailableCoinsAll(vCoins, mapAssetCoins, setAssetOutPoint, fWithAssets, fOnlySafe, coinControl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount, nMinDepth, nMaxDepth); + AvailableCoinsAll(vCoins, mapAssetCoins, false, true, fOnlySafe, coinControl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount, nMinDepth, nMaxDepth); } +void CWallet::AvailableCoinsWithAssets(std::vector &vCoins, std::map > &mapAssetCoins, + bool fOnlySafe, const CCoinControl *coinControl, const CAmount &nMinimumAmount, + const CAmount &nMaximumAmount, const CAmount &nMinimumSumAmount, + const uint64_t &nMaximumCount, const int &nMinDepth, const int &nMaxDepth) const +{ + AvailableCoinsAll(vCoins, mapAssetCoins, true, AreAssetsDeployed(), fOnlySafe, coinControl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount, nMinDepth, nMaxDepth); +} -void CWallet::AvailableCoinsAll(std::vector& vCoins, std::map >& mapAssetCoins, const std::set& setAssetOutPoint, bool fWithAssets, bool fOnlySafe, const CCoinControl *coinControl, const CAmount& nMinimumAmount, const CAmount& nMaximumAmount, const CAmount& nMinimumSumAmount, const uint64_t& nMaximumCount, const int& nMinDepth, const int& nMaxDepth) const { +void CWallet::AvailableCoinsAll(std::vector& vCoins, std::map >& mapAssetCoins, bool fGetRVN, bool fGetAssets, bool fOnlySafe, const CCoinControl *coinControl, const CAmount& nMinimumAmount, const CAmount& nMaximumAmount, const CAmount& nMinimumSumAmount, const uint64_t& nMaximumCount, const int& nMinDepth, const int& nMaxDepth) const { vCoins.clear(); { @@ -2162,142 +2179,14 @@ void CWallet::AvailableCoinsAll(std::vector& vCoins, std::map mapAssetTotals; - std::map mapOutPoints; - if (fWithAssets) { - std::set setAssetMaxFound; - // Turn the OutPoints into a map that is easily interatable. - for (auto out : setAssetOutPoint) { - if (mapWallet.count(out.hash)) { - const CWalletTx *pcoin = &mapWallet.at(out.hash); - - if (!CheckFinalTx(*pcoin)) - continue; - - if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0) - continue; - - int nDepth = pcoin->GetDepthInMainChain(); - if (nDepth < 0) - continue; - - // We should not consider coins which aren't at least in our mempool - // It's possible for these to be conflicted via ancestors which we may never be able to detect - if (nDepth == 0 && !pcoin->InMempool()) - continue; - - bool safeTx = pcoin->IsTrusted(); - - // see explanation below - if (nDepth == 0 && pcoin->mapValue.count("replaces_txid")) { - safeTx = false; - } - - // see explanation below - if (nDepth == 0 && pcoin->mapValue.count("replaced_by_txid")) { - safeTx = false; - } - - if (fOnlySafe && !safeTx) { - continue; - } - - isminetype mine = IsMine(pcoin->tx->vout[out.n]); - - if (mine == ISMINE_NO) { - continue; - } - - bool fSpendableIn = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || - (coinControl && coinControl->fAllowWatchOnly && - (mine & ISMINE_WATCH_SOLVABLE) != ISMINE_NO); - bool fSolvableIn = (mine & (ISMINE_SPENDABLE | ISMINE_WATCH_SOLVABLE)) != ISMINE_NO; - - std::string address; - CAssetTransfer assetTransfer; - CNewAsset asset; - CReissueAsset reissue; - std::string ownerName; - bool fWasNewAssetOutPoint = false; - bool fWasTransferAssetOutPoint = false; - bool fWasOwnerAssetOutPoint = false; - bool fWasReissueAssetOutPoint = false; - std::string strAssetName; - if (TransferAssetFromScript(pcoin->tx->vout[out.n].scriptPubKey, assetTransfer, address)) { - strAssetName = assetTransfer.strName; - fWasTransferAssetOutPoint = true; - } else if (AssetFromScript(pcoin->tx->vout[out.n].scriptPubKey, asset, address)) { - strAssetName = asset.strName; - fWasNewAssetOutPoint = true; - } else if (OwnerAssetFromScript(pcoin->tx->vout[out.n].scriptPubKey, ownerName, address)) { - strAssetName = ownerName; - fWasOwnerAssetOutPoint = true; - } else if (ReissueAssetFromScript(pcoin->tx->vout[out.n].scriptPubKey, reissue, address)) { - strAssetName = reissue.strName; - fWasReissueAssetOutPoint = true; - } else { - continue; - } - - if (fWasNewAssetOutPoint || fWasTransferAssetOutPoint || fWasOwnerAssetOutPoint || - fWasReissueAssetOutPoint) { - - // If we already have the maximum amount or size for this asset, skip it - if (setAssetMaxFound.count(strAssetName)) - continue; - - // Initialize the map vector is it doesn't exist yet - if (!mapAssetCoins.count(strAssetName)) { - std::vector vOutput; - mapAssetCoins.insert(std::make_pair(strAssetName, vOutput)); - } - - // Add the COutput to the map of available Asset Coins - mapAssetCoins.at(strAssetName).push_back( - COutput(pcoin, out.n, nDepth, fSpendableIn, fSolvableIn, safeTx)); - - // Initialize the map of current asset totals - if (!mapAssetTotals.count(strAssetName)) - mapAssetTotals[strAssetName] = 0; - - // Update the map of totals depending the which type of asset tx we are looking at - if (fWasNewAssetOutPoint) - mapAssetTotals[strAssetName] += asset.nAmount; - else if (fWasTransferAssetOutPoint) - mapAssetTotals[strAssetName] += assetTransfer.nAmount; - else if (fWasReissueAssetOutPoint) - mapAssetTotals[strAssetName] += reissue.nAmount; - else if (fWasOwnerAssetOutPoint) - mapAssetTotals[strAssetName] = OWNER_ASSET_AMOUNT; - } - - // Checks the sum amount of all UTXO's, and adds to the set of assets that we found the max for - if (nMinimumSumAmount != MAX_MONEY) { - if (mapAssetTotals[strAssetName] >= nMinimumSumAmount) - setAssetMaxFound.insert(strAssetName); - } - - // Checks the maximum number of UTXO's, and addes to set of of asset that we found the max for - if (nMaximumCount > 0 && mapAssetCoins[strAssetName].size() >= nMaximumCount) { - setAssetMaxFound.insert(strAssetName); - } - } - } - } - - // TODO Remove when done logging - for (auto asset : mapAssetTotals) - LogPrintf("%s : Found a total number of assets available from transfer. Asset: %s, Amount: %d\n", - __func__, asset.first, asset.second); - - for (auto asset : mapAssetCoins) - for (auto out : asset.second) - LogPrintf("%s : %s Output Found: %s\n", __func__, asset.first, out.ToString()); - - } - /** RVN END */ - + bool fRVNLimitHit = false; + // A set of the hashes that have already been used + std::set usedMempoolHashes; + + std::map mapAssetTotals; + std::map mapOutPoints; + std::set setAssetMaxFound; + // Turn the OutPoints into a map that is easily interatable. for (std::map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) { const uint256 &wtxid = it->first; const CWalletTx *pcoin = &(*it).second; @@ -2358,8 +2247,6 @@ void CWallet::AvailableCoinsAll(std::vector& vCoins, std::maptx->vout.size(); i++) { - if (pcoin->tx->vout[i].nValue < nMinimumAmount || pcoin->tx->vout[i].nValue > nMaximumAmount) - continue; if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected(COutPoint((*it).first, i))) continue; @@ -2376,26 +2263,123 @@ void CWallet::AvailableCoinsAll(std::vector& vCoins, std::mapfAllowWatchOnly && (mine & ISMINE_WATCH_SOLVABLE) != ISMINE_NO); + bool fSpendableIn = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || + (coinControl && coinControl->fAllowWatchOnly && + (mine & ISMINE_WATCH_SOLVABLE) != ISMINE_NO); bool fSolvableIn = (mine & (ISMINE_SPENDABLE | ISMINE_WATCH_SOLVABLE)) != ISMINE_NO; - vCoins.push_back(COutput(pcoin, i, nDepth, fSpendableIn, fSolvableIn, safeTx)); + std::string address; + CAssetTransfer assetTransfer; + CNewAsset asset; + CReissueAsset reissue; + std::string ownerName; + bool fWasNewAssetOutPoint = false; + bool fWasTransferAssetOutPoint = false; + bool fWasOwnerAssetOutPoint = false; + bool fWasReissueAssetOutPoint = false; + std::string strAssetName; + + int nType; + bool fIsOwner; - // Checks the sum amount of all UTXO's. - if (nMinimumSumAmount != MAX_MONEY) { - nTotal += pcoin->tx->vout[i].nValue; + // Looking for Asset Tx OutPoints Only + if (fGetAssets && AreAssetsDeployed() && pcoin->tx->vout[i].scriptPubKey.IsAssetScript(nType, fIsOwner)) { - if (nTotal >= nMinimumSumAmount) { - return; + if ((txnouttype) nType == TX_TRANSFER_ASSET) { + if (TransferAssetFromScript(pcoin->tx->vout[i].scriptPubKey, assetTransfer, address)) { + strAssetName = assetTransfer.strName; + fWasTransferAssetOutPoint = true; + } + } else if ((txnouttype) nType == TX_NEW_ASSET && !fIsOwner) { + if (AssetFromScript(pcoin->tx->vout[i].scriptPubKey, asset, address)) { + strAssetName = asset.strName; + fWasNewAssetOutPoint = true; + } + } else if ((txnouttype) nType == TX_NEW_ASSET && fIsOwner) { + if (OwnerAssetFromScript(pcoin->tx->vout[i].scriptPubKey, ownerName, address)) { + strAssetName = ownerName; + fWasOwnerAssetOutPoint = true; + } + } else if ((txnouttype) nType == TX_REISSUE_ASSET) { + if (ReissueAssetFromScript(pcoin->tx->vout[i].scriptPubKey, reissue, address)) { + strAssetName = reissue.strName; + fWasReissueAssetOutPoint = true; + } + } else { + continue; + } + + if (fWasNewAssetOutPoint || fWasTransferAssetOutPoint || fWasOwnerAssetOutPoint || fWasReissueAssetOutPoint) { + + // If we already have the maximum amount or size for this asset, skip it + if (setAssetMaxFound.count(strAssetName)) + continue; + + // Initialize the map vector is it doesn't exist yet + if (!mapAssetCoins.count(strAssetName)) { + std::vector vOutput; + mapAssetCoins.insert(std::make_pair(strAssetName, vOutput)); + } + + // Add the COutput to the map of available Asset Coins + mapAssetCoins.at(strAssetName).push_back( + COutput(pcoin, i, nDepth, fSpendableIn, fSolvableIn, safeTx)); + + // Initialize the map of current asset totals + if (!mapAssetTotals.count(strAssetName)) + mapAssetTotals[strAssetName] = 0; + + // Update the map of totals depending the which type of asset tx we are looking at + if (fWasNewAssetOutPoint) + mapAssetTotals[strAssetName] += asset.nAmount; + else if (fWasTransferAssetOutPoint) + mapAssetTotals[strAssetName] += assetTransfer.nAmount; + else if (fWasReissueAssetOutPoint) + mapAssetTotals[strAssetName] += reissue.nAmount; + else if (fWasOwnerAssetOutPoint) + mapAssetTotals[strAssetName] = OWNER_ASSET_AMOUNT; + + // Checks the sum amount of all UTXO's, and adds to the set of assets that we found the max for + if (nMinimumSumAmount != MAX_MONEY) { + if (mapAssetTotals[strAssetName] >= nMinimumSumAmount) + setAssetMaxFound.insert(strAssetName); + } + + // Checks the maximum number of UTXO's, and addes to set of of asset that we found the max for + if (nMaximumCount > 0 && mapAssetCoins[strAssetName].size() >= nMaximumCount) { + setAssetMaxFound.insert(strAssetName); + } } } - // Checks the maximum number of UTXO's. - if (nMaximumCount > 0 && vCoins.size() >= nMaximumCount) { - return; + if (fGetRVN) { // Looking for RVN Tx OutPoints Only + if (fRVNLimitHit) // We hit our limit + continue; + + // We only want RVN OutPoints. Don't include Asset OutPoints + if (pcoin->tx->vout[i].scriptPubKey.IsAssetScript()) + continue; + + vCoins.push_back(COutput(pcoin, i, nDepth, fSpendableIn, fSolvableIn, safeTx)); + + // Checks the sum amount of all UTXO's. + if (nMinimumSumAmount != MAX_MONEY) { + nTotal += pcoin->tx->vout[i].nValue; + + if (nTotal >= nMinimumSumAmount) { + fRVNLimitHit = true; + } + } + + // Checks the maximum number of UTXO's. + if (nMaximumCount > 0 && vCoins.size() >= nMaximumCount) { + fRVNLimitHit = true; + } + continue; } } } + /** RVN END */ } } @@ -2508,6 +2492,52 @@ static void ApproximateBestSubset(const std::vector& vValue, const C } } +static void ApproximateBestAssetSubset(const std::vector >& vValue, const CAmount& nTotalLower, const CAmount& nTargetValue, + std::vector& vfBest, CAmount& nBest, int iterations = 1000) +{ + std::vector vfIncluded; + + vfBest.assign(vValue.size(), true); + nBest = nTotalLower; + + FastRandomContext insecure_rand; + + for (int nRep = 0; nRep < iterations && nBest != nTargetValue; nRep++) + { + vfIncluded.assign(vValue.size(), false); + CAmount nTotal = 0; + bool fReachedTarget = false; + for (int nPass = 0; nPass < 2 && !fReachedTarget; nPass++) + { + for (unsigned int i = 0; i < vValue.size(); i++) + { + //The solver here uses a randomized algorithm, + //the randomness serves no real security purpose but is just + //needed to prevent degenerate behavior and it is important + //that the rng is fast. We do not use a constant random sequence, + //because there may be some privacy improvement by making + //the selection random. + if (nPass == 0 ? insecure_rand.randbool() : !vfIncluded[i]) + { + nTotal += vValue[i].second; + vfIncluded[i] = true; + if (nTotal >= nTargetValue) + { + fReachedTarget = true; + if (nTotal < nBest) + { + nBest = nTotal; + vfBest = vfIncluded; + } + nTotal -= vValue[i].second; + vfIncluded[i] = false; + } + } + } + } + } +} + bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const int nConfMine, const int nConfTheirs, const uint64_t nMaxAncestors, std::vector vCoins, std::set& setCoinsRet, CAmount& nValueRet) const { @@ -2684,75 +2714,210 @@ bool CWallet::SelectCoins(const std::vector& vAvailableCoins, const CAm } /** RVN START */ -bool CWallet::SelectAssets(const std::map >& mapAvailableAssets, const std::map& mapAssetTargetValue, std::set& setCoinsRet, std::map& mapValueRet) const +bool CWallet::SelectAssetsMinConf(const CAmount& nTargetValue, const int nConfMine, const int nConfTheirs, const uint64_t nMaxAncestors, const std::string& strAssetName, std::vector vCoins, + std::set& setCoinsRet, CAmount& nValueRet) const { - if (!AreAssetsDeployed()) - return false; + setCoinsRet.clear(); + nValueRet = 0; + + // List of values less than target + boost::optional coinLowestLarger; + boost::optional coinLowestLargerAmount; + std::vector > vValue; + std::map mapValueAmount; + CAmount nTotalLower = 0; - for (auto asset : mapAvailableAssets) { - if (!mapAssetTargetValue.count(asset.first)) + random_shuffle(vCoins.begin(), vCoins.end(), GetRandInt); + + for (const COutput &output : vCoins) + { + if (!output.fSpendable) continue; - if (mapAssetTargetValue.at(asset.first) <= 0) + const CWalletTx *pcoin = output.tx; + + if (output.nDepth < (pcoin->IsFromMe(ISMINE_ALL) ? nConfMine : nConfTheirs)) continue; - if (!mapValueRet.count(asset.first)) - mapValueRet.insert(std::make_pair(asset.first, 0)); + if (!mempool.TransactionWithinChainLimit(pcoin->GetHash(), nMaxAncestors)) + continue; - for (auto out : asset.second) { - if (!out.fSpendable) - continue; + int i = output.i; - int nType = 0; - bool fIsOwner = false; - if (!out.tx->tx->vout[out.i].scriptPubKey.IsAssetScript(nType, fIsOwner)) - continue; + CInputCoin coin = CInputCoin(pcoin, i); - txnouttype type = (txnouttype) nType; + //------------------------------- - if (type == TX_NEW_ASSET && !fIsOwner) { - CNewAsset assetTemp; - std::string address; - if (!AssetFromScript(out.tx->tx->vout[out.i].scriptPubKey, assetTemp, address)) - continue; - mapValueRet.at(assetTemp.strName) += assetTemp.nAmount; - } else if (type == TX_TRANSFER_ASSET) { - CAssetTransfer transferTemp; - std::string address; - if (!TransferAssetFromScript(out.tx->tx->vout[out.i].scriptPubKey, transferTemp, address)) - continue; - mapValueRet.at(transferTemp.strName) += transferTemp.nAmount; - } else if (type == TX_NEW_ASSET && fIsOwner) { - std::string ownerName; - std::string address; - if (!OwnerAssetFromScript(out.tx->tx->vout[out.i].scriptPubKey, ownerName, address)) - continue; - mapValueRet.at(ownerName) = OWNER_ASSET_AMOUNT; - } else if (type == TX_REISSUE_ASSET) { - CReissueAsset reissueTemp; - std::string address; - if (!ReissueAssetFromScript(out.tx->tx->vout[out.i].scriptPubKey, reissueTemp, address)) - continue; - mapValueRet.at(reissueTemp.strName) += reissueTemp.nAmount; - } else { - continue; - } + int nType = 0; + bool fIsOwner = false; + if (!coin.txout.scriptPubKey.IsAssetScript(nType, fIsOwner)) { + // TODO - Remove std::cout this before mainnet release + std::cout << "This shouldn't be occuring: Non Asset Script pub key made it to the SelectAssetsMinConf function call. Look into this!" << std::endl; + continue; + } + txnouttype type = (txnouttype) nType; - setCoinsRet.insert(CInputCoin(out.tx, out.i)); + CAmount nTempAmount = 0; + if (type == TX_NEW_ASSET && !fIsOwner) { // Root/Sub Asset + CNewAsset assetTemp; + std::string address; + if (!AssetFromScript(coin.txout.scriptPubKey, assetTemp, address)) + continue; + nTempAmount = assetTemp.nAmount; + } else if (type == TX_TRANSFER_ASSET) { // Transfer Asset + CAssetTransfer transferTemp; + std::string address; + if (!TransferAssetFromScript(coin.txout.scriptPubKey, transferTemp, address)) + continue; + nTempAmount = transferTemp.nAmount; + } else if (type == TX_NEW_ASSET && fIsOwner) { // Owner Asset + std::string ownerName; + std::string address; + if (!OwnerAssetFromScript(coin.txout.scriptPubKey, ownerName, address)) + continue; + nTempAmount = OWNER_ASSET_AMOUNT; + } else if (type == TX_REISSUE_ASSET) { // Reissue Asset + CReissueAsset reissueTemp; + std::string address; + if (!ReissueAssetFromScript(coin.txout.scriptPubKey, reissueTemp, address)) + continue; + nTempAmount = reissueTemp.nAmount; + } else { + continue; + } - if (mapValueRet.at(asset.first) >= mapAssetTargetValue.at(asset.first)) - break; + if (nTempAmount == nTargetValue) + { + setCoinsRet.insert(coin); + nValueRet += nTempAmount; + return true; + } + else if (nTempAmount < nTargetValue + MIN_CHANGE) + { + vValue.push_back(std::make_pair(coin, nTempAmount)); + nTotalLower += nTempAmount; } + else if (!coinLowestLarger || !coinLowestLargerAmount || nTempAmount < coinLowestLargerAmount) + { + coinLowestLarger = coin; + coinLowestLargerAmount = nTempAmount; + } + } - if (mapValueRet.at(asset.first) < mapAssetTargetValue.at(asset.first)) { - return error( - "%s : Tried to transfer an asset but this wallet didn't have enough, Asset Name: %s, Transfer Amount: %d, Wallet Total: %d", - __func__, asset.first, mapValueRet.at(asset.first), mapAssetTargetValue.at(asset.first)); + if (nTotalLower == nTargetValue) + { + for (const auto& pair : vValue) + { + setCoinsRet.insert(pair.first); + nValueRet += pair.second; } + return true; } + if (nTotalLower < nTargetValue) + { + if (!coinLowestLarger || !coinLowestLargerAmount) + return false; + setCoinsRet.insert(coinLowestLarger.get()); + nValueRet += coinLowestLargerAmount.get(); return true; + } + + // Solve subset sum by stochastic approximation + std::sort(vValue.begin(), vValue.end(), CompareAssetValueOnly()); + std::reverse(vValue.begin(), vValue.end()); + std::vector vfBest; + CAmount nBest; + + ApproximateBestAssetSubset(vValue, nTotalLower, nTargetValue, vfBest, nBest); + if (nBest != nTargetValue && nTotalLower >= nTargetValue + MIN_CHANGE) + ApproximateBestAssetSubset(vValue, nTotalLower, nTargetValue + MIN_CHANGE, vfBest, nBest); + + // If we have a bigger coin and (either the stochastic approximation didn't find a good solution, + // or the next bigger coin is closer), return the bigger coin + if (coinLowestLarger && coinLowestLargerAmount && + ((nBest != nTargetValue && nBest < nTargetValue + MIN_CHANGE) || coinLowestLargerAmount <= nBest)) + { + setCoinsRet.insert(coinLowestLarger.get()); + nValueRet += coinLowestLargerAmount.get(); + } + else { + for (unsigned int i = 0; i < vValue.size(); i++) + if (vfBest[i]) + { + setCoinsRet.insert(vValue[i].first); + nValueRet += vValue[i].second; + } + + if (LogAcceptCategory(BCLog::SELECTCOINS)) { + LogPrint(BCLog::SELECTCOINS, "SelectAssets() best subset: "); + for (unsigned int i = 0; i < vValue.size(); i++) { + if (vfBest[i]) { + LogPrint(BCLog::SELECTCOINS, "%s : %s", strAssetName, FormatMoney(vValue[i].second)); + } + } + LogPrint(BCLog::SELECTCOINS, "total %s : %s\n", strAssetName, FormatMoney(nBest)); + } + } + + return true; +} + + +bool CWallet::SelectAssets(const std::map >& mapAvailableAssets, const std::map& mapAssetTargetValue, std::set& setCoinsRet, std::map& mapValueRet) const +{ + if (!AreAssetsDeployed()) + return false; + + size_t nMaxChainLength = std::min(gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT), gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT)); + bool fRejectLongChains = gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS); + + for (auto assetVector : mapAvailableAssets) { + // Setup temporay variables + std::vector vAssets(assetVector.second); + + std::set tempCoinsRet; + CAmount nTempAmountRet; + CAmount nTempTargetValue; + std::string strAssetName = assetVector.first; + + CAmount nValueFromPresetInputs = 0; // This is used with coincontrol, which assets doesn't support yet + + // If we dont have a target value for this asset, don't select coins for it + if (!mapAssetTargetValue.count(strAssetName)) + continue; + + // If we dont have a target value greater than zero, don't select coins for it + if (mapAssetTargetValue.at(strAssetName) <= 0) + continue; + + // Add the starting value into the mapValueRet + if (!mapValueRet.count(strAssetName)) + mapValueRet.insert(std::make_pair(strAssetName, 0)); + + // assign our temporary variable + nTempAmountRet = mapValueRet.at(strAssetName); + nTempTargetValue = mapAssetTargetValue.at(strAssetName); + + bool res = nTempTargetValue <= nValueFromPresetInputs || + SelectAssetsMinConf(nTempTargetValue - nValueFromPresetInputs, 1, 6, 0, strAssetName, vAssets, tempCoinsRet, nTempAmountRet) || + SelectAssetsMinConf(nTempTargetValue - nValueFromPresetInputs, 1, 1, 0, strAssetName, vAssets, tempCoinsRet, nTempAmountRet) || + (bSpendZeroConfChange && SelectAssetsMinConf(nTempTargetValue - nValueFromPresetInputs, 0, 1, 2, strAssetName, vAssets, tempCoinsRet, nTempAmountRet)) || + (bSpendZeroConfChange && SelectAssetsMinConf(nTempTargetValue - nValueFromPresetInputs, 0, 1, std::min((size_t)4, nMaxChainLength/3), strAssetName, vAssets, tempCoinsRet, nTempAmountRet)) || + (bSpendZeroConfChange && SelectAssetsMinConf(nTempTargetValue - nValueFromPresetInputs, 0, 1, nMaxChainLength/2, strAssetName, vAssets, tempCoinsRet, nTempAmountRet)) || + (bSpendZeroConfChange && SelectAssetsMinConf(nTempTargetValue - nValueFromPresetInputs, 0, 1, nMaxChainLength, strAssetName, vAssets, tempCoinsRet, nTempAmountRet)) || + (bSpendZeroConfChange && !fRejectLongChains && SelectAssetsMinConf(nTempTargetValue - nValueFromPresetInputs, 0, 1, std::numeric_limits::max(), strAssetName, vAssets, tempCoinsRet, nTempAmountRet)); + + if (res) { + setCoinsRet.insert(tempCoinsRet.begin(), tempCoinsRet.end()); + mapValueRet.at(strAssetName) = nTempAmountRet + nValueFromPresetInputs; + } else { + return false; + } + } + + return true; } /** RVN END */ @@ -2835,32 +3000,28 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC } bool CWallet::CreateTransactionWithAsset(const std::vector& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut, - std::string& strFailReason, const CCoinControl& coin_control, const CNewAsset& asset, const CTxDestination destination, const std::set& setAssetOutPoints, const AssetType& type, bool sign) + std::string& strFailReason, const CCoinControl& coin_control, const CNewAsset& asset, const CTxDestination destination, const AssetType& type, bool sign) { CReissueAsset reissueAsset; - return CreateTransactionAll(vecSend, wtxNew, reservekey, nFeeRet, nChangePosInOut, strFailReason, coin_control, true, asset, destination, false, setAssetOutPoints, false, reissueAsset, type, sign); + return CreateTransactionAll(vecSend, wtxNew, reservekey, nFeeRet, nChangePosInOut, strFailReason, coin_control, true, asset, destination, false, false, reissueAsset, type, sign); } bool CWallet::CreateTransactionWithTransferAsset(const std::vector& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut, - std::string& strFailReason, const CCoinControl& coin_control, const std::set& setAssetOutPoints, bool sign) + std::string& strFailReason, const CCoinControl& coin_control, bool sign) { - - if (setAssetOutPoints.size() == 0) - return error("%s : Tried transfering an asset and didn't have any asset outpoints selected", __func__); - CNewAsset asset; CReissueAsset reissueAsset; CTxDestination destination; AssetType assetType = AssetType::INVALID; - return CreateTransactionAll(vecSend, wtxNew, reservekey, nFeeRet, nChangePosInOut, strFailReason, coin_control, false, asset, destination, true, setAssetOutPoints, false, reissueAsset, assetType, sign); + return CreateTransactionAll(vecSend, wtxNew, reservekey, nFeeRet, nChangePosInOut, strFailReason, coin_control, false, asset, destination, true, false, reissueAsset, assetType, sign); } bool CWallet::CreateTransactionWithReissueAsset(const std::vector& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut, - std::string& strFailReason, const CCoinControl& coin_control, const CReissueAsset& reissueAsset, const CTxDestination destination, const std::set& setAssetOutPoints, bool sign) + std::string& strFailReason, const CCoinControl& coin_control, const CReissueAsset& reissueAsset, const CTxDestination destination, bool sign) { CNewAsset asset; AssetType assetType = AssetType::REISSUE; - return CreateTransactionAll(vecSend, wtxNew, reservekey, nFeeRet, nChangePosInOut, strFailReason, coin_control, false, asset, destination, false, setAssetOutPoints, true, reissueAsset, assetType, sign); + return CreateTransactionAll(vecSend, wtxNew, reservekey, nFeeRet, nChangePosInOut, strFailReason, coin_control, false, asset, destination, false, true, reissueAsset, assetType, sign); } bool CWallet::CreateTransaction(const std::vector& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut, @@ -2870,32 +3031,25 @@ bool CWallet::CreateTransaction(const std::vector& vecSend, CWalletT CNewAsset asset; CReissueAsset reissueAsset; CTxDestination destination; - std::set setAssetOutPoints; AssetType assetType = AssetType::INVALID; - return CreateTransactionAll(vecSend, wtxNew, reservekey, nFeeRet, nChangePosInOut, strFailReason, coin_control, false, asset, destination, false, setAssetOutPoints, false, reissueAsset, assetType, sign); + return CreateTransactionAll(vecSend, wtxNew, reservekey, nFeeRet, nChangePosInOut, strFailReason, coin_control, false, asset, destination, false, false, reissueAsset, assetType, sign); } bool CWallet::CreateTransactionAll(const std::vector& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, - int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool fNewAsset, const CNewAsset& asset, const CTxDestination destination, bool fTransferAsset, const std::set& setAssetOutPoints, bool fReissueAsset, const CReissueAsset& reissueAsset, const AssetType& assetType, bool sign) + int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool fNewAsset, const CNewAsset& asset, const CTxDestination destination, bool fTransferAsset, bool fReissueAsset, const CReissueAsset& reissueAsset, const AssetType& assetType, bool sign) { if (!AreAssetsDeployed() && (fTransferAsset || fNewAsset || fReissueAsset)) return false; /** RVN START */ - if (fTransferAsset && setAssetOutPoints.size() == 0) - return error("%s : Tried transfering an asset and didn't have any asset outpoints selected", __func__); - if (fNewAsset && (asset.IsNull() || !IsValidDestination(destination))) return error("%s : Tried creating a new asset transaction and the asset was null or the destination was invalid", __func__); if ((fNewAsset && fTransferAsset) || (fReissueAsset && fTransferAsset) || (fReissueAsset && fNewAsset)) return error("%s : Only one type of asset transaction allowed per transaction"); - if (fReissueAsset && setAssetOutPoints.size() == 0) - return error("%s : Tried reissuing an asset and didn't have the owner asset outpoint selected", __func__); - if (fReissueAsset && (reissueAsset.IsNull() || !IsValidDestination(destination))) return error("%s : Tried reissuing an asset and the reissue data was null or the destination was invalid", __func__); /** RVN END */ @@ -2988,7 +3142,7 @@ bool CWallet::CreateTransactionAll(const std::vector& vecSend, CWall std::vector vAvailableCoins; std::map > mapAssetCoins; if (fTransferAsset || fReissueAsset || assetType == AssetType::SUB) - AvailableCoinsWithAssets(vAvailableCoins, mapAssetCoins, setAssetOutPoints, true, true, &coin_control); + AvailableCoinsWithAssets(vAvailableCoins, mapAssetCoins, true, &coin_control); else AvailableCoins(vAvailableCoins, true, &coin_control); /** RVN END */ @@ -3090,6 +3244,8 @@ bool CWallet::CreateTransactionAll(const std::vector& vecSend, CWall /** RVN START */ if (AreAssetsDeployed()) { + setAssets.clear(); + mapAssetsIn.clear(); if (!SelectAssets(mapAssetCoins, mapAssetValue, setAssets, mapAssetsIn)) { strFailReason = _("Insufficient asset funds"); return false; @@ -3410,6 +3566,8 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey, CCon if (!wtxNew.AcceptToMemoryPool(maxTxFee, state)) { LogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", state.GetRejectReason()); // TODO: if we expect the failure to be long term or permanent, instead delete wtx from the wallet and return failure. + AbandonTransaction(wtxNew.tx->GetHash()); + return false; } else { wtxNew.RelayWalletTransaction(connman); } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 25b3892a10..195c3f5d63 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -836,17 +836,35 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface /** * populate vCoins with vector of available COutputs, and populates vAssetCoins in fWithAssets is set to true. */ - void AvailableCoinsAll(std::vector& vCoins, std::map >& mapAssetCoins, const std::set& setAssetOutPoint, bool fWithAssets = false, bool fOnlySafe = true, const CCoinControl *coinControl = nullptr, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t& nMaximumCount = 0, const int& nMinDepth = 0, const int& nMaxDepth = 9999999) const; + void AvailableCoinsAll(std::vector& vCoins, std::map >& mapAssetCoins, + bool fGetRVN = true, bool fOnlyAssets = false, + bool fOnlySafe = true, const CCoinControl *coinControl = nullptr, + const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, + const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t& nMaximumCount = 0, + const int& nMinDepth = 0, const int& nMaxDepth = 9999999) const; /** * Helper function that calls AvailableCoinsAll, used for transfering assets */ - void AvailableCoinsWithAssets(std::vector& vCoins, std::map >& mapAssetCoins, const std::set& setAssetOutPoint, bool fWithAssets = false, bool fOnlySafe = true, const CCoinControl *coinControl = nullptr, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t& nMaximumCount = 0, const int& nMinDepth = 0, const int& nMaxDepth = 9999999) const; + void AvailableAssets(std::map > &mapAssetCoins, bool fOnlySafe = true, + const CCoinControl *coinControl = nullptr, const CAmount &nMinimumAmount = 1, + const CAmount &nMaximumAmount = MAX_MONEY, const CAmount &nMinimumSumAmount = MAX_MONEY, + const uint64_t &nMaximumCount = 0, const int &nMinDepth = 0, const int &nMaxDepth = 9999999) const; + /** + * Helper function that calls AvailableCoinsAll, used to receive all coins, Assets and RVN + */ + void AvailableCoinsWithAssets(std::vector &vCoins, std::map > &mapAssetCoins, + bool fOnlySafe = true, const CCoinControl *coinControl = nullptr, const CAmount &nMinimumAmount = 1, + const CAmount &nMaximumAmount = MAX_MONEY, const CAmount &nMinimumSumAmount = MAX_MONEY, + const uint64_t &nMaximumCount = 0, const int &nMinDepth = 0, const int &nMaxDepth = 9999999) const; /** * populate vCoins with vector of available COutputs. */ - void AvailableCoins(std::vector& vCoins, bool fOnlySafe=true, const CCoinControl *coinControl = nullptr, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t& nMaximumCount = 0, const int& nMinDepth = 0, const int& nMaxDepth = 9999999) const; + void AvailableCoins(std::vector& vCoins, bool fOnlySafe=true, const CCoinControl *coinControl = nullptr, + const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, + const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t& nMaximumCount = 0, + const int& nMinDepth = 0, const int& nMaxDepth = 9999999) const; /** * Return list of available coins and locked coins grouped by non-change output address. @@ -865,6 +883,7 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface * assembled */ bool SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int nConfTheirs, uint64_t nMaxAncestors, std::vector vCoins, std::set& setCoinsRet, CAmount& nValueRet) const; + bool SelectAssetsMinConf(const CAmount& nTargetValue, const int nConfMine, const int nConfTheirs, const uint64_t nMaxAncestors, const std::string& strAssetName, std::vector vCoins, std::set& setCoinsRet, CAmount& nValueRet) const; bool IsSpent(const uint256& hash, unsigned int n) const; @@ -971,13 +990,13 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface /** RVN START */ bool CreateTransactionWithAsset(const std::vector& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut, - std::string& strFailReason, const CCoinControl& coin_control, const CNewAsset& asset, const CTxDestination dest, const std::set& setAssetOutPoints, const AssetType& assetType, bool sign = true); + std::string& strFailReason, const CCoinControl& coin_control, const CNewAsset& asset, const CTxDestination dest, const AssetType& assetType, bool sign = true); bool CreateTransactionWithTransferAsset(const std::vector& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut, - std::string& strFailReason, const CCoinControl& coin_control, const std::set& setAssetOutPoints, bool sign = true); + std::string& strFailReason, const CCoinControl& coin_control, bool sign = true); bool CreateTransactionWithReissueAsset(const std::vector& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut, - std::string& strFailReason, const CCoinControl& coin_control, const CReissueAsset& reissueAsset, const CTxDestination destination, const std::set& setAssetOutPoints, bool sign = true); + std::string& strFailReason, const CCoinControl& coin_control, const CReissueAsset& reissueAsset, const CTxDestination destination, bool sign = true); bool CreateTransaction(const std::vector& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool sign = true); @@ -988,7 +1007,7 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface * @note passing nChangePosInOut as -1 will result in setting a random position */ bool CreateTransactionAll(const std::vector& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut, - std::string& strFailReason, const CCoinControl& coin_control, bool fNewAsset, const CNewAsset& asset, const CTxDestination dest, bool fTransferAsset, const std::set& setAssetOutPoints, bool fReissueAsset, const CReissueAsset& reissueAsset, const AssetType& assetType, bool sign = true); + std::string& strFailReason, const CCoinControl& coin_control, bool fNewAsset, const CNewAsset& asset, const CTxDestination dest, bool fTransferAsset, bool fReissueAsset, const CReissueAsset& reissueAsset, const AssetType& assetType, bool sign = true); /** RVN END */ diff --git a/test/functional/assets.py b/test/functional/assets.py index 7fe4e5bf9b..fcec767f3c 100755 --- a/test/functional/assets.py +++ b/test/functional/assets.py @@ -12,6 +12,8 @@ assert_is_hash_string, ) +import string + class AssetTest(RavenTestFramework): def set_test_params(self): self.setup_clean_chain = True @@ -28,6 +30,8 @@ def run_test(self): n2.generate(431) self.sync_all() assert_equal(n0.getbalance(), 5000) + n0.generate(431) + self.sync_all() self.log.info("Calling issue()...") address0 = n0.getnewaddress() @@ -159,6 +163,44 @@ def run_test(self): assert_equal(raven_assets[1], "RAVEN3") self.sync_all() + self.log.info("Issuing chained assets in depth issue()...") + chain_address = n0.getnewaddress() + chain_string = "CHAIN1" + n0.issue(asset_name=chain_string, qty=1000, to_address=chain_address, change_address="", \ + units=4, reissuable=True, has_ipfs=True, ipfs_hash=ipfs_hash) + + for c in string.ascii_uppercase: + chain_string += '/' + c + if len(chain_string) > 30: + break + n0.issue(asset_name=chain_string, qty=1000, to_address=chain_address, change_address="", \ + units=4, reissuable=True, has_ipfs=True, ipfs_hash=ipfs_hash) + + n0.generate(1) + self.sync_all() + + chain_assets = n1.listassets(asset="CHAIN1*", verbose=False) + print(len(chain_assets)) + assert_equal(len(chain_assets), 13) + + self.log.info("Issuing chained assets in width issue()...") + chain_address = n0.getnewaddress() + chain_string = "CHAIN2" + n0.issue(asset_name=chain_string, qty=1000, to_address=chain_address, change_address="", \ + units=4, reissuable=True, has_ipfs=True, ipfs_hash=ipfs_hash) + + for c in string.ascii_uppercase: + asset_name = chain_string + '/' + c + + n0.issue(asset_name=asset_name, qty=1000, to_address=chain_address, change_address="", \ + units=4, reissuable=True, has_ipfs=True, ipfs_hash=ipfs_hash) + + n0.generate(1) + self.sync_all() + + chain_assets = n1.listassets(asset="CHAIN2/*", verbose=False) + print(len(chain_assets)) + assert_equal(len(chain_assets), 26) if __name__ == '__main__': AssetTest().main() From d836d858ad7047200b630ec6a15a13fa1457be5c Mon Sep 17 00:00:00 2001 From: Mark Ney Date: Mon, 20 Aug 2018 18:20:56 -0600 Subject: [PATCH 003/105] Fix mempool_limit test --- src/test/miner_tests.cpp | 28 ++++++++++++++++++++++++++-- test/config.ini.in | 2 +- test/functional/mempool_limit.py | 7 ++++--- test/functional/test_runner.py | 6 +++--- 4 files changed, 34 insertions(+), 9 deletions(-) diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index eb9a0fc080..fc8fa37795 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -46,7 +46,7 @@ struct { unsigned char extranonce; unsigned int nonce; } blockinfo[] = { - {3, 0x1147b7d}, {1, 0x115c9f0}, {6, 0x11c6644}, {1, 0x1377d45}, + {3, 0x1147b7d}, {1, 0x115c9f0}, {6, 0x11c6644}, {1, 0x1377d45}, {1, 0x1050f1c}, {6, 0x1092cdb}, {5, 0x1201fdf}, {4, 0x1081b87}, {1, 0x10ecd8e}, {5, 0x1124186}, {2, 0x16556d5}, {1, 0x1445631}, {4, 0x13b8cb3}, {6, 0x13bfe16}, {3, 0x154cec8}, {6, 0x11e277f}, @@ -133,7 +133,31 @@ struct { {6, 0x4a135a6a}, {1, 0x6097d9}, {2, 0x57379c00}, {3, 0x6518eec7}, {5, 0x52570c9b}, {4, 0x4a605a0c}, {3, 0x511a59df}, {2, 0x1044545}, {4, 0x49164b13}, {5, 0x31d9f3c8}, {2, 0x22a65438}, {3, 0x1244df94}, - {6, 0xd6b4ae2}, {6, 0x16c76fc6}, //<- 350 total, want 448, need 98 more + {6, 0xd6b4ae2}, {6, 0x16c76fc6}, {1, 0x113d9ca2}, {3, 0x5219e5bd}, + {4, 0x5f5be7e5}, {2, 0x4f4e106d}, {3, 0x5253d40a}, {5, 0x46f8af46}, + {1, 0x32f32dc}, {2, 0x55256c2c}, {2, 0x185bafc6}, {5, 0x268525fd}, + {5, 0x30c76cb9}, {5, 0x2f045e40}, {5, 0x5857479e}, {1, 0x33d5a215}, + {1, 0x11610502}, {6, 0x2fd7145c}, {3, 0x442b9891}, {5, 0x1da4de52}, + {1, 0x1693e77a}, {4, 0x3aecd3c8}, {3, 0x2ac07f4e}, {5, 0x3ce13f5c}, + {6, 0x21730c2}, {1, 0x5d61d76f}, {5, 0x6579b666}, {3, 0x60bf9944}, + {3, 0x35c370ae}, {4, 0xb7d2d99}, {6, 0x4c1ef701}, {3, 0x39c59e0f}, + {1, 0x531a9400}, {2, 0x3be44044}, {2, 0x5b335555}, {1, 0xc66ac80}, + {2, 0x103d444e}, {1, 0x445f840d}, {6, 0x30b41dbc}, {5, 0x531fb3f0}, + {2, 0x1763bbff}, {6, 0xa749665}, {1, 0x58346cdc}, {1, 0x2fb0e8d8}, + {3, 0x628acf5}, {2, 0x2b99fdd0}, {6, 0x196b3765}, {1, 0x36de5152}, + {1, 0x288aac81}, {5, 0x2ea74688}, {6, 0x10c15368}, {4, 0x553a5052}, + {4, 0x113e1a14}, {6, 0xf59df78}, {1, 0x44ea272e}, {6, 0x5254aa32}, + {1, 0xf448ca0}, {5, 0x2bcac04b}, {6, 0x4a04651e}, {6, 0xb4be03a}, + {4, 0x38ef9569}, {5, 0x1e7a1130}, {2, 0x55352321}, {2, 0x6261af8b}, + {4, 0x490d3f21}, {4, 0x1d99468d}, {6, 0x1f6ad569}, {3, 0x5d8df13d}, + {5, 0x6560b11f}, {3, 0x56675bc0}, {5, 0x5c45c62a}, {2, 0x239b329c}, + {1, 0x38cf1221}, {4, 0x38ce517e}, {2, 0x234eeec}, {2, 0x233d6f74}, + {4, 0x45f03db2}, {6, 0x4901d57b}, {3, 0x32cd4a47}, {6, 0x5e628617}, + {1, 0x3e882544}, {1, 0x2fb7c17e}, {3, 0x3af1461c}, {5, 0x31dc024b}, + {3, 0x23b1d0fb}, {6, 0x637fa04a}, {2, 0x36d43eb2}, {6, 0x46ee75b0}, + {5, 0x5c448fe1}, {6, 0x5d6c8c8e}, {3, 0x5645015c}, {1, 0x3ad2d9ae}, + {1, 0x125add93}, {3, 0x64686cc2}, {3, 0x21f43601}, {3, 0x28bcc0b0}, + {1, 0x3bd174b3}, {1, 0x3de01db1}, {4, 0x2385f3ca}, {4, 0x55371d14}, }; CBlockIndex CreateBlockIndex(int nHeight) diff --git a/test/config.ini.in b/test/config.ini.in index 3d136deae0..19835553fe 100644 --- a/test/config.ini.in +++ b/test/config.ini.in @@ -14,6 +14,6 @@ EXEEXT=@EXEEXT@ [components] # Which components are enabled. These are commented out by `configure` if they were disabled when running config. @ENABLE_WALLET_TRUE@ENABLE_WALLET=true -@BUILD_RAVEN_UTILS_TRUE@ENABLE_UTILS=true +@BUILD_RAVEN_CLI_TRUE@ENABLE_UTILS=true @BUILD_RAVEND_TRUE@ENABLE_RAVEND=true @ENABLE_ZMQ_TRUE@ENABLE_ZMQ=true diff --git a/test/functional/mempool_limit.py b/test/functional/mempool_limit.py index 32daef4436..93ac1b716f 100755 --- a/test/functional/mempool_limit.py +++ b/test/functional/mempool_limit.py @@ -12,14 +12,15 @@ class MempoolLimitTest(RavenTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 - self.extra_args = [["-maxmempool=5", "-spendzeroconfchange=0"]] + self.extra_args = [["-maxmempool=9", "-spendzeroconfchange=0"]] + self.thirtyTransactions = 9 #tx are created in groups of 30. Value here will be multiplied by thirty for the number of tx. def run_test(self): txouts = gen_return_txouts() relayfee = self.nodes[0].getnetworkinfo()['relayfee'] txids = [] - utxos = create_confirmed_utxos(relayfee, self.nodes[0], 91) + utxos = create_confirmed_utxos(relayfee, self.nodes[0], self.thirtyTransactions*30) #create a mempool tx that will be evicted us0 = utxos.pop() @@ -34,7 +35,7 @@ def run_test(self): relayfee = self.nodes[0].getnetworkinfo()['relayfee'] base_fee = relayfee*100 - for i in range (3): + for i in range (self.thirtyTransactions): txids.append([]) txids[i] = create_lots_of_big_transactions(self.nodes[0], txouts, utxos[30*i:30*i+30], 30, (i+1)*base_fee) diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 49f120f91d..76a13a9a9e 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -216,7 +216,7 @@ def main(): logging.debug("Temporary test directory at %s" % tmpdir) enable_wallet = config["components"].getboolean("ENABLE_WALLET") - enable_utils = config["components"].getboolean("ENABLE_UTILS") + enable_cli = config["components"].getboolean("ENABLE_UTILS") enable_ravend = config["components"].getboolean("ENABLE_RAVEND") if config["environment"]["EXEEXT"] == ".exe" and not args.force: @@ -225,9 +225,9 @@ def main(): print("Tests currently disabled on Windows by default. Use --force option to enable") sys.exit(0) - if not (enable_wallet and enable_utils and enable_ravend): + if not (enable_wallet and enable_cli and enable_ravend): print("No functional tests to run. Wallet, utils, and ravend must all be enabled") - print("Rerun `configure` with -enable-wallet, -with-utils and -with-daemon and rerun make") + print("Rerun `configure` with --enable-wallet, --with-cli and --with-daemon and rerun make") sys.exit(0) # Build list of tests From 01efaa5c7c344e74abbce22416d310a8ff6a6cfb Mon Sep 17 00:00:00 2001 From: Under <35243703+underdarkskies@users.noreply.github.com> Date: Mon, 20 Aug 2018 21:01:55 -0400 Subject: [PATCH 004/105] invoke python interpreter python scripts should invoke the python interpreter. hopefully these are written in python3, if not adjust. --- assets/tools/issuebulk.py | 1 + assets/tools/signed_promises.py | 1 + 2 files changed, 2 insertions(+) diff --git a/assets/tools/issuebulk.py b/assets/tools/issuebulk.py index 7a2fe08b74..630fc59f11 100644 --- a/assets/tools/issuebulk.py +++ b/assets/tools/issuebulk.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 # Script to issue assets on the Ravencoin platform # Reads from a csv file # Template Google Spreadsheet at: diff --git a/assets/tools/signed_promises.py b/assets/tools/signed_promises.py index 3a75cd8964..06b0f29855 100644 --- a/assets/tools/signed_promises.py +++ b/assets/tools/signed_promises.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 # Script to find signed contract_urls # Reads from a Ravencoin node - make sure its running # Runs through the assets looking for ones with meta data From d050c44ba15f571501d67b0f621376d2c20f2775 Mon Sep 17 00:00:00 2001 From: Under <35243703+underdarkskies@users.noreply.github.com> Date: Tue, 21 Aug 2018 12:18:52 -0400 Subject: [PATCH 005/105] Update Asset RPC man (#239) Changes the descriptors to the standard "numeric" which is used to denote either Float or Integer in the rest of the codebase, as issue and reissue and transfer all accept non integer values --- src/rpc/assets.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/rpc/assets.cpp b/src/rpc/assets.cpp index 0fae439330..ad06429f3c 100644 --- a/src/rpc/assets.cpp +++ b/src/rpc/assets.cpp @@ -66,12 +66,11 @@ UniValue issue(const JSONRPCRequest& request) + AssetActivationWarning() + "\nIssue an asset or subasset with unique name.\n" "Unit as the number of decimals precision for the asset (0 for whole units (\"1\"), 8 for max precision (\"1.00000000\")\n" - "Qty should be whole number.\n" "Reissuable is true/false for whether additional units can be issued by the original issuer.\n" "\nArguments:\n" "1. \"asset_name\" (string, required) a unique name\n" - "2. \"qty\" (integer, required) the number of units to be issued\n" + "2. \"qty\" (numeric, required) the number of units to be issued\n" "3. \"to_address\" (string), optional, default=\"\"), address asset will be sent to, if it is empty, address will be generated for you\n" "4. \"change_address\" (string), optional, default=\"\"), address the the rvn change will be sent to, if it is empty, change address will be generated for you\n" "5. \"units\" (integer, optional, default=8, min=0, max=8), the number of decimals precision for the asset (0 for whole units (\"1\"), 8 for max precision (\"1.00000000\")\n" @@ -515,7 +514,7 @@ UniValue transfer(const JSONRPCRequest& request) "\nArguments:\n" "1. \"asset_name\" (string, required) name of asset\n" - "2. \"qty\" (number, required) number of assets you want to send to the address\n" + "2. \"qty\" (numeric, required) number of assets you want to send to the address\n" "3. \"to_address\" (string, required) address to send the asset to\n" "\nResult:\n" @@ -580,7 +579,7 @@ UniValue reissue(const JSONRPCRequest& request) "\nArguments:\n" "1. \"asset_name\" (string, required) name of asset that is being reissued\n" - "2. \"qty\" (number, required) number of assets to reissue\n" + "2. \"qty\" (numeric, required) number of assets to reissue\n" "3. \"to_address\" (string, required) address to send the asset to\n" "4. \"change_address\" (string, optional) address that the change of the transaction will be sent to\n" "5. \"reissuable\" (boolean, optional, default=true), whether future reissuance is allowed\n" From f2cde1b9d6be613fd7f05458b21020ae4360bc9e Mon Sep 17 00:00:00 2001 From: Mark Ney Date: Tue, 21 Aug 2018 11:12:58 -0600 Subject: [PATCH 006/105] Adding release notes for the 2.0.4 release --- doc/release-notes/release-notes-2.0.4.md | 87 ++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 doc/release-notes/release-notes-2.0.4.md diff --git a/doc/release-notes/release-notes-2.0.4.md b/doc/release-notes/release-notes-2.0.4.md new file mode 100644 index 0000000000..4538349296 --- /dev/null +++ b/doc/release-notes/release-notes-2.0.4.md @@ -0,0 +1,87 @@ +Raven Core version *2.0.4.0* is now available!! +============== + + + + +This is a major release containing bug fixes for 2.0.3.0. It is highly recommended that users +upgrade to this version. + +Please report bugs using the issue tracker at GitHub: + + + +To receive security and update notifications, please subscribe to: + + + +How to Upgrade +============== + +If you are running an older version, shut it down. Wait until it has completely +shut down (which might take a few minutes for older versions), then run the +installer (on Windows) or just copy over `/Applications/Raven-Qt` (on Mac) +or `ravend`/`raven-qt` (on Linux). + +The first time you run version 2.0.4 or higher, your chainstate database may +be converted to a new format, which will take anywhere from a few minutes to +half an hour, depending on the speed of your machine. + +Downgrading warning +============== + +The chainstate database for this release is not compatible with previous +releases, so if you run 2.0.4 and then decide to switch back to any +older version, you will need to run the old release with the `-reindex-chainstate` +option to rebuild the chainstate data structures in the old format. + +If your node has pruning enabled, this will entail re-downloading and +processing the entire blockchain. + +It is not recommended that users downgrade their version. This version contains +changes that *will* fork the chain, users not running 2.0.4 (or later) will be not +participate in this fork process and will be left on the old chain which will not +be valid. + +Compatibility +============== + +Raven Core is extensively tested on multiple operating systems using +the Linux kernel, macOS 10.8+, and Windows Vista and later. 32-bit versions of Windows, +and Windows XP are not supported. + +Raven Core should also work on most other Unix-like systems but is not +frequently tested on them. + +Notable changes +============== + +- Fix testnet chain syncing. +- Increase chaining depth. +- Fix for -txindex after immediate BIP activation +- Fix null pointer bug. +- Fix qt amount formatting. +- Python script for bulk issuance in assets/tools. +- Python script for finding signed meta data. + + +2.0.4.0-b175d7350 Change log +============== + +Changelog available here: + +Credits +============== + +Thanks to everyone who directly contributed to this release: + +- Most importantly - The Raven Community! +- Tron Black +- Jesse Empey +- Jeremy Anderson +- Corbin Fox +- Daben Steele +- Cade Call +- @Roshii +- @underdarkskies +- Mark Ney From 1cbee6c77e3b0754587e8189a3bcd7763c842cdf Mon Sep 17 00:00:00 2001 From: Mark Ney <33036650+spyder46n2@users.noreply.github.com> Date: Tue, 21 Aug 2018 11:24:38 -0600 Subject: [PATCH 007/105] Updating verbage as per cfox's suggestion --- doc/release-notes/release-notes-2.0.4.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/release-notes/release-notes-2.0.4.md b/doc/release-notes/release-notes-2.0.4.md index 4538349296..c5507fbf07 100644 --- a/doc/release-notes/release-notes-2.0.4.md +++ b/doc/release-notes/release-notes-2.0.4.md @@ -40,8 +40,8 @@ processing the entire blockchain. It is not recommended that users downgrade their version. This version contains changes that *will* fork the chain, users not running 2.0.4 (or later) will be not -participate in this fork process and will be left on the old chain which will not -be valid. +be able to participate in this fork process and will be left on the old chain which +will not be valid. Compatibility ============== From f2a0d74c099b4d2bd00d3fa2b930c957cd1de47a Mon Sep 17 00:00:00 2001 From: blondfrogs Date: Wed, 22 Aug 2018 14:30:46 -0600 Subject: [PATCH 008/105] Remove the ability to chain reissue transactions (#245) * Don't allow reissue chaining --- src/assets/assetdb.cpp | 26 +++++++++++++++++++---- src/assets/assetdb.h | 2 ++ src/assets/assets.cpp | 3 +++ src/assets/assets.h | 5 +++++ src/consensus/tx_verify.cpp | 9 +++++++- src/consensus/tx_verify.h | 4 +++- src/init.cpp | 3 +++ src/test/assets/asset_tx_tests.cpp | 17 +++++++++------ src/txmempool.cpp | 13 +++++++++++- src/validation.cpp | 24 ++++++++++++++++++++-- test/functional/assets.py | 33 ++++++++++++++++++++++++++++-- 11 files changed, 122 insertions(+), 17 deletions(-) diff --git a/src/assets/assetdb.cpp b/src/assets/assetdb.cpp index 2658bb785c..d33939c6cb 100644 --- a/src/assets/assetdb.cpp +++ b/src/assets/assetdb.cpp @@ -16,6 +16,7 @@ static const char ASSET_FLAG = 'A'; static const char ASSET_ADDRESS_QUANTITY_FLAG = 'B'; static const char MY_ASSET_FLAG = 'M'; static const char BLOCK_ASSET_UNDO_DATA = 'U'; +static const char MEMPOOL_REISSUED_TX = 'Z'; CAssetsDB::CAssetsDB(size_t nCacheSize, bool fMemory, bool fWipe) : CDBWrapper(GetDataDir() / "assets", nCacheSize, fMemory, fWipe) { } @@ -77,17 +78,34 @@ bool CAssetsDB::WriteBlockUndoAssetData(const uint256& blockhash, const std::vec return Write(std::make_pair(BLOCK_ASSET_UNDO_DATA, blockhash), vIPFSHashes); } -bool CAssetsDB::ReadBlockUndoAssetData(const uint256 &blockhash, - std::vector > &vIPFSHashes) { - +bool CAssetsDB::ReadBlockUndoAssetData(const uint256 &blockhash, std::vector > &vIPFSHashes) +{ // If it exists, return the read value. if (Exists(std::make_pair(BLOCK_ASSET_UNDO_DATA, blockhash))) - return Read(std::make_pair(BLOCK_ASSET_UNDO_DATA, blockhash), vIPFSHashes); + return Read(std::make_pair(BLOCK_ASSET_UNDO_DATA, blockhash), vIPFSHashes); // If it doesn't exist, we just return true because we don't want to fail just because it didn't exist in the db return true; } +bool CAssetsDB::WriteReissuedMempoolState() +{ + return Write(MEMPOOL_REISSUED_TX, mapReissuedAssets); +} + +bool CAssetsDB::ReadReissuedMempoolState() +{ + mapReissuedAssets.clear(); + mapReissuedTx.clear(); + // If it exists, return the read value. + bool rv = Read(MEMPOOL_REISSUED_TX, mapReissuedAssets); + if (rv) { + for (auto pair : mapReissuedAssets) + mapReissuedTx.insert(std::make_pair(pair.second, pair.first)); + } + return rv; +} + bool CAssetsDB::LoadAssets() { std::unique_ptr pcursor(NewIterator()); diff --git a/src/assets/assetdb.h b/src/assets/assetdb.h index 97afb08427..fbbd5e24bf 100644 --- a/src/assets/assetdb.h +++ b/src/assets/assetdb.h @@ -30,12 +30,14 @@ class CAssetsDB : public CDBWrapper bool WriteMyAssetsData(const std::string &strName, const std::set& setOuts); bool WriteAssetAddressQuantity(const std::string& assetName, const std::string& address, const CAmount& quantity); bool WriteBlockUndoAssetData(const uint256& blockhash, const std::vector >& vIPFSHashes); + bool WriteReissuedMempoolState(); // Read from database functions bool ReadAssetData(const std::string& strName, CNewAsset& asset); bool ReadMyAssetsData(const std::string &strName, std::set& setOuts); bool ReadAssetAddressQuantity(const std::string& assetName, const std::string& address, CAmount& quantity); bool ReadBlockUndoAssetData(const uint256& blockhash, std::vector >& vIPFSHashes); + bool ReadReissuedMempoolState(); // Erase from database functions bool EraseAssetData(const std::string& assetName); diff --git a/src/assets/assets.cpp b/src/assets/assets.cpp index 806e128e6b..49c0b59098 100644 --- a/src/assets/assets.cpp +++ b/src/assets/assets.cpp @@ -28,6 +28,9 @@ #include "utilmoneystr.h" #include "coins.h" +std::map mapReissuedTx; +std::map mapReissuedAssets; + // excluding owner tag ('!') static const auto MAX_NAME_LENGTH = 30; diff --git a/src/assets/assets.h b/src/assets/assets.h index 3e3041d253..d603b9e9ad 100644 --- a/src/assets/assets.h +++ b/src/assets/assets.h @@ -49,6 +49,11 @@ struct CAssetOutputEntry; // 50000 * 82 Bytes == 4.1 Mb #define MAX_CACHE_ASSETS_SIZE 50000 +// Create map that store that state of current reissued transaction that the mempool as accepted. +// If an asset name is in this map, any other reissue transactions wont be accepted into the mempool +extern std::map mapReissuedTx; +extern std::map mapReissuedAssets; + class CAssets { public: std::map > mapMyUnspentAssets; // Asset Name -> COutPoint diff --git a/src/consensus/tx_verify.cpp b/src/consensus/tx_verify.cpp index 076fab3039..80a6d772eb 100644 --- a/src/consensus/tx_verify.cpp +++ b/src/consensus/tx_verify.cpp @@ -337,7 +337,7 @@ bool Consensus::CheckTxInputs(const CTransaction& tx, CValidationState& state, c } //! Check to make sure that the inputs and outputs CAmount match exactly. -bool Consensus::CheckTxAssets(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, const bool fRunningUnitTests) +bool Consensus::CheckTxAssets(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, std::vector >& vPairReissueAssets, const bool fRunningUnitTests) { // are the actual inputs available? if (!inputs.HaveInputs(tx)) { @@ -415,6 +415,13 @@ bool Consensus::CheckTxAssets(const CTransaction& tx, CValidationState& state, c "bad-txns" + strError); } } + + if (mapReissuedAssets.count(reissue.strName)) { + if (mapReissuedAssets.at(reissue.strName) != tx.GetHash()) + return state.DoS(100, false, REJECT_INVALID, "bad-tx-reissue-chaining-not-allowed"); + } else { + vPairReissueAssets.emplace_back(std::make_pair(reissue.strName, tx.GetHash())); + } } } diff --git a/src/consensus/tx_verify.h b/src/consensus/tx_verify.h index 2430525666..86898c4083 100644 --- a/src/consensus/tx_verify.h +++ b/src/consensus/tx_verify.h @@ -16,6 +16,8 @@ class CCoinsViewCache; class CTransaction; class CValidationState; class CAssetsCache; +class CTxOut; +class uint256; /** Transaction validation functions */ @@ -32,7 +34,7 @@ namespace Consensus { bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee); /** RVN START */ -bool CheckTxAssets(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, const bool fRunningUnitTests = false); +bool CheckTxAssets(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, std::vector >& vPairReissueAssets, const bool fRunningUnitTests = false); /** RVN END */ } // namespace Consensus diff --git a/src/init.cpp b/src/init.cpp index 613f92f77e..25905fe659 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1456,6 +1456,9 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler) break; } + if (!passetsdb->ReadReissuedMempoolState()) + LogPrintf("Database failed to load last Reissued Mempool State. Will have to start from empty state"); + LogPrintf("Loaded Assets from database without error\nCache of assets size: %d\nNumber of assets I have: %d\n", passetsCache->Size(), passets->mapMyUnspentAssets.size()); if (fReset) { diff --git a/src/test/assets/asset_tx_tests.cpp b/src/test/assets/asset_tx_tests.cpp index ee9c18f051..5355150797 100644 --- a/src/test/assets/asset_tx_tests.cpp +++ b/src/test/assets/asset_tx_tests.cpp @@ -56,7 +56,8 @@ BOOST_FIXTURE_TEST_SUITE(asset_tx_tests, BasicTestingSetup) // The inputs are spending 1000 Assets // The outputs are assigning a destination to 1000 Assets // This test should pass because all assets are assigned a destination - BOOST_CHECK_MESSAGE(Consensus::CheckTxAssets(tx, state, coins, true), "CheckTxAssets Failed"); + std::vector> vReissueAssets; + BOOST_CHECK_MESSAGE(Consensus::CheckTxAssets(tx, state, coins, vReissueAssets, true), "CheckTxAssets Failed"); } BOOST_AUTO_TEST_CASE(asset_tx_not_valid) { @@ -109,7 +110,8 @@ BOOST_FIXTURE_TEST_SUITE(asset_tx_tests, BasicTestingSetup) // The inputs of this transaction are spending 1000 Assets // The outputs are assigning a destination to only 100 Assets // This should fail because 900 Assets aren't being assigned a destination (Trying to burn 900 Assets) - BOOST_CHECK_MESSAGE(!Consensus::CheckTxAssets(tx, state, coins, true), "CheckTxAssets should of failed"); + std::vector> vReissueAssets; + BOOST_CHECK_MESSAGE(!Consensus::CheckTxAssets(tx, state, coins, vReissueAssets, true), "CheckTxAssets should of failed"); } BOOST_AUTO_TEST_CASE(asset_tx_valid_multiple_outs) { @@ -165,7 +167,8 @@ BOOST_FIXTURE_TEST_SUITE(asset_tx_tests, BasicTestingSetup) // The inputs are spending 1000 Assets // The outputs are assigned 100 Assets to 10 destinations (10 * 100) = 1000 // This test should pass all assets that are being spent are assigned to a destination - BOOST_CHECK_MESSAGE(Consensus::CheckTxAssets(tx, state, coins, true), "CheckTxAssets failed"); + std::vector> vReissueAssets; + BOOST_CHECK_MESSAGE(Consensus::CheckTxAssets(tx, state, coins, vReissueAssets, true), "CheckTxAssets failed"); } BOOST_AUTO_TEST_CASE(asset_tx_multiple_outs_invalid) { @@ -221,7 +224,8 @@ BOOST_FIXTURE_TEST_SUITE(asset_tx_tests, BasicTestingSetup) // The inputs are spending 1000 Assets // The outputs are assigning 100 Assets to 12 destinations (12 * 100 = 1200) // This test should fail because the Outputs are greater than the inputs - BOOST_CHECK_MESSAGE(!Consensus::CheckTxAssets(tx, state, coins, true), "CheckTxAssets passed when it should of failed"); + std::vector> vReissueAssets; + BOOST_CHECK_MESSAGE(!Consensus::CheckTxAssets(tx, state, coins, vReissueAssets, true), "CheckTxAssets passed when it should of failed"); } BOOST_AUTO_TEST_CASE(asset_tx_multiple_assets) { @@ -336,7 +340,8 @@ BOOST_FIXTURE_TEST_SUITE(asset_tx_tests, BasicTestingSetup) // The inputs are spending 3000 Assets (1000 of each RAVEN, RAVENTEST, RAVENTESTTEST) // The outputs are spending 100 Assets to 10 destinations (10 * 100 = 1000) (of each RAVEN, RAVENTEST, RAVENTESTTEST) // This test should pass because for each asset that is spent. It is assigned a destination - BOOST_CHECK_MESSAGE(Consensus::CheckTxAssets(tx, state, coins, true), "CheckTxAssets Failed"); + std::vector> vReissueAssets; + BOOST_CHECK_MESSAGE(Consensus::CheckTxAssets(tx, state, coins, vReissueAssets, true), "CheckTxAssets Failed"); // Try it not but only spend 900 of each asset instead of 1000 @@ -388,7 +393,7 @@ BOOST_FIXTURE_TEST_SUITE(asset_tx_tests, BasicTestingSetup) // Check the transaction that contains inputs that are spending 1000 Assets for 3 different assets // While only outputs only contain 900 Assets being sent to a destination // This should fail because 100 of each Asset isn't being sent to a destination (Trying to burn 100 Assets each) - BOOST_CHECK_MESSAGE(!Consensus::CheckTxAssets(tx2, state, coins, true), "CheckTxAssets should of failed"); + BOOST_CHECK_MESSAGE(!Consensus::CheckTxAssets(tx2, state, coins, vReissueAssets, true), "CheckTxAssets should of failed"); } BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/src/txmempool.cpp b/src/txmempool.cpp index 394b92bab9..4d85378ca5 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -597,6 +597,16 @@ void CTxMemPool::removeUnchecked(txiter it, MemPoolRemovalReason reason) if (minerPolicyEstimator) {minerPolicyEstimator->removeTx(hash, false);} removeAddressIndex(hash); removeSpentIndex(hash); + + /** RVN START */ + // If the transaction being removed from the mempool is locking other reissues. Free them + if (mapReissuedTx.count(hash)) { + if (mapReissuedAssets.count(mapReissuedTx.at(hash))) { + mapReissuedAssets.erase(mapReissuedTx.at((hash))); + mapReissuedTx.erase(hash); + } + } + /** RVN END */ } // Calculates descendants of entry that are not already in setDescendants, and adds to @@ -771,7 +781,8 @@ static void CheckInputsAndUpdateCoins(const CTransaction& tx, CCoinsViewCache& m bool fCheckResult = tx.IsCoinBase() || Consensus::CheckTxInputs(tx, state, mempoolDuplicate, spendheight, txfee); /** RVN START */ if (AreAssetsDeployed()) { - bool fCheckAssets = Consensus::CheckTxAssets(tx, state, mempoolDuplicate); + std::vector> vReissueAssets; + bool fCheckAssets = Consensus::CheckTxAssets(tx, state, mempoolDuplicate, vReissueAssets); assert(fCheckResult && fCheckAssets); } else assert(fCheckResult); diff --git a/src/validation.cpp b/src/validation.cpp index 52310e2bc4..90736ac227 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -465,6 +465,9 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool { const CTransaction& tx = *ptx; const uint256 hash = tx.GetHash(); + + /** RVN START */ + std::vector> vReissueAssets; AssertLockHeld(cs_main); if (pfMissingInputs) *pfMissingInputs = false; @@ -605,7 +608,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool } if (AreAssetsDeployed()) { - if (!Consensus::CheckTxAssets(tx, state, view)) + if (!Consensus::CheckTxAssets(tx, state, view, vReissueAssets)) return error("%s: Consensus::CheckTxAssets: %s, %s", __func__, tx.GetHash().ToString(), FormatStateMessage(state)); } @@ -913,6 +916,11 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool if (!pool.exists(hash)) return state.DoS(0, false, REJECT_INSUFFICIENTFEE, "mempool full"); } + + for (auto out : vReissueAssets) { + mapReissuedAssets.insert(out); + mapReissuedTx.insert(std::make_pair(out.second, out.first)); + } } GetMainSignals().TransactionAddedToMempool(ptx); @@ -2104,7 +2112,8 @@ static bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockInd } if (AreAssetsDeployed()) { - if (!Consensus::CheckTxAssets(tx, state, view)) { + std::vector> vReissueAssets; + if (!Consensus::CheckTxAssets(tx, state, view, vReissueAssets)) { return error("%s: Consensus::CheckTxAssets: %s, %s", __func__, tx.GetHash().ToString(), FormatStateMessage(state)); } @@ -2492,6 +2501,9 @@ bool static FlushStateToDisk(const CChainParams& chainparams, CValidationState & return AbortNode(state, "Failed to write to asset database"); } } + // Write the reissue mempool data to database + if (passetsdb) + passetsdb->WriteReissuedMempoolState(); /** RVN END */ nLastFlush = nNow; @@ -2754,6 +2766,14 @@ bool static ConnectTip(CValidationState& state, const CChainParams& chainparams, InvalidBlockFound(pindexNew, state); return error("ConnectTip(): ConnectBlock %s failed", pindexNew->GetBlockHash().ToString()); } + + for (auto tx : blockConnecting.vtx) { + uint256 txHash = tx->GetHash(); + if (mapReissuedTx.count(txHash)) { + mapReissuedAssets.erase(mapReissuedTx.at(txHash)); + mapReissuedTx.erase(txHash); + } + } nTime3 = GetTimeMicros(); nTimeConnectTotal += nTime3 - nTime2; LogPrint(BCLog::BENCH, " - Connect total: %.2fms [%.2fs (%.2fms/blk)]\n", (nTime3 - nTime2) * MILLI, nTimeConnectTotal * MICRO, nTimeConnectTotal * MILLI / nBlocksTotal); bool flushed = view.Flush(); diff --git a/test/functional/assets.py b/test/functional/assets.py index fcec767f3c..75b5a99fd4 100755 --- a/test/functional/assets.py +++ b/test/functional/assets.py @@ -10,6 +10,7 @@ from test_framework.util import ( assert_equal, assert_is_hash_string, + assert_raises_rpc_error ) import string @@ -180,7 +181,6 @@ def run_test(self): self.sync_all() chain_assets = n1.listassets(asset="CHAIN1*", verbose=False) - print(len(chain_assets)) assert_equal(len(chain_assets), 13) self.log.info("Issuing chained assets in width issue()...") @@ -199,8 +199,37 @@ def run_test(self): self.sync_all() chain_assets = n1.listassets(asset="CHAIN2/*", verbose=False) - print(len(chain_assets)) assert_equal(len(chain_assets), 26) + + self.log.info("Chaining reissue transactions...") + address0 = n0.getnewaddress() + n0.issue(asset_name="CHAIN_REISSUE", qty=1000, to_address=address0, change_address="", \ + units=4, reissuable=True, has_ipfs=False) + + n0.generate(1) + self.sync_all() + + n0.reissue(asset_name="CHAIN_REISSUE", qty=1000, to_address=address0, change_address="", \ + reissuable=True) + assert_raises_rpc_error(-4, "Error: The transaction was rejected! Reason given: bad-tx-reissue-chaining-not-allowed", n0.reissue, "CHAIN_REISSUE", 1000, address0, "", True) + + n0.generate(1) + self.sync_all() + + n0.reissue(asset_name="CHAIN_REISSUE", qty=1000, to_address=address0, change_address="", \ + reissuable=True) + + n0.generate(1) + self.sync_all() + + assetdata = n0.getassetdata("CHAIN_REISSUE") + assert_equal(assetdata["name"], "CHAIN_REISSUE") + assert_equal(assetdata["amount"], 3000) + assert_equal(assetdata["units"], 4) + assert_equal(assetdata["reissuable"], 1) + assert_equal(assetdata["has_ipfs"], 0) + + if __name__ == '__main__': AssetTest().main() From 393aa8ab76bc24e47510da749f5f89e27e1ae9e6 Mon Sep 17 00:00:00 2001 From: blondfrogs Date: Wed, 22 Aug 2018 15:00:59 -0600 Subject: [PATCH 009/105] Add ability to create assets of other types with the GUI (#243) * Add ability to create assets of other types with the GUI --- src/assets/assets.cpp | 18 +-- src/assets/assets.h | 2 +- src/assets/assettypes.h | 7 ++ src/qt/createassetdialog.cpp | 187 +++++++++++++++++++++++++++--- src/qt/createassetdialog.h | 11 +- src/qt/forms/createassetdialog.ui | 45 ++++++- src/qt/reissueassetdialog.cpp | 2 +- src/qt/sendassetsentry.cpp | 2 +- 8 files changed, 246 insertions(+), 28 deletions(-) diff --git a/src/assets/assets.cpp b/src/assets/assets.cpp index 49c0b59098..eb5865554a 100644 --- a/src/assets/assets.cpp +++ b/src/assets/assets.cpp @@ -849,10 +849,7 @@ bool CAssetsCache::TrySpendCoin(const COutPoint& out, const CTxOut& txOut) // Update the cache so we can save to database vSpentAssets.push_back(spend); - } else { - return error("%s : ERROR Failed to find current assets address amount. Asset %s: , Address : %s", __func__, assetName, address); } - } else { return error("%s : ERROR Failed to get asset from the OutPoint: %s", __func__, out.ToString()); } @@ -1955,12 +1952,17 @@ void GetAssetData(const CScript& script, CAssetOutputEntry& data) } } -void GetAllOwnedAssets(std::vector& names) +void GetAllOwnedAssets(CWallet* pwallet, std::vector& names) { - for (auto owned : passets->mapMyUnspentAssets) { - if (IsAssetNameAnOwner(owned.first)) { - names.emplace_back(owned.first); - } + if(!pwallet) + return; + + std::map > mapAssets; + pwallet->AvailableAssets(mapAssets); + + for (auto item : mapAssets) { + if (IsAssetNameAnOwner(item.first)) + names.emplace_back(item.first); } } diff --git a/src/assets/assets.h b/src/assets/assets.h index d603b9e9ad..315310b9a3 100644 --- a/src/assets/assets.h +++ b/src/assets/assets.h @@ -343,7 +343,7 @@ bool IsScriptTransferAsset(const CScript& scriptPubKey); bool IsNewOwnerTxValid(const CTransaction& tx, const std::string& assetName, const std::string& address, std::string& errorMsg); -void GetAllOwnedAssets(std::vector& names); +void GetAllOwnedAssets(CWallet* pwallet, std::vector& names); void GetAllMyAssets(std::vector& names); void UpdatePossibleAssets(); diff --git a/src/assets/assettypes.h b/src/assets/assettypes.h index f304703cf4..c64c29db57 100644 --- a/src/assets/assettypes.h +++ b/src/assets/assettypes.h @@ -27,6 +27,13 @@ enum AssetType REISSUE }; +enum IssueAssetType +{ + ISSUE_ROOT = 0, + ISSUE_SUB = 1, + ISSUE_UNIQUE = 2 +}; + class CNewAsset { public: std::string strName; // MAX 31 Bytes diff --git a/src/qt/createassetdialog.cpp b/src/qt/createassetdialog.cpp index 3bf963a30b..572b8717cc 100644 --- a/src/qt/createassetdialog.cpp +++ b/src/qt/createassetdialog.cpp @@ -41,9 +41,13 @@ CreateAssetDialog::CreateAssetDialog(const PlatformStyle *_platformStyle, QWidge connect(ui->unitBox, SIGNAL(valueChanged(int)), this, SLOT(onUnitChanged(int))); connect(ui->rvnChangeBox, SIGNAL(clicked()), this, SLOT(onCustomAddressClicked())); connect(ui->changeAddressText, SIGNAL(textChanged(QString)), this, SLOT(onChangeAddressChanged(QString))); + connect(ui->assetType, SIGNAL(activated(int)), this, SLOT(onAssetTypeActivated(int))); + connect(ui->assetList, SIGNAL(activated(int)), this, SLOT(onAssetListActivated(int))); // Setup the default values setUpValues(); + + format = "%1%2%3"; } CreateAssetDialog::~CreateAssetDialog() @@ -67,6 +71,30 @@ void CreateAssetDialog::setUpValues() CheckFormState(); ui->unitExampleLabel->setStyleSheet("font-weight: bold"); + + // Setup the asset types + QStringList list; + list.append(tr("Main Asset")); + list.append(tr("Sub Asset")); +// list.append(tr("Unique Asset")); + ui->assetType->addItems(list); + type = ISSUE_ROOT; + ui->assetTypeLabel->setText(tr("Asset Type") + ":"); + + // Setup the asset list + ui->assetList->hide(); + std::vector names; + GetAllOwnedAssets(model->getWallet(), names); + for (auto item : names) { + std::string name = QString::fromStdString(item).split("!").first().toStdString(); + if (name.size() != 30) + ui->assetList->addItem(QString::fromStdString(name)); + } + ui->assetFullName->setTextFormat(Qt::RichText); + ui->assetFullName->setStyleSheet("font-weight: bold"); + + ui->assetType->setStyleSheet("font-weight: bold"); + } void CreateAssetDialog::toggleIPFSText() @@ -129,8 +157,19 @@ void CreateAssetDialog::CheckFormState() const CTxDestination dest = DecodeDestination(ui->addressText->text().toStdString()); + QString name = GetAssetName(); + AssetType assetType; - bool assetNameValid = IsAssetNameValid(ui->nameText->text().toStdString(), assetType) && assetType == AssetType::ROOT; + bool assetNameValid = IsAssetNameValid(name.toStdString(), assetType); + if (assetNameValid && assetType == ROOT && type != ISSUE_ROOT) + return; + + if (assetNameValid && assetType == SUB && type != ISSUE_SUB) + return; + + if (assetNameValid && assetType == UNIQUE && type != ISSUE_UNIQUE) + return; + if (!(IsValidDestination(dest) || ui->addressText->text().isEmpty()) && assetNameValid) { ui->addressText->setStyleSheet("border: 1px solid red"); showMessage("Warning: Invalid Raven address"); @@ -170,7 +209,7 @@ void CreateAssetDialog::ipfsStateChanged() void CreateAssetDialog::checkAvailabilityClicked() { - QString name = ui->nameText->text(); + QString name = GetAssetName(); LOCK(cs_main); if (passets) { @@ -197,26 +236,60 @@ void CreateAssetDialog::checkAvailabilityClicked() void CreateAssetDialog::onNameChanged(QString name) { + // Update the displayed name to uppercase if the type only accepts uppercase + name = name.toUpper(); + UpdateAssetNameToUpper(); + + QString assetName = name; + + // Get the identifier for the asset type + QString identifier = GetSpecialCharacter(); + if (name.size() == 0) { hideMessage(); + ui->availabilityButton->setDisabled(true); + updatePresentedAssetName(name); return; } - if (name.size() < 3) { - ui->nameText->setStyleSheet("border: 1px solid red"); - showMessage("Invalid: Minimum of 3 character in length"); - return; - } + if (type == ISSUE_ROOT) { + if (name.size() < 3) { + ui->nameText->setStyleSheet("border: 1px solid red"); + showMessage("Invalid: Minimum of 3 character in length"); + ui->availabilityButton->setDisabled(true); + return; + } - AssetType assetType; - if (!IsAssetNameValid(name.toStdString(), assetType) || assetType != AssetType::ROOT) { - ui->nameText->setStyleSheet("border: 1px solid red"); - showMessage("Invalid: Max Size 30 Characters. Allowed characters include: A-Z 0-9 . _"); - } else { - hideMessage(); - ui->availabilityButton->setDisabled(false); + AssetType assetType; + if (IsAssetNameValid(name.toStdString(), assetType) && assetType == ROOT) { + hideMessage(); + ui->availabilityButton->setDisabled(false); + + } else { + ui->nameText->setStyleSheet("border: 1px solid red"); + showMessage("Invalid: Max Size 30 Characters. Allowed characters include: A-Z 0-9 . _"); + } + } else if (type == ISSUE_SUB || type == ISSUE_UNIQUE) { + if (name.size() == 0) { + hideMessage(); + ui->availabilityButton->setDisabled(true); + return; + } + + AssetType assetType; + if (IsAssetNameValid(ui->assetList->currentText().toStdString() + identifier.toStdString() + name.toStdString(), assetType) && (assetType == SUB || assetType == UNIQUE)) { + hideMessage(); + ui->availabilityButton->setDisabled(false); + } else { + ui->nameText->setStyleSheet("border: 1px solid red"); + showMessage("Invalid: Max Size 30 Characters. Allowed characters include: A-Z 0-9 . _"); + ui->availabilityButton->setDisabled(true); + } } + // Set the assetName + updatePresentedAssetName(format.arg(type == ISSUE_ROOT ? "" : ui->assetList->currentText(), identifier, name)); + checkedAvailablity = false; disableCreateButton(); } @@ -244,8 +317,8 @@ void CreateAssetDialog::onCreateAssetClicked() } else { address = ui->addressText->text(); } + QString name = GetAssetName(); - QString name = ui->nameText->text(); CAmount quantity = ui->quantitySpinBox->value() * COIN; int units = ui->unitBox->value(); bool reissuable = ui->reissuableBox->isChecked(); @@ -398,4 +471,88 @@ void CreateAssetDialog::onCustomAddressClicked() void CreateAssetDialog::onChangeAddressChanged(QString changeAddress) { CheckFormState(); +} + +void CreateAssetDialog::onAssetTypeActivated(int index) +{ + // Update the selected type + type = index; + + // Get the identifier for the asset type + QString identifier = GetSpecialCharacter(); + + if (index != 0) { + ui->assetList->show(); + } else { + ui->assetList->hide(); + } + + UpdateAssetNameMaxSize(); + + // Set assetName + updatePresentedAssetName(format.arg(type == ISSUE_ROOT ? "" : ui->assetList->currentText(), identifier, ui->nameText->text())); + + if (ui->nameText->text().size()) + ui->availabilityButton->setDisabled(false); + else + ui->availabilityButton->setDisabled(true); + ui->createAssetButton->setDisabled(true); +} + +void CreateAssetDialog::onAssetListActivated(int index) +{ + // Get the identifier for the asset type + QString identifier = GetSpecialCharacter(); + + UpdateAssetNameMaxSize(); + + // Set assetName + updatePresentedAssetName(format.arg(type == ISSUE_ROOT ? "" : ui->assetList->currentText(), identifier, ui->nameText->text())); + + if (ui->nameText->text().size()) + ui->availabilityButton->setDisabled(false); + else + ui->availabilityButton->setDisabled(true); + ui->createAssetButton->setDisabled(true); +} + +void CreateAssetDialog::updatePresentedAssetName(QString name) +{ + ui->assetFullName->setText(name); +} + +QString CreateAssetDialog::GetSpecialCharacter() +{ + if (type == ISSUE_SUB) + return "/"; + else if (type == ISSUE_UNIQUE) + return "#"; + + return ""; +} + +QString CreateAssetDialog::GetAssetName() +{ + if (type == ISSUE_ROOT) + return ui->nameText->text(); + else if (type == ISSUE_SUB) + return ui->assetList->currentText() + "/" + ui->nameText->text(); + else if (type == ISSUE_UNIQUE) + return ui->assetList->currentText() + "#" + ui->nameText->text(); +} + +void CreateAssetDialog::UpdateAssetNameMaxSize() +{ + if (type == ISSUE_ROOT) { + ui->nameText->setMaxLength(30); + } else if (type == ISSUE_SUB || type == ISSUE_UNIQUE) { + ui->nameText->setMaxLength(30 - (ui->assetList->currentText().size() + 1)); + } +} + +void CreateAssetDialog::UpdateAssetNameToUpper() +{ + if (type == ISSUE_ROOT || type == ISSUE_SUB) { + ui->nameText->setText(ui->nameText->text().toUpper()); + } } \ No newline at end of file diff --git a/src/qt/createassetdialog.h b/src/qt/createassetdialog.h index 060fb72cce..681a538c2f 100644 --- a/src/qt/createassetdialog.h +++ b/src/qt/createassetdialog.h @@ -28,6 +28,9 @@ Q_OBJECT explicit CreateAssetDialog(const PlatformStyle *platformStyle, QWidget *parent = 0, WalletModel *model = NULL); ~CreateAssetDialog(); + int type; + QString format; + private: Ui::CreateAssetDialog *ui; WalletModel *model; @@ -35,7 +38,6 @@ Q_OBJECT bool checkedAvailablity = false; - void toggleIPFSText(); void setUpValues(); void showMessage(QString string); @@ -44,6 +46,11 @@ Q_OBJECT void disableCreateButton(); void enableCreateButton(); void CheckFormState(); + void updatePresentedAssetName(QString name); + QString GetSpecialCharacter(); + QString GetAssetName(); + void UpdateAssetNameMaxSize(); + void UpdateAssetNameToUpper(); private Q_SLOTS: void ipfsStateChanged(); @@ -56,6 +63,8 @@ private Q_SLOTS: void onUnitChanged(int value); void onCustomAddressClicked(); void onChangeAddressChanged(QString changeAddress); + void onAssetTypeActivated(int index); + void onAssetListActivated(int index); }; #endif // RAVEN_QT_CREATEASSETDIALOG_H diff --git a/src/qt/forms/createassetdialog.ui b/src/qt/forms/createassetdialog.ui index 7332617c83..2f7f9c7403 100644 --- a/src/qt/forms/createassetdialog.ui +++ b/src/qt/forms/createassetdialog.ui @@ -7,13 +7,43 @@ 0 0 1001 - 436 + 516 Transaction details + + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + @@ -28,11 +58,24 @@ A-Z 0-9 and . or _ as the second character + + 30 + The name of the asset you would like to create + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + diff --git a/src/qt/reissueassetdialog.cpp b/src/qt/reissueassetdialog.cpp index 120043fa16..5eab53e04a 100644 --- a/src/qt/reissueassetdialog.cpp +++ b/src/qt/reissueassetdialog.cpp @@ -71,7 +71,7 @@ void ReissueAssetDialog::setUpValues() LOCK(cs_main); std::vector assets; - GetAllOwnedAssets(assets); + GetAllOwnedAssets(model->getWallet(), assets); ui->comboBox->addItem("Select an asset"); diff --git a/src/qt/sendassetsentry.cpp b/src/qt/sendassetsentry.cpp index 927a6f587f..ce7c5beb63 100644 --- a/src/qt/sendassetsentry.cpp +++ b/src/qt/sendassetsentry.cpp @@ -309,7 +309,7 @@ void SendAssetsEntry::onSendOwnershipChanged() if (ui->ownerCheckBox->isChecked()) { LOCK(cs_main); std::vector names; - GetAllOwnedAssets(names); + GetAllOwnedAssets(model->getWallet(), names); QStringList list; for (auto name: names) From 16ed3ca602716bdd40f0de5ea0f5da96c03fc841 Mon Sep 17 00:00:00 2001 From: blondfrogs Date: Thu, 23 Aug 2018 10:36:06 -0600 Subject: [PATCH 010/105] Check Units when issuing assets (#249) --- src/assets/assets.cpp | 60 ++++++++++++++++++++++-------- src/assets/assets.h | 2 + src/consensus/tx_verify.cpp | 5 +-- src/test/assets/asset_tx_tests.cpp | 58 +++++++++++++++++++++++++++++ 4 files changed, 107 insertions(+), 18 deletions(-) diff --git a/src/assets/assets.cpp b/src/assets/assets.cpp index eb5865554a..b37f7f218b 100644 --- a/src/assets/assets.cpp +++ b/src/assets/assets.cpp @@ -239,28 +239,52 @@ bool CNewAsset::IsValid(std::string& strError, CAssetsCache& assetCache, bool fC } } - if (!IsAssetNameValid(std::string(strName))) + if (!IsAssetNameValid(std::string(strName))) { strError = "Invalid parameter: asset_name must only consist of valid characters and have a size between 3 and 30 characters. See help for more details."; + return false; + } - if (IsAssetNameAnOwner(std::string(strName))) + if (IsAssetNameAnOwner(std::string(strName))) { strError = "Invalid parameters: asset_name can't have a '!' at the end of it. See help for more details."; + return false; + } - if (nAmount <= 0) - strError = "Invalid parameter: asset amount can't be equal to or less than zero."; + if (nAmount <= 0) { + strError = "Invalid parameter: asset amount can't be equal to or less than zero."; + return false; + } - if (units < 0 || units > 8) - strError = "Invalid parameter: units must be between 0-8."; + if (nAmount > MAX_MONEY) { + strError = "Invalid parameter: asset amount greater than max money: " + MAX_MONEY / COIN; + return false; + } - if (nReissuable != 0 && nReissuable != 1) - strError = "Invalid parameter: reissuable must be 0 or 1"; + if (units < 0 || units > 8) { + strError = "Invalid parameter: units must be between 0-8."; + return false; + } - if (nHasIPFS != 0 && nHasIPFS != 1) - strError = "Invalid parameter: has_ipfs must be 0 or 1."; + if (!CheckAmountWithUnits(nAmount, units)) { + strError = "Invalid parameter: amount must be divisible by the smaller unit assigned to the asset"; + return false; + } - if (nHasIPFS && strIPFSHash.size() != 34) - strError = "Invalid parameter: ipfs_hash must be 34 bytes."; + if (nReissuable != 0 && nReissuable != 1) { + strError = "Invalid parameter: reissuable must be 0 or 1"; + return false; + } - return strError == ""; + if (nHasIPFS != 0 && nHasIPFS != 1) { + strError = "Invalid parameter: has_ipfs must be 0 or 1."; + return false; + } + + if (nHasIPFS && strIPFSHash.size() != 34) { + strError = "Invalid parameter: ipfs_hash must be 34 bytes."; + return false; + } + + return true; } CNewAsset::CNewAsset(const CNewAsset& asset) @@ -696,8 +720,8 @@ bool CReissueAsset::IsValid(std::string &strError, CAssetsCache& assetCache) con return false; } - if (nAmount % int64_t(pow(10, (MAX_UNIT - asset.units))) != 0) { - strError = "Unable to reissue asset: amount must be divisable by the smaller unit assigned to the asset"; + if (!CheckAmountWithUnits(nAmount, asset.units)) { + strError = "Unable to reissue asset: amount must be divisible by the smaller unit assigned to the asset"; return false; } @@ -2472,4 +2496,10 @@ bool VerifyWalletHasAsset(const std::string& asset_name, std::pair Date: Thu, 23 Aug 2018 15:22:25 -0600 Subject: [PATCH 011/105] Fixing Linux build errror - missing string include in header --- src/consensus/tx_verify.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/consensus/tx_verify.h b/src/consensus/tx_verify.h index 86898c4083..1716d3c268 100644 --- a/src/consensus/tx_verify.h +++ b/src/consensus/tx_verify.h @@ -10,6 +10,7 @@ #include #include +#include class CBlockIndex; class CCoinsViewCache; From b44ff458a1048854bde1739aa5d85af0e2416e34 Mon Sep 17 00:00:00 2001 From: Corbin Fox Date: Mon, 27 Aug 2018 11:14:43 -0600 Subject: [PATCH 012/105] unique assets (#244) * implements unique assets via createrawtransaction, issue and issueunique RPC calls * changes some of the parameter defaults/optionality in issue --- src/assets/assets.cpp | 195 +++++++++++++++++++++--- src/assets/assets.h | 9 ++ src/assets/assettypes.h | 14 ++ src/coins.cpp | 21 +++ src/consensus/tx_verify.cpp | 58 ++++++- src/primitives/transaction.h | 2 + src/rpc/assets.cpp | 183 ++++++++++++++++++++-- src/rpc/client.cpp | 2 + src/rpc/rawtransaction.cpp | 90 ++++++++++- src/validation.cpp | 63 +++++++- src/wallet/wallet.cpp | 61 +++++--- src/wallet/wallet.h | 8 +- test/functional/assets.py | 52 +++++-- test/functional/rawassettransactions.py | 144 ++++++++++++----- test/functional/test_framework/util.py | 12 ++ test/functional/unique_assets.py | 148 ++++++++++++++++++ 16 files changed, 951 insertions(+), 111 deletions(-) create mode 100755 test/functional/unique_assets.py diff --git a/src/assets/assets.cpp b/src/assets/assets.cpp index b37f7f218b..49268f91ff 100644 --- a/src/assets/assets.cpp +++ b/src/assets/assets.cpp @@ -207,6 +207,17 @@ std::string GetParentName(const std::string& name) return name; } +std::string GetUniqueAssetName(const std::string& parent, const std::string& tag) +{ + if (!IsRootNameValid(parent)) + return ""; + + if (!IsUniqueTagValid(tag)) + return ""; + + return parent + "#" + tag; +} + bool CNewAsset::IsNull() const { return strName == ""; @@ -542,6 +553,10 @@ bool CTransaction::IsNewAsset() const if (!CheckIssueDataTx(vout[vout.size() - 1])) return false; + // Don't overlap with IsNewUniqueAsset() + if (IsScriptNewUniqueAsset(vout[vout.size() - 1].scriptPubKey)) + return false; + return true; } @@ -584,6 +599,95 @@ bool CTransaction::VerifyNewAsset() const return false; } +bool CTransaction::IsNewUniqueAsset() const +{ + // Check trailing outpoint for issue data with unique asset name + if (!CheckIssueDataTx(vout[vout.size() - 1])) + return false; + + if (!IsScriptNewUniqueAsset(vout[vout.size() - 1].scriptPubKey)) + return false; + + return true; +} + +bool CTransaction::VerifyNewUniqueAsset(CCoinsViewCache& view) const +{ + // Must contain at least 3 outpoints (RVN burn, owner change and one or more new unique assets that share a root (should be in trailing position)) + if (vout.size() < 3) + return false; + + // check for (and count) new unique asset outpoints. make sure they share a root. + std::string assetRoot = ""; + int assetOutpointCount = 0; + for (auto out : vout) { + if (IsScriptNewUniqueAsset(out.scriptPubKey)) { + CNewAsset asset; + std::string address; + if (!AssetFromScript(out.scriptPubKey, asset, address)) + return false; + std::string root = GetParentName(asset.strName); + if (assetRoot.compare("") == 0) + assetRoot = root; + if (assetRoot.compare(root) != 0) + return false; + assetOutpointCount += 1; + } + } + if (assetOutpointCount == 0) + return false; + + // check for burn outpoint (must account for each new asset) + bool fBurnOutpointFound = false; + for (auto out : vout) + if (CheckIssueBurnTx(out, AssetType::UNIQUE, assetOutpointCount)) { + fBurnOutpointFound = true; + break; + } + if (!fBurnOutpointFound) + return false; + + // check for owner change outpoint that matches root + bool fOwnerOutFound = false; + for (auto out : vout) { + if (CheckTransferOwnerTx(out)) { + fOwnerOutFound = true; + break; + } + } + + if (!fOwnerOutFound) + return false; + + // The owner change output must match a corresponding owner input + bool fFoundCorrectInput = false; + for (unsigned int i = 0; i < vin.size(); ++i) { + const COutPoint &prevout = vin[i].prevout; + const Coin& coin = view.AccessCoin(prevout); + assert(!coin.IsSpent()); + + int nType = -1; + bool fOwner = false; + if (coin.out.scriptPubKey.IsAssetScript(nType, fOwner)) { + std::string strAssetName; + CAmount nAssetAmount; + if (!GetAssetInfoFromCoin(coin, strAssetName, nAssetAmount)) + continue; + if (IsAssetNameAnOwner(strAssetName)) { + if (strAssetName == assetRoot + OWNER_TAG) { + fFoundCorrectInput = true; + break; + } + } + } + } + + if (!fFoundCorrectInput) + return false; + + return true; +} + bool CTransaction::IsReissueAsset() const { // Check for the reissue asset data CTxOut. This will always be the last output in the transaction @@ -1591,7 +1695,7 @@ bool IsAssetUnitsValid(const CAmount& units) return false; } -bool CheckIssueBurnTx(const CTxOut& txOut, const AssetType& type) +bool CheckIssueBurnTx(const CTxOut& txOut, const AssetType& type, const int numberIssued) { CAmount burnAmount = 0; std::string burnAddress = ""; @@ -1609,6 +1713,9 @@ bool CheckIssueBurnTx(const CTxOut& txOut, const AssetType& type) return false; } + // If issuing multiple (unique) assets need to burn for each + burnAmount *= numberIssued; + // Check the first transaction for the required Burn Amount for the asset type if (!(txOut.nValue == burnAmount)) return false; @@ -1630,6 +1737,11 @@ bool CheckIssueBurnTx(const CTxOut& txOut, const AssetType& type) return true; } +bool CheckIssueBurnTx(const CTxOut& txOut, const AssetType& type) +{ + return CheckIssueBurnTx(txOut, type, 1); +} + bool CheckReissueBurnTx(const CTxOut& txOut) { // Check the first transaction and verify that the correct RVN Amount @@ -1702,6 +1814,31 @@ bool IsScriptNewAsset(const CScript& scriptPubKey, int& nStartingIndex) return false; } +bool IsScriptNewUniqueAsset(const CScript& scriptPubKey) +{ + int index = 0; + return IsScriptNewUniqueAsset(scriptPubKey, index); +} + +bool IsScriptNewUniqueAsset(const CScript& scriptPubKey, int& nStartingIndex) +{ + int nType = 0; + bool fIsOwner = false; + if (!scriptPubKey.IsAssetScript(nType, fIsOwner, nStartingIndex)) + return false; + + CNewAsset asset; + std::string address; + if (!AssetFromScript(scriptPubKey, asset, address)) + return false; + + AssetType assetType; + if (!IsAssetNameValid(asset.strName, assetType)) + return false; + + return AssetType::UNIQUE == assetType; +} + bool IsScriptOwnerAsset(const CScript& scriptPubKey) { @@ -2165,14 +2302,22 @@ std::string EncodeIPFS(std::string decoded){ bool CreateAssetTransaction(CWallet* pwallet, const CNewAsset& asset, const std::string& address, std::pair& error, std::string& rvnChangeAddress, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRequired) { + std::vector assets; + assets.push_back(asset); + return CreateAssetTransaction(pwallet, assets, address, error, rvnChangeAddress, wtxNew, reservekey, nFeeRequired); +} +bool CreateAssetTransaction(CWallet* pwallet, const std::vector assets, const std::string& address, std::pair& error, std::string& rvnChangeAddress, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRequired) +{ std::string change_address = rvnChangeAddress; // Validate the assets data std::string strError; - if (!asset.IsValid(strError, *passets)) { - error = std::make_pair(RPC_INVALID_PARAMETER, strError); - return false; + for (auto asset : assets) { + if (!asset.IsValid(strError, *passets)) { + error = std::make_pair(RPC_INVALID_PARAMETER, strError); + return false; + } } if (!change_address.empty()) { @@ -2202,15 +2347,28 @@ bool CreateAssetTransaction(CWallet* pwallet, const CNewAsset& asset, const std: change_address = EncodeDestination(keyID); } - AssetType assetType; - if (!IsAssetNameValid(asset.strName, assetType)) { - error = std::make_pair(RPC_INVALID_PARAMETER, "Asset name not valid"); - return false; + std::string parentName; + for (auto asset : assets) { + if (!IsAssetNameValid(asset.strName, assetType)) { + error = std::make_pair(RPC_INVALID_PARAMETER, "Asset name not valid"); + return false; + } + if (assets.size() > 1 && assetType != AssetType::UNIQUE) { + error = std::make_pair(RPC_INVALID_PARAMETER, "Only unique assets can be issued in bulk."); + return false; + } + std::string parent = GetParentName(asset.strName); + if (parentName.empty()) + parentName = parent; + if (parentName != parent) { + error = std::make_pair(RPC_INVALID_PARAMETER, "All assets must have the same parent."); + return false; + } } // Assign the correct burn amount and the correct burn address depending on the type of asset issuance that is happening - CAmount burnAmount = GetBurnAmount(assetType); + CAmount burnAmount = GetBurnAmount(assetType) * assets.size(); CScript scriptPubKey = GetScriptForDestination(DecodeDestination(GetBurnAddress(assetType))); CAmount curBalance = pwallet->GetBalance(); @@ -2242,27 +2400,28 @@ bool CreateAssetTransaction(CWallet* pwallet, const CNewAsset& asset, const std: CRecipient recipient = {scriptPubKey, burnAmount, fSubtractFeeFromAmount}; vecSend.push_back(recipient); - // If the asset is a subasset. We need to send the ownertoken change back to ourselfs - if (assetType == AssetType::SUB) { + // If the asset is a subasset or unique asset. We need to send the ownertoken change back to ourselfs + if (assetType == AssetType::SUB || assetType == AssetType::UNIQUE) { // Get the script for the destination address for the assets CScript scriptTransferOwnerAsset = GetScriptForDestination(DecodeDestination(change_address)); - std::string parent_name = GetParentName(asset.strName); - CAssetTransfer assetTransfer(parent_name + OWNER_TAG, OWNER_ASSET_AMOUNT); + CAssetTransfer assetTransfer(parentName + OWNER_TAG, OWNER_ASSET_AMOUNT); assetTransfer.ConstructTransaction(scriptTransferOwnerAsset); CRecipient rec = {scriptTransferOwnerAsset, 0, fSubtractFeeFromAmount}; vecSend.push_back(rec); } - // Get the owner outpoints if this is a subasset - if (assetType == AssetType::SUB) { + // Get the owner outpoints if this is a subasset or unique asset + if (assetType == AssetType::SUB || assetType == AssetType::UNIQUE) { // Verify that this wallet is the owner for the asset, and get the owner asset outpoint - if (!VerifyWalletHasAsset(GetParentName(asset.strName) + OWNER_TAG, error)) { - return false; + for (auto asset : assets) { + if (!VerifyWalletHasAsset(parentName + OWNER_TAG, error)) { + return false; + } } } - if (!pwallet->CreateTransactionWithAsset(vecSend, wtxNew, reservekey, nFeeRequired, nChangePosRet, strTxError, coin_control, asset, DecodeDestination(address), assetType)) { + if (!pwallet->CreateTransactionWithAssets(vecSend, wtxNew, reservekey, nFeeRequired, nChangePosRet, strTxError, coin_control, assets, DecodeDestination(address), assetType)) { if (!fSubtractFeeFromAmount && burnAmount + nFeeRequired > curBalance) strTxError = strprintf("Error: This transaction requires a transaction fee of at least %s", FormatMoney(nFeeRequired)); error = std::make_pair(RPC_WALLET_ERROR, strTxError); diff --git a/src/assets/assets.h b/src/assets/assets.h index ad18358717..75c2fd70ef 100644 --- a/src/assets/assets.h +++ b/src/assets/assets.h @@ -30,6 +30,9 @@ #define OWNER_UNITS 0 #define MIN_ASSET_LENGTH 3 #define OWNER_ASSET_AMOUNT 1 * COIN +#define UNIQUE_ASSET_AMOUNT 1 * COIN +#define UNIQUE_ASSET_UNITS 0 +#define UNIQUE_ASSETS_REISSUABLE 0 #define ASSET_TRANSFER_STRING "transfer_asset" #define ASSET_NEW_STRING "new_asset" @@ -308,8 +311,10 @@ std::string GetBurnAddress(const AssetType type); bool IsAssetNameValid(const std::string& name); bool IsAssetNameValid(const std::string& name, AssetType& assetType); +bool IsUniqueTagValid(const std::string& tag); bool IsAssetNameAnOwner(const std::string& name); std::string GetParentName(const std::string& name); // Gets the parent name of a subasset TEST/TESTSUB would return TEST +std::string GetUniqueAssetName(const std::string& parent, const std::string& tag); bool IsAssetNameSizeValid(const std::string& name); @@ -324,6 +329,7 @@ bool AssetFromScript(const CScript& scriptPubKey, CNewAsset& asset, std::string& bool OwnerAssetFromScript(const CScript& scriptPubKey, std::string& assetName, std::string& strAddress); bool ReissueAssetFromScript(const CScript& scriptPubKey, CReissueAsset& reissue, std::string& strAddress); +bool CheckIssueBurnTx(const CTxOut& txOut, const AssetType& type, const int numberIssued); bool CheckIssueBurnTx(const CTxOut& txOut, const AssetType& type); bool CheckReissueBurnTx(const CTxOut& txOut); @@ -335,10 +341,12 @@ bool CheckTransferOwnerTx(const CTxOut& txOut); bool CheckAmountWithUnits(const CAmount& nAmount, const uint8_t nUnits); bool IsScriptNewAsset(const CScript& scriptPubKey, int& nStartingIndex); +bool IsScriptNewUniqueAsset(const CScript& scriptPubKey, int& nStartingIndex); bool IsScriptOwnerAsset(const CScript& scriptPubKey, int& nStartingIndex); bool IsScriptReissueAsset(const CScript& scriptPubKey, int& nStartingIndex); bool IsScriptTransferAsset(const CScript& scriptPubKey, int& nStartingIndex); bool IsScriptNewAsset(const CScript& scriptPubKey); +bool IsScriptNewUniqueAsset(const CScript& scriptPubKey); bool IsScriptOwnerAsset(const CScript& scriptPubKey); bool IsScriptReissueAsset(const CScript& scriptPubKey); bool IsScriptTransferAsset(const CScript& scriptPubKey); @@ -370,6 +378,7 @@ std::string DecodeIPFS(std::string encoded); std::string EncodeIPFS(std::string decoded); bool CreateAssetTransaction(CWallet* pwallet, const CNewAsset& asset, const std::string& address, std::pair& error, std::string& rvnChangeAddress, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRequired); +bool CreateAssetTransaction(CWallet* pwallet, const std::vector assets, const std::string& address, std::pair& error, std::string& rvnChangeAddress, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRequired); bool CreateReissueAssetTransaction(CWallet* pwallet, const CReissueAsset& asset, const std::string& address, const std::string& changeAddress, std::pair& error, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRequired); bool CreateTransferAssetTransaction(CWallet* pwallet, const std::vector< std::pair >vTransfers, const std::string& changeAddress, std::pair& error, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRequired); bool SendAssetTransaction(CWallet* pwallet, CWalletTx& transaction, CReserveKey& reserveKey, std::pair& error, std::string& txid); diff --git a/src/assets/assettypes.h b/src/assets/assettypes.h index c64c29db57..54266832c0 100644 --- a/src/assets/assettypes.h +++ b/src/assets/assettypes.h @@ -27,6 +27,20 @@ enum AssetType REISSUE }; +std::string PrintAssetType(AssetType& assetType) { + switch (assetType) { + case ROOT: return "ROOT"; + case OWNER: return "OWNER"; + case SUB: return "SUB"; + case UNIQUE: return "UNIQUE"; + case MSGCHANNEL: return "MSGCHANNEL"; + case VOTE: return "VOTE"; + case INVALID: return "INVALID"; + case REISSUE: return "REISSUE"; + default: return "UNKNOWN"; + } +} + enum IssueAssetType { ISSUE_ROOT = 0, diff --git a/src/coins.cpp b/src/coins.cpp index fc74296db8..19d8456b8f 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -161,6 +161,27 @@ void AddCoins(CCoinsViewCache& cache, const CTransaction &tx, int nHeight, bool if (!assetsCache->AddPossibleOutPoint(possibleMine)) error("%s: Failed to add an reissued asset I own to my Unspent Asset Cache. Asset Name : %s", __func__, reissue.strName); + } else if (tx.IsNewUniqueAsset()) { + for (int n = 0; n < tx.vout.size(); n++) { + auto out = tx.vout[n]; + + CNewAsset asset; + std::string strAddress; + + if (IsScriptNewUniqueAsset(out.scriptPubKey)) { + AssetFromScript(out.scriptPubKey, asset, strAddress); + + // Add the new asset to cache + if (!assetsCache->AddNewAsset(asset, strAddress)) + error("%s : Failed at adding a new asset to our cache. asset: %s", __func__, + asset.strName); + + CAssetCachePossibleMine possibleMine(asset.strName, COutPoint(tx.GetHash(), n), out); + if (!assetsCache->AddPossibleOutPoint(possibleMine)) + error("%s: Failed to add an asset I own to my Unspent Asset Cache. Asset Name : %s", + __func__, asset.strName); + } + } } } } diff --git a/src/consensus/tx_verify.cpp b/src/consensus/tx_verify.cpp index 224a199f59..9ce9559dc2 100644 --- a/src/consensus/tx_verify.cpp +++ b/src/consensus/tx_verify.cpp @@ -209,11 +209,24 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state, CAssetsCa if (!TransferAssetFromScript(txout.scriptPubKey, transfer, address)) return state.DoS(100, false, REJECT_INVALID, "bad-txns-transfer-asset-bad-deserialize"); + // Check asset name validity and get type + AssetType assetType; + if (!IsAssetNameValid(transfer.strName, assetType)) { + return state.DoS(100, false, REJECT_INVALID, "bad-txns-transfer-asset-name-invalid"); + } + // If the transfer is an ownership asset. Check to make sure that it is OWNER_ASSET_AMOUNT if (IsAssetNameAnOwner(transfer.strName)) { if (transfer.nAmount != OWNER_ASSET_AMOUNT) return state.DoS(100, false, REJECT_INVALID, "bad-txns-transfer-owner-amount-was-not-1"); } + + // If the transfer is a unique asset. Check to make sure that it is UNIQUE_ASSET_AMOUNT + if (assetType == AssetType::UNIQUE) { + if (transfer.nAmount != UNIQUE_ASSET_AMOUNT) + return state.DoS(100, false, REJECT_INVALID, "bad-txns-transfer-unique-amount-was-not-1"); + } + } } } @@ -245,7 +258,6 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state, CAssetsCa /** RVN START */ if (AreAssetsDeployed()) { if (assetCache) { - // Get the new asset from the transaction if (tx.IsNewAsset()) { if(!tx.VerifyNewAsset()) return state.DoS(100, false, REJECT_INVALID, "bad-txns-verifying-issue-asset"); @@ -284,6 +296,50 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state, CAssetsCa if (!foundOwnerAsset) return state.DoS(100, false, REJECT_INVALID, "bad-txns-reissue-asset-bad-owner-asset"); + } else if (tx.IsNewUniqueAsset()) { + + std::string assetRoot = ""; + int assetCount = 0; + + for (auto out : tx.vout) { + CNewAsset asset; + std::string strAddress; + + if (IsScriptNewUniqueAsset(out.scriptPubKey)) { + if (!AssetFromScript(out.scriptPubKey, asset, strAddress)) + return state.DoS(100, false, REJECT_INVALID, "bad-txns-issue-unique-asset"); + + std::string strError = ""; + if (!asset.IsValid(strError, *assetCache, fMemPoolCheck, fCheckDuplicateInputs)) + return state.DoS(100, false, REJECT_INVALID, "bad-txns-" + strError); + + std::string root = GetParentName(asset.strName); + if (assetRoot.compare("") == 0) + assetRoot = root; + if (assetRoot.compare(root) != 0) + return state.DoS(100, false, REJECT_INVALID, "bad-txns-issue-unique-asset-mismatched-root"); + + assetCount += 1; + } + } + + if (assetCount < 1) + return state.DoS(100, false, REJECT_INVALID, "bad-txns-issue-unique-asset-no-outputs"); + + bool foundOwnerAsset = false; + for (auto out : tx.vout) { + CAssetTransfer transfer; + std::string transferAddress; + if (TransferAssetFromScript(out.scriptPubKey, transfer, transferAddress)) { + if (assetRoot + OWNER_TAG == transfer.strName) { + foundOwnerAsset = true; + break; + } + } + } + + if (!foundOwnerAsset) + return state.DoS(100, false, REJECT_INVALID, "bad-txns-issue-unique-asset-bad-owner-asset"); } } } diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index c3cf095ee7..dbc985159e 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -329,6 +329,8 @@ class CTransaction /** RVN START */ bool IsNewAsset() const; bool VerifyNewAsset() const; + bool IsNewUniqueAsset() const; + bool VerifyNewUniqueAsset(CCoinsViewCache& view) const; bool IsReissueAsset() const; bool VerifyReissueAsset(CCoinsViewCache& view) const; /** RVN END */ diff --git a/src/rpc/assets.cpp b/src/rpc/assets.cpp index ad06429f3c..4e362c52c2 100644 --- a/src/rpc/assets.cpp +++ b/src/rpc/assets.cpp @@ -60,21 +60,23 @@ UniValue UnitValueFromAmount(const CAmount& amount, const std::string asset_name UniValue issue(const JSONRPCRequest& request) { - if (request.fHelp || !AreAssetsDeployed() || request.params.size() < 2 || request.params.size() > 8) + if (request.fHelp || !AreAssetsDeployed() || request.params.size() < 1 || request.params.size() > 8) throw std::runtime_error( "issue \"asset_name\" qty \"( to_address )\" \"( change_address )\" ( units ) ( reissuable ) ( has_ipfs ) \"( ipfs_hash )\"\n" + AssetActivationWarning() + - "\nIssue an asset or subasset with unique name.\n" + "\nIssue an asset, subasset or unique asset.\n" + "Asset name must not conflict with any existing asset.\n" "Unit as the number of decimals precision for the asset (0 for whole units (\"1\"), 8 for max precision (\"1.00000000\")\n" "Reissuable is true/false for whether additional units can be issued by the original issuer.\n" + "If issuing a unique asset these values are required (and will be defaulted to): qty=1, units=0, reissuable=false.\n" "\nArguments:\n" "1. \"asset_name\" (string, required) a unique name\n" - "2. \"qty\" (numeric, required) the number of units to be issued\n" + "2. \"qty\" (numeric, optional, default=1) the number of units to be issued\n" "3. \"to_address\" (string), optional, default=\"\"), address asset will be sent to, if it is empty, address will be generated for you\n" "4. \"change_address\" (string), optional, default=\"\"), address the the rvn change will be sent to, if it is empty, change address will be generated for you\n" - "5. \"units\" (integer, optional, default=8, min=0, max=8), the number of decimals precision for the asset (0 for whole units (\"1\"), 8 for max precision (\"1.00000000\")\n" - "6. \"reissuable\" (boolean, optional, default=true), whether future reissuance is allowed\n" + "5. \"units\" (integer, optional, default=0, min=0, max=8), the number of decimals precision for the asset (0 for whole units (\"1\"), 8 for max precision (\"1.00000000\")\n" + "6. \"reissuable\" (boolean, optional, default=true (false for unique assets)), whether future reissuance is allowed\n" "7. \"has_ipfs\" (boolean, optional, default=false), whether ifps hash is going to be added to the asset\n" "8. \"ipfs_hash\" (string, optional but required if has_ipfs = 1), an ipfs hash\n" @@ -87,6 +89,7 @@ UniValue issue(const JSONRPCRequest& request) + HelpExampleCli("issue", "\"myassetname\" 1000 \"myaddress\" \"changeaddress\" 4") + HelpExampleCli("issue", "\"myassetname\" 1000 \"myaddress\" \"changeaddress\" 2 true") + HelpExampleCli("issue", "\"myassetname/mysubasset\" 1000 \"myaddress\" \"changeaddress\" 2 true") + + HelpExampleCli("issue", "\"myassetname#uniquetag\"") ); CWallet * const pwallet = GetWalletForJSONRPCRequest(request); @@ -99,9 +102,21 @@ UniValue issue(const JSONRPCRequest& request) EnsureWalletIsUnlocked(pwallet); - std::string asset_name = request.params[0].get_str(); + // Check asset name and infer assetType + std::string assetName = request.params[0].get_str(); + AssetType assetType; + if (!IsAssetNameValid(assetName, assetType)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid asset name: ") + assetName); + } - CAmount nAmount = AmountFromValue(request.params[1]); + // Check assetType supported + if (!(assetType == AssetType::ROOT || assetType == AssetType::SUB || assetType == AssetType::UNIQUE)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Unsupported asset type: ") + PrintAssetType(assetType)); + } + + CAmount nAmount = COIN; + if (request.params.size() > 1) + nAmount = AmountFromValue(request.params[1]); std::string address = ""; if (request.params.size() > 2) @@ -143,10 +158,11 @@ UniValue issue(const JSONRPCRequest& request) } } - int units = 8; + int units = 0; if (request.params.size() > 4) units = request.params[4].get_int(); - bool reissuable = true; + + bool reissuable = assetType != AssetType::UNIQUE; if (request.params.size() > 5) reissuable = request.params[5].get_bool(); @@ -158,7 +174,12 @@ UniValue issue(const JSONRPCRequest& request) if (request.params.size() > 7 && has_ipfs) ipfs_hash = request.params[7].get_str(); - CNewAsset asset(asset_name, nAmount, units, reissuable ? 1 : 0, has_ipfs ? 1 : 0, DecodeIPFS(ipfs_hash)); + // check for required unique asset params + if (assetType == AssetType::UNIQUE && (nAmount != COIN || units != 0 || reissuable)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameters for issuing a unique asset.")); + } + + CNewAsset asset(assetName, nAmount, units, reissuable ? 1 : 0, has_ipfs ? 1 : 0, DecodeIPFS(ipfs_hash)); CReserveKey reservekey(pwallet); CWalletTx transaction; @@ -179,6 +200,147 @@ UniValue issue(const JSONRPCRequest& request) return result; } +UniValue issueunique(const JSONRPCRequest& request) +{ + if (request.fHelp || !AreAssetsDeployed() || request.params.size() < 2 || request.params.size() > 3) + throw std::runtime_error( + "issueunique \"root_name\" [asset_tags] ( [ipfs_hashes] ) \"( to_address )\" \"( change_address )\"\n" + + AssetActivationWarning() + + "\nIssue unique asset(s).\n" + "root_name must be an asset you own.\n" + "An asset will be created for each element of asset_tags.\n" + "If provided ipfs_hashes must be the same length as asset_tags.\n" + "Five (5) RVN will be burned for each asset created.\n" + + "\nArguments:\n" + "1. \"root_name\" (string, required) name of the asset the unique asset(s) are being issued under\n" + "2. \"asset_tags\" (array, required) the unique tag for each asset which is to be issued\n" + "3. \"ipfs_hashes\" (array, optional) ipfs hashes corresponding to each supplied tag (should be same size as \"asset_tags\")\n" + "4. \"to_address\" (string, optional, default=\"\"), address assets will be sent to, if it is empty, address will be generated for you\n" + "5. \"change_address\" (string, optional, default=\"\"), address the the rvn change will be sent to, if it is empty, change address will be generated for you\n" + + "\nResult:\n" + "\"txid\" (string) The transaction id\n" + + "\nExamples:\n" + + HelpExampleCli("issueunique", "\"MY_ASSET\" [\"primo\",\"secundo\"]") + + HelpExampleCli("issueunique", "\"MY_ASSET\" [\"primo\",\"secundo\"] [\"first_hash\",\"second_hash\"]") + ); + + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + ObserveSafeMode(); + LOCK2(cs_main, pwallet->cs_wallet); + + EnsureWalletIsUnlocked(pwallet); + + + const std::string rootName = request.params[0].get_str(); + AssetType assetType; + if (!IsAssetNameValid(rootName, assetType)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid asset name: ") + rootName); + } + if (assetType != AssetType::ROOT && assetType != AssetType::SUB) { + throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Root asset must be a regular top-level or sub-asset.")); + } + + const UniValue& assetTags = request.params[1]; + if (!assetTags.isArray() || assetTags.size() < 1) { + throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Asset tags must be a non-empty array.")); + } + + const UniValue& ipfsHashes = request.params[2]; + if (!ipfsHashes.isNull()) { + if (!ipfsHashes.isArray() || ipfsHashes.size() != assetTags.size()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("If provided, IPFS hashes must be an array of the same size as the asset tags array.")); + } + } + + std::string address = ""; + if (request.params.size() > 3) + address = request.params[3].get_str(); + + if (!address.empty()) { + CTxDestination destination = DecodeDestination(address); + if (!IsValidDestination(destination)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Raven address: ") + address); + } + } else { + // Create a new address + std::string strAccount; + + if (!pwallet->IsLocked()) { + pwallet->TopUpKeyPool(); + } + + // Generate a new key that is added to wallet + CPubKey newKey; + if (!pwallet->GetKeyFromPool(newKey)) { + throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); + } + CKeyID keyID = newKey.GetID(); + + pwallet->SetAddressBook(keyID, strAccount, "receive"); + + address = EncodeDestination(keyID); + } + + std::string changeAddress = ""; + if (request.params.size() > 4) + changeAddress = request.params[4].get_str(); + if (!changeAddress.empty()) { + CTxDestination destination = DecodeDestination(changeAddress); + if (!IsValidDestination(destination)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, + std::string("Invalid Change Address: Invalid Raven address: ") + changeAddress); + } + } + + std::vector assets; + for (int i = 0; i < assetTags.size(); i++) { + std::string tag = assetTags[i].get_str(); + + if (!IsUniqueTagValid(tag)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Unique asset tag is invalid: " + tag)); + } + + std::string assetName = GetUniqueAssetName(rootName, tag); + CNewAsset asset; + + if (ipfsHashes.isNull()) + { + asset = CNewAsset(assetName, UNIQUE_ASSET_AMOUNT, UNIQUE_ASSET_UNITS, UNIQUE_ASSETS_REISSUABLE, 0, ""); + } + else + { + asset = CNewAsset(assetName, UNIQUE_ASSET_AMOUNT, UNIQUE_ASSET_UNITS, UNIQUE_ASSETS_REISSUABLE, 1, + DecodeIPFS(ipfsHashes[i].get_str())); + } + + assets.push_back(asset); + } + + CReserveKey reservekey(pwallet); + CWalletTx transaction; + CAmount nRequiredFee; + std::pair error; + + // Create the Transaction + if (!CreateAssetTransaction(pwallet, assets, address, error, changeAddress, transaction, reservekey, nRequiredFee)) + throw JSONRPCError(error.first, error.second); + + // Send the Transaction to the network + std::string txid; + if (!SendAssetTransaction(pwallet, transaction, reservekey, error, txid)) + throw JSONRPCError(error.first, error.second); + + UniValue result(UniValue::VARR); + result.push_back(txid); + return result; +} UniValue listassetbalancesbyaddress(const JSONRPCRequest& request) { @@ -745,6 +907,7 @@ static const CRPCCommand commands[] = { // category name actor (function) argNames // ----------- ------------------------ ----------------------- ---------- { "assets", "issue", &issue, {"asset_name","qty","to_address","change_address","units","reissuable","has_ipfs","ipfs_hash"} }, + { "assets", "issueunique", &issueunique, {"root_name", "asset_tags", "ipfs_hashes", "to_address", "change_address"}}, { "assets", "listassetbalancesbyaddress", &listassetbalancesbyaddress, {"address"} }, { "assets", "getassetdata", &getassetdata, {"asset_name"}}, { "assets", "listmyassets", &listmyassets, {"asset", "verbose", "count", "start"}}, diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 902da8acf2..5db8f5cc05 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -33,6 +33,8 @@ static const CRPCConvertParam vRPCConvertParams[] = { "issue", 4, "units" }, { "issue", 5, "reissuable" }, { "issue", 6, "has_ipfs" }, + { "issueunique", 1, "asset_tags"}, + { "issueunique", 2, "ipfs_hashes"}, { "transfer", 1, "qty"}, { "reissue", 1, "qty"}, { "reissue", 4, "reissuable"}, diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index d1c97dccaa..3640987017 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -356,17 +356,19 @@ UniValue createrawtransaction(const JSONRPCRequest& request) " Some operations require an amount of RVN to be sent to a burn address:\n" " transfer: 0\n" " issue: 500 to Issue Burn Address\n" + " issue_unique 5 to Issue Unique Burn Address\n" " reissue: 100 to Reissue Burn Address\n" "\nOwnership:\n" " These operations require an ownership token input for the asset being operated upon:\n" + " issue_unique\n" " reissue\n" "\nOutput Ordering:\n" " Asset operations require the following:\n" " 1) All coin outputs come first (including the burn output).\n" " 2) The owner token change output comes next (if required).\n" - " 3) An issue, reissue or any number of transfers comes last\n" + " 3) An issue, issue_unique, reissue or any number of transfers comes last\n" " (different types can't be mixed in a single transaction).\n" "\nArguments:\n" @@ -405,6 +407,15 @@ UniValue createrawtransaction(const JSONRPCRequest& request) " }\n" " }\n" " or\n" + " { (object) A json object describing new unique assets to issue\n" + " \"issue_unique\":\n" + " {\n" + " \"root_name\":\"root-name\", (string, required) name of the asset the unique asset(s) are being issued under\n" + " \"asset_tags\":[\"asset_tag\", ...], (array, required) the unique tag for each asset which is to be issued\n" + " \"ipfs_hashes\":[\"hash\", ...], (array, optional) ipfs hashes corresponding to each supplied tag (should be same size as \"asset_tags\")\n" + " }\n" + " }\n" + " or\n" " { (object) A json object describing follow-on asset issue. Requires matching ownership input.\n" " \"reissue\":\n" " {\n" @@ -429,6 +440,7 @@ UniValue createrawtransaction(const JSONRPCRequest& request) + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"mycoin\\\",\\\"vout\\\":0}]\" \"{\\\"address\\\":0.01}\"") + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"mycoin\\\",\\\"vout\\\":0}]\" \"{\\\"data\\\":\\\"00010203\\\"}\"") + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"mycoin\\\",\\\"vout\\\":0}]\" \"{\\\"RXissueAssetXXXXXXXXXXXXXXXXXhhZGt\\\":500,\\\"change_address\\\":change_amount,\\\"issuer_address\\\":{\\\"issue\\\":{\\\"asset_name\\\":\\\"MYASSET\\\",\\\"asset_quantity\\\":1000000,\\\"units\\\":1,\\\"reissuable\\\":0,\\\"has_ipfs\\\":1,\\\"ipfs_hash\\\":\\\"43f81c6f2c0593bde5a85e09ae662816eca80797\\\"}}}\"") + + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"mycoin\\\",\\\"vout\\\":0}]\" \"{\\\"RXissueUniqueAssetXXXXXXXXXXWEAe58\\\":20,\\\"change_address\\\":change_amount,\\\"issuer_address\\\":{\\\"issue_unique\\\":{\\\"root_name\\\":\\\"MYASSET\\\",\\\"asset_tags\\\":[\\\"ALPHA\\\",\\\"BETA\\\"],\\\"ipfs_hashes\\\":[\\\"43f81c6f2c0593bde5a85e09ae662816eca80797\\\",\\\"43f81c6f2c0593bde5a85e09ae662816eca80797\\\"]}}}\"") + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"mycoin\\\",\\\"vout\\\":0},{\\\"txid\\\":\\\"myasset\\\",\\\"vout\\\":0}]\" \"{\\\"address\\\":{\\\"transfer\\\":{\\\"MYASSET\\\":50}}}\"") + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"mycoin\\\",\\\"vout\\\":0},{\\\"txid\\\":\\\"myownership\\\",\\\"vout\\\":0}]\" \"{\\\"issuer_address\\\":{\\\"reissue\\\":{\\\"asset_name\\\":\\\"MYASSET\\\",\\\"asset_quantity\\\":2000000}}}\"") + HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"mycoin\\\",\\\"vout\\\":0}]\", \"{\\\"data\\\":\\\"00010203\\\"}\"") @@ -523,7 +535,8 @@ UniValue createrawtransaction(const JSONRPCRequest& request) auto asset_ = sendTo[name_].get_obj(); auto assetKey_ = asset_.getKeys()[0]; - if (assetKey_ == "issue") { + if (assetKey_ == "issue") + { if (asset_[0].type() != UniValue::VOBJ) throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, the format must follow { \"issue\": {\"key\": value}, ...}")); @@ -562,7 +575,7 @@ UniValue createrawtransaction(const JSONRPCRequest& request) CAmount nAmount = AmountFromValue(asset_quantity); // Create a new asset - CNewAsset asset(asset_name.get_str(), nAmount, units.get_int(), reissuable.get_int(), has_ipfs.get_int(), ipfs_hash.get_str()); + CNewAsset asset(asset_name.get_str(), nAmount, units.get_int(), reissuable.get_int(), has_ipfs.get_int(), DecodeIPFS(ipfs_hash.get_str())); // Verify that data std::string strError = ""; @@ -582,7 +595,74 @@ UniValue createrawtransaction(const JSONRPCRequest& request) CTxOut out(0, scriptPubKey); rawTx.vout.push_back(out); - } else if (assetKey_ == "reissue") { + } + else if (assetKey_ == "issue_unique") + { + + if (asset_[0].type() != UniValue::VOBJ) + throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, the format must follow { \"issue_unique\": {\"root_name\": value}, ...}")); + + // Get the asset data object from the json + auto assetData = asset_.getValues()[0].get_obj(); + + /**-------Process the assets data-------**/ + const UniValue& root_name = find_value(assetData, "root_name"); + if (!root_name.isStr()) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, missing asset data for key: root_name"); + + const UniValue& asset_tags = find_value(assetData, "asset_tags"); + if (!asset_tags.isArray() || asset_tags.size() < 1) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, missing asset data for key: asset_tags"); + + const UniValue& ipfs_hashes = find_value(assetData, "ipfs_hashes"); + if (!ipfs_hashes.isNull()) { + if (!ipfs_hashes.isArray() || ipfs_hashes.size() != asset_tags.size()) { + if (!ipfs_hashes.isNum()) + throw JSONRPCError(RPC_INVALID_PARAMETER, + "Invalid parameter, missing asset metadata for key: units"); + } + } + + // Create the scripts for the change of the ownership token + CScript scriptTransferOwnerAsset = GetScriptForDestination(destination); + CAssetTransfer assetTransfer(root_name.get_str() + OWNER_TAG, OWNER_ASSET_AMOUNT); + assetTransfer.ConstructTransaction(scriptTransferOwnerAsset); + + // Create the CTxOut for the owner token + CTxOut out(0, scriptTransferOwnerAsset); + rawTx.vout.push_back(out); + + // Create the assets + for (int i = 0; i < asset_tags.size(); i++) { + + // Create a new asset + CNewAsset asset; + if (ipfs_hashes.isNull()) { + asset = CNewAsset(GetUniqueAssetName(root_name.get_str(), asset_tags[i].get_str()), + UNIQUE_ASSET_AMOUNT, UNIQUE_ASSET_UNITS, UNIQUE_ASSETS_REISSUABLE, 0, ""); + } else { + asset = CNewAsset(GetUniqueAssetName(root_name.get_str(), asset_tags[i].get_str()), + UNIQUE_ASSET_AMOUNT, UNIQUE_ASSET_UNITS, UNIQUE_ASSETS_REISSUABLE, + 1, DecodeIPFS(ipfs_hashes[i].get_str())); + } + + // Verify that data + std::string strError = ""; + if (!asset.IsValid(strError, *passets)) + throw JSONRPCError(RPC_INVALID_PARAMETER, strError); + + // Construct the asset transaction + scriptPubKey = GetScriptForDestination(destination); + asset.ConstructTransaction(scriptPubKey); + + // Push the scriptPubKey into the vouts. + CTxOut out(0, scriptPubKey); + rawTx.vout.push_back(out); + + } + } + else if (assetKey_ == "reissue") + { if (asset_[0].type() != UniValue::VOBJ) throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, the format must follow { \"reissue\": {\"key\": value}, ...}")); @@ -619,7 +699,7 @@ UniValue createrawtransaction(const JSONRPCRequest& request) if (!ipfs_hash.isStr()) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, missing reissue metadata for key: ipfs_hash"); - reissueObj.strIPFSHash = ipfs_hash.get_str(); + reissueObj.strIPFSHash = DecodeIPFS(ipfs_hash.get_str()); } // Add the received data into the reissue object diff --git a/src/validation.cpp b/src/validation.cpp index 90736ac227..4a35a6e0b7 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1733,6 +1733,27 @@ static DisconnectResult DisconnectBlock(const CBlock& block, const CBlockIndex* return DISCONNECT_FAILED; } } + } else if (tx.IsNewUniqueAsset()) { + for (int n = 0; n < tx.vout.size(); n++) { + auto out = tx.vout[n]; + CNewAsset asset; + std::string strAddress; + + if (IsScriptNewUniqueAsset(out.scriptPubKey)) { + if (!AssetFromScript(out.scriptPubKey, asset, strAddress)) { + error("%s : Failed to get unique asset from transaction. TXID : %s, vout: %s", __func__, + tx.GetHash().GetHex(), n); + return DISCONNECT_FAILED; + } + + if (assetsCache->ContainsAsset(asset.strName)) { + if (!assetsCache->RemoveNewAsset(asset, strAddress)) { + error("%s : Failed to Undo Unique Asset. Asset Name : %s", __func__, asset.strName); + return DISCONNECT_FAILED; + } + } + } + } } for (auto index : vAssetTxIndex) { @@ -2196,7 +2217,8 @@ static bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockInd /** RVN START */ if (assetsCache) { - if (tx.IsNewAsset()) { + if (tx.IsNewAsset()) + { if (!AreAssetsDeployed()) return state.DoS(100, false, REJECT_INVALID, "bad-txns-new-asset-when-assets-is-not-active"); @@ -2215,7 +2237,9 @@ static bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockInd if (!asset.IsValid(strError, *assetsCache)) return state.DoS(100, error("%s: %s", __func__, strError), REJECT_INVALID, "bad-txns-issue-asset"); - } else if (tx.IsReissueAsset()) { + } + else if (tx.IsReissueAsset()) + { if (!AreAssetsDeployed()) return state.DoS(100, false, REJECT_INVALID, "bad-txns-reissue-asset-when-assets-is-not-active"); @@ -2231,6 +2255,29 @@ static bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockInd if (!reissue.IsValid(strError, *assetsCache)) return state.DoS(100, false, REJECT_INVALID, strError); } + else if (tx.IsNewUniqueAsset()) + { + if (!AreAssetsDeployed()) + return state.DoS(100, false, REJECT_INVALID, "bad-txns-issue-unique-asset-when-assets-is-not-active"); + + if (!tx.VerifyNewUniqueAsset(view)) + return state.DoS(100, false, REJECT_INVALID, "bad-txns-issue-unique-asset-failed-verify"); + + for (auto out : tx.vout) + { + if (IsScriptNewUniqueAsset(out.scriptPubKey)) + { + CNewAsset asset; + std::string strAddress; + if (!AssetFromScript(out.scriptPubKey, asset, strAddress)) + return state.DoS(100, false, REJECT_INVALID, "bad-txns-issue-unique-asset-serialization"); + + std::string strError = ""; + if (!asset.IsValid(strError, *assetsCache)) + return state.DoS(100, false, REJECT_INVALID, strError); + } + } + } } /** RVN END */ if (fAddressIndex) { @@ -3548,6 +3595,18 @@ static bool ContextualCheckBlock(const CBlock& block, CValidationState& state, c if (!ReissueAssetFromTransaction(*tx, reissue, strAddress)) return state.DoS(100, false, REJECT_INVALID, "bad-txns-reissue-asset"); } + + if (tx->IsNewUniqueAsset()) { + for (auto out : tx->vout) { + CNewAsset asset; + std::string strAddress; + + if (IsScriptNewUniqueAsset(out.scriptPubKey)) { + if (!AssetFromScript(out.scriptPubKey, asset, strAddress)) + return state.DoS(100, false, REJECT_INVALID, "bad-txns-issue-unique-asset"); + } + } + } } // Enforce rule that the coinbase starts with serialized block height diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 7a1d431de5..4e74e0c483 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2999,11 +2999,11 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC return true; } -bool CWallet::CreateTransactionWithAsset(const std::vector& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut, - std::string& strFailReason, const CCoinControl& coin_control, const CNewAsset& asset, const CTxDestination destination, const AssetType& type, bool sign) +bool CWallet::CreateTransactionWithAssets(const std::vector& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut, + std::string& strFailReason, const CCoinControl& coin_control, const std::vector assets, const CTxDestination destination, const AssetType& type, bool sign) { CReissueAsset reissueAsset; - return CreateTransactionAll(vecSend, wtxNew, reservekey, nFeeRet, nChangePosInOut, strFailReason, coin_control, true, asset, destination, false, false, reissueAsset, type, sign); + return CreateTransactionAll(vecSend, wtxNew, reservekey, nFeeRet, nChangePosInOut, strFailReason, coin_control, true, assets, destination, false, false, reissueAsset, type, sign); } bool CWallet::CreateTransactionWithTransferAsset(const std::vector& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut, @@ -3035,16 +3035,32 @@ bool CWallet::CreateTransaction(const std::vector& vecSend, CWalletT return CreateTransactionAll(vecSend, wtxNew, reservekey, nFeeRet, nChangePosInOut, strFailReason, coin_control, false, asset, destination, false, false, reissueAsset, assetType, sign); } +bool CWallet::CreateTransactionAll(const std::vector& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, + CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, + const CCoinControl& coin_control, bool fNewAsset, const CNewAsset& asset, + const CTxDestination destination, bool fTransferAsset, bool fReissueAsset, + const CReissueAsset& reissueAsset, const AssetType& assetType, bool sign) +{ + std::vector assets; + assets.push_back(asset); + return CreateTransactionAll(vecSend, wtxNew, reservekey, nFeeRet, nChangePosInOut, strFailReason, coin_control, + fNewAsset, assets, destination, fTransferAsset, fReissueAsset, reissueAsset, assetType, + sign); +} -bool CWallet::CreateTransactionAll(const std::vector& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, - int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool fNewAsset, const CNewAsset& asset, const CTxDestination destination, bool fTransferAsset, bool fReissueAsset, const CReissueAsset& reissueAsset, const AssetType& assetType, bool sign) +bool CWallet::CreateTransactionAll(const std::vector& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, + CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, + const CCoinControl& coin_control, bool fNewAsset, + const std::vector assets, const CTxDestination destination, + bool fTransferAsset, bool fReissueAsset, const CReissueAsset& reissueAsset, + const AssetType& assetType, bool sign) { + /** RVN START */ if (!AreAssetsDeployed() && (fTransferAsset || fNewAsset || fReissueAsset)) return false; - /** RVN START */ - if (fNewAsset && (asset.IsNull() || !IsValidDestination(destination))) + if (fNewAsset && (assets.size() < 1 || !IsValidDestination(destination))) return error("%s : Tried creating a new asset transaction and the asset was null or the destination was invalid", __func__); if ((fNewAsset && fTransferAsset) || (fReissueAsset && fTransferAsset) || (fReissueAsset && fNewAsset)) @@ -3061,7 +3077,7 @@ bool CWallet::CreateTransactionAll(const std::vector& vecSend, CWall for (const auto& recipient : vecSend) { /** RVN START */ - if (fTransferAsset || fReissueAsset || assetType == AssetType::SUB) { + if (fTransferAsset || fReissueAsset || assetType == AssetType::SUB || assetType == AssetType::UNIQUE) { CAssetTransfer assetTransfer; std::string address; if (TransferAssetFromScript(recipient.scriptPubKey, assetTransfer, address)) { @@ -3141,7 +3157,7 @@ bool CWallet::CreateTransactionAll(const std::vector& vecSend, CWall /** RVN START */ std::vector vAvailableCoins; std::map > mapAssetCoins; - if (fTransferAsset || fReissueAsset || assetType == AssetType::SUB) + if (fTransferAsset || fReissueAsset || assetType == AssetType::SUB || assetType == AssetType::UNIQUE) AvailableCoinsWithAssets(vAvailableCoins, mapAssetCoins, true, &coin_control); else AvailableCoins(vAvailableCoins, true, &coin_control); @@ -3315,18 +3331,21 @@ bool CWallet::CreateTransactionAll(const std::vector& vecSend, CWall /** RVN START */ if (AreAssetsDeployed()) { if (fNewAsset) { - // Create the asset transaction and push it back so it is the last CTxOut in the transaction - CScript scriptPubKey = GetScriptForDestination(destination); - CScript ownerScript = GetScriptForDestination(destination); - - asset.ConstructTransaction(scriptPubKey); - asset.ConstructOwnerTransaction(ownerScript); - - CTxOut ownerTxOut(0, ownerScript); - txNew.vout.push_back(ownerTxOut); - - CTxOut newTxOut(0, scriptPubKey); - txNew.vout.push_back(newTxOut); + for (auto asset : assets) { + // Create the owner token output for non-unique assets + if (assetType != AssetType::UNIQUE) { + CScript ownerScript = GetScriptForDestination(destination); + asset.ConstructOwnerTransaction(ownerScript); + CTxOut ownerTxOut(0, ownerScript); + txNew.vout.push_back(ownerTxOut); + } + + // Create the asset transaction and push it back so it is the last CTxOut in the transaction + CScript scriptPubKey = GetScriptForDestination(destination); + asset.ConstructTransaction(scriptPubKey); + CTxOut newTxOut(0, scriptPubKey); + txNew.vout.push_back(newTxOut); + } } else if (fReissueAsset) { // Create the asset transaction and push it back so it is the last CTxOut in the transaction CScript reissueScript = GetScriptForDestination(destination); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 195c3f5d63..fb792a9a64 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -989,8 +989,8 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface bool SignTransaction(CMutableTransaction& tx); /** RVN START */ - bool CreateTransactionWithAsset(const std::vector& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut, - std::string& strFailReason, const CCoinControl& coin_control, const CNewAsset& asset, const CTxDestination dest, const AssetType& assetType, bool sign = true); + bool CreateTransactionWithAssets(const std::vector& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut, + std::string& strFailReason, const CCoinControl& coin_control, const std::vector assets, const CTxDestination dest, const AssetType& assetType, bool sign = true); bool CreateTransactionWithTransferAsset(const std::vector& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool sign = true); @@ -1009,6 +1009,10 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface bool CreateTransactionAll(const std::vector& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool fNewAsset, const CNewAsset& asset, const CTxDestination dest, bool fTransferAsset, bool fReissueAsset, const CReissueAsset& reissueAsset, const AssetType& assetType, bool sign = true); + bool CreateTransactionAll(const std::vector& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, + int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool fNewAsset, const std::vector assets, const CTxDestination destination, bool fTransferAsset, bool fReissueAsset, const CReissueAsset& reissueAsset, const AssetType& assetType, bool sign); + + /** RVN END */ bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey, CConnman* connman, CValidationState& state); diff --git a/test/functional/assets.py b/test/functional/assets.py index 75b5a99fd4..022f630b71 100755 --- a/test/functional/assets.py +++ b/test/functional/assets.py @@ -10,7 +10,7 @@ from test_framework.util import ( assert_equal, assert_is_hash_string, - assert_raises_rpc_error + assert_raises_rpc_error, ) import string @@ -20,19 +20,19 @@ def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 3 - def run_test(self): - self.log.info("Running test!") - + def activate_assets(self): + self.log.info("Generating RVN for node[0] and activating assets...") n0, n1, n2 = self.nodes[0], self.nodes[1], self.nodes[2] - self.log.info("Generating RVN for node[0] and activating assets...") n0.generate(1) self.sync_all() - n2.generate(431) - self.sync_all() - assert_equal(n0.getbalance(), 5000) n0.generate(431) self.sync_all() + assert_equal("active", n0.getblockchaininfo()['bip9_softforks']['assets']['status']) + + def big_test(self): + self.log.info("Running big test!") + n0, n1, n2 = self.nodes[0], self.nodes[1], self.nodes[2] self.log.info("Calling issue()...") address0 = n0.getnewaddress() @@ -164,8 +164,36 @@ def run_test(self): assert_equal(raven_assets[1], "RAVEN3") self.sync_all() + def issue_param_checks(self): + self.log.info("Checking bad parameter handling!") + n0, n1, n2 = self.nodes[0], self.nodes[1], self.nodes[2] + + # just plain bad asset name + assert_raises_rpc_error(-8, "Invalid asset name: bad-asset-name", \ + n0.issue, "bad-asset-name"); + + # trying to issue things that can't be issued + assert_raises_rpc_error(-8, "Unsupported asset type: OWNER", \ + n0.issue, "AN_OWNER!"); + assert_raises_rpc_error(-8, "Unsupported asset type: MSGCHANNEL", \ + n0.issue, "A_MSGCHANNEL~CHANNEL_4"); + assert_raises_rpc_error(-8, "Unsupported asset type: VOTE", \ + n0.issue, "A_VOTE^PEDRO"); + + # check bad unique params + assert_raises_rpc_error(-8, "Invalid parameters for issuing a unique asset.", \ + n0.issue, "A_UNIQUE#ASSET", 2) + assert_raises_rpc_error(-8, "Invalid parameters for issuing a unique asset.", \ + n0.issue, "A_UNIQUE#ASSET", 1, "", "", 1) + assert_raises_rpc_error(-8, "Invalid parameters for issuing a unique asset.", \ + n0.issue, "A_UNIQUE#ASSET", 1, "", "", 0, True) + + def chain_assets(self): self.log.info("Issuing chained assets in depth issue()...") + n0, n1, n2 = self.nodes[0], self.nodes[1], self.nodes[2] + chain_address = n0.getnewaddress() + ipfs_hash = "QmacSRmrkVmvJfbCpmU6pK72furJ8E8fbKHindrLxmYMQo" chain_string = "CHAIN1" n0.issue(asset_name=chain_string, qty=1000, to_address=chain_address, change_address="", \ units=4, reissuable=True, has_ipfs=True, ipfs_hash=ipfs_hash) @@ -201,7 +229,6 @@ def run_test(self): chain_assets = n1.listassets(asset="CHAIN2/*", verbose=False) assert_equal(len(chain_assets), 26) - self.log.info("Chaining reissue transactions...") address0 = n0.getnewaddress() n0.issue(asset_name="CHAIN_REISSUE", qty=1000, to_address=address0, change_address="", \ @@ -211,7 +238,7 @@ def run_test(self): self.sync_all() n0.reissue(asset_name="CHAIN_REISSUE", qty=1000, to_address=address0, change_address="", \ - reissuable=True) + reissuable=True) assert_raises_rpc_error(-4, "Error: The transaction was rejected! Reason given: bad-tx-reissue-chaining-not-allowed", n0.reissue, "CHAIN_REISSUE", 1000, address0, "", True) n0.generate(1) @@ -230,6 +257,11 @@ def run_test(self): assert_equal(assetdata["reissuable"], 1) assert_equal(assetdata["has_ipfs"], 0) + def run_test(self): + self.activate_assets(); + self.big_test(); + self.issue_param_checks(); + self.chain_assets(); if __name__ == '__main__': AssetTest().main() diff --git a/test/functional/rawassettransactions.py b/test/functional/rawassettransactions.py index ce9423ff07..6c40fdf70a 100755 --- a/test/functional/rawassettransactions.py +++ b/test/functional/rawassettransactions.py @@ -6,7 +6,7 @@ """Test the rawtransaction RPCs for asset transactions. """ from io import BytesIO - +from pprint import * from test_framework.test_framework import RavenTestFramework from test_framework.util import * from test_framework.mininode import CTransaction, CScriptTransfer @@ -14,19 +14,27 @@ class RawAssetTransactionsTest(RavenTestFramework): def set_test_params(self): self.setup_clean_chain = True - self.num_nodes = 2 + self.num_nodes = 3 - def issue_reissue_transfer_test(self): - ######################################## - # activate assets - self.nodes[0].generate(500) + def activate_assets(self): + self.log.info("Generating RVN for node[0] and activating assets...") + n0, n1, n2 = self.nodes[0], self.nodes[1], self.nodes[2] + + n0.generate(1) self.sync_all() + n0.generate(431) + self.sync_all() + assert_equal("active", n0.getblockchaininfo()['bip9_softforks']['assets']['status']) + + def issue_reissue_transfer_test(self): + self.log.info("Doing a big issue-reissue-transfer test...") + n0, n1, n2 = self.nodes[0], self.nodes[1], self.nodes[2] ######################################## # issue - to_address = self.nodes[0].getnewaddress() - change_address = self.nodes[0].getnewaddress() - unspent = self.nodes[0].listunspent()[0] + to_address = n0.getnewaddress() + change_address = n0.getnewaddress() + unspent = n0.listunspent()[0] inputs = [{k: unspent[k] for k in ['txid', 'vout']}] outputs = { 'n1issueAssetXXXXXXXXXXXXXXXXWdnemQ': 500, @@ -41,21 +49,21 @@ def issue_reissue_transfer_test(self): } } } - tx_issue = self.nodes[0].createrawtransaction(inputs, outputs) - tx_issue_signed = self.nodes[0].signrawtransaction(tx_issue) - tx_issue_hash = self.nodes[0].sendrawtransaction(tx_issue_signed['hex']) + tx_issue = n0.createrawtransaction(inputs, outputs) + tx_issue_signed = n0.signrawtransaction(tx_issue) + tx_issue_hash = n0.sendrawtransaction(tx_issue_signed['hex']) assert_is_hash_string(tx_issue_hash) self.log.info("issue tx: " + tx_issue_hash) - self.nodes[0].generate(1) + n0.generate(1) self.sync_all() - assert_equal(1000, self.nodes[0].listmyassets('TEST_ASSET')['TEST_ASSET']) - assert_equal(1, self.nodes[0].listmyassets('TEST_ASSET!')['TEST_ASSET!']) + assert_equal(1000, n0.listmyassets('TEST_ASSET')['TEST_ASSET']) + assert_equal(1, n0.listmyassets('TEST_ASSET!')['TEST_ASSET!']) ######################################## # reissue - unspent = self.nodes[0].listunspent()[0] - unspent_asset_owner = self.nodes[0].listmyassets('TEST_ASSET!', True)['TEST_ASSET!']['outpoints'][0] + unspent = n0.listunspent()[0] + unspent_asset_owner = n0.listmyassets('TEST_ASSET!', True)['TEST_ASSET!']['outpoints'][0] inputs = [ {k: unspent[k] for k in ['txid', 'vout']}, @@ -73,24 +81,24 @@ def issue_reissue_transfer_test(self): } } - tx_reissue = self.nodes[0].createrawtransaction(inputs, outputs) - tx_reissue_signed = self.nodes[0].signrawtransaction(tx_reissue) - tx_reissue_hash = self.nodes[0].sendrawtransaction(tx_reissue_signed['hex']) + tx_reissue = n0.createrawtransaction(inputs, outputs) + tx_reissue_signed = n0.signrawtransaction(tx_reissue) + tx_reissue_hash = n0.sendrawtransaction(tx_reissue_signed['hex']) assert_is_hash_string(tx_reissue_hash) self.log.info("reissue tx: " + tx_reissue_hash) - self.nodes[0].generate(1) + n0.generate(1) self.sync_all() - assert_equal(2000, self.nodes[0].listmyassets('TEST_ASSET')['TEST_ASSET']) - assert_equal(1, self.nodes[0].listmyassets('TEST_ASSET!')['TEST_ASSET!']) + assert_equal(2000, n0.listmyassets('TEST_ASSET')['TEST_ASSET']) + assert_equal(1, n0.listmyassets('TEST_ASSET!')['TEST_ASSET!']) self.sync_all() ######################################## # transfer - remote_to_address = self.nodes[1].getnewaddress() - unspent = self.nodes[0].listunspent()[0] - unspent_asset = self.nodes[0].listmyassets('TEST_ASSET', True)['TEST_ASSET']['outpoints'][0] + remote_to_address = n1.getnewaddress() + unspent = n0.listunspent()[0] + unspent_asset = n0.listmyassets('TEST_ASSET', True)['TEST_ASSET']['outpoints'][0] inputs = [ {k: unspent[k] for k in ['txid', 'vout']}, {k: unspent_asset[k] for k in ['txid', 'vout']}, @@ -108,8 +116,8 @@ def issue_reissue_transfer_test(self): } } } - tx_transfer = self.nodes[0].createrawtransaction(inputs, outputs) - tx_transfer_signed = self.nodes[0].signrawtransaction(tx_transfer) + tx_transfer = n0.createrawtransaction(inputs, outputs) + tx_transfer_signed = n0.signrawtransaction(tx_transfer) tx_hex = tx_transfer_signed['hex'] ######################################## @@ -122,7 +130,7 @@ def issue_reissue_transfer_test(self): tx.vin[1].scriptSig = hex_str_to_bytes(tampered_sig) tampered_hex = bytes_to_hex_str(tx.serialize()) assert_raises_rpc_error(-26, "mandatory-script-verify-flag-failed (Script failed an OP_EQUALVERIFY operation)", - self.nodes[0].sendrawtransaction, tampered_hex) + n0.sendrawtransaction, tampered_hex) ######################################## # try tampering with the asset script @@ -145,7 +153,7 @@ def issue_reissue_transfer_test(self): t2.deserialize(BytesIO(hex_str_to_bytes(tampered_transfer))) tampered_hex = bytes_to_hex_str(tx.serialize()) assert_raises_rpc_error(-26, "mandatory-script-verify-flag-failed (Signature must be zero for failed CHECK(MULTI)SIG operation)", - self.nodes[0].sendrawtransaction, tampered_hex) + n0.sendrawtransaction, tampered_hex) ######################################## # try tampering with asset outs so ins and outs don't add up @@ -163,11 +171,11 @@ def issue_reissue_transfer_test(self): } } } - tx_bad_transfer = self.nodes[0].createrawtransaction(inputs, bad_outputs) - tx_bad_transfer_signed = self.nodes[0].signrawtransaction(tx_bad_transfer) + tx_bad_transfer = n0.createrawtransaction(inputs, bad_outputs) + tx_bad_transfer_signed = n0.signrawtransaction(tx_bad_transfer) tx_bad_hex = tx_bad_transfer_signed['hex'] - assert_raises_rpc_error(-26, "bad-tx-asset-inputs-amount-mismatch-with-outputs-amount", - self.nodes[0].sendrawtransaction, tx_bad_hex) + assert_raises_rpc_error(-26, "bad-tx-inputs-outputs-mismatch Bad Transaction - Assets would be burnt TEST_ASSET", + n0.sendrawtransaction, tx_bad_hex) ######################################## # try tampering with asset outs so they don't use proper units @@ -185,27 +193,79 @@ def issue_reissue_transfer_test(self): } } } - tx_bad_transfer = self.nodes[0].createrawtransaction(inputs, bad_outputs) - tx_bad_transfer_signed = self.nodes[0].signrawtransaction(tx_bad_transfer) + tx_bad_transfer = n0.createrawtransaction(inputs, bad_outputs) + tx_bad_transfer_signed = n0.signrawtransaction(tx_bad_transfer) tx_bad_hex = tx_bad_transfer_signed['hex'] assert_raises_rpc_error(-26, "bad-txns-transfer-asset-amount-not-match-units", - self.nodes[0].sendrawtransaction, tx_bad_hex) + n0.sendrawtransaction, tx_bad_hex) ######################################## # send the good transfer - tx_transfer_hash = self.nodes[0].sendrawtransaction(tx_hex) + tx_transfer_hash = n0.sendrawtransaction(tx_hex) assert_is_hash_string(tx_transfer_hash) self.log.info("transfer tx: " + tx_transfer_hash) - self.nodes[0].generate(1) + n0.generate(1) + self.sync_all() + assert_equal(1600, n0.listmyassets('TEST_ASSET')['TEST_ASSET']) + assert_equal(1, n0.listmyassets('TEST_ASSET!')['TEST_ASSET!']) + assert_equal(400, n1.listmyassets('TEST_ASSET')['TEST_ASSET']) + + + def unique_assets_test(self): + self.log.info("Testing unique assets...") + n0, n1, n2 = self.nodes[0], self.nodes[1], self.nodes[2] + + root = "RINGU" + owner = f"{root}!" + n0.issue(root) + n0.generate(1) + self.sync_all() + + asset_tags = ["myprecious1", "bind3", "gold7", "men9"] + ipfs_hashes = ["QmWWQSuPMS6aXCbZKpEjPHPUZN2NjB3YrhJTHsV4X3vb2t"] * len(asset_tags) + + to_address = n0.getnewaddress() + change_address = n0.getnewaddress() + unspent = n0.listunspent()[0] + unspent_asset_owner = n0.listmyassets(owner, True)[owner]['outpoints'][0] + + inputs = [ + {k: unspent[k] for k in ['txid', 'vout']}, + {k: unspent_asset_owner[k] for k in ['txid', 'vout']}, + ] + + burn = 5 * len(asset_tags) + outputs = { + 'n1issueUniqueAssetXXXXXXXXXXS4695i': burn, + change_address: float(unspent['amount']) - (burn + 0.0001), + to_address: { + 'issue_unique': { + 'root_name': root, + 'asset_tags': asset_tags, + 'ipfs_hashes': ipfs_hashes, + } + } + } + + hex = n0.createrawtransaction(inputs, outputs) + signed_hex = n0.signrawtransaction(hex)['hex'] + tx_hash = n0.sendrawtransaction(signed_hex) + n0.generate(1) self.sync_all() - assert_equal(1600, self.nodes[0].listmyassets('TEST_ASSET')['TEST_ASSET']) - assert_equal(1, self.nodes[0].listmyassets('TEST_ASSET!')['TEST_ASSET!']) - assert_equal(400, self.nodes[1].listmyassets('TEST_ASSET')['TEST_ASSET']) + + for tag in asset_tags: + asset_name = f"{root}#{tag}" + assert_equal(1, n0.listmyassets()[asset_name]) + assert_equal(1, n0.listassets(asset_name, True)[asset_name]['has_ipfs']) + assert_equal(ipfs_hashes[0], n0.listassets(asset_name, True)[asset_name]['ipfs_hash']) def run_test(self): + self.activate_assets() self.issue_reissue_transfer_test() + self.unique_assets_test() + if __name__ == '__main__': RawAssetTransactionsTest().main() diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index 332de618ca..a80ef35df7 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -26,10 +26,22 @@ # Assert functions ################## +def assert_contains(val, arr): + if not (val in arr): + raise AssertionError("val %s not in arr" % (val)) + +def assert_does_not_contain(val, arr): + if (val in arr): + raise AssertionError("val %s is in arr" % (val)) + def assert_contains_pair(key, val, dict): if not (key in dict and val == dict[key]): raise AssertionError("k/v pair (%s,%s) not in dict" % (key, val)) +def assert_does_not_contain_key(key, dict): + if (key in dict): + raise AssertionError("key %s is in dict" % (key)) + def assert_fee_amount(fee, tx_size, fee_per_kB): """Assert the fee was in range""" target_fee = tx_size * fee_per_kB / 1000 diff --git a/test/functional/unique_assets.py b/test/functional/unique_assets.py new file mode 100755 index 0000000000..0ca51094cd --- /dev/null +++ b/test/functional/unique_assets.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python3 +# Copyright (c) 2017 The Bitcoin Core developers +# Copyright (c) 2017 The Raven Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Testing unique asset use cases + +""" +import random + +from test_framework.test_framework import RavenTestFramework +from test_framework.util import ( + assert_contains, + assert_does_not_contain_key, + assert_equal, + assert_raises_rpc_error, +) + + +def gen_root_asset_name(): + size = random.randint(3, 14) + name = "" + for _ in range(1, size+1): + ch = random.randint(65, 65+25) + name += chr(ch) + return name + +def gen_unique_asset_name(root): + tag_ab = "-ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@$%&*()[]{}<>_.;?\\:" + name = root + "#" + tag_size = random.randint(1, 15) + for _ in range(1, tag_size+1): + tag_c = tag_ab[random.randint(0, len(tag_ab) - 1)] + name += tag_c + return name + +class UniqueAssetTest(RavenTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 3 + + def activate_assets(self): + self.log.info("Generating RVN for node[0] and activating assets...") + n0 = self.nodes[0] + n0.generate(432) + self.sync_all() + assert_equal("active", n0.getblockchaininfo()['bip9_softforks']['assets']['status']) + + def issue_one(self): + self.log.info("Issuing a unique asset...") + n0 = self.nodes[0] + root = gen_root_asset_name() + n0.issue(asset_name=root) + n0.generate(1) + asset_name = gen_unique_asset_name(root) + tx_hash = n0.issue(asset_name=asset_name) + n0.generate(1) + assert_equal(1, n0.listmyassets()[asset_name]) + + def issue_invalid(self): + self.log.info("Trying some invalid calls...") + n0, n1 = self.nodes[0], self.nodes[1] + n1.generate(10) + self.sync_all() + + root = gen_root_asset_name() + asset_name = gen_unique_asset_name(root) + + # no root + assert_raises_rpc_error(-32600, f"Wallet doesn't have asset: {root}!", n0.issue, asset_name) + + # don't own root + n0.sendtoaddress(n1.getnewaddress(), 501) + n0.generate(1) + self.sync_all() + n1.issue(root) + n1.generate(1) + self.sync_all() + assert_contains(root, n0.listassets()) + assert_raises_rpc_error(-32600, f"Wallet doesn't have asset: {root}!", n0.issue, asset_name) + n1.transfer(f"{root}!", 1, n0.getnewaddress()) + n1.generate(1) + self.sync_all() + + # bad qty + assert_raises_rpc_error(-8, "Invalid parameters for issuing a unique asset.", n0.issue, asset_name, 2) + + # bad units + assert_raises_rpc_error(-8, "Invalid parameters for issuing a unique asset.", n0.issue, asset_name, 1, "", "", 1) + + # reissuable + assert_raises_rpc_error(-8, "Invalid parameters for issuing a unique asset.", n0.issue, asset_name, 1, "", "", 0, True) + + # already exists + n0.issue(asset_name) + n0.generate(1) + self.sync_all() + assert_raises_rpc_error(-8, f"Invalid parameter: asset_name '{asset_name}' has already been used", n0.issue, asset_name) + + + def issueunique_test(self): + self.log.info("Testing issueunique RPC...") + n0, n1 = self.nodes[0], self.nodes[1] + n0.sendtoaddress(n1.getnewaddress(), 501) + + root = gen_root_asset_name() + n0.issue(asset_name=root) + asset_tags = ["first", "second"] + ipfs_hashes = ["QmWWQSuPMS6aXCbZKpEjPHPUZN2NjB3YrhJTHsV4X3vb2t"] * len(asset_tags) + n0.issueunique(root, asset_tags, ipfs_hashes) + block_hash = n0.generate(1)[0] + + for tag in asset_tags: + asset_name = f"{root}#{tag}" + assert_equal(1, n0.listmyassets()[asset_name]) + assert_equal(1, n0.listassets(asset_name, True)[asset_name]['has_ipfs']) + assert_equal(ipfs_hashes[0], n0.listassets(asset_name, True)[asset_name]['ipfs_hash']) + + # invalidate + n0.invalidateblock(block_hash) + assert_does_not_contain_key(root, n0.listmyassets()) + assert_does_not_contain_key(asset_name, n0.listmyassets()) + + # reconsider + n0.reconsiderblock(block_hash) + assert_contains(root, n0.listmyassets()) + assert_contains(asset_name, n0.listmyassets()) + + # root doesn't exist + missing_asset = "VAPOUR" + assert_raises_rpc_error(-32600, f"Wallet doesn't have asset: {missing_asset}!", n0.issueunique, missing_asset, asset_tags) + + # don't own root + n1.issue(missing_asset) + n1.generate(1) + self.sync_all() + assert_contains(missing_asset, n0.listassets()) + assert_raises_rpc_error(-32600, f"Wallet doesn't have asset: {missing_asset}!", n0.issueunique, missing_asset, asset_tags) + + def run_test(self): + self.activate_assets() + self.issueunique_test() + self.issue_one() + self.issue_invalid() + + +if __name__ == '__main__': + UniqueAssetTest().main() From 9152c6f9dcf3df70d20b44663dff22212d05d4f5 Mon Sep 17 00:00:00 2001 From: blondfrogs Date: Thu, 23 Aug 2018 15:56:32 -0600 Subject: [PATCH 013/105] Implement Asset Control --- src/Makefile.qt.include | 7 + src/assets/assets.cpp | 37 +- src/assets/assets.h | 9 +- src/qt/assetcontroldialog.cpp | 809 +++++++++++++++++++++++++++++ src/qt/assetcontroldialog.h | 119 +++++ src/qt/assetcontroltreewidget.cpp | 35 ++ src/qt/assetcontroltreewidget.h | 23 + src/qt/assetsdialog.cpp | 231 ++++---- src/qt/assetsdialog.h | 27 +- src/qt/createassetdialog.cpp | 3 +- src/qt/forms/assetcontroldialog.ui | 517 ++++++++++++++++++ src/qt/forms/assetsdialog.ui | 74 +-- src/qt/forms/sendassetsentry.ui | 6 +- src/qt/ravenunits.cpp | 5 + src/qt/ravenunits.h | 2 + src/qt/reissueassetdialog.cpp | 9 +- src/qt/sendassetsentry.cpp | 91 +++- src/qt/sendassetsentry.h | 6 + src/qt/walletmodel.cpp | 25 + src/qt/walletmodel.h | 5 +- src/rpc/assets.cpp | 4 +- src/wallet/coincontrol.h | 13 + src/wallet/wallet.cpp | 70 ++- src/wallet/wallet.h | 6 + 24 files changed, 1935 insertions(+), 198 deletions(-) create mode 100644 src/qt/assetcontroldialog.cpp create mode 100644 src/qt/assetcontroldialog.h create mode 100644 src/qt/assetcontroltreewidget.cpp create mode 100644 src/qt/assetcontroltreewidget.h create mode 100644 src/qt/forms/assetcontroldialog.ui diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 57b183151d..30cbde15af 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -98,6 +98,7 @@ QT_TS = \ QT_FORMS_UI = \ qt/forms/addressbookpage.ui \ qt/forms/askpassphrasedialog.ui \ + qt/forms/assetcontroldialog.ui \ qt/forms/coincontroldialog.ui \ qt/forms/editaddressdialog.ui \ qt/forms/helpmessagedialog.ui \ @@ -122,6 +123,8 @@ QT_MOC_CPP = \ qt/moc_addressbookpage.cpp \ qt/moc_addresstablemodel.cpp \ qt/moc_askpassphrasedialog.cpp \ + qt/moc_assetcontroldialog.cpp \ + qt/moc_assetcontroltreewidget.cpp \ qt/moc_bantablemodel.cpp \ qt/moc_ravenaddressvalidator.cpp \ qt/moc_ravenamountfield.cpp \ @@ -196,6 +199,8 @@ RAVEN_QT_H = \ qt/addressbookpage.h \ qt/addresstablemodel.h \ qt/askpassphrasedialog.h \ + qt/assetcontroldialog.h \ + qt/assetcontroltreewidget.h \ qt/assetsdialog.h \ qt/createassetdialog.h \ qt/bantablemodel.h \ @@ -343,6 +348,8 @@ RAVEN_QT_WALLET_CPP = \ qt/addressbookpage.cpp \ qt/addresstablemodel.cpp \ qt/askpassphrasedialog.cpp \ + qt/assetcontroldialog.cpp \ + qt/assetcontroltreewidget.cpp \ qt/assetsdialog.cpp \ qt/createassetdialog.cpp \ qt/coincontroldialog.cpp \ diff --git a/src/assets/assets.cpp b/src/assets/assets.cpp index b37f7f218b..6e6c812810 100644 --- a/src/assets/assets.cpp +++ b/src/assets/assets.cpp @@ -255,7 +255,7 @@ bool CNewAsset::IsValid(std::string& strError, CAssetsCache& assetCache, bool fC } if (nAmount > MAX_MONEY) { - strError = "Invalid parameter: asset amount greater than max money: " + MAX_MONEY / COIN; + strError = "Invalid parameter: asset amount greater than max money: " + std::to_string(MAX_MONEY / COIN); return false; } @@ -1976,7 +1976,15 @@ void GetAssetData(const CScript& script, CAssetOutputEntry& data) } } -void GetAllOwnedAssets(CWallet* pwallet, std::vector& names) +void GetAllAdministrativeAssets(CWallet *pwallet, std::vector &names) +{ + if(!pwallet) + return; + + GetAllMyAssets(pwallet, names, true, true); +} + +void GetAllMyAssets(CWallet* pwallet, std::vector& names, bool fIncludeAdministrator, bool fOnlyAdministrator) { if(!pwallet) return; @@ -1985,16 +1993,27 @@ void GetAllOwnedAssets(CWallet* pwallet, std::vector& names) pwallet->AvailableAssets(mapAssets); for (auto item : mapAssets) { - if (IsAssetNameAnOwner(item.first)) + bool isOwner = IsAssetNameAnOwner(item.first); + + if (isOwner) { + if (fOnlyAdministrator || fIncludeAdministrator) + names.emplace_back(item.first); + } else { + if (fOnlyAdministrator) + continue; names.emplace_back(item.first); + } } } -void GetAllMyAssets(std::vector& names) +void GetAllMyAssetsFromCache(std::vector& names) { - for (auto owned : passets->mapMyUnspentAssets) { + if (!passets) + return; + + for (auto owned : passets->mapMyUnspentAssets) names.emplace_back(owned.first); - } + } CAmount GetIssueAssetBurnAmount() @@ -2391,7 +2410,7 @@ bool CreateReissueAssetTransaction(CWallet* pwallet, const CReissueAsset& reissu return true; } -bool CreateTransferAssetTransaction(CWallet* pwallet, const std::vector< std::pair >vTransfers, const std::string& changeAddress, std::pair& error, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRequired) +bool CreateTransferAssetTransaction(CWallet* pwallet, const CCoinControl& coinControl, const std::vector< std::pair >vTransfers, const std::string& changeAddress, std::pair& error, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRequired) { // Initialize Values for transaction std::string strTxError; @@ -2451,10 +2470,8 @@ bool CreateTransferAssetTransaction(CWallet* pwallet, const std::vector< std::pa vecSend.push_back(recipient); } - CCoinControl coin_control; - // Create and send the transaction - if (!pwallet->CreateTransactionWithTransferAsset(vecSend, wtxNew, reservekey, nFeeRequired, nChangePosRet, strTxError, coin_control)) { + if (!pwallet->CreateTransactionWithTransferAsset(vecSend, wtxNew, reservekey, nFeeRequired, nChangePosRet, strTxError, coinControl)) { if (!fSubtractFeeFromAmount && nFeeRequired > curBalance) { error = std::make_pair(RPC_WALLET_ERROR, strprintf("Error: This transaction requires a transaction fee of at least %s", FormatMoney(nFeeRequired))); return false; diff --git a/src/assets/assets.h b/src/assets/assets.h index ad18358717..82733fa8db 100644 --- a/src/assets/assets.h +++ b/src/assets/assets.h @@ -45,6 +45,7 @@ class CWallet; class CReserveKey; class CWalletTx; struct CAssetOutputEntry; +class CCoinControl; // 50000 * 82 Bytes == 4.1 Mb #define MAX_CACHE_ASSETS_SIZE 50000 @@ -345,8 +346,10 @@ bool IsScriptTransferAsset(const CScript& scriptPubKey); bool IsNewOwnerTxValid(const CTransaction& tx, const std::string& assetName, const std::string& address, std::string& errorMsg); -void GetAllOwnedAssets(CWallet* pwallet, std::vector& names); -void GetAllMyAssets(std::vector& names); +void GetAllAdministrativeAssets(CWallet *pwallet, std::vector &names); +void GetAllMyAssets(CWallet* pwallet, std::vector& names, bool fIncludeAdministrator = false, bool fOnlyAdministrator = false); +/** TO BE USED ONLY ON STARTUP */ +void GetAllMyAssetsFromCache(std::vector& names); void UpdatePossibleAssets(); @@ -371,6 +374,6 @@ std::string EncodeIPFS(std::string decoded); bool CreateAssetTransaction(CWallet* pwallet, const CNewAsset& asset, const std::string& address, std::pair& error, std::string& rvnChangeAddress, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRequired); bool CreateReissueAssetTransaction(CWallet* pwallet, const CReissueAsset& asset, const std::string& address, const std::string& changeAddress, std::pair& error, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRequired); -bool CreateTransferAssetTransaction(CWallet* pwallet, const std::vector< std::pair >vTransfers, const std::string& changeAddress, std::pair& error, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRequired); +bool CreateTransferAssetTransaction(CWallet* pwallet, const CCoinControl& coinControl, const std::vector< std::pair >vTransfers, const std::string& changeAddress, std::pair& error, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRequired); bool SendAssetTransaction(CWallet* pwallet, CWalletTx& transaction, CReserveKey& reserveKey, std::pair& error, std::string& txid); #endif //RAVENCOIN_ASSET_PROTOCOL_H diff --git a/src/qt/assetcontroldialog.cpp b/src/qt/assetcontroldialog.cpp new file mode 100644 index 0000000000..14ea897f23 --- /dev/null +++ b/src/qt/assetcontroldialog.cpp @@ -0,0 +1,809 @@ +// Copyright (c) 2011-2016 The Bitcoin Core developers +// Copyright (c) 2017 The Raven Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "assetcontroldialog.h" +#include "ui_assetcontroldialog.h" + +#include "addresstablemodel.h" +#include "ravenunits.h" +#include "guiutil.h" +#include "optionsmodel.h" +#include "platformstyle.h" +#include "txmempool.h" +#include "walletmodel.h" + +#include "wallet/coincontrol.h" +#include "init.h" +#include "policy/fees.h" +#include "policy/policy.h" +#include "validation.h" // For mempool +#include "wallet/fees.h" +#include "wallet/wallet.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QList AssetControlDialog::payAmounts; +CCoinControl* AssetControlDialog::assetControl = new CCoinControl(); +bool AssetControlDialog::fSubtractFeeFromAmount = false; + +bool CAssetControlWidgetItem::operator<(const QTreeWidgetItem &other) const { + int column = treeWidget()->sortColumn(); + if (column == AssetControlDialog::COLUMN_AMOUNT || column == AssetControlDialog::COLUMN_DATE || column == AssetControlDialog::COLUMN_CONFIRMATIONS) + return data(column, Qt::UserRole).toLongLong() < other.data(column, Qt::UserRole).toLongLong(); + return QTreeWidgetItem::operator<(other); +} + +AssetControlDialog::AssetControlDialog(const PlatformStyle *_platformStyle, QWidget *parent) : + QDialog(parent), + ui(new Ui::AssetControlDialog), + model(0), + platformStyle(_platformStyle) +{ + ui->setupUi(this); + + // context menu actions + QAction *copyAddressAction = new QAction(tr("Copy address"), this); + QAction *copyLabelAction = new QAction(tr("Copy label"), this); + QAction *copyAmountAction = new QAction(tr("Copy amount"), this); + copyTransactionHashAction = new QAction(tr("Copy transaction ID"), this); // we need to enable/disable this + lockAction = new QAction(tr("Lock unspent"), this); // we need to enable/disable this + unlockAction = new QAction(tr("Unlock unspent"), this); // we need to enable/disable this + + // context menu + contextMenu = new QMenu(this); + contextMenu->addAction(copyAddressAction); + contextMenu->addAction(copyLabelAction); + contextMenu->addAction(copyAmountAction); + contextMenu->addAction(copyTransactionHashAction); + contextMenu->addSeparator(); + contextMenu->addAction(lockAction); + contextMenu->addAction(unlockAction); + + // context menu signals + connect(ui->treeWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showMenu(QPoint))); + connect(copyAddressAction, SIGNAL(triggered()), this, SLOT(copyAddress())); + connect(copyLabelAction, SIGNAL(triggered()), this, SLOT(copyLabel())); + connect(copyAmountAction, SIGNAL(triggered()), this, SLOT(copyAmount())); + connect(copyTransactionHashAction, SIGNAL(triggered()), this, SLOT(copyTransactionHash())); + connect(lockAction, SIGNAL(triggered()), this, SLOT(lockCoin())); + connect(unlockAction, SIGNAL(triggered()), this, SLOT(unlockCoin())); + + // clipboard actions + QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this); + QAction *clipboardAmountAction = new QAction(tr("Copy amount"), this); + QAction *clipboardFeeAction = new QAction(tr("Copy fee"), this); + QAction *clipboardAfterFeeAction = new QAction(tr("Copy after fee"), this); + QAction *clipboardBytesAction = new QAction(tr("Copy bytes"), this); + QAction *clipboardLowOutputAction = new QAction(tr("Copy dust"), this); + QAction *clipboardChangeAction = new QAction(tr("Copy change"), this); + + connect(clipboardQuantityAction, SIGNAL(triggered()), this, SLOT(clipboardQuantity())); + connect(clipboardAmountAction, SIGNAL(triggered()), this, SLOT(clipboardAmount())); + connect(clipboardFeeAction, SIGNAL(triggered()), this, SLOT(clipboardFee())); + connect(clipboardAfterFeeAction, SIGNAL(triggered()), this, SLOT(clipboardAfterFee())); + connect(clipboardBytesAction, SIGNAL(triggered()), this, SLOT(clipboardBytes())); + connect(clipboardLowOutputAction, SIGNAL(triggered()), this, SLOT(clipboardLowOutput())); + connect(clipboardChangeAction, SIGNAL(triggered()), this, SLOT(clipboardChange())); + + ui->labelAssetControlQuantity->addAction(clipboardQuantityAction); + ui->labelAssetControlAmount->addAction(clipboardAmountAction); + ui->labelAssetControlFee->addAction(clipboardFeeAction); + ui->labelAssetControlAfterFee->addAction(clipboardAfterFeeAction); + ui->labelAssetControlBytes->addAction(clipboardBytesAction); + ui->labelAssetControlLowOutput->addAction(clipboardLowOutputAction); + ui->labelAssetControlChange->addAction(clipboardChangeAction); + + // toggle tree/list mode + connect(ui->radioTreeMode, SIGNAL(toggled(bool)), this, SLOT(radioTreeMode(bool))); + connect(ui->radioListMode, SIGNAL(toggled(bool)), this, SLOT(radioListMode(bool))); + + // click on checkbox + connect(ui->treeWidget, SIGNAL(itemChanged(QTreeWidgetItem*, int)), this, SLOT(viewItemChanged(QTreeWidgetItem*, int))); + + // click on header +#if QT_VERSION < 0x050000 + ui->treeWidget->header()->setClickable(true); +#else + ui->treeWidget->header()->setSectionsClickable(true); +#endif + connect(ui->treeWidget->header(), SIGNAL(sectionClicked(int)), this, SLOT(headerSectionClicked(int))); + + // ok button + connect(ui->buttonBox, SIGNAL(clicked( QAbstractButton*)), this, SLOT(buttonBoxClicked(QAbstractButton*))); + + // (un)select all + connect(ui->pushButtonSelectAll, SIGNAL(clicked()), this, SLOT(buttonSelectAllClicked())); + + // change coin control first column label due Qt4 bug. + // see https://github.com/RavenProject/Ravencoin/issues/5716 + ui->treeWidget->headerItem()->setText(COLUMN_CHECKBOX, QString()); + + ui->treeWidget->setColumnWidth(COLUMN_CHECKBOX, 84); + ui->treeWidget->setColumnWidth(COLUMN_AMOUNT, 110); + ui->treeWidget->setColumnWidth(COLUMN_LABEL, 190); + ui->treeWidget->setColumnWidth(COLUMN_ADDRESS, 320); + ui->treeWidget->setColumnWidth(COLUMN_DATE, 130); + ui->treeWidget->setColumnWidth(COLUMN_CONFIRMATIONS, 110); + ui->treeWidget->setColumnHidden(COLUMN_TXHASH, true); // store transaction hash in this column, but don't show it + ui->treeWidget->setColumnHidden(COLUMN_VOUT_INDEX, true); // store vout index in this column, but don't show it + + // default view is sorted by amount desc + sortView(COLUMN_AMOUNT, Qt::DescendingOrder); + + // restore list mode and sortorder as a convenience feature + QSettings settings; + if (settings.contains("nCoinControlMode") && !settings.value("nCoinControlMode").toBool()) + ui->radioTreeMode->click(); + if (settings.contains("nCoinControlSortColumn") && settings.contains("nCoinControlSortOrder")) + sortView(settings.value("nCoinControlSortColumn").toInt(), ((Qt::SortOrder)settings.value("nCoinControlSortOrder").toInt())); + + // Add the assets into the dropdown menu + connect(ui->viewAdministrator, SIGNAL(clicked()), this, SLOT(viewAdministratorClicked())); + connect(ui->assetList, SIGNAL(currentIndexChanged(QString)), this, SLOT(onAssetSelected(QString))); +} + +AssetControlDialog::~AssetControlDialog() +{ + QSettings settings; + settings.setValue("nCoinControlMode", ui->radioListMode->isChecked()); + settings.setValue("nCoinControlSortColumn", sortColumn); + settings.setValue("nCoinControlSortOrder", (int)sortOrder); + + delete ui; +} + +void AssetControlDialog::setModel(WalletModel *_model) +{ + this->model = _model; + + if(_model && _model->getOptionsModel() && _model->getAddressTableModel()) + { + updateView(); + updateAssetList(true); + updateLabelLocked(); + AssetControlDialog::updateLabels(_model, this); + } +} + +// ok button +void AssetControlDialog::buttonBoxClicked(QAbstractButton* button) +{ + if (ui->buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole) { + if (AssetControlDialog::assetControl->HasSelected()) + AssetControlDialog::assetControl->strAssetSelected = ui->assetList->currentText().toStdString(); + done(QDialog::Accepted); // closes the dialog + } +} + +// (un)select all +void AssetControlDialog::buttonSelectAllClicked() +{ + Qt::CheckState state = Qt::Checked; + for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++) + { + if (ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX) != Qt::Unchecked) + { + state = Qt::Unchecked; + break; + } + } + ui->treeWidget->setEnabled(false); + for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++) + if (ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX) != state) + ui->treeWidget->topLevelItem(i)->setCheckState(COLUMN_CHECKBOX, state); + ui->treeWidget->setEnabled(true); + if (state == Qt::Unchecked) + assetControl->UnSelectAll(); // just to be sure + AssetControlDialog::updateLabels(model, this); +} + +// context menu +void AssetControlDialog::showMenu(const QPoint &point) +{ + QTreeWidgetItem *item = ui->treeWidget->itemAt(point); + if(item) + { + contextMenuItem = item; + + // disable some items (like Copy Transaction ID, lock, unlock) for tree roots in context menu + if (item->text(COLUMN_TXHASH).length() == 64) // transaction hash is 64 characters (this means its a child node, so its not a parent node in tree mode) + { + copyTransactionHashAction->setEnabled(true); + if (model->isLockedCoin(uint256S(item->text(COLUMN_TXHASH).toStdString()), item->text(COLUMN_VOUT_INDEX).toUInt())) + { + lockAction->setEnabled(false); + unlockAction->setEnabled(true); + } + else + { + lockAction->setEnabled(true); + unlockAction->setEnabled(false); + } + } + else // this means click on parent node in tree mode -> disable all + { + copyTransactionHashAction->setEnabled(false); + lockAction->setEnabled(false); + unlockAction->setEnabled(false); + } + + // show context menu + contextMenu->exec(QCursor::pos()); + } +} + +// context menu action: copy amount +void AssetControlDialog::copyAmount() +{ + GUIUtil::setClipboard(RavenUnits::removeSpaces(contextMenuItem->text(COLUMN_AMOUNT))); +} + +// context menu action: copy label +void AssetControlDialog::copyLabel() +{ + if (ui->radioTreeMode->isChecked() && contextMenuItem->text(COLUMN_LABEL).length() == 0 && contextMenuItem->parent()) + GUIUtil::setClipboard(contextMenuItem->parent()->text(COLUMN_LABEL)); + else + GUIUtil::setClipboard(contextMenuItem->text(COLUMN_LABEL)); +} + +// context menu action: copy address +void AssetControlDialog::copyAddress() +{ + if (ui->radioTreeMode->isChecked() && contextMenuItem->text(COLUMN_ADDRESS).length() == 0 && contextMenuItem->parent()) + GUIUtil::setClipboard(contextMenuItem->parent()->text(COLUMN_ADDRESS)); + else + GUIUtil::setClipboard(contextMenuItem->text(COLUMN_ADDRESS)); +} + +// context menu action: copy transaction id +void AssetControlDialog::copyTransactionHash() +{ + GUIUtil::setClipboard(contextMenuItem->text(COLUMN_TXHASH)); +} + +// context menu action: lock coin +void AssetControlDialog::lockCoin() +{ + if (contextMenuItem->checkState(COLUMN_CHECKBOX) == Qt::Checked) + contextMenuItem->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); + + COutPoint outpt(uint256S(contextMenuItem->text(COLUMN_TXHASH).toStdString()), contextMenuItem->text(COLUMN_VOUT_INDEX).toUInt()); + model->lockCoin(outpt); + contextMenuItem->setDisabled(true); + contextMenuItem->setIcon(COLUMN_CHECKBOX, platformStyle->SingleColorIcon(":/icons/lock_closed")); + updateLabelLocked(); +} + +// context menu action: unlock coin +void AssetControlDialog::unlockCoin() +{ + COutPoint outpt(uint256S(contextMenuItem->text(COLUMN_TXHASH).toStdString()), contextMenuItem->text(COLUMN_VOUT_INDEX).toUInt()); + model->unlockCoin(outpt); + contextMenuItem->setDisabled(false); + contextMenuItem->setIcon(COLUMN_CHECKBOX, QIcon()); + updateLabelLocked(); +} + +// copy label "Quantity" to clipboard +void AssetControlDialog::clipboardQuantity() +{ + GUIUtil::setClipboard(ui->labelAssetControlQuantity->text()); +} + +// copy label "Amount" to clipboard +void AssetControlDialog::clipboardAmount() +{ + GUIUtil::setClipboard(ui->labelAssetControlAmount->text().left(ui->labelAssetControlAmount->text().indexOf(" "))); +} + +// copy label "Fee" to clipboard +void AssetControlDialog::clipboardFee() +{ + GUIUtil::setClipboard(ui->labelAssetControlFee->text().left(ui->labelAssetControlFee->text().indexOf(" ")).replace(ASYMP_UTF8, "")); +} + +// copy label "After fee" to clipboard +void AssetControlDialog::clipboardAfterFee() +{ + GUIUtil::setClipboard(ui->labelAssetControlAfterFee->text().left(ui->labelAssetControlAfterFee->text().indexOf(" ")).replace(ASYMP_UTF8, "")); +} + +// copy label "Bytes" to clipboard +void AssetControlDialog::clipboardBytes() +{ + GUIUtil::setClipboard(ui->labelAssetControlBytes->text().replace(ASYMP_UTF8, "")); +} + +// copy label "Dust" to clipboard +void AssetControlDialog::clipboardLowOutput() +{ + GUIUtil::setClipboard(ui->labelAssetControlLowOutput->text()); +} + +// copy label "Change" to clipboard +void AssetControlDialog::clipboardChange() +{ + GUIUtil::setClipboard(ui->labelAssetControlChange->text().left(ui->labelAssetControlChange->text().indexOf(" ")).replace(ASYMP_UTF8, "")); +} + +// treeview: sort +void AssetControlDialog::sortView(int column, Qt::SortOrder order) +{ + sortColumn = column; + sortOrder = order; + ui->treeWidget->sortItems(column, order); + ui->treeWidget->header()->setSortIndicator(sortColumn, sortOrder); +} + +// treeview: clicked on header +void AssetControlDialog::headerSectionClicked(int logicalIndex) +{ + if (logicalIndex == COLUMN_CHECKBOX) // click on most left column -> do nothing + { + ui->treeWidget->header()->setSortIndicator(sortColumn, sortOrder); + } + else + { + if (sortColumn == logicalIndex) + sortOrder = ((sortOrder == Qt::AscendingOrder) ? Qt::DescendingOrder : Qt::AscendingOrder); + else + { + sortColumn = logicalIndex; + sortOrder = ((sortColumn == COLUMN_LABEL || sortColumn == COLUMN_ADDRESS) ? Qt::AscendingOrder : Qt::DescendingOrder); // if label or address then default => asc, else default => desc + } + + sortView(sortColumn, sortOrder); + } +} + +// toggle tree mode +void AssetControlDialog::radioTreeMode(bool checked) +{ + if (checked && model) + updateView(); +} + +// toggle list mode +void AssetControlDialog::radioListMode(bool checked) +{ + if (checked && model) + updateView(); +} + +// checkbox clicked by user +void AssetControlDialog::viewItemChanged(QTreeWidgetItem* item, int column) +{ + if (column == COLUMN_CHECKBOX && item->text(COLUMN_TXHASH).length() == 64) // transaction hash is 64 characters (this means its a child node, so its not a parent node in tree mode) + { + COutPoint outpt(uint256S(item->text(COLUMN_TXHASH).toStdString()), item->text(COLUMN_VOUT_INDEX).toUInt()); + + if (item->checkState(COLUMN_CHECKBOX) == Qt::Unchecked) + assetControl->UnSelect(outpt); + else if (item->isDisabled()) // locked (this happens if "check all" through parent node) + item->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); + else + assetControl->Select(outpt); + + // selection changed -> update labels + if (ui->treeWidget->isEnabled()) // do not update on every click for (un)select all + AssetControlDialog::updateLabels(model, this); + } + + // TODO: Remove this temporary qt5 fix after Qt5.3 and Qt5.4 are no longer used. + // Fixed in Qt5.5 and above: https://bugreports.qt.io/browse/QTBUG-43473 +#if QT_VERSION >= 0x050000 + else if (column == COLUMN_CHECKBOX && item->childCount() > 0) + { + if (item->checkState(COLUMN_CHECKBOX) == Qt::PartiallyChecked && item->child(0)->checkState(COLUMN_CHECKBOX) == Qt::PartiallyChecked) + item->setCheckState(COLUMN_CHECKBOX, Qt::Checked); + } +#endif +} + +// shows count of locked unspent outputs +void AssetControlDialog::updateLabelLocked() +{ + std::vector vOutpts; + model->listLockedCoins(vOutpts); + if (vOutpts.size() > 0) + { + ui->labelLocked->setText(tr("(%1 locked)").arg(vOutpts.size())); + ui->labelLocked->setVisible(true); + } + else ui->labelLocked->setVisible(false); +} + +void AssetControlDialog::updateLabels(WalletModel *model, QDialog* dialog) +{ + if (!model) + return; + + // nPayAmount + CAmount nPayAmount = 0; + bool fDust = false; + CMutableTransaction txDummy; + for (const CAmount &amount : AssetControlDialog::payAmounts) + { + nPayAmount += amount; + + if (amount > 0) + { + CTxOut txout(amount, (CScript)std::vector(24, 0)); + txDummy.vout.push_back(txout); + fDust |= IsDust(txout, ::dustRelayFee); + } + } + + std::string strAssetName = ""; + CAmount nAssetAmount = 0; + CAmount nAmount = 0; + CAmount nPayFee = 0; + CAmount nAfterFee = 0; + CAmount nChange = 0; + unsigned int nBytes = 0; + unsigned int nBytesInputs = 0; + unsigned int nQuantity = 0; + bool fWitness = false; + + std::vector vCoinControl; + std::vector vOutputs; + assetControl->ListSelected(vCoinControl); + model->getOutputs(vCoinControl, vOutputs); + + for (const COutput& out : vOutputs) { + // unselect already spent, very unlikely scenario, this could happen + // when selected are spent elsewhere, like rpc or another computer + uint256 txhash = out.tx->GetHash(); + COutPoint outpt(txhash, out.i); + if (model->isSpent(outpt)) + { + assetControl->UnSelect(outpt); + continue; + } + + // Quantity + nQuantity++; + + // Amount + CAmount nCoinAmount; + GetAssetInfoFromScript(out.tx->tx->vout[out.i].scriptPubKey, strAssetName, nCoinAmount); + nAssetAmount += nCoinAmount; + + // Bytes + CTxDestination address; + int witnessversion = 0; + std::vector witnessprogram; + if (out.tx->tx->vout[out.i].scriptPubKey.IsWitnessProgram(witnessversion, witnessprogram)) + { + nBytesInputs += (32 + 4 + 1 + (107 / WITNESS_SCALE_FACTOR) + 4); + fWitness = true; + } + else if(ExtractDestination(out.tx->tx->vout[out.i].scriptPubKey, address)) + { + CPubKey pubkey; + CKeyID *keyid = boost::get(&address); + if (keyid && model->getPubKey(*keyid, pubkey)) + { + nBytesInputs += (pubkey.IsCompressed() ? 148 : 180); + } + else + nBytesInputs += 148; // in all error cases, simply assume 148 here + } + else nBytesInputs += 148; + } + + // calculation + if (nQuantity > 0) + { + // Bytes + nBytes = nBytesInputs + ((AssetControlDialog::payAmounts.size() > 0 ? AssetControlDialog::payAmounts.size() + 1 : 2) * 34) + 10; // always assume +1 output for change here + if (fWitness) + { + // there is some fudging in these numbers related to the actual virtual transaction size calculation that will keep this estimate from being exact. + // usually, the result will be an overestimate within a couple of satoshis so that the confirmation dialog ends up displaying a slightly smaller fee. + // also, the witness stack size value is a variable sized integer. usually, the number of stack items will be well under the single byte var int limit. + nBytes += 2; // account for the serialized marker and flag bytes + nBytes += nQuantity; // account for the witness byte that holds the number of stack items for each input. + } + + // in the subtract fee from amount case, we can tell if zero change already and subtract the bytes, so that fee calculation afterwards is accurate + if (AssetControlDialog::fSubtractFeeFromAmount) + if (nAmount - nPayAmount == 0) + nBytes -= 34; + + // Fee + nPayFee = GetMinimumFee(nBytes, *assetControl, ::mempool, ::feeEstimator, nullptr /* FeeCalculation */); + + if (nPayAmount > 0) + { + nChange = nAssetAmount - nPayAmount; + + if (nChange == 0 && !AssetControlDialog::fSubtractFeeFromAmount) + nBytes -= 34; + } + + // after fee + nAfterFee = std::max(nPayFee, 0); + } + + // actually update labels + int nDisplayUnit = RavenUnits::RVN; + if (model && model->getOptionsModel()) + nDisplayUnit = model->getOptionsModel()->getDisplayUnit(); + + QLabel *l1 = dialog->findChild("labelAssetControlQuantity"); + QLabel *l2 = dialog->findChild("labelAssetControlAmount"); + QLabel *l3 = dialog->findChild("labelAssetControlFee"); + QLabel *l4 = dialog->findChild("labelAssetControlAfterFee"); + QLabel *l5 = dialog->findChild("labelAssetControlBytes"); + QLabel *l7 = dialog->findChild("labelAssetControlLowOutput"); + QLabel *l8 = dialog->findChild("labelAssetControlChange"); + + // enable/disable "dust" and "change" + dialog->findChild("labelAssetControlLowOutputText")->setEnabled(nPayAmount > 0); + dialog->findChild("labelAssetControlLowOutput") ->setEnabled(nPayAmount > 0); + dialog->findChild("labelAssetControlChangeText") ->setEnabled(nPayAmount > 0); + dialog->findChild("labelAssetControlChange") ->setEnabled(nPayAmount > 0); + + // stats + l1->setText(QString::number(nQuantity)); // Quantity + l2->setText(RavenUnits::formatWithCustomName(QString::fromStdString(strAssetName), nAssetAmount)); // Amount + l3->setText(RavenUnits::formatWithUnit(nDisplayUnit, nPayFee)); // Fee + l4->setText(RavenUnits::formatWithUnit(nDisplayUnit, nAfterFee)); // After Fee + l5->setText(((nBytes > 0) ? ASYMP_UTF8 : "") + QString::number(nBytes)); // Bytes + l7->setText(fDust ? tr("yes") : tr("no")); // Dust + l8->setText(RavenUnits::formatWithCustomName(QString::fromStdString(strAssetName), nChange)); // Change + if (nPayFee > 0) + { + l3->setText(ASYMP_UTF8 + l3->text()); + l4->setText(ASYMP_UTF8 + l4->text()); + } + + // turn label red when dust + l7->setStyleSheet((fDust) ? "color:red;" : ""); + + // tool tips + QString toolTipDust = tr("This label turns red if any recipient receives an amount smaller than the current dust threshold."); + + // how many satoshis the estimated fee can vary per byte we guess wrong + double dFeeVary = (nBytes != 0) ? (double)nPayFee / nBytes : 0; + + QString toolTip4 = tr("Can vary +/- %1 satoshi(s) per input.").arg(dFeeVary); + + l3->setToolTip(toolTip4); + l4->setToolTip(toolTip4); + l7->setToolTip(toolTipDust); + l8->setToolTip(toolTip4); + dialog->findChild("labelAssetControlFeeText") ->setToolTip(l3->toolTip()); + dialog->findChild("labelAssetControlAfterFeeText") ->setToolTip(l4->toolTip()); + dialog->findChild("labelAssetControlBytesText") ->setToolTip(l5->toolTip()); + dialog->findChild("labelAssetControlLowOutputText")->setToolTip(l7->toolTip()); + dialog->findChild("labelAssetControlChangeText") ->setToolTip(l8->toolTip()); + + // Insufficient funds + QLabel *label = dialog->findChild("labelAssetControlInsuffFunds"); + if (label) + label->setVisible(nChange < 0); +} + +void AssetControlDialog::updateView() +{ + if (!model || !model->getOptionsModel() || !model->getAddressTableModel()) + return; + + bool treeMode = ui->radioTreeMode->isChecked(); + + ui->treeWidget->clear(); + ui->treeWidget->setEnabled(false); // performance, otherwise updateLabels would be called for every checked checkbox + ui->treeWidget->setAlternatingRowColors(!treeMode); + QFlags flgCheckbox = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable; + QFlags flgTristate = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsTristate; + + int nDisplayUnit = model->getOptionsModel()->getDisplayUnit(); + + std::map > > mapCoins; + model->listAssets(mapCoins); + + QString assetToDisplay = ui->assetList->currentText(); + + // Double check to make sure that the asset selected has coins in the map + if (!mapCoins.count(assetToDisplay)) + return; + + // For now we only support for one assets coins being shown at a time + // So we only loop through coins for that specific asset + auto mapAssetCoins = mapCoins.at(assetToDisplay); + + for (const std::pair> &coins : mapAssetCoins) { + CAssetControlWidgetItem *itemWalletAddress = new CAssetControlWidgetItem(); + itemWalletAddress->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); + QString sWalletAddress = coins.first; + QString sWalletLabel = model->getAddressTableModel()->labelForAddress(sWalletAddress); + if (sWalletLabel.isEmpty()) + sWalletLabel = tr("(no label)"); + + if (treeMode) { + // wallet address + ui->treeWidget->addTopLevelItem(itemWalletAddress); + + itemWalletAddress->setFlags(flgTristate); + itemWalletAddress->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); + + // label + itemWalletAddress->setText(COLUMN_LABEL, sWalletLabel); + + // address + itemWalletAddress->setText(COLUMN_ADDRESS, sWalletAddress); + + // asset name + itemWalletAddress->setText(COLUMN_ASSET_NAME, assetToDisplay); + } + + CAmount nSum = 0; + int nChildren = 0; + for (const COutput &out : coins.second) { + std::string strAssetName; + CAmount nAmount; + if (!GetAssetInfoFromScript(out.tx->tx->vout[out.i].scriptPubKey, strAssetName, nAmount)) + continue; + + if (strAssetName != assetToDisplay.toStdString()) + continue; + + nSum += nAmount; + nChildren++; + + CAssetControlWidgetItem *itemOutput; + if (treeMode) itemOutput = new CAssetControlWidgetItem(itemWalletAddress); + else itemOutput = new CAssetControlWidgetItem(ui->treeWidget); + itemOutput->setFlags(flgCheckbox); + itemOutput->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); + + // address + CTxDestination outputAddress; + QString sAddress = ""; + if (ExtractDestination(out.tx->tx->vout[out.i].scriptPubKey, outputAddress)) { + sAddress = QString::fromStdString(EncodeDestination(outputAddress)); + + // if listMode or change => show raven address. In tree mode, address is not shown again for direct wallet address outputs + if (!treeMode || (!(sAddress == sWalletAddress))) { + itemOutput->setText(COLUMN_ADDRESS, sAddress); + // asset name + itemOutput->setText(COLUMN_ASSET_NAME, QString::fromStdString(strAssetName)); + } + } + + // label + if (!(sAddress == sWalletAddress)) // change + { + // tooltip from where the change comes from + itemOutput->setToolTip(COLUMN_LABEL, + tr("change from %1 (%2)").arg(sWalletLabel).arg(sWalletAddress)); + itemOutput->setText(COLUMN_LABEL, tr("(change)")); + } else if (!treeMode) { + QString sLabel = model->getAddressTableModel()->labelForAddress(sAddress); + if (sLabel.isEmpty()) + sLabel = tr("(no label)"); + itemOutput->setText(COLUMN_LABEL, sLabel); + } + + // amount + itemOutput->setText(COLUMN_AMOUNT, RavenUnits::format(nDisplayUnit, nAmount)); + itemOutput->setData(COLUMN_AMOUNT, Qt::UserRole, + QVariant((qlonglong) nAmount)); // padding so that sorting works correctly + + // date + itemOutput->setText(COLUMN_DATE, GUIUtil::dateTimeStr(out.tx->GetTxTime())); + itemOutput->setData(COLUMN_DATE, Qt::UserRole, QVariant((qlonglong) out.tx->GetTxTime())); + + // confirmations + itemOutput->setText(COLUMN_CONFIRMATIONS, QString::number(out.nDepth)); + itemOutput->setData(COLUMN_CONFIRMATIONS, Qt::UserRole, QVariant((qlonglong) out.nDepth)); + + // transaction hash + uint256 txhash = out.tx->GetHash(); + itemOutput->setText(COLUMN_TXHASH, QString::fromStdString(txhash.GetHex())); + + // vout index + itemOutput->setText(COLUMN_VOUT_INDEX, QString::number(out.i)); + + // disable locked coins + if (model->isLockedCoin(txhash, out.i)) { + COutPoint outpt(txhash, out.i); + assetControl->UnSelect(outpt); // just to be sure + itemOutput->setDisabled(true); + itemOutput->setIcon(COLUMN_CHECKBOX, platformStyle->SingleColorIcon(":/icons/lock_closed")); + } + + // set checkbox + if (assetControl->IsSelected(COutPoint(txhash, out.i))) + itemOutput->setCheckState(COLUMN_CHECKBOX, Qt::Checked); + } + + // amount + if (treeMode) { + itemWalletAddress->setText(COLUMN_CHECKBOX, "(" + QString::number(nChildren) + ")"); + itemWalletAddress->setText(COLUMN_AMOUNT, RavenUnits::format(nDisplayUnit, nSum)); + itemWalletAddress->setData(COLUMN_AMOUNT, Qt::UserRole, QVariant((qlonglong) nSum)); + } + } + + // expand all partially selected + if (treeMode) + { + for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++) + if (ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX) == Qt::PartiallyChecked) + ui->treeWidget->topLevelItem(i)->setExpanded(true); + } + + // sort view + sortView(sortColumn, sortOrder); + ui->treeWidget->setEnabled(true); +} + +void AssetControlDialog::viewAdministratorClicked() +{ + assetControl->UnSelectAll(); + AssetControlDialog::updateLabels(model, this); + updateAssetList(); +} + +void AssetControlDialog::updateAssetList(bool fSetOnStart) +{ + if (!model || !model->getOptionsModel() || !model->getAddressTableModel()) + return; + + bool showAdministrator = ui->viewAdministrator->isChecked(); + if (fSetOnStart) { + showAdministrator = IsAssetNameAnOwner(assetControl->strAssetSelected); + ui->viewAdministrator->setChecked(showAdministrator); + } + // Get the assets + std::vector assets; + if (showAdministrator) + GetAllAdministrativeAssets(model->getWallet(), assets); + else + GetAllMyAssets(model->getWallet(), assets); + + QStringList list; + for (auto name : assets) { + list << QString::fromStdString(name); + } + ui->assetList->clear(); + + // Add the assets into the dropdown menu + ui->assetList->addItem("Select as asset to view"); + ui->assetList->addItems(list); + + int index = ui->assetList->findText(QString::fromStdString(assetControl->strAssetSelected)); + if ( index != -1 ) { // -1 for not found + fOnStartUp = fSetOnStart; + ui->assetList->setCurrentText(QString::fromStdString(assetControl->strAssetSelected)); + } + + updateView(); +} + +void AssetControlDialog::onAssetSelected(QString name) +{ + if (fOnStartUp) { + fOnStartUp = false; + } else { + assetControl->UnSelectAll(); + } + + AssetControlDialog::updateLabels(model, this); + updateView(); +} diff --git a/src/qt/assetcontroldialog.h b/src/qt/assetcontroldialog.h new file mode 100644 index 0000000000..817f94d055 --- /dev/null +++ b/src/qt/assetcontroldialog.h @@ -0,0 +1,119 @@ +// Copyright (c) 2011-2016 The Bitcoin Core developers +// Copyright (c) 2017 The Raven Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef RAVEN_QT_ASSETCONTROLDIALOG_H +#define RAVEN_QT_ASSETCONTROLDIALOG_H + +#include "amount.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +class PlatformStyle; +class WalletModel; + +class CCoinControl; + +namespace Ui { + class AssetControlDialog; +} + +#define ASYMP_UTF8 "\xE2\x89\x88" + +class CAssetControlWidgetItem : public QTreeWidgetItem +{ +public: + explicit CAssetControlWidgetItem(QTreeWidget *parent, int type = Type) : QTreeWidgetItem(parent, type) {} + explicit CAssetControlWidgetItem(int type = Type) : QTreeWidgetItem(type) {} + explicit CAssetControlWidgetItem(QTreeWidgetItem *parent, int type = Type) : QTreeWidgetItem(parent, type) {} + + bool operator<(const QTreeWidgetItem &other) const; +}; + +class AssetControlDialog : public QDialog +{ + Q_OBJECT + +public: + explicit AssetControlDialog(const PlatformStyle *platformStyle, QWidget *parent = 0); + ~AssetControlDialog(); + + void setModel(WalletModel *model); + + // static because also called from sendcoinsdialog + static void updateLabels(WalletModel*, QDialog*); + + //update the list of assets + void updateAssetList(bool fSetOnStart = false); + + static QList payAmounts; + static CCoinControl *assetControl; + static bool fSubtractFeeFromAmount; + bool fOnStartUp; + +private: + Ui::AssetControlDialog *ui; + WalletModel *model; + int sortColumn; + Qt::SortOrder sortOrder; + + QMenu *contextMenu; + QTreeWidgetItem *contextMenuItem; + QAction *copyTransactionHashAction; + QAction *lockAction; + QAction *unlockAction; + + const PlatformStyle *platformStyle; + + void sortView(int, Qt::SortOrder); + void updateView(); + + enum + { + COLUMN_CHECKBOX = 0, + COLUMN_ASSET_NAME, + COLUMN_AMOUNT, + COLUMN_LABEL, + COLUMN_ADDRESS, + COLUMN_DATE, + COLUMN_CONFIRMATIONS, + COLUMN_TXHASH, + COLUMN_VOUT_INDEX, + }; + friend class CAssetControlWidgetItem; + +private Q_SLOTS: + void showMenu(const QPoint &); + void copyAmount(); + void copyLabel(); + void copyAddress(); + void copyTransactionHash(); + void lockCoin(); + void unlockCoin(); + void clipboardQuantity(); + void clipboardAmount(); + void clipboardFee(); + void clipboardAfterFee(); + void clipboardBytes(); + void clipboardLowOutput(); + void clipboardChange(); + void radioTreeMode(bool); + void radioListMode(bool); + void viewItemChanged(QTreeWidgetItem*, int); + void headerSectionClicked(int); + void buttonBoxClicked(QAbstractButton*); + void buttonSelectAllClicked(); + void updateLabelLocked(); + void viewAdministratorClicked(); + void onAssetSelected(QString name); +}; + +#endif // RAVEN_QT_ASSETCONTROLDIALOG_H diff --git a/src/qt/assetcontroltreewidget.cpp b/src/qt/assetcontroltreewidget.cpp new file mode 100644 index 0000000000..47f8a38fe2 --- /dev/null +++ b/src/qt/assetcontroltreewidget.cpp @@ -0,0 +1,35 @@ +// Copyright (c) 2011-2015 The Bitcoin Core developers +// Copyright (c) 2017 The Raven Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "assetcontroltreewidget.h" +#include "assetcontroldialog.h" + +AssetControlTreeWidget::AssetControlTreeWidget(QWidget *parent) : + QTreeWidget(parent) +{ + +} + +void AssetControlTreeWidget::keyPressEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Space) // press spacebar -> select checkbox + { + event->ignore(); + if (this->currentItem()) { + int COLUMN_CHECKBOX = 0; + this->currentItem()->setCheckState(COLUMN_CHECKBOX, ((this->currentItem()->checkState(COLUMN_CHECKBOX) == Qt::Checked) ? Qt::Unchecked : Qt::Checked)); + } + } + else if (event->key() == Qt::Key_Escape) // press esc -> close dialog + { + event->ignore(); + AssetControlDialog *assetControlDialog = (AssetControlDialog*)this->parentWidget(); + assetControlDialog->done(QDialog::Accepted); + } + else + { + this->QTreeWidget::keyPressEvent(event); + } +} diff --git a/src/qt/assetcontroltreewidget.h b/src/qt/assetcontroltreewidget.h new file mode 100644 index 0000000000..99db4e7359 --- /dev/null +++ b/src/qt/assetcontroltreewidget.h @@ -0,0 +1,23 @@ +// Copyright (c) 2011-2014 The Bitcoin Core developers +// Copyright (c) 2017 The Raven Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef RAVEN_QT_ASSETCONTROLTREEWIDGET_H +#define RAVEN_QT_ASSETCONTROLTREEWIDGET_H + +#include +#include + +class AssetControlTreeWidget : public QTreeWidget +{ + Q_OBJECT + +public: + explicit AssetControlTreeWidget(QWidget *parent = 0); + +protected: + virtual void keyPressEvent(QKeyEvent *event); +}; + +#endif // RAVEN_QT_ASSETCONTROLTREEWIDGET_H diff --git a/src/qt/assetsdialog.cpp b/src/qt/assetsdialog.cpp index 056d498dd3..56a1538424 100644 --- a/src/qt/assetsdialog.cpp +++ b/src/qt/assetsdialog.cpp @@ -10,7 +10,7 @@ #include "addresstablemodel.h" #include "ravenunits.h" #include "clientmodel.h" -#include "coincontroldialog.h" +#include "assetcontroldialog.h" #include "guiutil.h" #include "optionsmodel.h" #include "platformstyle.h" @@ -78,7 +78,7 @@ AssetsDialog::AssetsDialog(const PlatformStyle *_platformStyle, QWidget *parent) ui->sendButton->setIcon(_platformStyle->SingleColorIcon(":/icons/send")); } - GUIUtil::setupAddressWidget(ui->lineEditCoinControlChange, this); + GUIUtil::setupAddressWidget(ui->lineEditAssetControlChange, this); addEntry(); @@ -86,9 +86,9 @@ AssetsDialog::AssetsDialog(const PlatformStyle *_platformStyle, QWidget *parent) connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clear())); // Coin Control - connect(ui->pushButtonCoinControl, SIGNAL(clicked()), this, SLOT(coinControlButtonClicked())); - connect(ui->checkBoxCoinControlChange, SIGNAL(stateChanged(int)), this, SLOT(coinControlChangeChecked(int))); - connect(ui->lineEditCoinControlChange, SIGNAL(textEdited(const QString &)), this, SLOT(coinControlChangeEdited(const QString &))); + connect(ui->pushButtonAssetControl, SIGNAL(clicked()), this, SLOT(assetControlButtonClicked())); + connect(ui->checkBoxAssetControlChange, SIGNAL(stateChanged(int)), this, SLOT(assetControlChangeChecked(int))); + connect(ui->lineEditAssetControlChange, SIGNAL(textEdited(const QString &)), this, SLOT(assetControlChangeEdited(const QString &))); // Coin Control: clipboard actions QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this); @@ -98,20 +98,20 @@ AssetsDialog::AssetsDialog(const PlatformStyle *_platformStyle, QWidget *parent) QAction *clipboardBytesAction = new QAction(tr("Copy bytes"), this); QAction *clipboardLowOutputAction = new QAction(tr("Copy dust"), this); QAction *clipboardChangeAction = new QAction(tr("Copy change"), this); - connect(clipboardQuantityAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardQuantity())); - connect(clipboardAmountAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardAmount())); - connect(clipboardFeeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardFee())); - connect(clipboardAfterFeeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardAfterFee())); - connect(clipboardBytesAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardBytes())); - connect(clipboardLowOutputAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardLowOutput())); - connect(clipboardChangeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardChange())); - ui->labelCoinControlQuantity->addAction(clipboardQuantityAction); - ui->labelCoinControlAmount->addAction(clipboardAmountAction); - ui->labelCoinControlFee->addAction(clipboardFeeAction); - ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction); - ui->labelCoinControlBytes->addAction(clipboardBytesAction); - ui->labelCoinControlLowOutput->addAction(clipboardLowOutputAction); - ui->labelCoinControlChange->addAction(clipboardChangeAction); + connect(clipboardQuantityAction, SIGNAL(triggered()), this, SLOT(assetControlClipboardQuantity())); + connect(clipboardAmountAction, SIGNAL(triggered()), this, SLOT(assetControlClipboardAmount())); + connect(clipboardFeeAction, SIGNAL(triggered()), this, SLOT(assetControlClipboardFee())); + connect(clipboardAfterFeeAction, SIGNAL(triggered()), this, SLOT(assetControlClipboardAfterFee())); + connect(clipboardBytesAction, SIGNAL(triggered()), this, SLOT(assetControlClipboardBytes())); + connect(clipboardLowOutputAction, SIGNAL(triggered()), this, SLOT(assetControlClipboardLowOutput())); + connect(clipboardChangeAction, SIGNAL(triggered()), this, SLOT(assetControlClipboardChange())); + ui->labelAssetControlQuantity->addAction(clipboardQuantityAction); + ui->labelAssetControlAmount->addAction(clipboardAmountAction); + ui->labelAssetControlFee->addAction(clipboardFeeAction); + ui->labelAssetControlAfterFee->addAction(clipboardAfterFeeAction); + ui->labelAssetControlBytes->addAction(clipboardBytesAction); + ui->labelAssetControlLowOutput->addAction(clipboardLowOutputAction); + ui->labelAssetControlChange->addAction(clipboardChangeAction); // init transaction fee section QSettings settings; @@ -186,29 +186,29 @@ void AssetsDialog::setModel(WalletModel *_model) updateDisplayUnit(); // Coin Control - connect(_model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(coinControlUpdateLabels())); - connect(_model->getOptionsModel(), SIGNAL(coinControlFeaturesChanged(bool)), this, SLOT(coinControlFeatureChanged(bool))); + connect(_model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(assetControlUpdateLabels())); + connect(_model->getOptionsModel(), SIGNAL(coinControlFeaturesChanged(bool)), this, SLOT(assetControlFeatureChanged(bool))); - ui->frameCoinControl->setVisible(false); + ui->frameAssetControl->setVisible(false); //TODO Turn on the coin control features for assets -// ui->frameCoinControl->setVisible(_model->getOptionsModel()->getCoinControlFeatures()); - coinControlUpdateLabels(); + ui->frameAssetControl->setVisible(_model->getOptionsModel()->getCoinControlFeatures()); + assetControlUpdateLabels(); // fee section for (const int &n : confTargets) { ui->confTargetSelector->addItem(tr("%1 (%2 blocks)").arg(GUIUtil::formatNiceTimeOffset(n*Params().GetConsensus().nPowTargetSpacing)).arg(n)); } connect(ui->confTargetSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(updateSmartFeeLabel())); - connect(ui->confTargetSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(coinControlUpdateLabels())); + connect(ui->confTargetSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(assetControlUpdateLabels())); connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(updateFeeSectionControls())); - connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(coinControlUpdateLabels())); - connect(ui->customFee, SIGNAL(valueChanged()), this, SLOT(coinControlUpdateLabels())); + connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(assetControlUpdateLabels())); + connect(ui->customFee, SIGNAL(valueChanged()), this, SLOT(assetControlUpdateLabels())); connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(setMinimumFee())); connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(updateFeeSectionControls())); - connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(coinControlUpdateLabels())); + connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(assetControlUpdateLabels())); connect(ui->optInRBF, SIGNAL(stateChanged(int)), this, SLOT(updateSmartFeeLabel())); - connect(ui->optInRBF, SIGNAL(stateChanged(int)), this, SLOT(coinControlUpdateLabels())); + connect(ui->optInRBF, SIGNAL(stateChanged(int)), this, SLOT(assetControlUpdateLabels())); ui->customFee->setSingleStep(GetRequiredFee(1000)); updateFeeSectionControls(); updateMinFeeLabel(); @@ -289,12 +289,21 @@ void AssetsDialog::on_sendButton_clicked() vTransfers.emplace_back(std::make_pair(CAssetTransfer(recipient.assetName.toStdString(), recipient.amount), recipient.address.toStdString())); } + // Always use a CCoinControl instance, use the AssetControlDialog instance if CoinControl has been enabled + CCoinControl ctrl; + if (model->getOptionsModel()->getCoinControlFeatures()) + ctrl = *AssetControlDialog::assetControl; + + ctrl.fForAssets = true; + + updateAssetControlState(ctrl); + CWalletTx tx; CReserveKey reservekey(model->getWallet()); std::pair error; CAmount nFeeRequired; - if (!CreateTransferAssetTransaction(model->getWallet(), vTransfers, "", error, tx, reservekey, nFeeRequired)) { + if (!CreateTransferAssetTransaction(model->getWallet(), ctrl, vTransfers, "", error, tx, reservekey, nFeeRequired)) { QMessageBox msgBox; msgBox.setText(QString::fromStdString(error.second)); msgBox.exec(); @@ -379,8 +388,8 @@ void AssetsDialog::on_sendButton_clicked() if (sendStatus.status == WalletModel::OK) { accept(); - CoinControlDialog::coinControl->UnSelectAll(); - coinControlUpdateLabels(); + AssetControlDialog::assetControl->UnSelectAll(); + assetControlUpdateLabels(); } fNewRecipientAllowed = true; } @@ -411,20 +420,31 @@ SendAssetsEntry *AssetsDialog::addEntry() { LOCK(cs_main); std::vector assets; - GetAllMyAssets(assets); + if (model) + GetAllMyAssets(model->getWallet(), assets); + else // If the model isn't present. Grab the list of assets that the cache thinks you own + GetAllMyAssetsFromCache(assets); QStringList list; - for (auto name : assets) { - if (!IsAssetNameAnOwner(name)) - list << QString::fromStdString(name); + bool fIsOwner = false; + bool fIsAssetControl = false; + if (AssetControlDialog::assetControl->HasSelected()) { + list << QString::fromStdString(AssetControlDialog::assetControl->strAssetSelected); + fIsOwner = IsAssetNameAnOwner(AssetControlDialog::assetControl->strAssetSelected); + fIsAssetControl = true; + } else { + for (auto name : assets) { + if (!IsAssetNameAnOwner(name)) + list << QString::fromStdString(name); + } } SendAssetsEntry *entry = new SendAssetsEntry(platformStyle, list, this); entry->setModel(model); ui->entries->addWidget(entry); connect(entry, SIGNAL(removeEntry(SendAssetsEntry*)), this, SLOT(removeEntry(SendAssetsEntry*))); - connect(entry, SIGNAL(payAmountChanged()), this, SLOT(coinControlUpdateLabels())); - connect(entry, SIGNAL(subtractFeeFromAmountChanged()), this, SLOT(coinControlUpdateLabels())); + connect(entry, SIGNAL(payAmountChanged()), this, SLOT(assetControlUpdateLabels())); + connect(entry, SIGNAL(subtractFeeFromAmountChanged()), this, SLOT(assetControlUpdateLabels())); // Focus the field, so that entry can start immediately entry->clear(); @@ -435,14 +455,20 @@ SendAssetsEntry *AssetsDialog::addEntry() if(bar) bar->setSliderPosition(bar->maximum()); + entry->IsAssetControl(fIsAssetControl, fIsOwner); + + if (list.size() == 1) + entry->setCurrentIndex(1); + updateTabsAndLabels(); + return entry; } void AssetsDialog::updateTabsAndLabels() { setupTabChain(0); - coinControlUpdateLabels(); + assetControlUpdateLabels(); } void AssetsDialog::removeEntry(SendAssetsEntry* entry) @@ -658,7 +684,7 @@ void AssetsDialog::updateMinFeeLabel() ); } -void AssetsDialog::updateCoinControlState(CCoinControl& ctrl) +void AssetsDialog::updateAssetControlState(CCoinControl& ctrl) { if (ui->radioCustomFee->isChecked()) { ctrl.m_feerate = CFeeRate(ui->customFee->value()); @@ -676,7 +702,7 @@ void AssetsDialog::updateSmartFeeLabel() if(!model || !model->getOptionsModel()) return; CCoinControl coin_control; - updateCoinControlState(coin_control); + updateAssetControlState(coin_control); coin_control.m_feerate.reset(); // Explicitly use only fee estimation rate for smart fee labels FeeCalculation feeCalc; CFeeRate feeRate = CFeeRate(GetMinimumFee(1000, coin_control, ::mempool, ::feeEstimator, &feeCalc)); @@ -703,147 +729,148 @@ void AssetsDialog::updateSmartFeeLabel() } // Coin Control: copy label "Quantity" to clipboard -void AssetsDialog::coinControlClipboardQuantity() +void AssetsDialog::assetControlClipboardQuantity() { - GUIUtil::setClipboard(ui->labelCoinControlQuantity->text()); + GUIUtil::setClipboard(ui->labelAssetControlQuantity->text()); } // Coin Control: copy label "Amount" to clipboard -void AssetsDialog::coinControlClipboardAmount() +void AssetsDialog::assetControlClipboardAmount() { - GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left(ui->labelCoinControlAmount->text().indexOf(" "))); + GUIUtil::setClipboard(ui->labelAssetControlAmount->text().left(ui->labelAssetControlAmount->text().indexOf(" "))); } // Coin Control: copy label "Fee" to clipboard -void AssetsDialog::coinControlClipboardFee() +void AssetsDialog::assetControlClipboardFee() { - GUIUtil::setClipboard(ui->labelCoinControlFee->text().left(ui->labelCoinControlFee->text().indexOf(" ")).replace(ASYMP_UTF8, "")); + GUIUtil::setClipboard(ui->labelAssetControlFee->text().left(ui->labelAssetControlFee->text().indexOf(" ")).replace(ASYMP_UTF8, "")); } // Coin Control: copy label "After fee" to clipboard -void AssetsDialog::coinControlClipboardAfterFee() +void AssetsDialog::assetControlClipboardAfterFee() { - GUIUtil::setClipboard(ui->labelCoinControlAfterFee->text().left(ui->labelCoinControlAfterFee->text().indexOf(" ")).replace(ASYMP_UTF8, "")); + GUIUtil::setClipboard(ui->labelAssetControlAfterFee->text().left(ui->labelAssetControlAfterFee->text().indexOf(" ")).replace(ASYMP_UTF8, "")); } // Coin Control: copy label "Bytes" to clipboard -void AssetsDialog::coinControlClipboardBytes() +void AssetsDialog::assetControlClipboardBytes() { - GUIUtil::setClipboard(ui->labelCoinControlBytes->text().replace(ASYMP_UTF8, "")); + GUIUtil::setClipboard(ui->labelAssetControlBytes->text().replace(ASYMP_UTF8, "")); } // Coin Control: copy label "Dust" to clipboard -void AssetsDialog::coinControlClipboardLowOutput() +void AssetsDialog::assetControlClipboardLowOutput() { - GUIUtil::setClipboard(ui->labelCoinControlLowOutput->text()); + GUIUtil::setClipboard(ui->labelAssetControlLowOutput->text()); } // Coin Control: copy label "Change" to clipboard -void AssetsDialog::coinControlClipboardChange() +void AssetsDialog::assetControlClipboardChange() { - GUIUtil::setClipboard(ui->labelCoinControlChange->text().left(ui->labelCoinControlChange->text().indexOf(" ")).replace(ASYMP_UTF8, "")); + GUIUtil::setClipboard(ui->labelAssetControlChange->text().left(ui->labelAssetControlChange->text().indexOf(" ")).replace(ASYMP_UTF8, "")); } // Coin Control: settings menu - coin control enabled/disabled by user -void AssetsDialog::coinControlFeatureChanged(bool checked) +void AssetsDialog::assetControlFeatureChanged(bool checked) { - ui->frameCoinControl->setVisible(checked); + ui->frameAssetControl->setVisible(checked); if (!checked && model) // coin control features disabled - CoinControlDialog::coinControl->SetNull(); + AssetControlDialog::assetControl->SetNull(); - coinControlUpdateLabels(); + assetControlUpdateLabels(); } // Coin Control: button inputs -> show actual coin control dialog -void AssetsDialog::coinControlButtonClicked() +void AssetsDialog::assetControlButtonClicked() { - CoinControlDialog dlg(platformStyle); + AssetControlDialog dlg(platformStyle); dlg.setModel(model); dlg.exec(); - coinControlUpdateLabels(); + assetControlUpdateLabels(); + assetControlUpdateSendCoinsDialog(); } // Coin Control: checkbox custom change address -void AssetsDialog::coinControlChangeChecked(int state) +void AssetsDialog::assetControlChangeChecked(int state) { if (state == Qt::Unchecked) { - CoinControlDialog::coinControl->destChange = CNoDestination(); - ui->labelCoinControlChangeLabel->clear(); + AssetControlDialog::assetControl->destChange = CNoDestination(); + ui->labelAssetControlChangeLabel->clear(); } else // use this to re-validate an already entered address - coinControlChangeEdited(ui->lineEditCoinControlChange->text()); + assetControlChangeEdited(ui->lineEditAssetControlChange->text()); - ui->lineEditCoinControlChange->setEnabled((state == Qt::Checked)); + ui->lineEditAssetControlChange->setEnabled((state == Qt::Checked)); } // Coin Control: custom change address changed -void AssetsDialog::coinControlChangeEdited(const QString& text) +void AssetsDialog::assetControlChangeEdited(const QString& text) { if (model && model->getAddressTableModel()) { // Default to no change address until verified - CoinControlDialog::coinControl->destChange = CNoDestination(); - ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:red;}"); + AssetControlDialog::assetControl->destChange = CNoDestination(); + ui->labelAssetControlChangeLabel->setStyleSheet("QLabel{color:red;}"); const CTxDestination dest = DecodeDestination(text.toStdString()); if (text.isEmpty()) // Nothing entered { - ui->labelCoinControlChangeLabel->setText(""); + ui->labelAssetControlChangeLabel->setText(""); } else if (!IsValidDestination(dest)) // Invalid address { - ui->labelCoinControlChangeLabel->setText(tr("Warning: Invalid Raven address")); + ui->labelAssetControlChangeLabel->setText(tr("Warning: Invalid Raven address")); } else // Valid address { if (!model->IsSpendable(dest)) { - ui->labelCoinControlChangeLabel->setText(tr("Warning: Unknown change address")); + ui->labelAssetControlChangeLabel->setText(tr("Warning: Unknown change address")); // confirmation dialog QMessageBox::StandardButton btnRetVal = QMessageBox::question(this, tr("Confirm custom change address"), tr("The address you selected for change is not part of this wallet. Any or all funds in your wallet may be sent to this address. Are you sure?"), QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel); if(btnRetVal == QMessageBox::Yes) - CoinControlDialog::coinControl->destChange = dest; + AssetControlDialog::assetControl->destChange = dest; else { - ui->lineEditCoinControlChange->setText(""); - ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}"); - ui->labelCoinControlChangeLabel->setText(""); + ui->lineEditAssetControlChange->setText(""); + ui->labelAssetControlChangeLabel->setStyleSheet("QLabel{color:black;}"); + ui->labelAssetControlChangeLabel->setText(""); } } else // Known change address { - ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}"); + ui->labelAssetControlChangeLabel->setStyleSheet("QLabel{color:black;}"); // Query label QString associatedLabel = model->getAddressTableModel()->labelForAddress(text); if (!associatedLabel.isEmpty()) - ui->labelCoinControlChangeLabel->setText(associatedLabel); + ui->labelAssetControlChangeLabel->setText(associatedLabel); else - ui->labelCoinControlChangeLabel->setText(tr("(no label)")); + ui->labelAssetControlChangeLabel->setText(tr("(no label)")); - CoinControlDialog::coinControl->destChange = dest; + AssetControlDialog::assetControl->destChange = dest; } } } } // Coin Control: update labels -void AssetsDialog::coinControlUpdateLabels() +void AssetsDialog::assetControlUpdateLabels() { if (!model || !model->getOptionsModel()) return; - updateCoinControlState(*CoinControlDialog::coinControl); + updateAssetControlState(*AssetControlDialog::assetControl); // set pay amounts - CoinControlDialog::payAmounts.clear(); - CoinControlDialog::fSubtractFeeFromAmount = false; + AssetControlDialog::payAmounts.clear(); + AssetControlDialog::fSubtractFeeFromAmount = false; for(int i = 0; i < ui->entries->count(); ++i) { @@ -851,27 +878,27 @@ void AssetsDialog::coinControlUpdateLabels() if(entry && !entry->isHidden()) { SendAssetsRecipient rcp = entry->getValue(); - CoinControlDialog::payAmounts.append(rcp.amount); + AssetControlDialog::payAmounts.append(rcp.amount); // if (rcp.fSubtractFeeFromAmount) -// CoinControlDialog::fSubtractFeeFromAmount = true; +// AssetControlDialog::fSubtractFeeFromAmount = true; } } - if (CoinControlDialog::coinControl->HasSelected()) + if (AssetControlDialog::assetControl->HasSelected()) { // actual coin control calculation - CoinControlDialog::updateLabels(model, this); + AssetControlDialog::updateLabels(model, this); // show coin control stats - ui->labelCoinControlAutomaticallySelected->hide(); - ui->widgetCoinControl->show(); + ui->labelAssetControlAutomaticallySelected->hide(); + ui->widgetAssetControl->show(); } else { // hide coin control stats - ui->labelCoinControlAutomaticallySelected->show(); - ui->widgetCoinControl->hide(); - ui->labelCoinControlInsuffFunds->hide(); + ui->labelAssetControlAutomaticallySelected->show(); + ui->widgetAssetControl->hide(); + ui->labelAssetControlInsuffFunds->hide(); } } @@ -937,4 +964,20 @@ void AssetsDialog::mineButtonClicked() generateBlocks(coinbase_script, num_generate, max_tries, true); } + +void AssetsDialog::assetControlUpdateSendCoinsDialog() +{ + AssetControlDialog::assetControl->HasSelected(); + for(int i = 0; i < ui->entries->count(); ++i) + { + SendAssetsEntry *entry = qobject_cast(ui->entries->itemAt(i)->widget()); + if(entry) + { + removeEntry(entry); + } + } + + addEntry(); + +} /** RVN END */ diff --git a/src/qt/assetsdialog.h b/src/qt/assetsdialog.h index 532f74d831..b6d34f6412 100644 --- a/src/qt/assetsdialog.h +++ b/src/qt/assetsdialog.h @@ -70,7 +70,7 @@ public Q_SLOTS: void minimizeFeeSection(bool fMinimize); void updateFeeMinimizedLabel(); // Update the passed in CCoinControl with state from the GUI - void updateCoinControlState(CCoinControl& ctrl); + void updateAssetControlState(CCoinControl& ctrl); private Q_SLOTS: void on_sendButton_clicked(); @@ -78,18 +78,18 @@ private Q_SLOTS: void on_buttonMinimizeFee_clicked(); void removeEntry(SendAssetsEntry* entry); void updateDisplayUnit(); - void coinControlFeatureChanged(bool); - void coinControlButtonClicked(); - void coinControlChangeChecked(int); - void coinControlChangeEdited(const QString &); - void coinControlUpdateLabels(); - void coinControlClipboardQuantity(); - void coinControlClipboardAmount(); - void coinControlClipboardFee(); - void coinControlClipboardAfterFee(); - void coinControlClipboardBytes(); - void coinControlClipboardLowOutput(); - void coinControlClipboardChange(); + void assetControlFeatureChanged(bool); + void assetControlButtonClicked(); + void assetControlChangeChecked(int); + void assetControlChangeEdited(const QString &); + void assetControlUpdateLabels(); + void assetControlClipboardQuantity(); + void assetControlClipboardAmount(); + void assetControlClipboardFee(); + void assetControlClipboardAfterFee(); + void assetControlClipboardBytes(); + void assetControlClipboardLowOutput(); + void assetControlClipboardChange(); void setMinimumFee(); void updateFeeSectionControls(); void updateMinFeeLabel(); @@ -100,6 +100,7 @@ private Q_SLOTS: void ressieAssetButtonClicked(); void refreshButtonClicked(); void mineButtonClicked(); + void assetControlUpdateSendCoinsDialog(); /** RVN END */ Q_SIGNALS: diff --git a/src/qt/createassetdialog.cpp b/src/qt/createassetdialog.cpp index 572b8717cc..c2b1704142 100644 --- a/src/qt/createassetdialog.cpp +++ b/src/qt/createassetdialog.cpp @@ -84,7 +84,7 @@ void CreateAssetDialog::setUpValues() // Setup the asset list ui->assetList->hide(); std::vector names; - GetAllOwnedAssets(model->getWallet(), names); + GetAllAdministrativeAssets(model->getWallet(), names); for (auto item : names) { std::string name = QString::fromStdString(item).split("!").first().toStdString(); if (name.size() != 30) @@ -539,6 +539,7 @@ QString CreateAssetDialog::GetAssetName() return ui->assetList->currentText() + "/" + ui->nameText->text(); else if (type == ISSUE_UNIQUE) return ui->assetList->currentText() + "#" + ui->nameText->text(); + return ""; } void CreateAssetDialog::UpdateAssetNameMaxSize() diff --git a/src/qt/forms/assetcontroldialog.ui b/src/qt/forms/assetcontroldialog.ui new file mode 100644 index 0000000000..e8df076bc4 --- /dev/null +++ b/src/qt/forms/assetcontroldialog.ui @@ -0,0 +1,517 @@ + + + AssetControlDialog + + + + 0 + 0 + 1000 + 500 + + + + Asset Selection + + + + + + 0 + + + 10 + + + + + 10 + + + 10 + + + 6 + + + 6 + + + + + + 75 + true + + + + Quantity: + + + + + + + IBeamCursor + + + Qt::ActionsContextMenu + + + 0 + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + 75 + true + + + + Bytes: + + + + + + + IBeamCursor + + + Qt::ActionsContextMenu + + + 0 + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + 10 + + + 10 + + + 6 + + + 6 + + + + + + 75 + true + + + + Amount: + + + + + + + IBeamCursor + + + Qt::ActionsContextMenu + + + 0.00 RVN + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + false + + + + 75 + true + + + + Dust: + + + + + + + false + + + IBeamCursor + + + Qt::ActionsContextMenu + + + no + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + 10 + + + 10 + + + 6 + + + 6 + + + + + + 75 + true + + + + Fee: + + + + + + + IBeamCursor + + + Qt::ActionsContextMenu + + + 0.00 RVN + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + 10 + + + 10 + + + 6 + + + 6 + + + + + + 75 + true + + + + After Fee: + + + + + + + IBeamCursor + + + Qt::ActionsContextMenu + + + 0.00 RVN + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + false + + + + 75 + true + + + + Change: + + + + + + + false + + + IBeamCursor + + + Qt::ActionsContextMenu + + + 0.00 RVN + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + + + + 0 + 40 + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + + + + 14 + + + + + + 0 + 0 + + + + (un)select all + + + false + + + + + + + + 0 + 0 + + + + Tree mode + + + + + + + + 0 + 0 + + + + List mode + + + true + + + + + + + (1 locked) + + + + + + + 30 + + + + + + + View assets that you have the ownership asset for + + + View Administrator Assets + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + Qt::CustomContextMenu + + + false + + + 11 + + + true + + + false + + + + + + + + + Asset + + + + + Amount + + + + + Received with label + + + + + Received with address + + + + + Date + + + + + Confirmations + + + Confirmed + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + QDialogButtonBox::Ok + + + + + + + + AssetControlTreeWidget + QTreeWidget +
assetcontroltreewidget.h
+
+
+ + +
diff --git a/src/qt/forms/assetsdialog.ui b/src/qt/forms/assetsdialog.ui index 4f72ab1269..1639d43cc7 100644 --- a/src/qt/forms/assetsdialog.ui +++ b/src/qt/forms/assetsdialog.ui @@ -91,7 +91,7 @@
- + 0 @@ -110,7 +110,7 @@ QFrame::Sunken - + 0 @@ -127,7 +127,7 @@ 6 - + 0 @@ -138,12 +138,12 @@ 10 - + 15 - + 0 @@ -160,14 +160,14 @@ font-weight:bold; - Coin Control Features + Asset Control Features - + 8 @@ -175,7 +175,7 @@ 10 - + @@ -188,7 +188,7 @@ - + automatically selected @@ -198,7 +198,7 @@ - + 75 @@ -217,7 +217,7 @@ - + Qt::Horizontal @@ -232,7 +232,7 @@ - + 0 @@ -248,7 +248,7 @@ - + 0 @@ -262,7 +262,7 @@ 0 - + 20 @@ -273,7 +273,7 @@ 10 - + 10 @@ -290,7 +290,7 @@ 6 - + 75 @@ -306,7 +306,7 @@ - + IBeamCursor @@ -325,7 +325,7 @@ - + 75 @@ -338,7 +338,7 @@ - + IBeamCursor @@ -356,7 +356,7 @@ - + 10 @@ -373,7 +373,7 @@ 6 - + 75 @@ -389,7 +389,7 @@ - + IBeamCursor @@ -405,7 +405,7 @@ - + 75 @@ -418,7 +418,7 @@ - + IBeamCursor @@ -436,7 +436,7 @@ - + 10 @@ -453,7 +453,7 @@ 6 - + 75 @@ -469,7 +469,7 @@ - + IBeamCursor @@ -487,7 +487,7 @@ - + 10 @@ -504,7 +504,7 @@ 6 - + 75 @@ -520,7 +520,7 @@ - + IBeamCursor @@ -536,7 +536,7 @@ - + 75 @@ -549,7 +549,7 @@ - + IBeamCursor @@ -572,7 +572,7 @@ - + 12 @@ -586,7 +586,7 @@ 5 - + If this is activated, but the change address is empty or invalid, change will be sent to a newly generated address. @@ -596,7 +596,7 @@ - + false @@ -609,7 +609,7 @@ - + 0 @@ -633,7 +633,7 @@ - + Qt::Vertical diff --git a/src/qt/forms/sendassetsentry.ui b/src/qt/forms/sendassetsentry.ui index dafcb111f8..3bf346a3f1 100644 --- a/src/qt/forms/sendassetsentry.ui +++ b/src/qt/forms/sendassetsentry.ui @@ -7,7 +7,7 @@ 0 0 729 - 150 + 202 @@ -59,12 +59,12 @@ - + This transfer will send the ownership asset to the address specified - Send Ownership Asset + Send Administrator Asset diff --git a/src/qt/ravenunits.cpp b/src/qt/ravenunits.cpp index 7051bcbd3a..8cea621299 100644 --- a/src/qt/ravenunits.cpp +++ b/src/qt/ravenunits.cpp @@ -125,6 +125,11 @@ QString RavenUnits::formatWithUnit(int unit, const CAmount& amount, bool plussig return format(unit, amount, plussign, separators) + QString(" ") + name(unit); } +QString RavenUnits::formatWithCustomName(QString customName, const CAmount& amount, bool plussign, SeparatorStyle separators) +{ + return format(RVN, amount, plussign, separators) + QString(" ") + customName; +} + QString RavenUnits::formatHtmlWithUnit(int unit, const CAmount& amount, bool plussign, SeparatorStyle separators) { QString str(formatWithUnit(unit, amount, plussign, separators)); diff --git a/src/qt/ravenunits.h b/src/qt/ravenunits.h index 3caa34b233..230d5d0bee 100644 --- a/src/qt/ravenunits.h +++ b/src/qt/ravenunits.h @@ -89,6 +89,8 @@ class RavenUnits: public QAbstractListModel static QString format(int unit, const CAmount& amount, bool plussign=false, SeparatorStyle separators=separatorStandard); //! Format as string (with unit) static QString formatWithUnit(int unit, const CAmount& amount, bool plussign=false, SeparatorStyle separators=separatorStandard); + //! Format as string (with custom name) + static QString formatWithCustomName(QString customName, const CAmount& amount, bool plussign=false, SeparatorStyle separators=separatorStandard); //! Format as HTML string (with unit) static QString formatHtmlWithUnit(int unit, const CAmount& amount, bool plussign=false, SeparatorStyle separators=separatorStandard); //! Parse string to coin amount diff --git a/src/qt/reissueassetdialog.cpp b/src/qt/reissueassetdialog.cpp index 5eab53e04a..22dbbb99a9 100644 --- a/src/qt/reissueassetdialog.cpp +++ b/src/qt/reissueassetdialog.cpp @@ -59,6 +59,9 @@ ReissueAssetDialog::~ReissueAssetDialog() /** Helper Methods */ void ReissueAssetDialog::setUpValues() { + if (!model) + return; + ui->reissuableBox->setCheckState(Qt::CheckState::Checked); ui->ipfsText->hide(); ui->ipfsHashLabel->hide(); @@ -71,7 +74,7 @@ void ReissueAssetDialog::setUpValues() LOCK(cs_main); std::vector assets; - GetAllOwnedAssets(model->getWallet(), assets); + GetAllAdministrativeAssets(model->getWallet(), assets); ui->comboBox->addItem("Select an asset"); @@ -345,6 +348,9 @@ void ReissueAssetDialog::onAddressNameChanged(QString address) void ReissueAssetDialog::onReissueAssetClicked() { + if (!model) + return; + QString address; if (ui->addressText->text().isEmpty()) { address = model->getAddressTableModel()->addRow(AddressTableModel::Receive, "", ""); @@ -367,7 +373,6 @@ void ReissueAssetDialog::onReissueAssetClicked() CReissueAsset reissueAsset(name.toStdString(), quantity, reissuable ? 1 : 0, ipfsDecoded); - CWalletTx tx; CReserveKey reservekey(model->getWallet()); std::pair error; diff --git a/src/qt/sendassetsentry.cpp b/src/qt/sendassetsentry.cpp index ce7c5beb63..c1b548e5e3 100644 --- a/src/qt/sendassetsentry.cpp +++ b/src/qt/sendassetsentry.cpp @@ -13,6 +13,7 @@ #include "optionsmodel.h" #include "platformstyle.h" #include "walletmodel.h" +#include "assetcontroldialog.h" #include #include @@ -47,13 +48,14 @@ SendAssetsEntry::SendAssetsEntry(const PlatformStyle *_platformStyle, const QStr ui->payTo_is->setFont(GUIUtil::fixedPitchFont()); // Connect signals + connect(ui->payAmount, SIGNAL(valueChanged(double)), this, SIGNAL(payAmountChanged())); connect(ui->deleteButton, SIGNAL(clicked()), this, SLOT(deleteClicked())); connect(ui->deleteButton_is, SIGNAL(clicked()), this, SLOT(deleteClicked())); connect(ui->deleteButton_s, SIGNAL(clicked()), this, SLOT(deleteClicked())); connect(ui->assetSelectionBox, SIGNAL(activated(int)), this, SLOT(onAssetSelected(int))); - connect(ui->ownerCheckBox, SIGNAL(clicked()), this, SLOT(onSendOwnershipChanged())); + connect(ui->administratorCheckBox, SIGNAL(clicked()), this, SLOT(onSendOwnershipChanged())); - ui->assetSelectionBox->addItem("Select an asset to transfer"); + ui->assetSelectionBox->addItem(tr("Select an asset to transfer")); ui->assetSelectionBox->addItems(myAssetsNames); ui->payAmount->setValue(0.00000000); @@ -178,8 +180,8 @@ QWidget *SendAssetsEntry::setupTabChain(QWidget *prev) QWidget::setTabOrder(ui->payTo, ui->addAsLabel); QWidget::setTabOrder(ui->addressBookButton, ui->pasteButton); QWidget::setTabOrder(ui->pasteButton, ui->deleteButton); - QWidget::setTabOrder(ui->payAmount, ui->ownerCheckBox); - QWidget::setTabOrder(ui->ownerCheckBox, ui->assetAmountLabel); + QWidget::setTabOrder(ui->payAmount, ui->administratorCheckBox); + QWidget::setTabOrder(ui->administratorCheckBox, ui->assetAmountLabel); return ui->deleteButton; } @@ -215,6 +217,8 @@ void SendAssetsEntry::setValue(const SendAssetsRecipient &value) ui->payTo->setText(recipient.address); // this may set a label from addressbook if (!recipient.label.isEmpty()) // if a label had been set from the addressbook, don't overwrite with an empty label ui->addAsLabel->setText(recipient.label); + + ui->payAmount->setValue(recipient.amount / COIN); } } @@ -284,14 +288,13 @@ void SendAssetsEntry::onAssetSelected(int index) } ui->assetAmountLabel->setText( - "Balance: " + QString::fromStdString(ValueFromAmountString(amount, units)) + " " + name + bang); + "Wallet Balance: " + QString::fromStdString(ValueFromAmountString(amount, units)) + " " + name + bang); ui->messageLabel->hide(); ui->messageTextLabel->hide(); // If it is an ownership asset lock the amount if (!isOwnerAsset) { - ui->payAmount->setMaximum(amount / COIN); ui->payAmount->setDecimals(asset.units); ui->payAmount->setDisabled(false); } @@ -306,42 +309,74 @@ void SendAssetsEntry::onAssetSelected(int index) void SendAssetsEntry::onSendOwnershipChanged() { - if (ui->ownerCheckBox->isChecked()) { + if(!model) + return; + + if (ui->administratorCheckBox->isChecked()) { LOCK(cs_main); - std::vector names; - GetAllOwnedAssets(model->getWallet(), names); + if (!fUsingAssetControl) { + std::vector names; + GetAllAdministrativeAssets(model->getWallet(), names); - QStringList list; - for (auto name: names) - list << QString::fromStdString(name); + QStringList list; + for (auto name: names) + list << QString::fromStdString(name); - ui->assetSelectionBox->clear(); - ui->assetSelectionBox->addItem("Select an asset to transfer"); - ui->assetSelectionBox->addItems(list); - ui->assetSelectionBox->setCurrentIndex(0); + ui->assetSelectionBox->clear(); + ui->assetSelectionBox->addItem(tr("Select an asset to transfer")); + ui->assetSelectionBox->addItems(list); + ui->assetSelectionBox->setCurrentIndex(0); + } ui->payAmount->setValue(OWNER_ASSET_AMOUNT / COIN); ui->payAmount->setDecimals(MAX_UNIT); ui->payAmount->setDisabled(true); ui->assetAmountLabel->clear(); - ui->ownershipWarningMessage->setText("Warning: This asset transfer contains an ownership asset. You will no longer have ownership of this asset. Make sure this is what you want to do"); + ui->ownershipWarningMessage->setText(tr("Warning: This asset transfer contains an administrator asset. You will no longer be the administrator of this asset. Make sure this is what you want to do")); ui->ownershipWarningMessage->setStyleSheet("color: red"); ui->ownershipWarningMessage->show(); } else { LOCK(cs_main); - std::vector names; - GetAllMyAssets(names); - QStringList list; - for (auto name : names) { - if (!IsAssetNameAnOwner(name)) - list << QString::fromStdString(name); - } - ui->assetSelectionBox->clear(); + if (!fUsingAssetControl) { + std::vector names; + GetAllMyAssets(model->getWallet(), names); + QStringList list; + for (auto name : names) { + if (!IsAssetNameAnOwner(name)) + list << QString::fromStdString(name); + } + ui->assetSelectionBox->clear(); - ui->assetSelectionBox->addItem("Select an asset to transfer"); - ui->assetSelectionBox->addItems(list); - ui->assetSelectionBox->setCurrentIndex(0); + ui->assetSelectionBox->addItem(tr("Select an asset to transfer")); + ui->assetSelectionBox->addItems(list); + ui->assetSelectionBox->setCurrentIndex(0); + } ui->ownershipWarningMessage->hide(); } } + +void SendAssetsEntry::CheckOwnerBox() { + ui->administratorCheckBox->setChecked(true); + fUsingAssetControl = true; + onSendOwnershipChanged(); +} + +void SendAssetsEntry::IsAssetControl(bool fIsAssetControl, bool fIsOwner) +{ + if (fIsOwner) { + CheckOwnerBox(); + } + if (fIsAssetControl) { + ui->administratorCheckBox->setDisabled(true); + fUsingAssetControl = true; + } +} + +void SendAssetsEntry::setCurrentIndex(int index) +{ + if (index < ui->assetSelectionBox->count()) { + ui->assetSelectionBox->setCurrentIndex(index); + ui->assetSelectionBox->activated(index); + } +} diff --git a/src/qt/sendassetsentry.h b/src/qt/sendassetsentry.h index 548a899ba3..dac5dcd101 100644 --- a/src/qt/sendassetsentry.h +++ b/src/qt/sendassetsentry.h @@ -39,6 +39,9 @@ class SendAssetsEntry : public QStackedWidget void setValue(const SendAssetsRecipient &value); void setAddress(const QString &address); + void CheckOwnerBox(); + void IsAssetControl(bool fIsAssetControl, bool fIsOwner); + void setCurrentIndex(int index); /** Set up the tab chain manually, as Qt messes up the tab chain by default in some cases * (issue https://bugreports.qt-project.org/browse/QTBUG-10907). @@ -47,6 +50,9 @@ class SendAssetsEntry : public QStackedWidget void setFocus(); + bool fUsingAssetControl; + + public Q_SLOTS: void clear(); diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 335fc0bb4f..3a03eee3ee 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -663,6 +663,31 @@ void WalletModel::listCoins(std::map >& mapCoins) } } +/** RVN START */ +// AvailableCoins + LockedCoins grouped by wallet address (put change in one group with wallet address) +void WalletModel::listAssets(std::map > >& mapCoins) const +{ + std::map > > mapSortedByAssetName; + auto list = wallet->ListAssets(); + + for (auto& group : list) { + auto address = QString::fromStdString(EncodeDestination(group.first)); + + for (auto& coin : group.second) { + auto out = coin.tx->tx->vout[coin.i]; + std::string strAssetName; + CAmount nAmount; + if (!GetAssetInfoFromScript(out.scriptPubKey, strAssetName, nAmount)) + continue; + + QString assetName = QString::fromStdString(strAssetName); + auto& assetMap = mapCoins[assetName]; + assetMap[address].emplace_back(coin); + } + } +} +/** RVN END */ + bool WalletModel::isLockedCoin(uint256 hash, unsigned int n) const { LOCK2(cs_main, wallet->cs_wallet); diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 66fb9a0e98..457b9b5520 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -265,7 +265,10 @@ class WalletModel : public QObject void getOutputs(const std::vector& vOutpoints, std::vector& vOutputs); bool isSpent(const COutPoint& outpoint) const; void listCoins(std::map >& mapCoins) const; - + /** RVN START */ + // Map of asset name to map of address to CTxOut + void listAssets(std::map > >& mapCoins) const; + /** RVN END */ bool isLockedCoin(uint256 hash, unsigned int n) const; void lockCoin(COutPoint& output); void unlockCoin(COutPoint& output); diff --git a/src/rpc/assets.cpp b/src/rpc/assets.cpp index ad06429f3c..b38fffee82 100644 --- a/src/rpc/assets.cpp +++ b/src/rpc/assets.cpp @@ -552,8 +552,10 @@ UniValue transfer(const JSONRPCRequest& request) CWalletTx transaction; CAmount nRequiredFee; + CCoinControl ctrl; + // Create the Transaction - if (!CreateTransferAssetTransaction(pwallet, vTransfers, "", error, transaction, reservekey, nRequiredFee)) + if (!CreateTransferAssetTransaction(pwallet, ctrl, vTransfers, "", error, transaction, reservekey, nRequiredFee)) throw JSONRPCError(error.first, error.second); // Send the Transaction to the network diff --git a/src/wallet/coincontrol.h b/src/wallet/coincontrol.h index b1a55749d6..d147bead47 100644 --- a/src/wallet/coincontrol.h +++ b/src/wallet/coincontrol.h @@ -33,6 +33,14 @@ class CCoinControl //! Fee estimation mode to control arguments to estimateSmartFee FeeEstimateMode m_fee_mode; + /** RVN START */ + //! Name of the asset that is selected, used when sending assets with coincontrol + std::string strAssetSelected; + //! If this is being used for assets + bool fForAssets; + /** RVN END */ + + CCoinControl() { SetNull(); @@ -49,6 +57,8 @@ class CCoinControl m_confirm_target.reset(); signalRbf = fWalletRbf; m_fee_mode = FeeEstimateMode::UNSET; + strAssetSelected = ""; + fForAssets = false; } bool HasSelected() const @@ -69,11 +79,14 @@ class CCoinControl void UnSelect(const COutPoint& output) { setSelected.erase(output); + if (!setSelected.size()) + strAssetSelected = ""; } void UnSelectAll() { setSelected.clear(); + strAssetSelected = ""; } void ListSelected(std::vector& vOutpoints) const diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 7a1d431de5..92a2ee3c6f 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2248,8 +2248,14 @@ void CWallet::AvailableCoinsAll(std::vector& vCoins, std::maptx->vout.size(); i++) { - if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected(COutPoint((*it).first, i))) + if (coinControl && coinControl->fForAssets && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected(COutPoint((*it).first, i)) && pcoin->tx->vout[i].scriptPubKey.IsAssetScript()) { continue; + } + else { + if (coinControl && !coinControl->fForAssets && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && + !coinControl->IsSelected(COutPoint((*it).first, i))) + continue; + } if (IsLockedCoin((*it).first, i)) continue; @@ -2383,6 +2389,60 @@ void CWallet::AvailableCoinsAll(std::vector& vCoins, std::map> CWallet::ListAssets() const +{ + // TODO: Add AssertLockHeld(cs_wallet) here. + // + // Because the return value from this function contains pointers to + // CWalletTx objects, callers to this function really should acquire the + // cs_wallet lock before calling it. However, the current caller doesn't + // acquire this lock yet. There was an attempt to add the missing lock in + // https://github.com/RavenProject/Ravencoin/pull/10340, but that change has been + // postponed until after https://github.com/RavenProject/Ravencoin/pull/10244 to + // avoid adding some extra complexity to the Qt code. + + std::map> result; + + std::map > mapAssets; + AvailableAssets(mapAssets); + + LOCK2(cs_main, cs_wallet); + for (auto asset : mapAssets) { + for (auto &coin : asset.second) { + CTxDestination address; + if (coin.fSpendable && + ExtractDestination(FindNonChangeParentOutput(*coin.tx->tx, coin.i).scriptPubKey, address)) { + result[address].emplace_back(std::move(coin)); + } + } + } + + std::vector lockedCoins; + ListLockedCoins(lockedCoins); + for (const auto& output : lockedCoins) { + auto it = mapWallet.find(output.hash); + if (it != mapWallet.end()) { + if (!it->second.tx->vout[output.n].scriptPubKey.IsAssetScript()) // If not an asset script skip it + continue; + int depth = it->second.GetDepthInMainChain(); + if (depth >= 0 && output.n < it->second.tx->vout.size() && + IsMine(it->second.tx->vout[output.n]) == ISMINE_SPENDABLE) { + CTxDestination address; + if (ExtractDestination(FindNonChangeParentOutput(*it->second.tx, output.n).scriptPubKey, address)) { + result[address].emplace_back( + &it->second, output.n, depth, true /* spendable */, true /* solvable */, false /* safe */); + } + } + } + } + + return result; +} + +/** RVN END */ + std::map> CWallet::ListCoins() const { // TODO: Add AssertLockHeld(cs_wallet) here. @@ -2420,7 +2480,7 @@ std::map> CWallet::ListCoins() const CTxDestination address; if (ExtractDestination(FindNonChangeParentOutput(*it->second.tx, output.n).scriptPubKey, address)) { result[address].emplace_back( - &it->second, output.n, depth, true /* spendable */, true /* solvable */, false /* safe */); + &it->second, output.n, depth, true /* spendable */, true /* solvable */, false /* safe */); } } } @@ -2649,7 +2709,7 @@ bool CWallet::SelectCoins(const std::vector& vAvailableCoins, const CAm std::vector vCoins(vAvailableCoins); // coin control -> return all selected outputs (we want all selected to go into the transaction for sure) - if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs) + if (coinControl && !coinControl->fForAssets && coinControl->HasSelected() && !coinControl->fAllowOtherInputs) { for (const COutput& out : vCoins) { @@ -2666,7 +2726,7 @@ bool CWallet::SelectCoins(const std::vector& vAvailableCoins, const CAm CAmount nValueFromPresetInputs = 0; std::vector vPresetInputs; - if (coinControl) + if (coinControl && !coinControl->fForAssets) coinControl->ListSelected(vPresetInputs); for (const COutPoint& outpoint : vPresetInputs) { @@ -2684,7 +2744,7 @@ bool CWallet::SelectCoins(const std::vector& vAvailableCoins, const CAm } // remove preset inputs from vCoins - for (std::vector::iterator it = vCoins.begin(); it != vCoins.end() && coinControl && coinControl->HasSelected();) + for (std::vector::iterator it = vCoins.begin(); it != vCoins.end() && coinControl && !coinControl->fForAssets && coinControl->HasSelected();) { if (setPresetCoins.count(CInputCoin(it->tx, it->i))) it = vCoins.erase(it); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 195c3f5d63..ac6d2a32b7 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -871,6 +871,12 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface */ std::map> ListCoins() const; + /** + * Return list of available assets and locked assets grouped by non-change output address. + */ + std::map> ListAssets() const; + + /** * Find non-change parent output. */ From 6f3453c367a82b4141158172ff8bae718e572ad4 Mon Sep 17 00:00:00 2001 From: blondfrogs Date: Wed, 29 Aug 2018 14:42:10 -0600 Subject: [PATCH 014/105] Fix Unique QT issuance scree, remove unused enum --- src/assets/assets.cpp | 44 +++++++++++++++++++------------- src/assets/assets.h | 2 ++ src/assets/assettypes.h | 27 ++++++++------------ src/qt/createassetdialog.cpp | 49 ++++++++++++++++++++---------------- src/script/standard.h | 24 +++++++++--------- 5 files changed, 78 insertions(+), 68 deletions(-) diff --git a/src/assets/assets.cpp b/src/assets/assets.cpp index 841d8948d7..fe062ab0c4 100644 --- a/src/assets/assets.cpp +++ b/src/assets/assets.cpp @@ -1700,13 +1700,13 @@ bool CheckIssueBurnTx(const CTxOut& txOut, const AssetType& type, const int numb CAmount burnAmount = 0; std::string burnAddress = ""; - if (type == AssetType::SUB) { + if (type == SUB) { burnAmount = GetIssueSubAssetBurnAmount(); burnAddress = Params().IssueSubAssetBurnAddress(); - } else if (type == AssetType::ROOT) { + } else if (type == ROOT) { burnAmount = GetIssueAssetBurnAmount(); burnAddress = Params().IssueAssetBurnAddress(); - } else if (type == AssetType::UNIQUE) { + } else if (type == UNIQUE) { burnAmount = GetIssueUniqueAssetBurnAmount(); burnAddress = Params().IssueUniqueAssetBurnAddress(); } else { @@ -2173,44 +2173,54 @@ CAmount GetIssueUniqueAssetBurnAmount() return Params().IssueUniqueAssetBurnAmount(); } +CAmount GetBurnAmount(const int nType) +{ + return GetBurnAmount((AssetType(nType))); +} + CAmount GetBurnAmount(const AssetType type) { switch (type) { - case AssetType::ROOT: + case ROOT: return GetIssueAssetBurnAmount(); - case AssetType::SUB: + case SUB: return GetIssueSubAssetBurnAmount(); - case AssetType::MSGCHANNEL: + case MSGCHANNEL: return 0; - case AssetType::OWNER: + case OWNER: return 0; - case AssetType::UNIQUE: + case UNIQUE: return GetIssueUniqueAssetBurnAmount(); - case AssetType::VOTE: + case VOTE: return 0; - case AssetType::REISSUE: + case REISSUE: return GetReissueAssetBurnAmount(); default: return 0; } } +std::string GetBurnAddress(const int nType) +{ + return GetBurnAddress((AssetType(nType))); +} + std::string GetBurnAddress(const AssetType type) { switch (type) { - case AssetType::ROOT: + case ROOT: return Params().IssueAssetBurnAddress(); - case AssetType::SUB: + case SUB: return Params().IssueSubAssetBurnAddress(); - case AssetType::MSGCHANNEL: + case MSGCHANNEL: return ""; - case AssetType::OWNER: + case OWNER: return ""; - case AssetType::UNIQUE: + case UNIQUE: return Params().IssueUniqueAssetBurnAddress(); - case AssetType::VOTE: + case VOTE: return ""; - case AssetType::REISSUE: + case REISSUE: return Params().ReissueAssetBurnAddress(); default: return ""; diff --git a/src/assets/assets.h b/src/assets/assets.h index f095d30368..c3882904fb 100644 --- a/src/assets/assets.h +++ b/src/assets/assets.h @@ -308,7 +308,9 @@ CAmount GetReissueAssetBurnAmount(); CAmount GetIssueSubAssetBurnAmount(); CAmount GetIssueUniqueAssetBurnAmount(); CAmount GetBurnAmount(const AssetType type); +CAmount GetBurnAmount(const int nType); std::string GetBurnAddress(const AssetType type); +std::string GetBurnAddress(const int nType); bool IsAssetNameValid(const std::string& name); bool IsAssetNameValid(const std::string& name, AssetType& assetType); diff --git a/src/assets/assettypes.h b/src/assets/assettypes.h index 54266832c0..396842f1a3 100644 --- a/src/assets/assettypes.h +++ b/src/assets/assettypes.h @@ -17,37 +17,30 @@ class CAssetsCache; enum AssetType { - ROOT, - OWNER, - SUB, - UNIQUE, - MSGCHANNEL, - VOTE, - INVALID, - REISSUE + ROOT = 0, + SUB = 1, + UNIQUE = 2, + OWNER = 3, + MSGCHANNEL = 4, + VOTE = 5, + REISSUE = 6, + INVALID = 7 }; std::string PrintAssetType(AssetType& assetType) { switch (assetType) { case ROOT: return "ROOT"; - case OWNER: return "OWNER"; case SUB: return "SUB"; case UNIQUE: return "UNIQUE"; + case OWNER: return "OWNER"; case MSGCHANNEL: return "MSGCHANNEL"; case VOTE: return "VOTE"; - case INVALID: return "INVALID"; case REISSUE: return "REISSUE"; + case INVALID: return "INVALID"; default: return "UNKNOWN"; } } -enum IssueAssetType -{ - ISSUE_ROOT = 0, - ISSUE_SUB = 1, - ISSUE_UNIQUE = 2 -}; - class CNewAsset { public: std::string strName; // MAX 31 Bytes diff --git a/src/qt/createassetdialog.cpp b/src/qt/createassetdialog.cpp index c2b1704142..2d53f6b982 100644 --- a/src/qt/createassetdialog.cpp +++ b/src/qt/createassetdialog.cpp @@ -76,9 +76,9 @@ void CreateAssetDialog::setUpValues() QStringList list; list.append(tr("Main Asset")); list.append(tr("Sub Asset")); -// list.append(tr("Unique Asset")); + list.append(tr("Unique Asset")); ui->assetType->addItems(list); - type = ISSUE_ROOT; + type = ROOT; ui->assetTypeLabel->setText(tr("Asset Type") + ":"); // Setup the asset list @@ -161,13 +161,13 @@ void CreateAssetDialog::CheckFormState() AssetType assetType; bool assetNameValid = IsAssetNameValid(name.toStdString(), assetType); - if (assetNameValid && assetType == ROOT && type != ISSUE_ROOT) + if (assetNameValid && assetType == ROOT && type != ROOT) return; - if (assetNameValid && assetType == SUB && type != ISSUE_SUB) + if (assetNameValid && assetType == SUB && type != SUB) return; - if (assetNameValid && assetType == UNIQUE && type != ISSUE_UNIQUE) + if (assetNameValid && assetType == UNIQUE && type != UNIQUE) return; if (!(IsValidDestination(dest) || ui->addressText->text().isEmpty()) && assetNameValid) { @@ -237,7 +237,7 @@ void CreateAssetDialog::checkAvailabilityClicked() void CreateAssetDialog::onNameChanged(QString name) { // Update the displayed name to uppercase if the type only accepts uppercase - name = name.toUpper(); + name = type == UNIQUE ? name : name.toUpper(); UpdateAssetNameToUpper(); QString assetName = name; @@ -252,7 +252,7 @@ void CreateAssetDialog::onNameChanged(QString name) return; } - if (type == ISSUE_ROOT) { + if (type == ROOT) { if (name.size() < 3) { ui->nameText->setStyleSheet("border: 1px solid red"); showMessage("Invalid: Minimum of 3 character in length"); @@ -269,7 +269,7 @@ void CreateAssetDialog::onNameChanged(QString name) ui->nameText->setStyleSheet("border: 1px solid red"); showMessage("Invalid: Max Size 30 Characters. Allowed characters include: A-Z 0-9 . _"); } - } else if (type == ISSUE_SUB || type == ISSUE_UNIQUE) { + } else if (type == SUB || type == UNIQUE) { if (name.size() == 0) { hideMessage(); ui->availabilityButton->setDisabled(true); @@ -288,7 +288,7 @@ void CreateAssetDialog::onNameChanged(QString name) } // Set the assetName - updatePresentedAssetName(format.arg(type == ISSUE_ROOT ? "" : ui->assetList->currentText(), identifier, name)); + updatePresentedAssetName(format.arg(type == ROOT ? "" : ui->assetList->currentText(), identifier, name)); checkedAvailablity = false; disableCreateButton(); @@ -346,10 +346,10 @@ void CreateAssetDialog::onCreateAssetClicked() QStringList formatted; // generate bold amount string - QString amount = "" + QString::fromStdString(ValueFromAmountString(GetIssueAssetBurnAmount(), 8)) + " RVN"; + QString amount = "" + QString::fromStdString(ValueFromAmountString(GetBurnAmount(type), 8)) + " RVN"; amount.append(""); // generate monospace address string - QString addressburn = "" + QString::fromStdString(Params().IssueAssetBurnAddress()); + QString addressburn = "" + QString::fromStdString(GetBurnAddress(type)); addressburn.append(""); QString recipientElement1; @@ -410,7 +410,7 @@ void CreateAssetDialog::onCreateAssetClicked() // Create the transaction and broadcast it std::string txid; if (!SendAssetTransaction(model->getWallet(), tx, reservekey, error, txid)) { - showMessage("Invalid: " + QString::fromStdString(error.second)); + showMessage(tr("Invalid: ") + QString::fromStdString(error.second)); } else { QMessageBox msgBox; QPushButton *copyButton = msgBox.addButton(tr("Copy"), QMessageBox::ActionRole); @@ -478,6 +478,11 @@ void CreateAssetDialog::onAssetTypeActivated(int index) // Update the selected type type = index; + // Make sure the type is only the the supported issue types + if(!(type == ROOT || type == SUB || type == UNIQUE)) + type = ROOT; + + // Get the identifier for the asset type QString identifier = GetSpecialCharacter(); @@ -490,7 +495,7 @@ void CreateAssetDialog::onAssetTypeActivated(int index) UpdateAssetNameMaxSize(); // Set assetName - updatePresentedAssetName(format.arg(type == ISSUE_ROOT ? "" : ui->assetList->currentText(), identifier, ui->nameText->text())); + updatePresentedAssetName(format.arg(type == ROOT ? "" : ui->assetList->currentText(), identifier, ui->nameText->text())); if (ui->nameText->text().size()) ui->availabilityButton->setDisabled(false); @@ -507,7 +512,7 @@ void CreateAssetDialog::onAssetListActivated(int index) UpdateAssetNameMaxSize(); // Set assetName - updatePresentedAssetName(format.arg(type == ISSUE_ROOT ? "" : ui->assetList->currentText(), identifier, ui->nameText->text())); + updatePresentedAssetName(format.arg(type == ROOT ? "" : ui->assetList->currentText(), identifier, ui->nameText->text())); if (ui->nameText->text().size()) ui->availabilityButton->setDisabled(false); @@ -523,9 +528,9 @@ void CreateAssetDialog::updatePresentedAssetName(QString name) QString CreateAssetDialog::GetSpecialCharacter() { - if (type == ISSUE_SUB) + if (type == SUB) return "/"; - else if (type == ISSUE_UNIQUE) + else if (type == UNIQUE) return "#"; return ""; @@ -533,27 +538,27 @@ QString CreateAssetDialog::GetSpecialCharacter() QString CreateAssetDialog::GetAssetName() { - if (type == ISSUE_ROOT) + if (type == ROOT) return ui->nameText->text(); - else if (type == ISSUE_SUB) + else if (type == SUB) return ui->assetList->currentText() + "/" + ui->nameText->text(); - else if (type == ISSUE_UNIQUE) + else if (type == UNIQUE) return ui->assetList->currentText() + "#" + ui->nameText->text(); return ""; } void CreateAssetDialog::UpdateAssetNameMaxSize() { - if (type == ISSUE_ROOT) { + if (type == ROOT) { ui->nameText->setMaxLength(30); - } else if (type == ISSUE_SUB || type == ISSUE_UNIQUE) { + } else if (type == SUB || type == UNIQUE) { ui->nameText->setMaxLength(30 - (ui->assetList->currentText().size() + 1)); } } void CreateAssetDialog::UpdateAssetNameToUpper() { - if (type == ISSUE_ROOT || type == ISSUE_SUB) { + if (type == ROOT || type == SUB) { ui->nameText->setText(ui->nameText->text().toUpper()); } } \ No newline at end of file diff --git a/src/script/standard.h b/src/script/standard.h index f7e692b41f..8babd59b8e 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -56,20 +56,20 @@ static const unsigned int MANDATORY_SCRIPT_VERIFY_FLAGS = SCRIPT_VERIFY_P2SH; enum txnouttype { - TX_NONSTANDARD, + TX_NONSTANDARD = 0, // 'standard' transaction types: - TX_PUBKEY, - TX_PUBKEYHASH, - TX_SCRIPTHASH, - TX_MULTISIG, - TX_NULL_DATA, //!< unspendable OP_RETURN script that carries data - TX_WITNESS_V0_SCRIPTHASH, - TX_WITNESS_V0_KEYHASH, + TX_PUBKEY = 1, + TX_PUBKEYHASH = 2, + TX_SCRIPTHASH = 3, + TX_MULTISIG = 4, + TX_NULL_DATA = 5, //!< unspendable OP_RETURN script that carries data + TX_WITNESS_V0_SCRIPTHASH = 6, + TX_WITNESS_V0_KEYHASH = 7, /** RVN START */ - TX_NEW_ASSET, - TX_REISSUE_ASSET, - TX_TRANSFER_ASSET, - TX_RESERVED_ASSET + TX_NEW_ASSET = 8, + TX_REISSUE_ASSET = 9, + TX_TRANSFER_ASSET = 10, + TX_RESERVED_ASSET = 11 /** RVN END */ }; From 0b1f4694c3e8d71d24384f59fb776e359a71d08f Mon Sep 17 00:00:00 2001 From: blondfrogs Date: Thu, 30 Aug 2018 11:20:42 -0600 Subject: [PATCH 015/105] Fix compiler warnings, remove enum conversions --- src/assets/assets.cpp | 31 ++++++++++++++----------------- src/coins.cpp | 2 +- src/consensus/tx_verify.cpp | 2 +- src/qt/ravengui.cpp | 5 +++-- src/rpc/assets.cpp | 2 +- src/rpc/rawtransaction.cpp | 2 +- src/validation.cpp | 2 +- src/wallet/wallet.cpp | 20 +++++++++----------- 8 files changed, 31 insertions(+), 35 deletions(-) diff --git a/src/assets/assets.cpp b/src/assets/assets.cpp index fe062ab0c4..ff0edc177a 100644 --- a/src/assets/assets.cpp +++ b/src/assets/assets.cpp @@ -927,30 +927,29 @@ bool CAssetsCache::TrySpendCoin(const COutPoint& out, const CTxOut& txOut) CAmount nAmount = -1; // Get the asset tx data - int nType = 0; + int nType = -1; bool fIsOwner = false; if (txOut.scriptPubKey.IsAssetScript(nType, fIsOwner)) { - txnouttype type = (txnouttype)nType; // Get the New Asset or Transfer Asset from the scriptPubKey - if (type == TX_NEW_ASSET && !fIsOwner) { + if (nType == TX_NEW_ASSET && !fIsOwner) { CNewAsset asset; if (AssetFromScript(txOut.scriptPubKey, asset, address)) { assetName = asset.strName; nAmount = asset.nAmount; } - } else if (type == TX_TRANSFER_ASSET) { + } else if (nType == TX_TRANSFER_ASSET) { CAssetTransfer transfer; if (TransferAssetFromScript(txOut.scriptPubKey, transfer, address)) { assetName = transfer.strName; nAmount = transfer.nAmount; } - } else if (type == TX_NEW_ASSET && fIsOwner) { + } else if (nType == TX_NEW_ASSET && fIsOwner) { if (!OwnerAssetFromScript(txOut.scriptPubKey, assetName, address)) return error("%s : ERROR Failed to get owner asset from the OutPoint: %s", __func__, out.ToString()); nAmount = OWNER_ASSET_AMOUNT; - } else if (type == TX_REISSUE_ASSET) { + } else if (nType == TX_REISSUE_ASSET) { CReissueAsset reissue; if (ReissueAssetFromScript(txOut.scriptPubKey, reissue, address)) { assetName = reissue.strName; @@ -1045,13 +1044,11 @@ bool CAssetsCache::UndoAssetCoin(const Coin& coin, const COutPoint& out) CAmount nAmount = 0; // Get the asset tx from the script - txnouttype type; - int nType = 0; + int nType = -1; bool fIsOwner = false; if(coin.out.scriptPubKey.IsAssetScript(nType, fIsOwner)) { - type = (txnouttype) nType; - if (type == TX_NEW_ASSET && !fIsOwner) { + if (nType == TX_NEW_ASSET && !fIsOwner) { CNewAsset asset; if (!AssetFromScript(coin.out.scriptPubKey, asset, strAddress)) { return error("%s : Failed to get asset from script while trying to undo asset spend. OutPoint : %s", @@ -1061,7 +1058,7 @@ bool CAssetsCache::UndoAssetCoin(const Coin& coin, const COutPoint& out) assetName = asset.strName; nAmount = asset.nAmount; - } else if (type == TX_TRANSFER_ASSET) { + } else if (nType == TX_TRANSFER_ASSET) { CAssetTransfer transfer; if (!TransferAssetFromScript(coin.out.scriptPubKey, transfer, strAddress)) return error( @@ -1071,7 +1068,7 @@ bool CAssetsCache::UndoAssetCoin(const Coin& coin, const COutPoint& out) assetName = transfer.strName; nAmount = transfer.nAmount; - } else if (type == TX_NEW_ASSET && fIsOwner) { + } else if (nType == TX_NEW_ASSET && fIsOwner) { std::string ownerName; if (!OwnerAssetFromScript(coin.out.scriptPubKey, ownerName, strAddress)) return error( @@ -1079,7 +1076,7 @@ bool CAssetsCache::UndoAssetCoin(const Coin& coin, const COutPoint& out) __func__, out.ToString()); assetName = ownerName; nAmount = OWNER_ASSET_AMOUNT; - } else if (type == TX_REISSUE_ASSET) { + } else if (nType == TX_REISSUE_ASSET) { CReissueAsset reissue; if (!ReissueAssetFromScript(coin.out.scriptPubKey, reissue, strAddress)) return error( @@ -1808,7 +1805,7 @@ bool IsScriptNewAsset(const CScript& scriptPubKey, int& nStartingIndex) int nType = 0; bool fIsOwner =false; if (scriptPubKey.IsAssetScript(nType, fIsOwner, nStartingIndex)) { - return (txnouttype)nType == TX_NEW_ASSET && !fIsOwner; + return nType == TX_NEW_ASSET && !fIsOwner; } return false; @@ -1851,7 +1848,7 @@ bool IsScriptOwnerAsset(const CScript& scriptPubKey, int& nStartingIndex) int nType = 0; bool fIsOwner =false; if (scriptPubKey.IsAssetScript(nType, fIsOwner, nStartingIndex)) { - return (txnouttype)nType == TX_NEW_ASSET && fIsOwner; + return nType == TX_NEW_ASSET && fIsOwner; } return false; @@ -1868,7 +1865,7 @@ bool IsScriptReissueAsset(const CScript& scriptPubKey, int& nStartingIndex) int nType = 0; bool fIsOwner =false; if (scriptPubKey.IsAssetScript(nType, fIsOwner, nStartingIndex)) { - return (txnouttype)nType == TX_REISSUE_ASSET; + return nType == TX_REISSUE_ASSET; } return false; @@ -1885,7 +1882,7 @@ bool IsScriptTransferAsset(const CScript& scriptPubKey, int& nStartingIndex) int nType = 0; bool fIsOwner =false; if (scriptPubKey.IsAssetScript(nType, fIsOwner, nStartingIndex)) { - return (txnouttype)nType == TX_TRANSFER_ASSET; + return nType == TX_TRANSFER_ASSET; } return false; diff --git a/src/coins.cpp b/src/coins.cpp index 19d8456b8f..5c46a97126 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -162,7 +162,7 @@ void AddCoins(CCoinsViewCache& cache, const CTransaction &tx, int nHeight, bool error("%s: Failed to add an reissued asset I own to my Unspent Asset Cache. Asset Name : %s", __func__, reissue.strName); } else if (tx.IsNewUniqueAsset()) { - for (int n = 0; n < tx.vout.size(); n++) { + for (int n = 0; n < (int)tx.vout.size(); n++) { auto out = tx.vout[n]; CNewAsset asset; diff --git a/src/consensus/tx_verify.cpp b/src/consensus/tx_verify.cpp index 9ce9559dc2..558aff2dc6 100644 --- a/src/consensus/tx_verify.cpp +++ b/src/consensus/tx_verify.cpp @@ -203,7 +203,7 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state, CAssetsCa if (AreAssetsDeployed() && isAsset) { if (assetCache) { // Get the transfer transaction data from the scriptPubKey - if ((txnouttype) nType == TX_TRANSFER_ASSET) { + if ( nType == TX_TRANSFER_ASSET) { CAssetTransfer transfer; std::string address; if (!TransferAssetFromScript(txout.scriptPubKey, transfer, address)) diff --git a/src/qt/ravengui.cpp b/src/qt/ravengui.cpp index 56024e5adc..cc169db083 100644 --- a/src/qt/ravengui.cpp +++ b/src/qt/ravengui.cpp @@ -117,6 +117,7 @@ RavenGUI::RavenGUI(const PlatformStyle *_platformStyle, const NetworkStyle *netw openRPCConsoleAction(0), openAction(0), showHelpMessageAction(0), + assetAction(0), trayIcon(0), trayIconMenu(0), notificator(0), @@ -125,8 +126,8 @@ RavenGUI::RavenGUI(const PlatformStyle *_platformStyle, const NetworkStyle *netw modalOverlay(0), prevBlocks(0), spinnerFrame(0), - platformStyle(_platformStyle), - assetAction(0) + platformStyle(_platformStyle) + { QSettings settings; if (!restoreGeometry(settings.value("MainWindowGeometry").toByteArray())) { diff --git a/src/rpc/assets.cpp b/src/rpc/assets.cpp index 59ed9387ef..f865026ea8 100644 --- a/src/rpc/assets.cpp +++ b/src/rpc/assets.cpp @@ -300,7 +300,7 @@ UniValue issueunique(const JSONRPCRequest& request) } std::vector assets; - for (int i = 0; i < assetTags.size(); i++) { + for (int i = 0; i < (int)assetTags.size(); i++) { std::string tag = assetTags[i].get_str(); if (!IsUniqueTagValid(tag)) { diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 3640987017..b597305784 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -633,7 +633,7 @@ UniValue createrawtransaction(const JSONRPCRequest& request) rawTx.vout.push_back(out); // Create the assets - for (int i = 0; i < asset_tags.size(); i++) { + for (int i = 0; i < (int)asset_tags.size(); i++) { // Create a new asset CNewAsset asset; diff --git a/src/validation.cpp b/src/validation.cpp index 4a35a6e0b7..84877137ac 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1734,7 +1734,7 @@ static DisconnectResult DisconnectBlock(const CBlock& block, const CBlockIndex* } } } else if (tx.IsNewUniqueAsset()) { - for (int n = 0; n < tx.vout.size(); n++) { + for (int n = 0; n < (int)tx.vout.size(); n++) { auto out = tx.vout[n]; CNewAsset asset; std::string strAddress; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 8b912638f3..aeceb92349 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2291,22 +2291,22 @@ void CWallet::AvailableCoinsAll(std::vector& vCoins, std::maptx->vout[i].scriptPubKey.IsAssetScript(nType, fIsOwner)) { - if ((txnouttype) nType == TX_TRANSFER_ASSET) { + if ( nType == TX_TRANSFER_ASSET) { if (TransferAssetFromScript(pcoin->tx->vout[i].scriptPubKey, assetTransfer, address)) { strAssetName = assetTransfer.strName; fWasTransferAssetOutPoint = true; } - } else if ((txnouttype) nType == TX_NEW_ASSET && !fIsOwner) { + } else if ( nType == TX_NEW_ASSET && !fIsOwner) { if (AssetFromScript(pcoin->tx->vout[i].scriptPubKey, asset, address)) { strAssetName = asset.strName; fWasNewAssetOutPoint = true; } - } else if ((txnouttype) nType == TX_NEW_ASSET && fIsOwner) { + } else if ( nType == TX_NEW_ASSET && fIsOwner) { if (OwnerAssetFromScript(pcoin->tx->vout[i].scriptPubKey, ownerName, address)) { strAssetName = ownerName; fWasOwnerAssetOutPoint = true; } - } else if ((txnouttype) nType == TX_REISSUE_ASSET) { + } else if ( nType == TX_REISSUE_ASSET) { if (ReissueAssetFromScript(pcoin->tx->vout[i].scriptPubKey, reissue, address)) { strAssetName = reissue.strName; fWasReissueAssetOutPoint = true; @@ -2808,7 +2808,7 @@ bool CWallet::SelectAssetsMinConf(const CAmount& nTargetValue, const int nConfMi //------------------------------- - int nType = 0; + int nType = -1; bool fIsOwner = false; if (!coin.txout.scriptPubKey.IsAssetScript(nType, fIsOwner)) { // TODO - Remove std::cout this before mainnet release @@ -2816,28 +2816,26 @@ bool CWallet::SelectAssetsMinConf(const CAmount& nTargetValue, const int nConfMi continue; } - txnouttype type = (txnouttype) nType; - CAmount nTempAmount = 0; - if (type == TX_NEW_ASSET && !fIsOwner) { // Root/Sub Asset + if (nType == TX_NEW_ASSET && !fIsOwner) { // Root/Sub Asset CNewAsset assetTemp; std::string address; if (!AssetFromScript(coin.txout.scriptPubKey, assetTemp, address)) continue; nTempAmount = assetTemp.nAmount; - } else if (type == TX_TRANSFER_ASSET) { // Transfer Asset + } else if (nType == TX_TRANSFER_ASSET) { // Transfer Asset CAssetTransfer transferTemp; std::string address; if (!TransferAssetFromScript(coin.txout.scriptPubKey, transferTemp, address)) continue; nTempAmount = transferTemp.nAmount; - } else if (type == TX_NEW_ASSET && fIsOwner) { // Owner Asset + } else if (nType == TX_NEW_ASSET && fIsOwner) { // Owner Asset std::string ownerName; std::string address; if (!OwnerAssetFromScript(coin.txout.scriptPubKey, ownerName, address)) continue; nTempAmount = OWNER_ASSET_AMOUNT; - } else if (type == TX_REISSUE_ASSET) { // Reissue Asset + } else if (nType == TX_REISSUE_ASSET) { // Reissue Asset CReissueAsset reissueTemp; std::string address; if (!ReissueAssetFromScript(coin.txout.scriptPubKey, reissueTemp, address)) From 49986533e80624e75d634c65a27d4855a84981b5 Mon Sep 17 00:00:00 2001 From: Mark Ney Date: Thu, 30 Aug 2018 15:51:27 -0600 Subject: [PATCH 016/105] fixing Linux compile-linker error --- src/assets/assettypes.h | 71 ++++++++++++++++++++++------------------- src/rpc/assets.cpp | 24 +++++++++++--- 2 files changed, 58 insertions(+), 37 deletions(-) diff --git a/src/assets/assettypes.h b/src/assets/assettypes.h index 396842f1a3..acdbcd5dde 100644 --- a/src/assets/assettypes.h +++ b/src/assets/assettypes.h @@ -1,13 +1,14 @@ -// +// Copyright (c) 2018 The Raven Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. // Created by Jeremy Anderson on 5/15/18. -// #ifndef RAVENCOIN_NEWASSET_H #define RAVENCOIN_NEWASSET_H -#include #include #include +#include "amount.h" #include "serialize.h" #define MAX_UNIT 8 @@ -27,21 +28,9 @@ enum AssetType INVALID = 7 }; -std::string PrintAssetType(AssetType& assetType) { - switch (assetType) { - case ROOT: return "ROOT"; - case SUB: return "SUB"; - case UNIQUE: return "UNIQUE"; - case OWNER: return "OWNER"; - case MSGCHANNEL: return "MSGCHANNEL"; - case VOTE: return "VOTE"; - case REISSUE: return "REISSUE"; - case INVALID: return "INVALID"; - default: return "UNKNOWN"; - } -} - -class CNewAsset { + +class CNewAsset +{ public: std::string strName; // MAX 31 Bytes CAmount nAmount; // 8 Bytes @@ -82,7 +71,8 @@ class CNewAsset { ADD_SERIALIZE_METHODS; template - inline void SerializationOp(Stream& s, Operation ser_action) { + inline void SerializationOp(Stream& s, Operation ser_action) + { READWRITE(strName); READWRITE(nAmount); READWRITE(units); @@ -101,7 +91,8 @@ class AssetComparator } }; -class CAssetTransfer { +class CAssetTransfer +{ public: std::string strName; CAmount nAmount; @@ -121,7 +112,8 @@ class CAssetTransfer { ADD_SERIALIZE_METHODS; template - inline void SerializationOp(Stream& s, Operation ser_action) { + inline void SerializationOp(Stream& s, Operation ser_action) + { READWRITE(strName); READWRITE(nAmount); } @@ -131,7 +123,8 @@ class CAssetTransfer { void ConstructTransaction(CScript& script) const; }; -class CReissueAsset { +class CReissueAsset +{ public: std::string strName; CAmount nAmount; @@ -154,7 +147,8 @@ class CReissueAsset { ADD_SERIALIZE_METHODS; template - inline void SerializationOp(Stream& s, Operation ser_action) { + inline void SerializationOp(Stream& s, Operation ser_action) + { READWRITE(strName); READWRITE(nAmount); READWRITE(nReissuable); @@ -180,7 +174,8 @@ struct CAssetCacheNewAsset this->address = address; } - bool operator<(const CAssetCacheNewAsset& rhs) const { + bool operator<(const CAssetCacheNewAsset& rhs) const + { return asset.strName < rhs.asset.strName; } }; @@ -198,7 +193,8 @@ struct CAssetCacheReissueAsset this->out = out; } - bool operator<(const CAssetCacheReissueAsset& rhs) const { + bool operator<(const CAssetCacheReissueAsset& rhs) const + { return out < rhs.out; } @@ -244,7 +240,8 @@ struct CAssetCacheNewOwner this->address = address; } - bool operator<(const CAssetCacheNewOwner& rhs) const { + bool operator<(const CAssetCacheNewOwner& rhs) const + { return assetName < rhs.assetName; // if (assetName < rhs.assetName) @@ -301,12 +298,14 @@ struct CAssetCachePossibleMine // Least Recently Used Cache template -class CLRUCache { +class CLRUCache +{ public: typedef typename std::pair key_value_pair_t; typedef typename std::list::iterator list_iterator_t; - CLRUCache(size_t max_size) : maxSize(max_size) { + CLRUCache(size_t max_size) : maxSize(max_size) + { } CLRUCache() { @@ -317,13 +316,15 @@ class CLRUCache { { auto it = cacheItemsMap.find(key); cacheItemsList.push_front(key_value_pair_t(key, value)); - if (it != cacheItemsMap.end()) { + if (it != cacheItemsMap.end()) + { cacheItemsList.erase(it->second); cacheItemsMap.erase(it); } cacheItemsMap[key] = cacheItemsList.begin(); - if (cacheItemsMap.size() > maxSize) { + if (cacheItemsMap.size() > maxSize) + { auto last = cacheItemsList.end(); last--; cacheItemsMap.erase(last->first); @@ -334,7 +335,8 @@ class CLRUCache { void Erase(const cache_key_t& key) { auto it = cacheItemsMap.find(key); - if (it != cacheItemsMap.end()) { + if (it != cacheItemsMap.end()) + { cacheItemsList.erase(it->second); cacheItemsMap.erase(it); } @@ -343,9 +345,12 @@ class CLRUCache { const cache_value_t& Get(const cache_key_t& key) { auto it = cacheItemsMap.find(key); - if (it == cacheItemsMap.end()) { + if (it == cacheItemsMap.end()) + { throw std::range_error("There is no such key in cache"); - } else { + } + else + { cacheItemsList.splice(cacheItemsList.begin(), cacheItemsList, it->second); return it->second->second; } diff --git a/src/rpc/assets.cpp b/src/rpc/assets.cpp index 59ed9387ef..7488375129 100644 --- a/src/rpc/assets.cpp +++ b/src/rpc/assets.cpp @@ -4,10 +4,10 @@ //#include //#include -#include -#include +#include "assets/assets.h" +#include "assets/assetdb.h" #include -#include +#include "tinyformat.h" //#include //#include