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()