diff --git a/src/addressindex.h b/src/addressindex.h index 90bff10c1..e21b62087 100644 --- a/src/addressindex.h +++ b/src/addressindex.h @@ -41,7 +41,7 @@ struct CMempoolAddressDeltaKey { CMempoolAddressDeltaKey(int addressType, uint160 addressHash, uint256 hash, unsigned int i, int s) { type = addressType; addressBytes = addressHash; - asset = ""; + asset = "RTM"; txhash = hash; index = i; spending = s; @@ -50,7 +50,7 @@ struct CMempoolAddressDeltaKey { CMempoolAddressDeltaKey(int addressType, uint160 addressHash, std::string assetId, uint256 hash, unsigned int i, int s) { type = addressType; addressBytes = addressHash; - assetId = assetId; + asset = assetId; txhash = hash; index = i; spending = s; @@ -68,7 +68,7 @@ struct CMempoolAddressDeltaKey { CMempoolAddressDeltaKey(int addressType, uint160 addressHash) { type = addressType; addressBytes = addressHash; - asset = ""; + asset = "RTM"; txhash.SetNull(); index = 0; spending = 0; diff --git a/src/assets/assets.cpp b/src/assets/assets.cpp index 0d0805113..7c86f27d8 100644 --- a/src/assets/assets.cpp +++ b/src/assets/assets.cpp @@ -13,12 +13,17 @@ #include #include -static const std::regex name_characters("^[a-zA-Z0-9 ]{3,}$"); +static const std::regex name_root_characters("^[A-Z0-9._]{3,}$"); +static const std::regex name_sub_characters("^[a-zA-Z0-9 ]{3,}$"); static const std::regex rtm_names("^RTM$|^RAPTOREUM$|^wRTM$|^WRTM$|^RTMcoin$|^RTMCOIN$"); -bool IsAssetNameValid(std::string name) { + +bool IsAssetNameValid(std::string name, bool isRoot) { if (name.length() < 3 || name.length() > 128) return false; - return std::regex_match(name, name_characters) && !std::regex_match(name, rtm_names); + if (isRoot) + return std::regex_match(name, name_root_characters) && !std::regex_match(name, rtm_names); + else + return std::regex_match(name, name_sub_characters) && !std::regex_match(name, rtm_names); } CAmount getAssetsFeesCoin() { @@ -46,7 +51,14 @@ CAssetMetaData::CAssetMetaData(const std::string txid, const CNewAssetTx assetTx assetId = txid; circulatingSupply = 0; mintCount = 0; - name = assetTx.name; + if (assetTx.nVersion == 2 && !assetTx.isRoot){ + CAssetMetaData rootAsset; + passetsCache->GetAssetMetaData(assetTx.rootId, rootAsset); + name = rootAsset.name + "|" + assetTx.name; + } else { + name = assetTx.name; + } + isRoot = assetTx.isRoot; updatable = assetTx.updatable; isUnique = assetTx.isUnique; decimalPoint = assetTx.decimalPoint; diff --git a/src/assets/assets.h b/src/assets/assets.h index 2d72eed73..a99a36db7 100644 --- a/src/assets/assets.h +++ b/src/assets/assets.h @@ -28,7 +28,7 @@ CAmount getAssetsFeesCoin(); uint16_t getAssetsFees(); -bool IsAssetNameValid(std::string name); +bool IsAssetNameValid(std::string name, bool isRoot=false); bool GetAssetId(const CScript &script, std::string &assetId); @@ -42,6 +42,7 @@ class CAssetMetaData { CAmount circulatingSupply; //update every mint transaction. uint16_t mintCount; std::string name; + bool isRoot = false; bool updatable = false; //if true this asset meta can be modify using assetTx update process. bool isUnique = false; //true if this is asset is unique it has an identity per token (NFT flag) uint8_t decimalPoint = 0; @@ -65,7 +66,7 @@ class CAssetMetaData { SERIALIZE_METHODS(CAssetMetaData, obj ) { - READWRITE(obj.assetId, obj.circulatingSupply, obj.mintCount, obj.name, obj.updatable, + READWRITE(obj.assetId, obj.circulatingSupply, obj.mintCount, obj.name, obj.isRoot, obj.updatable, obj.isUnique, obj.maxMintCount, obj.decimalPoint, obj.referenceHash, obj.fee, obj.type, obj.targetAddress, obj.issueFrequency, obj.amount, obj.ownerAddress, obj.collateralAddress); @@ -76,6 +77,7 @@ class CAssetMetaData { circulatingSupply = CAmount(-1); mintCount = uint16_t(-1); name = ""; + isRoot = false; updatable = false; isUnique = false; decimalPoint = uint8_t(-1); diff --git a/src/chainparams.cpp b/src/chainparams.cpp index fd9e6cb48..14bb253fa 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -205,6 +205,7 @@ class CMainParams : public CChainParams { consensus.smartnodePaymentFixedBlock = 6800; consensus.nFutureForkBlock = 420420; consensus.nAssetsForkBlock = 9999999; + consensus.nRootAssetsForkBlock = consensus.nAssetsForkBlock; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].bit = 28; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nStartTime = 1199145601; // January 1, 2008 consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nTimeout = 1230767999; // December 31, 2008 @@ -373,6 +374,7 @@ class CTestNetParams : public CChainParams { consensus.nMinerConfirmationWindow = 2016; // nPowTargetTimespan / nPowTargetSpacing consensus.nFutureForkBlock = 1000; consensus.nAssetsForkBlock = 411300; + consensus.nRootAssetsForkBlock = 9999999; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].bit = 28; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nStartTime = 1199145601; // January 1, 2008 consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nTimeout = 1230767999; // December 31, 2008 @@ -526,6 +528,7 @@ class CDevNetParams : public CChainParams { consensus.nMinerConfirmationWindow = 2016; // nPowTargetTimespan / nPowTargetSpacing consensus.nFutureForkBlock = 1; consensus.nAssetsForkBlock = 1; + consensus.nRootAssetsForkBlock = 1; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].bit = 28; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nStartTime = 1199145601; // January 1, 2008 consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nTimeout = 1230767999; // December 31, 2008 @@ -698,6 +701,7 @@ class CRegTestParams : public CChainParams { consensus.nMinerConfirmationWindow = 144; // Faster than normal for regtest (144 instead of 2016) consensus.nFutureForkBlock = 1; consensus.nAssetsForkBlock = 1; + consensus.nRootAssetsForkBlock = 1; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].bit = 28; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nStartTime = 0; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nTimeout = 999999999999ULL; diff --git a/src/chainparams.h b/src/chainparams.h index 71dae8c23..9f391f005 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -148,6 +148,10 @@ class CChainParams { return height >= GetConsensus().nAssetsForkBlock; }; + bool IsRootAssetsActive(CBlockIndex *index) const { + int height = index == nullptr ? 0 : index->nHeight; + return height >= GetConsensus().nRootAssetsForkBlock; + }; const std::vector &SporkAddresses() const { return vSporkAddresses; } int MinSporkKeys() const { return nMinSporkKeys; } diff --git a/src/consensus/params.h b/src/consensus/params.h index 736cd072b..419b7f0b1 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -132,6 +132,7 @@ namespace Consensus { int smartnodePaymentFixedBlock; int nFutureForkBlock; int nAssetsForkBlock; + int nRootAssetsForkBlock; }; } // namespace Consensus diff --git a/src/evo/providertx.cpp b/src/evo/providertx.cpp index 19804c61d..6a4f4b70b 100644 --- a/src/evo/providertx.cpp +++ b/src/evo/providertx.cpp @@ -141,16 +141,49 @@ bool CheckNewAssetTx(const CTransaction &tx, const CBlockIndex *pindexPrev, CVal return state.DoS(100, false, REJECT_INVALID, "bad-assets-version"); } + if (Params().IsRootAssetsActive(::ChainActive().Tip()) && assetTx.nVersion != 2) { + return state.DoS(100, false, REJECT_INVALID, "bad-assets-invalid-version"); + } else { + if (!Params().IsRootAssetsActive(::ChainActive().Tip()) && assetTx.nVersion != 1) { + return state.DoS(100, false, REJECT_INVALID, "bad-assets-invalid-version"); + } + } + //validate asset name - if (!IsAssetNameValid(assetTx.name)) { + if (!IsAssetNameValid(assetTx.name, assetTx.isRoot)) { return state.DoS(100, false, REJECT_INVALID, "bad-assets-name"); } //Check if a asset already exist with give name std::string assetId = assetTx.name; - if (assetsCache->GetAssetId(assetTx.name, assetId)) { - if (assetsCache->CheckIfAssetExists(assetId)) { - return state.DoS(100, false, REJECT_INVALID, "bad-assets-dup-name"); + CAssetMetaData rootAsset; + if (assetTx.nVersion == 2 && !assetTx.isRoot) { + if (!assetsCache->GetAssetMetaData(assetTx.rootId, rootAsset)){ + return state.DoS(100, false, REJECT_INVALID, "bad-assets-root-id"); + } + //sub asset name is stored as "root_name|sub_name" + if (assetsCache->GetAssetId(assetTx.rootId + "|" + assetTx.name, assetId)) { + if (assetsCache->CheckIfAssetExists(assetId)){ + return state.DoS(100, false, REJECT_INVALID, "bad-assets-dup-name"); + } + } + } else { //v1 or ROOT asset + if (assetsCache->GetAssetId(assetTx.name, assetId)) { + if (assetsCache->CheckIfAssetExists(assetId)) { + return state.DoS(100, false, REJECT_INVALID, "bad-assets-dup-name"); + } + } + } + + //check if the root asset is valid + if (assetTx.nVersion == 2 && !assetTx.isRoot) { + if (!rootAsset.isRoot) { + return state.DoS(100, false, REJECT_INVALID, "bad-assets-invalid-root-id"); + } + //check the root asset owner signature + std::string strError; + if (!CMessageSigner::VerifyMessage(rootAsset.ownerAddress, assetTx.vchSig, assetTx.MakeSignString(assetsCache), strError)) { + return state.DoS(100, false, REJECT_INVALID, "bad-assets-sig", false, strError); } } @@ -238,9 +271,23 @@ bool CheckUpdateAssetTx(const CTransaction &tx, const CBlockIndex *pindexPrev, C return state.DoS(100, false, REJECT_INVALID, "bad-assets-not-updateable"); } - //Check if fees is paid by the owner address - if (!checkAssetFeesPayment(tx, state, view, asset)) - return false; + if (Params().IsRootAssetsActive(::ChainActive().Tip())) { + if (assetTx.nVersion != 2) { + return state.DoS(100, false, REJECT_INVALID, "bad-assets-invalid-version"); + } + //check the asset owner signature + std::string strError; + if (!CMessageSigner::VerifyMessage(asset.ownerAddress, assetTx.vchSig, assetTx.MakeSignString(assetsCache), strError)) { + return state.DoS(100, false, REJECT_INVALID, "bad-assets-sig", false, strError); + } + } else { + if (assetTx.nVersion != 1) { + return state.DoS(100, false, REJECT_INVALID, "bad-assets-invalid-version"); + } + //Check if fees is paid by the owner address + if (!checkAssetFeesPayment(tx, state, view, asset)) + return false; + } if (assetTx.ownerAddress.IsNull()) { return state.DoS(100, false, REJECT_INVALID, "bad-assets-ownerAddress"); @@ -356,9 +403,23 @@ bool CheckMintAssetTx(const CTransaction &tx, const CBlockIndex *pindexPrev, CVa } if (asset.type == 0 || asset.isUnique) { // manual mint or unique - //Check if fees is paid by the owner address - if (!checkAssetFeesPayment(tx, state, view, asset)) - return false; + if (Params().IsRootAssetsActive(::ChainActive().Tip())) { + if (assetTx.nVersion != 2) { + return state.DoS(100, false, REJECT_INVALID, "bad-assets-invalid-version"); + } + //check the asset owner signature + std::string strError; + if (!CMessageSigner::VerifyMessage(asset.ownerAddress, assetTx.vchSig, assetTx.MakeSignString(assetsCache), strError)) { + return state.DoS(100, false, REJECT_INVALID, "bad-mint-assets-sig", false, strError); + } + } else { + if (assetTx.nVersion != 1) { + return state.DoS(100, false, REJECT_INVALID, "bad-assets-invalid-version"); + } + //Check if fees is paid by the owner address + if (!checkAssetFeesPayment(tx, state, view, asset)) + return false; + } if (!checkAssetMintAmount(tx, state, asset)) return false; @@ -755,3 +816,57 @@ std::string CProUpRevTx::ToString() const { return strprintf("CProUpRevTx(nVersion=%d, proTxHash=%s, nReason=%d)", nVersion, proTxHash.ToString(), nReason); } + +std::string CNewAssetTx::MakeSignString(CAssetsCache *assetsCache) const { + std::string s; + + // We only include the important stuff in the string form... + s = name + "|"; + s += EncodeDestination(ownerAddress) + "|"; + + CAssetMetaData rootAsset; + if (assetsCache->GetAssetMetaData(rootId, rootAsset)){ + s += EncodeDestination(rootAsset.ownerAddress) + "|"; + } + + // ... and also the full hash of the payload as a protection agains malleability and replays + s += ::SerializeHash(*this).ToString(); + + return s; +} + +std::string CUpdateAssetTx::MakeSignString(CAssetsCache *assetsCache) const { + std::string s; + + // We only include the important stuff in the string form... + CAssetMetaData asset; + if (assetsCache->GetAssetMetaData(assetId, asset)){ + s = asset.name + "|"; + s += EncodeDestination(asset.targetAddress) + "|"; + s += EncodeDestination(asset.ownerAddress) + "|"; + s += std::to_string(asset.circulatingSupply)+ "|"; + } + + // ... and also the full hash of the payload as a protection agains malleability and replays + s += ::SerializeHash(*this).ToString(); + + return s; +} + +std::string CMintAssetTx::MakeSignString(CAssetsCache *assetsCache) const { + std::string s; + + // We only include the important stuff in the string form... + CAssetMetaData asset; + if (assetsCache->GetAssetMetaData(assetId, asset)){ + s = asset.name + "|"; + s += EncodeDestination(asset.ownerAddress) + "|"; + s += EncodeDestination(asset.targetAddress) + "|"; + s += std::to_string(asset.circulatingSupply) + "|"; + } + + // ... and also the full hash of the payload as a protection agains malleability and replays + s += ::SerializeHash(*this).ToString(); + + return s; +} \ No newline at end of file diff --git a/src/evo/providertx.h b/src/evo/providertx.h index f35e682f4..5677616c8 100644 --- a/src/evo/providertx.h +++ b/src/evo/providertx.h @@ -251,10 +251,12 @@ class CFutureTx { class CNewAssetTx { public: - static const uint16_t CURRENT_VERSION = 1; + static const uint16_t CURRENT_VERSION = 2; uint16_t nVersion{CURRENT_VERSION}; // message version std::string name; + bool isRoot = false; + std::string rootId; bool updatable = true; // If true this asset metadata can be modified using assetTx update process. bool isUnique = false; // If true this asset is unique it has an identity per token (NFT flag) uint16_t maxMintCount = 0; @@ -268,6 +270,7 @@ class CNewAssetTx { CAmount amount; CKeyID ownerAddress; CKeyID collateralAddress; + std::vector vchSig; //Root asset Signature uint16_t exChainType = 0; // External chain type. each 15 bit unsigned number will be map to a external chain. i.e. 0 for btc CScript externalPayoutScript; @@ -277,16 +280,27 @@ class CNewAssetTx { public: - SERIALIZE_METHODS(CNewAssetTx, obj - ) + SERIALIZE_METHODS(CNewAssetTx, obj) { READWRITE(obj.nVersion, obj.name, obj.updatable, obj.isUnique, obj.maxMintCount, obj.decimalPoint, obj.referenceHash, obj.fee, obj.type, obj.targetAddress, - obj.issueFrequency, obj.amount, obj.ownerAddress, obj.collateralAddress, - obj.exChainType, obj.externalPayoutScript, obj.externalTxid, + obj.issueFrequency, obj.amount, obj.ownerAddress, obj.collateralAddress); + if(obj.nVersion == 2 ) { //testnet use v1 and v2, mainnet v2 only + READWRITE(obj.isRoot); + if (!obj.isRoot) { + //sub asset: serialise the root id and owner signature + READWRITE(obj.rootId); + if (!(s.GetType() & SER_GETHASH)) { + READWRITE(obj.vchSig); + } + } + } + READWRITE(obj.exChainType, obj.externalPayoutScript, obj.externalTxid, obj.externalConfirmations, obj.inputsHash); } + std::string MakeSignString(CAssetsCache *assetsCache) const; + std::string ToString() const; void ToJson(UniValue &obj) const { @@ -294,6 +308,9 @@ class CNewAssetTx { obj.setObject(); obj.pushKV("version", nVersion); obj.pushKV("name", name); + obj.pushKV("isRoot", isRoot); + if (!isRoot && nVersion == 2) + obj.pushKV("rootId", rootId); obj.pushKV("isUnique", isUnique); obj.pushKV("maxMintCount", maxMintCount); obj.pushKV("updatable", updatable); @@ -326,7 +343,7 @@ class CNewAssetTx { class CUpdateAssetTx { public: - static const uint16_t CURRENT_VERSION = 1; + static const uint16_t CURRENT_VERSION = 2; uint16_t nVersion{CURRENT_VERSION}; // message version std::string assetId; @@ -341,6 +358,7 @@ class CUpdateAssetTx { CAmount amount; CKeyID ownerAddress; CKeyID collateralAddress; + std::vector vchSig; //owner Signature uint16_t exChainType = 0; // External chain type. Each 15 bit unsigned number will be map to a external chain. i.e. 0 for btc CScript externalPayoutScript; @@ -356,8 +374,15 @@ class CUpdateAssetTx { obj.type, obj.targetAddress, obj.issueFrequency, obj.maxMintCount, obj.amount, obj.ownerAddress, obj.collateralAddress, obj.exChainType, obj.externalPayoutScript, obj.externalTxid, obj.externalConfirmations, obj.inputsHash); + if(obj.nVersion == 2 ) { //testnet use v1 and v2, mainnet v2 only + if (!(s.GetType() & SER_GETHASH)) { + READWRITE(obj.vchSig); + } + } } + std::string MakeSignString(CAssetsCache *assetsCache) const; + std::string ToString() const; void ToJson(UniValue &obj) const { @@ -394,12 +419,13 @@ class CUpdateAssetTx { class CMintAssetTx { public: - static const uint16_t CURRENT_VERSION = 1; + static const uint16_t CURRENT_VERSION = 2; uint16_t nVersion{CURRENT_VERSION}; // message version std::string assetId; uint16_t fee; uint256 inputsHash; // replay protection + std::vector vchSig; //owner Signature public: @@ -407,8 +433,15 @@ class CMintAssetTx { ) { READWRITE(obj.nVersion, obj.assetId, obj.fee, obj.inputsHash); + if(obj.nVersion == 2 ) { //testnet use v1 and v2, mainnet v2 only + if (!(s.GetType() & SER_GETHASH)) { + READWRITE(obj.vchSig); + } + } } + std::string MakeSignString(CAssetsCache *assetsCache) const; + std::string ToString() const; void ToJson(UniValue &obj) const { diff --git a/src/qt/assetsdialog.cpp b/src/qt/assetsdialog.cpp index a13fb02ae..e2e4d5cf8 100644 --- a/src/qt/assetsdialog.cpp +++ b/src/qt/assetsdialog.cpp @@ -326,38 +326,39 @@ void AssetsDialog::mintAsset() { CMintAssetTx mintAsset; mintAsset.assetId = tmpAsset.assetId; mintAsset.fee = getAssetsFees(); - - CTxDestination ownerAddress = CTxDestination(tmpAsset.ownerAddress); - if (!IsValidDestination(ownerAddress)) { - QMessageBox msgBox; - msgBox.setText("ERROR: Invalid owner address"); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.exec(); - return; - } - + CCoinControl coinControl; + if (!Params().IsRootAssetsActive(::ChainActive().Tip())) { + CTxDestination ownerAddress = CTxDestination(tmpAsset.ownerAddress); + if (!IsValidDestination(ownerAddress)) { + QMessageBox msgBox; + msgBox.setText("ERROR: Invalid owner address"); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.exec(); + return; + } - coinControl.destChange = ownerAddress; - coinControl.fRequireAllInputs = false; + coinControl.destChange = ownerAddress; + coinControl.fRequireAllInputs = false; - std::vector vecOutputs; - //select only confirmed inputs, nMinDepth >= 1 - walletModel->wallet().AvailableCoins(vecOutputs, true, nullptr, 1, MAX_MONEY , MAX_MONEY, 0, 1); + std::vector vecOutputs; + //select only confirmed inputs, nMinDepth >= 1 + walletModel->wallet().AvailableCoins(vecOutputs, true, nullptr, 1, MAX_MONEY , MAX_MONEY, 0, 1); - for (const auto &out: vecOutputs) { - CTxDestination txDest; - if (ExtractDestination(out.tx->tx->vout[out.i].scriptPubKey, txDest) && txDest == ownerAddress) { - coinControl.Select(COutPoint(out.tx->tx->GetHash(), out.i)); + for (const auto &out: vecOutputs) { + CTxDestination txDest; + if (ExtractDestination(out.tx->tx->vout[out.i].scriptPubKey, txDest) && txDest == ownerAddress) { + coinControl.Select(COutPoint(out.tx->tx->GetHash(), out.i)); + } } - } - if (!coinControl.HasSelected()) { - QMessageBox msgBox; - msgBox.setText(QString::fromStdString(strprintf("Error: No funds at specified address %s", EncodeDestination(ownerAddress)))); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.exec(); - return; + if (!coinControl.HasSelected()) { + QMessageBox msgBox; + msgBox.setText(QString::fromStdString(strprintf("Error: No funds at specified address %s", EncodeDestination(ownerAddress)))); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.exec(); + return; + } } CTransactionRef wtx; diff --git a/src/qt/createassetsdialog.cpp b/src/qt/createassetsdialog.cpp index 48e999562..a71c5e3c9 100644 --- a/src/qt/createassetsdialog.cpp +++ b/src/qt/createassetsdialog.cpp @@ -34,6 +34,8 @@ #include #include #include +#include +#include #include #define SEND_CONFIRM_DELAY 3 @@ -77,6 +79,10 @@ CreateAssetsDialog::CreateAssetsDialog(QWidget *parent) : ui->ipfsLabel, ui->uniqueBox, ui->updatableBox, + ui->AssetTypeLabel, + ui->AssetTypeBox, + ui->RootAssetLabel, + ui->RootAssetBox, ui->IssueFrequencyLabel}, GUIUtil::FontWeight::Normal, 15); GUIUtil::setupAddressWidget(ui->lineEditCoinControlChange, this); @@ -88,6 +94,7 @@ CreateAssetsDialog::CreateAssetsDialog(QWidget *parent) : connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clear())); connect(ui->availabilityButton, SIGNAL(clicked()), this, SLOT(checkAvailabilityClicked())); connect(ui->uniqueBox, SIGNAL(clicked()), this, SLOT(onUniqueChanged())); + connect(ui->AssetTypeBox, SIGNAL(currentIndexChanged(QString)), this, SLOT(onAssetTypeSelected(QString))); // Coin Control connect(ui->pushButtonCoinControl, SIGNAL(clicked()), this, SLOT(CoinControlButtonClicked())); @@ -151,7 +158,21 @@ CreateAssetsDialog::CreateAssetsDialog(QWidget *parent) : ui->distributionBox->setToolTip(tr("Manual only until other types are developed")); ui->openIpfsButton->hide(); + ui->RootAssetLabel->setVisible(false); + ui->RootAssetBox->setVisible(false); + m_coin_control->UseCoinJoin(false); + + /** Setup the root asset list combobox */ + stringModel = new QStringListModel; + + proxy = new QSortFilterProxyModel; + proxy->setSourceModel(stringModel); + proxy->setFilterCaseSensitivity(Qt::CaseInsensitive); + + ui->RootAssetBox->setModel(proxy); + ui->RootAssetBox->setEditable(true); + ui->RootAssetBox->lineEdit()->setPlaceholderText("Select Root asset"); } void CreateAssetsDialog::setClientModel(ClientModel *_clientModel) { @@ -306,6 +327,21 @@ void CreateAssetsDialog::createAsset() { assetTx.maxMintCount = ui->maxmintSpinBox->value(); assetTx.issueFrequency = ui->IssueFrequencyBox->value(); + if (Params().IsRootAssetsActive(::ChainActive().Tip())) { + assetTx.isRoot = ui->AssetTypeBox->currentText() == "Root"; + if (!assetTx.isRoot) {//sub asset + if (ui->RootAssetBox->currentIndex() > 0) { + std::string assetId; + if (passetsCache->GetAssetId(ui->RootAssetBox->currentText().toStdString(), assetId)) { + assetTx.rootId = assetId; + } else { + //shold never hapen + return; + } + } + } + } + CTransactionRef newTx; CAmount nFee; int nChangePos = -1; @@ -380,11 +416,28 @@ bool CreateAssetsDialog::validateInputs() { bool retval{true}; std::string assetname = ui->assetnameText->text().toStdString(); + bool isRoot = false; + if (Params().IsRootAssetsActive(::ChainActive().Tip())){ + isRoot = ui->AssetTypeBox->currentText() == "Root"; + } + //check if asset name is valid - if (!IsAssetNameValid(assetname)) { + if (!IsAssetNameValid(assetname, isRoot)) { retval = false; ui->assetnameText->setValid(false); } + + if (Params().IsRootAssetsActive(::ChainActive().Tip())) { + if (!isRoot) {//sub asset + if (ui->RootAssetBox->currentIndex() > 0) { + assetname = ui->RootAssetBox->currentText().toStdString() + "|" + assetname; + } else { + //root asset not selected, set name as invalid + ui->assetnameText->setValid(false); + retval = false; + } + } + } // check if asset already exist std::string assetId; if (passetsCache->GetAssetId(assetname, assetId)) { @@ -737,10 +790,26 @@ void CreateAssetsDialog::CoinControlUpdateLabels() { void CreateAssetsDialog::checkAvailabilityClicked() { std::string assetname = ui->assetnameText->text().toStdString(); + bool isRoot = false; + if (Params().IsRootAssetsActive(::ChainActive().Tip())) + isRoot = ui->AssetTypeBox->currentText() == "Root"; + //check if asset name is valid - if (!IsAssetNameValid(assetname)) { + if (!IsAssetNameValid(assetname, isRoot)) { ui->assetnameText->setValid(false); } + + if (Params().IsRootAssetsActive(::ChainActive().Tip())) { + if (!isRoot) {//sub asset + if (ui->RootAssetBox->currentIndex() > 0) { + assetname = ui->RootAssetBox->currentText().toStdString() + "|" + assetname; + } else { + //root asset not selected, set name as invalid + ui->assetnameText->setValid(false); + return; + } + } + } // check if asset already exist std::string assetId; if (passetsCache->GetAssetId(assetname, assetId)) { @@ -755,6 +824,35 @@ void CreateAssetsDialog::checkAvailabilityClicked() } } +void CreateAssetsDialog::onAssetTypeSelected(QString name) { + if (name == "Root") { + ui->RootAssetLabel->setVisible(false); + ui->RootAssetBox->setVisible(false); + ui->assetnameText->setToolTip("A-Z 0-9 and space"); + } else if (name == "Sub") { + ui->RootAssetLabel->setVisible(true); + ui->RootAssetBox->setVisible(true); + ui->assetnameText->setToolTip("a-z A-Z 0-9 and space"); + + // Get available assets list + std::map > assets = model->wallet().getMyAssets(); + + QStringList list; + list << "Select an asset"; + for (auto asset: assets) { + CAssetMetaData assetData; + std::string assetId = asset.first.ToString(); + if (passetsCache->GetAssetMetaData(assetId, assetData) && assetData.isRoot) { + if (model->wallet().isSpendable(assetData.ownerAddress)){ + list << QString::fromStdString(assetData.name); + } + } + } + //update the list + stringModel->setStringList(list); + } +} + CreateAssetConfirmationDialog::CreateAssetConfirmationDialog(const QString &title, const QString &text, int _secDelay, QWidget *parent) : QMessageBox(QMessageBox::Question, title, text, QMessageBox::Yes | QMessageBox::Cancel, parent), diff --git a/src/qt/createassetsdialog.h b/src/qt/createassetsdialog.h index 56ab1fcd3..fa58028d1 100644 --- a/src/qt/createassetsdialog.h +++ b/src/qt/createassetsdialog.h @@ -17,6 +17,10 @@ class CCoinControl; class ClientModel; +class QStringListModel; + +class QSortFilterProxyModel; + namespace Ui { class CreateAssetsDialog; } @@ -61,6 +65,9 @@ public WalletModel *model; std::unique_ptr m_coin_control; + QStringListModel *stringModel; + QSortFilterProxyModel *proxy; + bool validateInputs(); bool filladdress(QString address, CKeyID &field); @@ -88,6 +95,8 @@ private void on_buttonMinimizeFee_clicked(); + void onAssetTypeSelected(QString name); + void updateDisplayUnit(); void CoinControlFeatureChanged(bool); diff --git a/src/qt/forms/createassetsdialog.ui b/src/qt/forms/createassetsdialog.ui index 91b4913c7..26b87d392 100644 --- a/src/qt/forms/createassetsdialog.ui +++ b/src/qt/forms/createassetsdialog.ui @@ -540,7 +540,7 @@ 0 0 953 - 248 + 283 @@ -571,7 +571,51 @@ QFrame::NoFrame - + + + + <html><head/><body><p>The RTM address that will receive the minted asset</p></body></html> + + + Target Address: + + + + + + + <html><head/><body><p>RTM address that will own this asset</p></body></html> + + + Owner Address: + + + + + + + + + <html><head/><body><p>The RTM address that will receive the minted asset</p></body></html> + + + The RTM address that will receive the minted asset + + + + + + + + + <html><head/><body><p>[Optional] The ipfs/file hash that contains information about the asset</p></body></html> + + + IPFS/file hash: + + + + @@ -595,27 +639,97 @@ - - - - <html><head/><body><p>[Optional] The ipfs/file hash that contains information about the asset</p></body></html> - - - IPFS/file hash: - - + + + + + + <html><head/><body><p>a-z A-Z 0-9 and space</p></body></html> + + + 128 + + + The name of the asset you would like to create + + + + + + + Check Availabilty + + + + - - - - <html><head/><body><p>The RTM address that will receive the minted asset</p></body></html> - + + + + + + <html><head/><body><p>RTM address that will own this asset</p></body></html> + + + + + + The RTM address that own this asset + + + + + + + - Target Address: + Asset Name: - + + + + + + + + <html><head/><body><p>Make this asset unique also known as NFT</p></body></html> + + + Unique Asset + + + + + + + + + + + + + + + + Distribution Type: + + + 0 + + + + + + + Mint count: + + + + + + 0 @@ -755,118 +869,78 @@ - - - - <html><head/><body><p>RTM address that will own this asset</p></body></html> - - - Owner Address: - - - - + - Asset Name: + Asset type: - - + + - - - <html><head/><body><p>The RTM address that will receive the minted asset</p></body></html> + + + 20 - - The RTM address that will receive the minted asset + + 6 - - - - - - - - - - - - <html><head/><body><p>Make this asset unique also known as NFT</p></body></html> + + + + + 200 + 0 + - - Unique Asset + + + Root + + + + + Sub + + + + + + + + + 200 + 0 + - - + + + + true + - + Root Asset: + + + + Qt::Horizontal + + + + 40 + 20 + + + + - - - - Distribution Type: - - - 0 - - - - - - - Mint count: - - - - - - - - - - - <html><head/><body><p>RTM address that will own this asset</p></body></html> - - - - - - The RTM address that own this asset - - - - - - - - - - - <html><head/><body><p>a-z A-Z 0-9 and space</p></body></html> - - - 128 - - - The name of the asset you would like to create - - - - - - - Check Availabilty - - - diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp index eab45bcb5..0d182b76e 100644 --- a/src/qt/transactiontablemodel.cpp +++ b/src/qt/transactiontablemodel.cpp @@ -102,7 +102,7 @@ class TransactionTablePriv { // Find bounds of this transaction in model QList::iterator lower = std::lower_bound(cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan()); - QList::iterator upper = std::lower_bound(cachedWallet.begin(), cachedWallet.end(), hash, + QList::iterator upper = std::upper_bound(cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan()); int lowerIndex = (lower - cachedWallet.begin()); int upperIndex = (upper - cachedWallet.begin()); diff --git a/src/qt/updateassetsdialog.cpp b/src/qt/updateassetsdialog.cpp index 38f0aebf6..eab4c659d 100644 --- a/src/qt/updateassetsdialog.cpp +++ b/src/qt/updateassetsdialog.cpp @@ -355,35 +355,37 @@ void UpdateAssetsDialog::updateAsset() { return; } - CTxDestination ownerAddress = CTxDestination(assetData.ownerAddress); - if (!IsValidDestination(ownerAddress)) { - QMessageBox msgBox; - msgBox.setText("ERROR: Invalid owner address"); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.exec(); - return; - } + if (!Params().IsRootAssetsActive(::ChainActive().Tip())) { + CTxDestination ownerAddress = CTxDestination(assetData.ownerAddress); + if (!IsValidDestination(ownerAddress)) { + QMessageBox msgBox; + msgBox.setText("ERROR: Invalid owner address"); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.exec(); + return; + } + m_coin_control->destChange = ownerAddress; - m_coin_control->destChange = ownerAddress; - m_coin_control->fRequireAllInputs = false; + m_coin_control->fRequireAllInputs = false; - std::vector vecOutputs; - //select only confirmed inputs, nMinDepth >= 1 - model->wallet().AvailableCoins(vecOutputs, true, nullptr, 1, MAX_MONEY , MAX_MONEY, 0, 1); + std::vector vecOutputs; + //select only confirmed inputs, nMinDepth >= 1 + model->wallet().AvailableCoins(vecOutputs, true, nullptr, 1, MAX_MONEY , MAX_MONEY, 0, 1); - for (const auto &out: vecOutputs) { - CTxDestination txDest; - if (ExtractDestination(out.tx->tx->vout[out.i].scriptPubKey, txDest) && txDest == ownerAddress) { - m_coin_control->Select(COutPoint(out.tx->tx->GetHash(), out.i)); + for (const auto &out: vecOutputs) { + CTxDestination txDest; + if (ExtractDestination(out.tx->tx->vout[out.i].scriptPubKey, txDest) && txDest == ownerAddress) { + m_coin_control->Select(COutPoint(out.tx->tx->GetHash(), out.i)); + } } - } - if (!m_coin_control->HasSelected()) { - QMessageBox msgBox; - msgBox.setText(QString::fromStdString(strprintf("Error: No funds at specified address %s", EncodeDestination(ownerAddress)))); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.exec(); - return; + if (!m_coin_control->HasSelected()) { + QMessageBox msgBox; + msgBox.setText(QString::fromStdString(strprintf("Error: No funds at specified address %s", EncodeDestination(ownerAddress)))); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.exec(); + return; + } } CTransactionRef newTx; diff --git a/src/rpc/rpcassets.cpp b/src/rpc/rpcassets.cpp index 8c78fad6a..8c7d3435d 100644 --- a/src/rpc/rpcassets.cpp +++ b/src/rpc/rpcassets.cpp @@ -61,6 +61,8 @@ UniValue createasset(const JSONRPCRequest &request) { "{\n" " \"name:\" (string) Asset name\n" " \"updatable:\" (bool, optional, default=true) if true this asset can be modify using reissue process.\n" + " \"is_root:\" (bool, required) if this asset is root.\n" + " \"root_name:\" (string) the root asset name for this sub asset.\n" " \"is_unique:\" (bool, optional, default=false) if true this is asset is unique it has an identity per token (NFT flag)\n" " \"decimalpoint:\" (numeric) [0 to 8] has to be 0 if is_unique is true.\n" " \"referenceHash:\" (string) hash of the underlying physical or digital assets, IPFS hash can be used here.\n" @@ -105,27 +107,10 @@ UniValue createasset(const JSONRPCRequest &request) { const UniValue &name = find_value(asset, "name"); std::string assetname = name.getValStr(); - - //check if asset name is valid - if (!IsAssetNameValid(assetname)) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Error: Invalid asset name"); - } - // check if asset already exist - std::string assetId; - if (passetsCache->GetAssetId(assetname, assetId)) { - CAssetMetaData tmpAsset; - if (passetsCache->GetAssetMetaData(assetId, tmpAsset)) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Error: Asset already exist"); - } - } - - //check on mempool if asset already exist - if (mempool.CheckForNewAssetConflict(assetname)) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Error: Asset already exist on mempool"); - } - + CNewAssetTx assetTx; assetTx.name = assetname; + const UniValue &updatable = find_value(asset, "updatable"); if (!updatable.isNull()) { assetTx.updatable = updatable.get_bool(); @@ -133,6 +118,87 @@ UniValue createasset(const JSONRPCRequest &request) { assetTx.updatable = true; } + if (Params().IsRootAssetsActive(::ChainActive().Tip())) { + const UniValue &isroot = find_value(asset, "is_root"); + if (!isroot.isNull()) { + assetTx.isRoot = isroot.get_bool(); + } else { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Error: asset type not found"); + } + + //check if asset name is valid + if (!IsAssetNameValid(assetname, assetTx.isRoot)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Error: Invalid asset name"); + } + + if (!assetTx.isRoot) { //sub asset + const UniValue &root_name = find_value(asset, "root_name"); + if (root_name.isNull()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Error: Root asset not found (required)"); + } + std::string rootname = root_name.getValStr(); + // check if root asset exist + std::string assetId; + if (passetsCache->GetAssetId(rootname, assetId)) { + //set the root asset id + assetTx.rootId = assetId; + CAssetMetaData tmpAsset; + if (!passetsCache->GetAssetMetaData(assetId, tmpAsset)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Error: Root asset metadata not found"); + } + + if (!tmpAsset.isRoot) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Error: Invalid root asset name"); + } + + //check if we own the root asset + if (!IsMine(*pwallet, tmpAsset.ownerAddress) & ISMINE_SPENDABLE) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Error: Invalid root asset key"); + } + + //combine the root name with the asset name + std::string tmpname = tmpAsset.name + "|" + assetname; + + //check if asset already exist + std::string tmp_id; + if (passetsCache->GetAssetId(tmpname, tmp_id)) { + CAssetMetaData tmp_Asset; + if (passetsCache->GetAssetMetaData(tmp_id, tmp_Asset)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Error: Asset already exist"); + } + } + + //check on mempool if asset already exist + if (mempool.CheckForNewAssetConflict(tmpname)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Error: Asset already exist on mempool"); + } + } else { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Error: Root asset metadata not found"); + } + } + } else { + //this section can be removed later on + //on testnet set version to 1 until new fork + assetTx.nVersion = 1; + //check if asset name is valid + if (!IsAssetNameValid(assetname, false)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Error: Invalid asset name v1"); + } + + std::string assetId; + if (passetsCache->GetAssetId(assetname, assetId)) { + CAssetMetaData tmpAsset; + if (passetsCache->GetAssetMetaData(assetId, tmpAsset)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Error: Asset already exist"); + } + } + + //check on mempool if asset already exist + if (mempool.CheckForNewAssetConflict(assetname)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Error: Asset already exist on mempool"); + } + } + const UniValue &referenceHash = find_value(asset, "referenceHash"); if (!referenceHash.isNull()) { std::string ref = referenceHash.get_str(); @@ -424,23 +490,25 @@ UniValue updateasset(const JSONRPCRequest &request) { CCoinControl coinControl; - coinControl.destChange = ownerAddress; - coinControl.fRequireAllInputs = false; + if (!Params().IsRootAssetsActive(::ChainActive().Tip())) { + coinControl.destChange = ownerAddress; + coinControl.fRequireAllInputs = false; - std::vector vecOutputs; - //select only confirmed inputs, nMinDepth >= 1 - pwallet->AvailableCoins(vecOutputs, true, nullptr, 1, MAX_MONEY , MAX_MONEY, 0, 1); + std::vector vecOutputs; + //select only confirmed inputs, nMinDepth >= 1 + pwallet->AvailableCoins(vecOutputs, true, nullptr, 1, MAX_MONEY , MAX_MONEY, 0, 1); - for (const auto &out: vecOutputs) { - CTxDestination txDest; - if (ExtractDestination(out.tx->tx->vout[out.i].scriptPubKey, txDest) && txDest == ownerAddress) { - coinControl.Select(COutPoint(out.tx->tx->GetHash(), out.i)); + for (const auto &out: vecOutputs) { + CTxDestination txDest; + if (ExtractDestination(out.tx->tx->vout[out.i].scriptPubKey, txDest) && txDest == ownerAddress) { + coinControl.Select(COutPoint(out.tx->tx->GetHash(), out.i)); + } } - } - if (!coinControl.HasSelected()) { - throw JSONRPCError(RPC_INTERNAL_ERROR, - strprintf("No funds at specified address %s", EncodeDestination(ownerAddress))); + if (!coinControl.HasSelected()) { + throw JSONRPCError(RPC_INTERNAL_ERROR, + strprintf("No funds at specified address %s", EncodeDestination(ownerAddress))); + } } CTransactionRef newTx; @@ -541,23 +609,25 @@ UniValue mintasset(const JSONRPCRequest &request) { } CCoinControl coinControl; - coinControl.destChange = ownerAddress; - coinControl.fRequireAllInputs = false; + if (!Params().IsRootAssetsActive(::ChainActive().Tip())) { + coinControl.destChange = ownerAddress; + coinControl.fRequireAllInputs = false; - std::vector vecOutputs; - //select only confirmed inputs, nMinDepth >= 1 - pwallet->AvailableCoins(vecOutputs, true, nullptr, 1, MAX_MONEY , MAX_MONEY, 0, 1); + std::vector vecOutputs; + //select only confirmed inputs, nMinDepth >= 1 + pwallet->AvailableCoins(vecOutputs, true, nullptr, 1, MAX_MONEY , MAX_MONEY, 0, 1); - for (const auto &out: vecOutputs) { - CTxDestination txDest; - if (ExtractDestination(out.tx->tx->vout[out.i].scriptPubKey, txDest) && txDest == ownerAddress) { - coinControl.Select(COutPoint(out.tx->tx->GetHash(), out.i)); + for (const auto &out: vecOutputs) { + CTxDestination txDest; + if (ExtractDestination(out.tx->tx->vout[out.i].scriptPubKey, txDest) && txDest == ownerAddress) { + coinControl.Select(COutPoint(out.tx->tx->GetHash(), out.i)); + } } - } - if (!coinControl.HasSelected()) { - throw JSONRPCError(RPC_INTERNAL_ERROR, - strprintf("No funds at specified address %s", EncodeDestination(ownerAddress))); + if (!coinControl.HasSelected()) { + throw JSONRPCError(RPC_INTERNAL_ERROR, + strprintf("No funds at specified address %s", EncodeDestination(ownerAddress))); + } } diff --git a/src/test/assets_tests.cpp b/src/test/assets_tests.cpp index 7f5c68271..a4fd2b351 100644 --- a/src/test/assets_tests.cpp +++ b/src/test/assets_tests.cpp @@ -110,6 +110,7 @@ CreateNewAssetTx(const CTxMemPool& mempool, SimpleUTXOMap& utxos, const CKey& co CNewAssetTx newAsset; newAsset.name = name; + newAsset.isRoot = true; newAsset.updatable = updatable; newAsset.isUnique = is_unique; newAsset.decimalPoint = decimalPoint; @@ -161,6 +162,11 @@ CreateUpdateAssetTx(const CTxMemPool& mempool, SimpleUTXOMap& utxos, const CKey& FundTransaction(tx, utxos, GetScriptForDestination(coinbaseKey.GetPubKey().GetID()), upAsset.fee * COIN + 1 * COIN, coinbaseKey); upAsset.inputsHash = CalcTxInputsHash(tx); + + std::string m = upAsset.MakeSignString(passetsCache.get()); + // lets prove we own the asset + BOOST_ASSERT(CMessageSigner::SignMessage(m, upAsset.vchSig, coinbaseKey)); + SetTxPayload(tx, upAsset); BOOST_ASSERT(SignTransaction(mempool, tx, coinbaseKey)); @@ -201,6 +207,11 @@ CreateMintAssetTx(const CTxMemPool& mempool, SimpleUTXOMap& utxos, const CKey& c FundTransaction(tx, utxos, GetScriptForDestination(coinbaseKey.GetPubKey().GetID()), mint.fee * COIN + 1 * COIN, coinbaseKey); mint.inputsHash = CalcTxInputsHash(tx); + + std::string m = mint.MakeSignString(passetsCache.get()); + // lets prove we own the asset + BOOST_ASSERT(CMessageSigner::SignMessage(m, mint.vchSig, coinbaseKey)); + SetTxPayload(tx, mint); BOOST_ASSERT(SignTransaction(mempool, tx, coinbaseKey)); @@ -227,7 +238,7 @@ BOOST_FIXTURE_TEST_CASE(assets_creation, TestChainDIP3BeforeActivationSetup) auto utxos = BuildSimpleUtxoMap(m_coinbase_txns); - auto tx = CreateNewAssetTx(*m_node.mempool, utxos, coinbaseKey, "Test Asset", true, false, 0, 8, 1000); + auto tx = CreateNewAssetTx(*m_node.mempool, utxos, coinbaseKey, "TEST_ASSET", true, false, 0, 8, 1000); std::vector txns = {tx}; int nHeight = ::ChainActive().Height(); @@ -249,7 +260,7 @@ BOOST_FIXTURE_TEST_CASE(assets_creation, TestChainDIP3BeforeActivationSetup) BOOST_ASSERT(::ChainActive().Height() == nHeight + 1); //invalid distribution type - tx = CreateNewAssetTx(*m_node.mempool, utxos, coinbaseKey, "Test Asset", true, false, 5, 8, 1000); + tx = CreateNewAssetTx(*m_node.mempool, utxos, coinbaseKey, "TEST_ASSET", true, false, 5, 8, 1000); txns = {tx}; block = std::make_shared(CreateBlock(txns, coinbaseKey)); //block should be rejected @@ -258,7 +269,7 @@ BOOST_FIXTURE_TEST_CASE(assets_creation, TestChainDIP3BeforeActivationSetup) BOOST_ASSERT(::ChainActive().Height() == nHeight + 1); //invalid decimalPoint - tx = CreateNewAssetTx(*m_node.mempool, utxos, coinbaseKey, "Test Asset", true, false, 0, 9, 1000); + tx = CreateNewAssetTx(*m_node.mempool, utxos, coinbaseKey, "TEST_ASSET", true, false, 0, 9, 1000); txns = {tx}; block = std::make_shared(CreateBlock(txns, coinbaseKey)); //block should be rejected @@ -278,7 +289,7 @@ BOOST_FIXTURE_TEST_CASE(assets_update, TestChainDIP3BeforeActivationSetup) auto utxos = BuildSimpleUtxoMap(m_coinbase_txns); - auto tx = CreateNewAssetTx(*m_node.mempool, utxos, coinbaseKey, "Test Asset", true, false, 0, 8, 1000); + auto tx = CreateNewAssetTx(*m_node.mempool, utxos, coinbaseKey, "TEST_ASSET", true, false, 0, 8, 1000); std::vector txns = {tx}; int nHeight = ::ChainActive().Height(); @@ -332,7 +343,7 @@ BOOST_FIXTURE_TEST_CASE(assets_mint, TestChainDIP3BeforeActivationSetup) auto utxos = BuildSimpleUtxoMap(m_coinbase_txns); - auto tx = CreateNewAssetTx(*m_node.mempool, utxos, coinbaseKey, "Test Asset", true, false, 0, 8, 1000); + auto tx = CreateNewAssetTx(*m_node.mempool, utxos, coinbaseKey, "TEST_ASSET", true, false, 0, 8, 1000); std::vector txns = {tx}; int nHeight = ::ChainActive().Height(); @@ -407,7 +418,7 @@ BOOST_FIXTURE_TEST_CASE(assets_invalid_cases, TestChainDIP3BeforeActivationSetup auto utxos = BuildSimpleUtxoMap(m_coinbase_txns); //create a asset - auto tx = CreateNewAssetTx(*m_node.mempool, utxos, coinbaseKey, "Test Asset", false, false, 0, 2, 100); + auto tx = CreateNewAssetTx(*m_node.mempool, utxos, coinbaseKey, "TEST_ASSET", false, false, 0, 2, 100); std::vector txns = {tx}; int nHeight = ::ChainActive().Height(); @@ -531,7 +542,7 @@ BOOST_FIXTURE_TEST_CASE(assets_invalid_cases, TestChainDIP3BeforeActivationSetup } //create a unique asset - tx = CreateNewAssetTx(*m_node.mempool, utxos, coinbaseKey, "Unique Asset", false, true, 0, 0, 10); + tx = CreateNewAssetTx(*m_node.mempool, utxos, coinbaseKey, "UNIQUE_ASSET", false, true, 0, 0, 10); txns = {tx}; nHeight = ::ChainActive().Height(); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 1757682b4..6b1ae235b 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1230,8 +1230,17 @@ bool CWallet::AddToWallet(const CWalletTx &wtxIn, bool fFlushOnClose, bool resca if (wtxIn.tx->nType == TRANSACTION_NEW_ASSET && fInsertedNew){ CNewAssetTx assetTx; - if (GetTxPayload(wtxIn.tx->vExtraPayload, assetTx)) + if (GetTxPayload(wtxIn.tx->vExtraPayload, assetTx)) { + if (assetTx.nVersion == 2 && !assetTx.isRoot) { + CAssetMetaData rootAsset; + if (passetsCache->GetAssetMetaData(assetTx.rootId, rootAsset)) + mapAsset.emplace(hash, std::make_pair(rootAsset.name + "|" +assetTx.name, assetTx.ownerAddress)); + else + mapAsset.emplace(hash, std::make_pair("cache error|" +assetTx.name, assetTx.ownerAddress)); + } else { mapAsset.emplace(hash, std::make_pair(assetTx.name, assetTx.ownerAddress)); + } + } } //// debug print @@ -1304,8 +1313,17 @@ void CWallet::LoadToWallet(CWalletTx &wtxIn) { } if (wtx.tx->nType == TRANSACTION_NEW_ASSET && !wtx.isAbandoned()){ CNewAssetTx assetTx; - if (GetTxPayload(wtx.tx->vExtraPayload, assetTx)) + if (GetTxPayload(wtxIn.tx->vExtraPayload, assetTx)) { + if (assetTx.nVersion == 2 && !assetTx.isRoot) { + CAssetMetaData rootAsset; + if (passetsCache->GetAssetMetaData(assetTx.rootId, rootAsset)) + mapAsset.emplace(hash, std::make_pair(rootAsset.name + "|" +assetTx.name, assetTx.ownerAddress)); + else + mapAsset.emplace(hash, std::make_pair("cache error|" +assetTx.name, assetTx.ownerAddress)); + } else { mapAsset.emplace(hash, std::make_pair(assetTx.name, assetTx.ownerAddress)); + } + } } } @@ -4254,19 +4272,19 @@ bool CWallet::CreateTransaction(const std::vector &vecSend, CTransa txNew.nVersion = 3; txNew.nType = TRANSACTION_NEW_ASSET; atx = *newAsset; - atx.nVersion = CNewAssetTx::CURRENT_VERSION; + atx.nVersion = Params().IsRootAssetsActive(::ChainActive().Tip()) ? 2 : 1; specialFees = getAssetsFeesCoin(); } else if (mint) { txNew.nVersion = 3; txNew.nType = TRANSACTION_MINT_ASSET; mtx = *mint; - mtx.nVersion = CMintAssetTx::CURRENT_VERSION; + mtx.nVersion = Params().IsRootAssetsActive(::ChainActive().Tip()) ? 2 : 1; specialFees = getAssetsFeesCoin(); } else if (updateAsset){ txNew.nVersion = 3; txNew.nType = TRANSACTION_UPDATE_ASSET; uptx = *updateAsset; - uptx.nVersion = CUpdateAssetTx::CURRENT_VERSION; + uptx.nVersion = Params().IsRootAssetsActive(::ChainActive().Tip()) ? 2 : 1; specialFees = getAssetsFeesCoin(); } // Discourage fee sniping. @@ -4769,12 +4787,78 @@ bool CWallet::CreateTransaction(const std::vector &vecSend, CTransa SetTxPayload(txNew, ftx); } else if (newAsset) { UpdateSpecialTxInputsHash(txNew, atx); + if (atx.nVersion == 2 && !atx.isRoot) { + atx.vchSig.clear(); + + CAssetMetaData assetData; + if (passetsCache->GetAssetMetaData(atx.rootId, assetData)) { + std::string m = atx.MakeSignString(passetsCache.get()); + // lets prove we own the root asset + CKey key; + if (!CCryptoKeyStore::GetKey(assetData.ownerAddress, key)) { + strFailReason = _("Root asset key not in wallet"); + return false; + } + if(!CMessageSigner::SignMessage(m, atx.vchSig, key)) + { + strFailReason = _("Failed to sign special tx"); + return false; + } + } else { + strFailReason = _("Failed to get root metadata"); + return false; + } + } SetTxPayload(txNew, atx); } else if (mint) { UpdateSpecialTxInputsHash(txNew, mtx); + if (mtx.nVersion == 2) { + mtx.vchSig.clear(); + + CAssetMetaData assetData; + if (passetsCache->GetAssetMetaData(mtx.assetId, assetData)) { + std::string m = mtx.MakeSignString(passetsCache.get()); + // lets prove we own the asset + CKey key; + if (!CCryptoKeyStore::GetKey(assetData.ownerAddress, key)) { + strFailReason = _("Asset owner key not in wallet"); + return false; + } + if(!CMessageSigner::SignMessage(m, mtx.vchSig, key)) + { + strFailReason = _("Failed to sign special tx"); + return false; + } + } else { + strFailReason = _("Failed to get root metadata"); + return false; + } + } SetTxPayload(txNew, mtx); } else if (updateAsset) { UpdateSpecialTxInputsHash(txNew, uptx); + if (uptx.nVersion == 2) { + uptx.vchSig.clear(); + + CAssetMetaData assetData; + if (passetsCache->GetAssetMetaData(uptx.assetId, assetData)) { + std::string m = uptx.MakeSignString(passetsCache.get()); + // lets prove we own the asset + CKey key; + if (!CCryptoKeyStore::GetKey(assetData.ownerAddress, key)) { + strFailReason = _("Asset owner key not in wallet"); + return false; + } + if(!CMessageSigner::SignMessage(m, uptx.vchSig, key)) + { + strFailReason = _("Failed to sign special tx"); + return false; + } + } else { + strFailReason = _("Failed to get root metadata"); + return false; + } + } SetTxPayload(txNew, uptx); }