Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow Asset Tx Chaining #237

Merged
merged 13 commits into from
Aug 20, 2018
169 changes: 103 additions & 66 deletions src/assets/assets.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand All @@ -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))
Expand All @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should never happen right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can happen, only with chained transactions. It occurs when blocks are being undone because of a chain split

mapAssetsAddressAmount.at(pair) = 0;
if (mapAssetsAddressAmount.at(pair) == 0 &&
mapAssetsAddresses.count(assetName))
mapAssetsAddresses.at(assetName).erase(address);

Expand Down Expand Up @@ -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;
}

Expand All @@ -1799,31 +1860,31 @@ 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;
return true;
} 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;
return true;
} 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;
return true;
} 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;
Expand All @@ -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
Expand Down Expand Up @@ -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<std::string>& names)
{
for (auto owned : passets->mapMyUnspentAssets) {
Expand Down Expand Up @@ -2169,15 +2226,14 @@ bool CreateAssetTransaction(CWallet* pwallet, const CNewAsset& asset, const std:
}

// Get the owner outpoints if this is a subasset
std::set<COutPoint> 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);
Expand Down Expand Up @@ -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<COutPoint> myAssetOutPoints;
if (!VerifyAssetOwner(asset_name, myAssetOutPoints, error)) {
if (!VerifyWalletHasAsset(asset_name, error)) {
return false;
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -2328,7 +2383,6 @@ bool CreateTransferAssetTransaction(CWallet* pwallet, const std::vector< std::pa
return false;
}

std::set<COutPoint> myAssetOutPoints;
// Loop through all transfers and create scriptpubkeys for them
for (auto transfer : vTransfers) {
std::string address = transfer.second;
Expand All @@ -2345,27 +2399,17 @@ bool CreateTransferAssetTransaction(CWallet* pwallet, const std::vector< std::pa
return false;
}

std::set<COutPoint> 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));
Expand All @@ -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;
Expand All @@ -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<COutPoint>& myOwnerOutPoints, std::pair<int, std::string>& error)
bool VerifyWalletHasAsset(const std::string& asset_name, std::pair<int, std::string>& 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<COutput> vCoins;
std::map<std::string, std::vector<COutput> > 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;
}
7 changes: 4 additions & 3 deletions src/assets/assets.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string>& names);
void GetAllMyAssets(std::vector<std::string>& 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);

Expand All @@ -356,7 +356,8 @@ bool GetMyAssetBalance(CAssetsCache& cache, const std::string& assetName, CAmoun
bool GetMyAssetBalances(CAssetsCache& cache, const std::vector<std::string>& assetNames, std::map<std::string, CAmount>& balances);
bool GetMyAssetBalances(CAssetsCache& cache, std::map<std::string, CAmount>& balances);

bool VerifyAssetOwner(const std::string& asset_name, std::set<COutPoint>& myOwnerOutPoints, std::pair<int, std::string>& error);
/** Verifies that this wallet owns the give asset */
bool VerifyWalletHasAsset(const std::string& asset_name, std::pair<int, std::string>& pairError);

std::string DecodeIPFS(std::string encoded);
std::string EncodeIPFS(std::string decoded);
Expand Down
2 changes: 1 addition & 1 deletion src/chainparams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Loading