From a04abd321c40c2e3ffcb0da56970b9f2d182ae79 Mon Sep 17 00:00:00 2001 From: ShengguangXiao Date: Tue, 29 Sep 2020 12:46:14 +0800 Subject: [PATCH 1/3] Remove destroyToken API --- src/masternodes/mn_checks.cpp | 28 ------- src/masternodes/mn_checks.h | 2 - src/masternodes/mn_rpc.cpp | 101 ------------------------ src/masternodes/tokens.cpp | 38 --------- src/masternodes/tokens.h | 2 - src/rpc/client.cpp | 1 - src/test/storage_tests.cpp | 19 ----- test/functional/feature_tokens_basic.py | 38 --------- 8 files changed, 229 deletions(-) diff --git a/src/masternodes/mn_checks.cpp b/src/masternodes/mn_checks.cpp index 3e58883aba..a4c9252022 100644 --- a/src/masternodes/mn_checks.cpp +++ b/src/masternodes/mn_checks.cpp @@ -141,10 +141,6 @@ Res ApplyCustomTx(CCustomCSView & base_mnview, CCoinsViewCache const & coins, CT if(height < consensusParams.AMKHeight) { return Res::Err("Token tx before AMK height"); } res = ApplyCreateTokenTx(mnview, coins, tx, height, metadata); break; - case CustomTxType::DestroyToken: - if(height < consensusParams.AMKHeight) { return Res::Err("Token tx before AMK height"); } - res = ApplyDestroyTokenTx(mnview, coins, tx, height, metadata); - break; case CustomTxType::UpdateToken: if(height < consensusParams.AMKHeight) { return Res::Err("Token tx before AMK height"); } res = ApplyUpdateTokenTx(mnview, coins, tx, height, metadata); @@ -305,30 +301,6 @@ Res ApplyCreateTokenTx(CCustomCSView & mnview, CCoinsViewCache const & coins, CT return Res::Ok(base); } -Res ApplyDestroyTokenTx(CCustomCSView & mnview, CCoinsViewCache const & coins, CTransaction const & tx, uint32_t height, std::vector const & metadata) -{ - const std::string base{"Token destruction"}; - - if (metadata.size() != sizeof(uint256)) { - return Res::Err("%s: metadata must contain 32 bytes", base); - } - uint256 tokenTx(metadata); - auto pair = mnview.GetTokenByCreationTx(tokenTx); - if (!pair) { - return Res::Err("%s: token with creationTx %s does not exist", base, tokenTx.ToString()); - } - CTokenImplementation const & token = pair->second; - if (!HasCollateralAuth(tx, coins, token.creationTx)) { - return Res::Err("%s: %s", base, "tx must have at least one input from token owner"); - } - - auto res = mnview.DestroyToken(token.creationTx, tx.GetHash(), height); - if (!res.ok) { - return Res::Err("%s %s: %s", base, token.symbol, res.msg); - } - return Res::Ok(base); -} - Res ApplyUpdateTokenTx(CCustomCSView & mnview, CCoinsViewCache const & coins, CTransaction const & tx, uint32_t height, std::vector const & metadata) { const std::string base{"Token update"}; diff --git a/src/masternodes/mn_checks.h b/src/masternodes/mn_checks.h index c28bdf1426..f10acd2a76 100644 --- a/src/masternodes/mn_checks.h +++ b/src/masternodes/mn_checks.h @@ -35,7 +35,6 @@ enum class CustomTxType : unsigned char // custom tokens: CreateToken = 'T', MintToken = 'M', - DestroyToken = 'D', UpdateToken = 'N', // dex orders - just not to overlap in future // CreateOrder = 'O', @@ -83,7 +82,6 @@ Res ApplyCreateMasternodeTx(CCustomCSView & mnview, CTransaction const & tx, uin Res ApplyResignMasternodeTx(CCustomCSView & mnview, CCoinsViewCache const & coins, CTransaction const & tx, uint32_t height, std::vector const & metadata); Res ApplyCreateTokenTx(CCustomCSView & mnview, CCoinsViewCache const & coins, CTransaction const & tx, uint32_t height, std::vector const & metadata); -Res ApplyDestroyTokenTx(CCustomCSView & mnview, CCoinsViewCache const & coins, CTransaction const & tx, uint32_t height, std::vector const & metadata); Res ApplyUpdateTokenTx(CCustomCSView & mnview, CCoinsViewCache const & coins, CTransaction const & tx, uint32_t height, std::vector const & metadata); Res ApplyMintTokenTx(CCustomCSView & mnview, CCoinsViewCache const & coins, CTransaction const & tx, std::vector const & metadata); diff --git a/src/masternodes/mn_rpc.cpp b/src/masternodes/mn_rpc.cpp index e4a751e131..d5ff2e0b57 100644 --- a/src/masternodes/mn_rpc.cpp +++ b/src/masternodes/mn_rpc.cpp @@ -671,106 +671,6 @@ UniValue createtoken(const JSONRPCRequest& request) { return signsend(rawTx, request, pwallet)->GetHash().GetHex(); } -UniValue destroytoken(const JSONRPCRequest& request) { - CWallet* const pwallet = GetWallet(request); - - RPCHelpMan{"destroytoken", - "\nCreates (and submits to local node and network) a transaction destroying your token. Collateral will be unlocked.\n" - "The second optional argument (may be empty array) is an array of specific UTXOs to spend. One of UTXO's must belong to the token's owner (collateral) address" + - HelpRequiringPassphrase(pwallet) + "\n", - { - {"token", RPCArg::Type::STR, RPCArg::Optional::NO, "The tokens's symbol, id or creation tx"}, - {"inputs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, - "A json array of json objects. Provide it if you want to spent specific UTXOs", - { - {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", - { - {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"}, - {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"}, - }, - }, - }, - }, - }, - RPCResult{ - "\"hash\" (string) The hex-encoded hash of broadcasted transaction\n" - }, - RPCExamples{ - HelpExampleCli("destroytoken", "\"symbol\"") - + HelpExampleCli("destroytoken", "\"symbol\" \"[{\\\"txid\\\":\\\"id\\\",\\\"vout\\\":0}]\"") - + HelpExampleRpc("destroytoken", "\"symbol\" \"[{\\\"txid\\\":\\\"id\\\",\\\"vout\\\":0}]\"") - }, - }.Check(request); - - if (pwallet->chain().isInitialBlockDownload()) { - throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, - "Cannot destroy token while still in Initial Block Download"); - } - pwallet->BlockUntilSyncedToCurrentChain(); - - if (::ChainActive().Tip()->height < Params().GetConsensus().AMKHeight) { - throw JSONRPCError(RPC_TRANSACTION_REJECTED, "No tokenization transaction before block height " + std::to_string(Params().GetConsensus().AMKHeight)); - } - - RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR}, true); - - std::string const tokenStr = trim_ws(request.params[0].getValStr()); - UniValue txInputs = request.params[1]; - if (txInputs.isNull()) - { - txInputs.setArray(); - } - CTxDestination ownerDest; - uint256 creationTx{}; - { - LOCK(cs_main); - DCT_ID id; - auto token = pcustomcsview->GetTokenGuessId(tokenStr, id); - if (!token) { - throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Token %s does not exist!", tokenStr)); - } - if (id < CTokensView::DCT_ID_START) { - throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Token %s is a 'stable coin'", tokenStr)); - } - LOCK(pwallet->cs_wallet); - auto tokenImpl = static_cast(*token); - auto wtx = pwallet->GetWalletTx(tokenImpl.creationTx); - if (!wtx || !ExtractDestination(wtx->tx->vout[1].scriptPubKey, ownerDest)) { - throw JSONRPCError(RPC_INVALID_PARAMETER, - strprintf("Can't extract destination for token's %s collateral", tokenStr)); - } - creationTx = tokenImpl.creationTx; - } - - const auto txVersion = GetTransactionVersion(::ChainActive().Height()); - CMutableTransaction rawTx(txVersion); - - rawTx.vin = GetAuthInputs(pwallet, ownerDest, txInputs.get_array()); - - CDataStream metadata(DfTxMarker, SER_NETWORK, PROTOCOL_VERSION); - metadata << static_cast(CustomTxType::DestroyToken) - << creationTx; - - CScript scriptMeta; - scriptMeta << OP_RETURN << ToByteVector(metadata); - - rawTx.vout.push_back(CTxOut(0, scriptMeta)); - - rawTx = fund(rawTx, request, pwallet); - - // check execution - { - LOCK(cs_main); - CCustomCSView mnview_dummy(*pcustomcsview); // don't write into actual DB - const auto res = ApplyDestroyTokenTx(mnview_dummy, ::ChainstateActive().CoinsTip(), CTransaction(rawTx), ::ChainActive().Tip()->height + 1, - ToByteVector(CDataStream{SER_NETWORK, PROTOCOL_VERSION, creationTx})); - if (!res.ok) { - throw JSONRPCError(RPC_INVALID_REQUEST, "Execution test failed:\n" + res.msg); - } - } - return signsend(rawTx, request, pwallet)->GetHash().GetHex(); -} - UniValue updatetoken(const JSONRPCRequest& request) { CWallet* const pwallet = GetWallet(request); @@ -1694,7 +1594,6 @@ static const CRPCCommand commands[] = {"masternodes", "getmasternode", &getmasternode, {"mn_id"}}, {"masternodes", "listcriminalproofs", &listcriminalproofs, {}}, {"tokens", "createtoken", &createtoken, {"metadata", "inputs"}}, - {"tokens", "destroytoken", &destroytoken, {"token", "inputs"}}, {"tokens", "updatetoken", &updatetoken, {"metadata", "inputs"}}, {"tokens", "listtokens", &listtokens, {"pagination", "verbose"}}, {"tokens", "gettoken", &gettoken, {"key" }}, diff --git a/src/masternodes/tokens.cpp b/src/masternodes/tokens.cpp index b3870f14e4..7d283a42f0 100644 --- a/src/masternodes/tokens.cpp +++ b/src/masternodes/tokens.cpp @@ -197,44 +197,6 @@ Res CTokensView::UpdateToken(const uint256 &tokenTx) return Res::Ok(); } -Res CTokensView::DestroyToken(uint256 const & tokenTx, const uint256 & txid, int height) -{ - auto pair = GetTokenByCreationTx(tokenTx); - if (!pair) { - return Res::Err("token with creationTx %s does not exist!", tokenTx.ToString()); - } - /// @todo token: check for token supply / utxos - - CTokenImpl & tokenImpl = pair->second; - if (tokenImpl.destructionTx != uint256{}) { - return Res::Err("token with creationTx %s was already destroyed by tx %s!", tokenTx.ToString(), tokenImpl.destructionTx.ToString()); - } - - tokenImpl.destructionTx = txid; - tokenImpl.destructionHeight = height; - WriteBy(WrapVarInt(pair->first.v), tokenImpl); - return Res::Ok(); -} - -bool CTokensView::RevertDestroyToken(uint256 const & tokenTx, const uint256 & txid) -{ - auto pair = GetTokenByCreationTx(tokenTx); - if (!pair) { - LogPrintf("Token destruction revert error: token with creationTx %s does not exist!\n", tokenTx.ToString()); - return false; - } - CTokenImpl & tokenImpl = pair->second; - if (tokenImpl.destructionTx != txid) { - LogPrintf("Token destruction revert error: token with creationTx %s was not destroyed by tx %s!\n", tokenTx.ToString(), txid.ToString()); - return false; - } - - tokenImpl.destructionTx = uint256{}; - tokenImpl.destructionHeight = -1; - WriteBy(WrapVarInt(pair->first.v), tokenImpl); - return true; -} - DCT_ID CTokensView::IncrementLastDctId() { DCT_ID result{DCT_ID_START}; diff --git a/src/masternodes/tokens.h b/src/masternodes/tokens.h index 113caa4665..aeb0037720 100644 --- a/src/masternodes/tokens.h +++ b/src/masternodes/tokens.h @@ -121,8 +121,6 @@ class CTokensView : public virtual CStorageView Res CreateToken(CTokenImpl const & token); bool RevertCreateToken(uint256 const & txid); Res UpdateToken(uint256 const & tokenTx); - Res DestroyToken(uint256 const & tokenTx, uint256 const & txid, int height); - bool RevertDestroyToken(uint256 const & tokenTx, uint256 const & txid); // tags struct ID { static const unsigned char prefix; }; diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 0105d33b4b..20919e3b2d 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -181,7 +181,6 @@ static const CRPCConvertParam vRPCConvertParams[] = { "createtoken", 1, "inputs"}, { "updatetoken", 0, "metadata"}, { "updatetoken", 1, "inputs"}, - { "destroytoken", 1, "inputs" }, { "listtokens", 0, "pagination" }, { "listtokens", 1, "verbose" }, { "minttokens", 1, "inputs"}, diff --git a/src/test/storage_tests.cpp b/src/test/storage_tests.cpp index 4cb82830de..d1e23f988a 100644 --- a/src/test/storage_tests.cpp +++ b/src/test/storage_tests.cpp @@ -250,25 +250,6 @@ BOOST_AUTO_TEST_CASE(tokens) BOOST_REQUIRE(token->symbol == "DCT3"); } - // destroy token -// BOOST_REQUIRE(pcustomcsview->DestroyToken(0, uint256S("0x2222"), 999) == false); // stable coin! - BOOST_REQUIRE(pcustomcsview->DestroyToken(uint256S("0x3333"), uint256S("0xaaaa"), 999).ok == false); // nonexist - BOOST_REQUIRE(pcustomcsview->DestroyToken(uint256S("0x2222"), uint256S("0xaaaa"), 999).ok); // ok - BOOST_REQUIRE(pcustomcsview->DestroyToken(uint256S("0x2222"), uint256S("0xbbbb"), 999).ok == false); // already destroyed - { // search by id - auto token = pcustomcsview->GetToken(DCT_ID{129}); - BOOST_REQUIRE(token); - auto tokenImpl = static_cast(*token); - BOOST_REQUIRE(tokenImpl.destructionHeight == 999); - BOOST_REQUIRE(tokenImpl.destructionTx == uint256S("0xaaaa")); - } - - // revert destroy token -// BOOST_REQUIRE(pcustomcsview->RevertDestroyToken(0, uint256S("0x2222")) == false); // stable coin! - BOOST_REQUIRE(pcustomcsview->RevertDestroyToken(uint256S("0x3333"), uint256S("0xaaaa")) == false); // nonexist - BOOST_REQUIRE(pcustomcsview->RevertDestroyToken(uint256S("0x1111"), uint256S("0xaaaa")) == false); // not destroyed, active - BOOST_REQUIRE(pcustomcsview->RevertDestroyToken(uint256S("0x2222"), uint256S("0xbbbb")) == false); // destroyed, but wrong tx for revert - BOOST_REQUIRE(pcustomcsview->RevertDestroyToken(uint256S("0x2222"), uint256S("0xaaaa"))); // ok! { // search by id auto token = pcustomcsview->GetToken(DCT_ID{129}); BOOST_REQUIRE(token); diff --git a/test/functional/feature_tokens_basic.py b/test/functional/feature_tokens_basic.py index 0dcd3e5e7c..9300df7317 100755 --- a/test/functional/feature_tokens_basic.py +++ b/test/functional/feature_tokens_basic.py @@ -124,44 +124,6 @@ def run_test(self): errorString = e.error['message'] assert("collateral-locked," in errorString) - # Create new GOLD token - newGoldTx = self.nodes[0].createtoken({ - "symbol": "GOLD", - "name": "shiny gold", - "collateralAddress": collateral0 - }, []) - self.nodes[0].generate(1) - - # Get token by SYMBOL#ID - t129 = self.nodes[0].gettoken("GOLD#129") - assert_equal(t129['129']['symbol'], "GOLD") - assert_equal(self.nodes[0].gettoken("GOLD#129"), t129) - - # RESIGNING: - #======================== - # Try to resign w/o auth (no money on auth/collateral address) - try: - self.nodes[0].destroytoken("GOLD#128", []) - except JSONRPCException as e: - errorString = e.error['message'] - assert("Can't find any UTXO's" in errorString) - - # Funding auth address for resigning - fundingTx = self.nodes[0].sendtoaddress(collateral0, 1) - self.nodes[0].generate(1) - - print ("Destroy token...") - destroyTx = self.nodes[0].destroytoken("GOLD#128", []) - self.nodes[0].generate(1) - assert_equal(self.nodes[0].listtokens()['128']['destructionTx'], destroyTx) - - # Try to mint destroyed token ('minting' is not the task of current test, but let's check it here) - try: - self.nodes[0].minttokens("100@GOLD#128", []) - except JSONRPCException as e: - errorString = e.error['message'] - assert("already destroyed" in errorString) - # Spend unlocked collateral # This checks two cases at once: # 1) Finally, we should not fail on accept to mempool From e01dd52cb9e65c9ce81c4797b07422b9a4366b68 Mon Sep 17 00:00:00 2001 From: ShengguangXiao Date: Tue, 29 Sep 2020 12:56:28 +0800 Subject: [PATCH 2/3] Update create token fee and collateral amount --- src/chainparams.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 5838d299d7..8f21e0f366 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -152,8 +152,8 @@ class CMainParams : public CChainParams { consensus.mn.anchoringFrequency = 15; consensus.mn.anchoringLag = 15; - consensus.token.creationFee = 1 * COIN; - consensus.token.collateralAmount = 1000 * COIN; + consensus.token.creationFee = 100 * COIN; + consensus.token.collateralAmount = 1 * COIN; consensus.spv.creationFee = 100000; // should be > bitcoin's dust consensus.spv.anchorSubsidy = 0 * COIN; @@ -307,8 +307,8 @@ class CTestNetParams : public CChainParams { consensus.mn.anchoringFrequency = 15; consensus.mn.anchoringLag = 15; - consensus.token.creationFee = 1 * COIN; - consensus.token.collateralAmount = 100 * COIN; + consensus.token.creationFee = 100 * COIN; + consensus.token.collateralAmount = 1 * COIN; consensus.spv.creationFee = 100000; // should be > bitcoin's dust consensus.spv.wallet_xpub = "tpubD9RkyYW1ixvD9vXVpYB1ka8rPZJaEQoKraYN7YnxbBxxsRYEMZgRTDRGEo1MzQd7r5KWxH8eRaQDVDaDuT4GnWgGd17xbk6An6JMdN4dwsY"; From a3764c732628a7f21e1353dc7e2c95187932a566 Mon Sep 17 00:00:00 2001 From: ShengguangXiao Date: Tue, 29 Sep 2020 13:06:53 +0800 Subject: [PATCH 3/3] Fix lint error --- test/functional/feature_tokens_basic.py | 40 +++++++++++++++++-------- 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/test/functional/feature_tokens_basic.py b/test/functional/feature_tokens_basic.py index 9300df7317..ff10beec74 100755 --- a/test/functional/feature_tokens_basic.py +++ b/test/functional/feature_tokens_basic.py @@ -124,6 +124,32 @@ def run_test(self): errorString = e.error['message'] assert("collateral-locked," in errorString) + # Create new GOLD token + newGoldTx = self.nodes[0].createtoken({ + "symbol": "GOLD", + "name": "shiny gold", + "collateralAddress": collateral0 + }, []) + self.nodes[0].generate(1) + + # Get token by SYMBOL#ID + t129 = self.nodes[0].gettoken("GOLD#129") + assert_equal(t129['129']['symbol'], "GOLD") + assert_equal(self.nodes[0].gettoken("GOLD#129"), t129) + + # RESIGNING: + #======================== + # Try to resign w/o auth (no money on auth/collateral address) + try: + self.nodes[0].destroytoken("GOLD#128", []) + except JSONRPCException as e: + errorString = e.error['message'] + assert("Can't find any UTXO's" in errorString) + + # Funding auth address for resigning + fundingTx = self.nodes[0].sendtoaddress(collateral0, 1) + self.nodes[0].generate(1) + # Spend unlocked collateral # This checks two cases at once: # 1) Finally, we should not fail on accept to mempool @@ -132,20 +158,10 @@ def run_test(self): # Don't mine here, check mempool after reorg! # self.nodes[0].generate(1) - - # REVERTING: - #======================== - print ("Reverting...") - # Revert token destruction! - self.start_node(1) - self.nodes[1].generate(5) - # Check that collateral spending tx is still in the mempool - assert_equal(sendedTxHash, self.nodes[0].getrawmempool()[0]) - connect_nodes_bi(self.nodes, 0, 1) self.sync_blocks(self.nodes[0:2]) - assert_equal(sorted(self.nodes[0].getrawmempool()), sorted([fundingTx, destroyTx, newGoldTx])) + assert_equal(sorted(self.nodes[0].getrawmempool()), sorted([fundingTx, newGoldTx])) assert_equal(self.nodes[0].listtokens()['128']['destructionHeight'], -1) assert_equal(self.nodes[0].listtokens()['128']['destructionTx'], '0000000000000000000000000000000000000000000000000000000000000000') @@ -156,7 +172,7 @@ def run_test(self): connect_nodes_bi(self.nodes, 0, 2) self.sync_blocks(self.nodes[0:3]) assert_equal(len(self.nodes[0].listtokens()), 1) - assert_equal(sorted(self.nodes[0].getrawmempool()), sorted([createTokenTx, fundingTx, destroyTx, newGoldTx])) + assert_equal(sorted(self.nodes[0].getrawmempool()), sorted([createTokenTx, fundingTx, newGoldTx])) if __name__ == '__main__': TokensBasicTest ().main ()