From 01f4d93c0a986c19e496c575ee7a316208ef1939 Mon Sep 17 00:00:00 2001 From: blondfrogs Date: Thu, 18 Oct 2018 11:55:13 -0600 Subject: [PATCH 1/2] fix_check_tx --- src/assets/assets.cpp | 207 ++++++++++-------- src/consensus/tx_verify.cpp | 80 +++---- src/primitives/transaction.h | 6 +- src/validation.cpp | 16 +- .../feature_rawassettransactions.py | 10 +- 5 files changed, 153 insertions(+), 166 deletions(-) diff --git a/src/assets/assets.cpp b/src/assets/assets.cpp index 4013d5f0c4..d34fa08fee 100644 --- a/src/assets/assets.cpp +++ b/src/assets/assets.cpp @@ -659,52 +659,75 @@ bool CTransaction::IsNewAsset() const } //! To be called on CTransactions where IsNewAsset returns true -bool CTransaction::VerifyNewAsset() const +bool CTransaction::VerifyNewAsset(std::string& strError) const { // Issuing an Asset must contain at least 3 CTxOut( Raven Burn Tx, Any Number of other Outputs ..., Owner Asset Tx, New Asset Tx) - if (vout.size() < 3) - return false; - - // Loop through all of the vouts and make sure only the expected asset creations are taking place - int nTransfers = 0; - int nOwners = 0; - int nIssues = 0; - int nReissues = 0; - GetTxOutAssetTypes(vout, nIssues, nReissues, nTransfers, nOwners); - - if (nOwners != 1 || nIssues != 1 || nReissues > 0) + if (vout.size() < 3) { + strError = "bad-txns-issue-vout-size-to-small"; return false; + } // Check for the assets data CTxOut. This will always be the last output in the transaction - if (!CheckIssueDataTx(vout[vout.size() - 1])) + if (!CheckIssueDataTx(vout[vout.size() - 1])) { + strError = "bad-txns-issue-data-not-found"; return false; + } // Check to make sure the owner asset is created - if (!CheckOwnerDataTx(vout[vout.size() - 2])) + if (!CheckOwnerDataTx(vout[vout.size() - 2])) { + strError = "bad-txns-issue-owner-data-not-found"; return false; + } // Get the asset type CNewAsset asset; std::string address; - if (!AssetFromScript(vout[vout.size() - 1].scriptPubKey, asset, address)) + if (!AssetFromScript(vout[vout.size() - 1].scriptPubKey, asset, address)) { + strError = "bad-txns-issue-serialzation-failed"; return error("%s : Failed to get new asset from transaction: %s", __func__, this->GetHash().GetHex()); + } AssetType assetType; IsAssetNameValid(asset.strName, assetType); std::string strOwnerName; - if (!OwnerAssetFromScript(vout[vout.size() - 2].scriptPubKey, strOwnerName, address)) + if (!OwnerAssetFromScript(vout[vout.size() - 2].scriptPubKey, strOwnerName, address)) { + strError = "bad-txns-issue-owner-serialzation-failed"; return false; + } - if (strOwnerName != asset.strName + OWNER_TAG) + if (strOwnerName != asset.strName + OWNER_TAG) { + strError = "bad-txns-issue-owner-name-doesn't-match"; 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)) - return true; + bool fFoundIssueBurnTx = false; + for (auto out : vout) { + if (CheckIssueBurnTx(out, assetType)) { + fFoundIssueBurnTx = true; + break; + } + } - return false; + if (!fFoundIssueBurnTx) { + strError = "bad-txns-issue-owner-burn-not-found"; + return false; + } + + // Loop through all of the vouts and make sure only the expected asset creations are taking place + int nTransfers = 0; + int nOwners = 0; + int nIssues = 0; + int nReissues = 0; + GetTxOutAssetTypes(vout, nIssues, nReissues, nTransfers, nOwners); + + if (nOwners != 1 || nIssues != 1 || nReissues > 0) { + strError = "bad-txns-failed-issue-asset-formatting-check"; + return false; + } + + return true; } //! Make sure to call VerifyNewUniqueAsset if this call returns true @@ -721,11 +744,13 @@ bool CTransaction::IsNewUniqueAsset() const } //! Call this function after IsNewUniqueAsset -bool CTransaction::VerifyNewUniqueAsset(CCoinsViewCache& view) const +bool CTransaction::VerifyNewUniqueAsset(std::string& strError) 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) + if (vout.size() < 3) { + strError = "bad-txns-unique-vout-size-to-small"; return false; + } // check for (and count) new unique asset outpoints. make sure they share a root. std::string assetRoot = ""; @@ -734,66 +759,57 @@ bool CTransaction::VerifyNewUniqueAsset(CCoinsViewCache& view) const if (IsScriptNewUniqueAsset(out.scriptPubKey)) { CNewAsset asset; std::string address; - if (!AssetFromScript(out.scriptPubKey, asset, address)) + if (!AssetFromScript(out.scriptPubKey, asset, address)) { + strError = "bad-txns-issue-unique-asset-from-script"; return false; + } std::string root = GetParentName(asset.strName); if (assetRoot.compare("") == 0) assetRoot = root; - if (assetRoot.compare(root) != 0) + if (assetRoot.compare(root) != 0) { + strError = "bad-txns-issue-unique-asset-compare-failed"; return false; + } assetOutpointCount += 1; } } - if (assetOutpointCount == 0) + + if (assetOutpointCount == 0) { + strError = "bad-txns-issue-unique-asset-bad-outpoint-count"; return false; + } // check for burn outpoint (must account for each new asset) bool fBurnOutpointFound = false; - for (auto out : vout) + for (auto out : vout) { if (CheckIssueBurnTx(out, AssetType::UNIQUE, assetOutpointCount)) { fBurnOutpointFound = true; break; } - if (!fBurnOutpointFound) + } + + if (!fBurnOutpointFound) { + strError = "bad-txns-issue-unique-asset-burn-outpoints-not-found"; 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; - } + CAssetTransfer transfer; + std::string transferAddress; + if (TransferAssetFromScript(out.scriptPubKey, transfer, transferAddress)) { + if (assetRoot + OWNER_TAG == transfer.strName) { + fOwnerOutFound = true; + break; } } } - if (!fFoundCorrectInput) + if (!fOwnerOutFound) { + strError = "bad-txns-issue-unique-asset-bad-owner-asset"; return false; + } // Loop through all of the vouts and make sure only the expected asset creations are taking place int nTransfers = 0; @@ -803,10 +819,10 @@ bool CTransaction::VerifyNewUniqueAsset(CCoinsViewCache& view) const GetTxOutAssetTypes(vout, nIssues, nReissues, nTransfers, nOwners); if (nOwners > 0 || nReissues > 0 || nIssues != assetOutpointCount) { + strError = "bad-txns-failed-unique-asset-formatting-check"; return false; } - return true; } @@ -820,63 +836,58 @@ bool CTransaction::IsReissueAsset() const } //! To be called on CTransactions where IsReissueAsset returns true -bool CTransaction::VerifyReissueAsset(CCoinsViewCache& view) const +bool CTransaction::VerifyReissueAsset(std::string& strError) 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) + if (vout.size() < 3) { + strError = "bad-txns-vout-size-to-small"; return false; + } // Check for the reissue asset data CTxOut. This will always be the last output in the transaction - if (!CheckReissueDataTx(vout[vout.size() - 1])) + if (!CheckReissueDataTx(vout[vout.size() - 1])) { + strError = "bad-txns-reissue-data-not-found"; return false; - - // Check that there is an asset transfer, this will be the owner asset change - bool fOwnerOutFound = false; - for (auto out : vout) { - if (CheckTransferOwnerTx(out)) { - fOwnerOutFound = true; - break; - } } - if (!fOwnerOutFound) - return false; - CReissueAsset reissue; std::string address; - if (!ReissueAssetFromScript(vout[vout.size() - 1].scriptPubKey, reissue, address)) + if (!ReissueAssetFromScript(vout[vout.size() - 1].scriptPubKey, reissue, address)) { + strError = "bad-txns-reissue-serialization-failed"; 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; - } + // Check that there is an asset transfer, this will be the owner asset change + bool fOwnerOutFound = false; + for (auto out : vout) { + CAssetTransfer transfer; + std::string transferAddress; + if (TransferAssetFromScript(out.scriptPubKey, transfer, transferAddress)) { + if (reissue.strName + OWNER_TAG == transfer.strName) { + fOwnerOutFound = true; + break; } } } - if (!fFoundCorrectInput) + if (!fOwnerOutFound) { + strError = "bad-txns-reissue-owner-outpoint-not-found"; 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 - for (auto out : vout) - if (CheckReissueBurnTx(out)) - return true; + bool fFoundReissueBurnTx = false; + for (auto out : vout) { + if (CheckReissueBurnTx(out)) { + fFoundReissueBurnTx = true; + break; + } + } + if (!fFoundReissueBurnTx) { + strError = "bad-txns-reissue-burn-outpoint-not-found"; + return false; + } // Loop through all of the vouts and make sure only the expected asset creations are taking place int nTransfers = 0; @@ -885,10 +896,12 @@ bool CTransaction::VerifyReissueAsset(CCoinsViewCache& view) const int nReissues = 0; GetTxOutAssetTypes(vout, nIssues, nReissues, nTransfers, nOwners); - if (nOwners > 0 || nReissues != 1 || nIssues > 0) + if (nOwners > 0 || nReissues != 1 || nIssues > 0) { + strError = "bad-txns-failed-reissue-asset-formatting-check"; return false; + } - return false; + return true; } CAssetTransfer::CAssetTransfer(const std::string& strAssetName, const CAmount& nAmount) diff --git a/src/consensus/tx_verify.cpp b/src/consensus/tx_verify.cpp index 7fcdd5b8cc..1843fefe15 100644 --- a/src/consensus/tx_verify.cpp +++ b/src/consensus/tx_verify.cpp @@ -231,7 +231,7 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state, CAssetsCa } } } - /** RVN END */ + /** RVN END */ if (fCheckDuplicateInputs) { std::set vInOutPoints; @@ -258,87 +258,59 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state, CAssetsCa if (AreAssetsDeployed()) { if (assetCache) { if (tx.IsNewAsset()) { - if(!tx.VerifyNewAsset()) - return state.DoS(100, false, REJECT_INVALID, "bad-txns-verifying-issue-asset"); + + /** Verify the reissue assets data */ + std::string strError = ""; + if(!tx.VerifyNewAsset(strError)) + return state.DoS(100, false, REJECT_INVALID, strError); CNewAsset asset; std::string strAddress; if (!AssetFromTransaction(tx, asset, strAddress)) - return state.DoS(100, false, REJECT_INVALID, "bad-txns-issue-asset"); + return state.DoS(100, false, REJECT_INVALID, "bad-txns-issue-asset-from-transaction"); // Validate the new assets information - std::string strError = ""; if (!IsNewOwnerTxValid(tx, asset.strName, strAddress, strError)) return state.DoS(100, false, REJECT_INVALID, strError); if (!asset.IsValid(strError, *assetCache, fMemPoolCheck, fCheckAssetDuplicate, fForceDuplicateCheck)) - return state.DoS(100, false, REJECT_INVALID, "bad-txns-" + strError); + return state.DoS(100, error("%s: %s", __func__, strError), REJECT_INVALID, "bad-txns-issue-" + strError); } else if (tx.IsReissueAsset()) { + /** Verify the reissue assets data */ + std::string strError; + if (!tx.VerifyReissueAsset(strError)) + return state.DoS(100, false, REJECT_INVALID, strError); CReissueAsset reissue; std::string strAddress; if (!ReissueAssetFromTransaction(tx, reissue, strAddress)) return state.DoS(100, false, REJECT_INVALID, "bad-txns-reissue-asset"); - bool foundOwnerAsset = false; - for (auto out : tx.vout) { - CAssetTransfer transfer; - std::string transferAddress; - if (TransferAssetFromScript(out.scriptPubKey, transfer, transferAddress)) { - if (reissue.strName + OWNER_TAG == transfer.strName) { - foundOwnerAsset = true; - break; - } - } - } + if (!reissue.IsValid(strError, *assetCache)) + return state.DoS(100, false, REJECT_INVALID, "bad-txns-reissue-" + strError); - 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; + /** Verify the unique assets data */ + std::string strError = ""; + if (!tx.VerifyNewUniqueAsset(strError)) { + return state.DoS(100, false, REJECT_INVALID, strError); + } - if (IsScriptNewUniqueAsset(out.scriptPubKey)) { + 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"); + return state.DoS(100, false, REJECT_INVALID, "bad-txns-check-transaction-issue-unique-asset-serialization"); - std::string strError = ""; if (!asset.IsValid(strError, *assetCache, fMemPoolCheck, fCheckAssetDuplicate, fForceDuplicateCheck)) 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"); } else { // Fail if transaction contains any non-transfer asset scripts and hasn't conformed to one of the // above transaction types. Also fail if it contains OP_RVN_ASSET opcode but wasn't a valid script. diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index dbc985159e..91b07ca099 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -328,11 +328,11 @@ class CTransaction /** RVN START */ bool IsNewAsset() const; - bool VerifyNewAsset() const; + bool VerifyNewAsset(std::string& strError) const; bool IsNewUniqueAsset() const; - bool VerifyNewUniqueAsset(CCoinsViewCache& view) const; + bool VerifyNewUniqueAsset(std::string& strError) const; bool IsReissueAsset() const; - bool VerifyReissueAsset(CCoinsViewCache& view) const; + bool VerifyReissueAsset(std::string& strError) const; /** RVN END */ /** diff --git a/src/validation.cpp b/src/validation.cpp index 42601f1957..cf078b51a7 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2237,7 +2237,8 @@ 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()) + std::string strError = ""; + if (!tx.VerifyNewAsset(strError)) return state.DoS(100, false, REJECT_INVALID, "bad-txns-issue-asset-failed-verify"); CNewAsset asset; @@ -2245,7 +2246,7 @@ static bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockInd if (!AssetFromTransaction(tx, asset, strAddress)) return state.DoS(100, false, REJECT_INVALID, "bad-txns-issue-asset-serialization"); - std::string strError = ""; + if(!IsNewOwnerTxValid(tx, asset.strName, strAddress, strError)) return state.DoS(100, false, REJECT_INVALID, strError); @@ -2258,15 +2259,15 @@ static bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockInd 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"); + std::string strError; + if (!tx.VerifyReissueAsset(strError)) + return state.DoS(100, false, REJECT_INVALID, strError); CReissueAsset reissue; std::string strAddress; if (!ReissueAssetFromTransaction(tx, reissue, strAddress)) return state.DoS(100, false, REJECT_INVALID, "bad-txns-reissue-asset-serialization"); - std::string strError = ""; if (!reissue.IsValid(strError, *assetsCache)) return state.DoS(100, false, REJECT_INVALID, strError); } @@ -2275,7 +2276,8 @@ static bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockInd if (!AreAssetsDeployed()) return state.DoS(100, false, REJECT_INVALID, "bad-txns-issue-unique-asset-when-assets-is-not-active"); - if (!tx.VerifyNewUniqueAsset(view)) + std::string error; + if (!tx.VerifyNewUniqueAsset(error)) return state.DoS(100, false, REJECT_INVALID, "bad-txns-issue-unique-asset-failed-verify"); for (auto out : tx.vout) @@ -2285,7 +2287,7 @@ static bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockInd CNewAsset asset; std::string strAddress; if (!AssetFromScript(out.scriptPubKey, asset, strAddress)) - return state.DoS(100, false, REJECT_INVALID, "bad-txns-issue-unique-asset-serialization"); + return state.DoS(100, false, REJECT_INVALID, "bad-txns-connect-block-issue-unique-asset-serialization"); std::string strError = ""; if (!asset.IsValid(strError, *assetsCache)) diff --git a/test/functional/feature_rawassettransactions.py b/test/functional/feature_rawassettransactions.py index 42637b728a..d7e3e75a26 100755 --- a/test/functional/feature_rawassettransactions.py +++ b/test/functional/feature_rawassettransactions.py @@ -132,7 +132,7 @@ def reissue_tampering_test(self): tx.vout[n].scriptPubKey = hex_str_to_bytes(tampered_script) tx_hex_bad = bytes_to_hex_str(tx.serialize()) tx_signed = n0.signrawtransaction(tx_hex_bad)['hex'] - assert_raises_rpc_error(-26, "bad-txns-reissue-asset-bad-owner-asset", n0.sendrawtransaction, tx_signed) + assert_raises_rpc_error(-26, "bad-txns-reissue-owner-outpoint-not-found", n0.sendrawtransaction, tx_signed) ######################################## # try tampering to remove owner output @@ -145,7 +145,7 @@ def reissue_tampering_test(self): tx.vout = bad_vout tx_hex_bad = bytes_to_hex_str(tx.serialize()) tx_signed = n0.signrawtransaction(tx_hex_bad)['hex'] - assert_raises_rpc_error(-26, "bad-txns-reissue-asset-bad-owner-asset", + assert_raises_rpc_error(-26, "bad-txns-reissue-owner-outpoint-not-found", n0.sendrawtransaction, tx_signed) ######################################## @@ -195,7 +195,7 @@ def issue_tampering_test(self): tx.vout.insert(-1, owner_vout) tx_bad_issue = bytes_to_hex_str(tx.serialize()) tx_bad_issue_signed = n0.signrawtransaction(tx_bad_issue)['hex'] - assert_raises_rpc_error(-26, "bad-txns-verifying-issue-asset", + assert_raises_rpc_error(-26, "bad-txns-failed-issue-asset-formatting-check", n0.sendrawtransaction, tx_bad_issue_signed) ######################################## @@ -235,7 +235,7 @@ def issue_tampering_test(self): tx.vout[n].scriptPubKey = hex_str_to_bytes(tampered_script) tx_bad_issue = bytes_to_hex_str(tx.serialize()) tx_bad_issue_signed = n0.signrawtransaction(tx_bad_issue)['hex'] - assert_raises_rpc_error(-26, "bad-txns-verifying-issue-asset", + assert_raises_rpc_error(-26, "bad-txns-issue-owner-name-doesn't-match", n0.sendrawtransaction, tx_bad_issue_signed) ######################################## @@ -264,7 +264,7 @@ def issue_tampering_test(self): n0.generate(1) assert_raises_rpc_error(-8, f"Invalid parameter: asset_name '{asset_name}' has already been used", get_tx_issue_hex, n0, asset_name, 55) - assert_raises_rpc_error(-26, f"bad-txns-Invalid parameter: asset_name '{asset_name}' has already been used", + assert_raises_rpc_error(-26, f"bad-txns-issue-Invalid parameter: asset_name '{asset_name}' has already been used", n0.sendrawtransaction, tx_duplicate_issue_hex) From cdceac5e9e0e3016839e32d373cd33f4c54cfd6c Mon Sep 17 00:00:00 2001 From: Corbin Fox Date: Thu, 18 Oct 2018 12:05:40 -0600 Subject: [PATCH 2/2] Adds regression test. --- .../feature_rawassettransactions.py | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/test/functional/feature_rawassettransactions.py b/test/functional/feature_rawassettransactions.py index d7e3e75a26..66f54cf627 100755 --- a/test/functional/feature_rawassettransactions.py +++ b/test/functional/feature_rawassettransactions.py @@ -519,6 +519,9 @@ def unique_assets_test(self): self.log.info("Testing unique assets...") n0, n1, n2 = self.nodes[0], self.nodes[1], self.nodes[2] + bad_burn = "n1BurnXXXXXXXXXXXXXXXXXXXXXXU1qejP" + unique_burn = "n1issueUniqueAssetXXXXXXXXXXS4695i" + root = "RINGU" owner = f"{root}!" n0.issue(root) @@ -538,8 +541,10 @@ def unique_assets_test(self): ] burn = 5 * len(asset_tags) + + # try first with bad burn address outputs = { - 'n1issueUniqueAssetXXXXXXXXXXS4695i': burn, + bad_burn: burn, change_address: float(unspent['amount']) - (burn + 0.0001), to_address: { 'issue_unique': { @@ -549,7 +554,23 @@ def unique_assets_test(self): } } } + hex = n0.createrawtransaction(inputs, outputs) + signed_hex = n0.signrawtransaction(hex)['hex'] + assert_raises_rpc_error(-26, "bad-txns-issue-unique-asset-burn-outpoints-not-found", \ + n0.sendrawtransaction, signed_hex) + # switch to proper burn address + outputs = { + unique_burn: 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)