diff --git a/src/Makefile.am b/src/Makefile.am index 7d380b6cf87..dd39d3d3c4d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -397,6 +397,7 @@ libdefi_server_a_SOURCES = \ masternodes/accountshistory.cpp \ masternodes/anchors.cpp \ masternodes/auctionhistory.cpp \ + masternodes/evm.cpp \ masternodes/govvariables/attributes.cpp \ masternodes/govvariables/icx_takerfee_per_btc.cpp \ masternodes/govvariables/loan_daily_reward.cpp \ diff --git a/src/masternodes/errors.h b/src/masternodes/errors.h index e43080a4d42..6595d12d8d4 100644 --- a/src/masternodes/errors.h +++ b/src/masternodes/errors.h @@ -441,6 +441,10 @@ class DeFiErrors { static Res TransferDomainInvalidDestinationDomain() { return Res::Err("Invalid domain set for \"dst\" argument"); } + + static Res DatabaseRWFailure(const std::string key) { + return Res::Err("DB r/w failure: %s", key); + } }; #endif // DEFI_MASTERNODES_ERRORS_H diff --git a/src/masternodes/evm.cpp b/src/masternodes/evm.cpp new file mode 100644 index 00000000000..11950e358be --- /dev/null +++ b/src/masternodes/evm.cpp @@ -0,0 +1,44 @@ +#include +#include +#include +#include + +Res CVMDomainMapView::SetVMDomainMapBlockHash(uint8_t type, uint256 blockHashKey, uint256 blockHash) +{ + return WriteBy(std::pair(type, blockHashKey), blockHash) ? Res::Ok() : DeFiErrors::DatabaseRWFailure(blockHashKey.GetHex()); +} + +ResVal CVMDomainMapView::GetVMDomainMapBlockHash(uint8_t type, uint256 blockHashKey) const +{ + uint256 blockHash; + if (ReadBy(std::pair(type, blockHashKey), blockHash)) + return ResVal(blockHash, Res::Ok()); + return DeFiErrors::DatabaseRWFailure(blockHashKey.GetHex()); +} + +Res CVMDomainMapView::SetVMDomainMapTxHash(uint8_t type, uint256 txHashKey, uint256 txHash) +{ + return WriteBy(std::pair(type, txHashKey), txHash) ? Res::Ok() : DeFiErrors::DatabaseRWFailure(txHashKey.GetHex()); +} + +ResVal CVMDomainMapView::GetVMDomainMapTxHash(uint8_t type, uint256 txHashKey) const +{ + uint256 txHash; + if (ReadBy(std::pair(type, txHashKey), txHash)) + return ResVal(txHash, Res::Ok()); + return DeFiErrors::DatabaseRWFailure(txHashKey.GetHex()); +} + +void CVMDomainMapView::ForEachVMDomainMapBlockIndexes(std::function &, const uint256 &)> callback) { + ForEach, uint256>( + [&callback](const std::pair &key, uint256 val) { + return callback(key, val); + }); +} + +void CVMDomainMapView::ForEachVMDomainMapTxIndexes(std::function &, const uint256 &)> callback) { + ForEach, uint256>( + [&callback](const std::pair &key, uint256 val) { + return callback(key, val); + }); +} \ No newline at end of file diff --git a/src/masternodes/evm.h b/src/masternodes/evm.h index cd9dc4da6d9..fcdf2c94138 100644 --- a/src/masternodes/evm.h +++ b/src/masternodes/evm.h @@ -26,4 +26,28 @@ struct CEvmTxMessage { } }; +enum VMDomainMapType : uint8_t { + DVMToEVM = 0x01, + EVMToDVM = 0x02, +}; + +class CVMDomainMapView : public virtual CStorageView { +public: + Res SetVMDomainMapBlockHash(uint8_t type, uint256 blockHashKey, uint256 blockHash); + ResVal GetVMDomainMapBlockHash(uint8_t type, uint256 blockHashKey) const; + void ForEachVMDomainMapBlockIndexes(std::function &, const uint256 &)> callback); + + Res SetVMDomainMapTxHash(uint8_t type, uint256 txHashKey, uint256 txHash); + ResVal GetVMDomainMapTxHash(uint8_t type, uint256 txHashKey) const; + void ForEachVMDomainMapTxIndexes(std::function &, const uint256 &)> callback); + + struct VMDomainBlockHash { + static constexpr uint8_t prefix() { return 'N'; } + }; + + struct VMDomainTxHash { + static constexpr uint8_t prefix() { return 'e'; } + }; +}; + #endif // DEFI_MASTERNODES_EVM_H diff --git a/src/masternodes/masternodes.h b/src/masternodes/masternodes.h index 62a22962043..f0c8ce80238 100644 --- a/src/masternodes/masternodes.h +++ b/src/masternodes/masternodes.h @@ -445,7 +445,8 @@ class CCustomCSView : public CMasternodesView, public CLoanView, public CVaultView, public CSettingsView, - public CProposalView { + public CProposalView, + public CVMDomainMapView { // clang-format off void CheckPrefixes() { @@ -477,7 +478,8 @@ class CCustomCSView : public CMasternodesView, LoanInterestV3ByVault, CVaultView :: VaultKey, OwnerVaultKey, CollateralKey, AuctionBatchKey, AuctionHeightKey, AuctionBidKey, CSettingsView :: KVSettings, - CProposalView :: ByType, ByCycle, ByMnVote, ByStatus + CProposalView :: ByType, ByCycle, ByMnVote, ByStatus, + CVMDomainMapView :: VMDomainBlockHash, VMDomainTxHash >(); } // clang-format on diff --git a/src/masternodes/mn_checks.cpp b/src/masternodes/mn_checks.cpp index 43c21f250ff..cbb2302377e 100644 --- a/src/masternodes/mn_checks.cpp +++ b/src/masternodes/mn_checks.cpp @@ -3875,6 +3875,12 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor { gasUsed = hashAndGas.used_gas; + std::vector evmTxHashBytes; + sha3(obj.evmTx, evmTxHashBytes); + auto txHash = tx.GetHash(); + auto evmTxHash = uint256(evmTxHashBytes); + mnview.SetVMDomainMapTxHash(VMDomainMapType::DVMToEVM, txHash, evmTxHash); + mnview.SetVMDomainMapTxHash(VMDomainMapType::EVMToDVM, evmTxHash, txHash); return Res::Ok(); } diff --git a/src/masternodes/rpc_evm.cpp b/src/masternodes/rpc_evm.cpp index 3823d215851..9b9938bf6d7 100644 --- a/src/masternodes/rpc_evm.cpp +++ b/src/masternodes/rpc_evm.cpp @@ -4,6 +4,24 @@ #include #include +enum class VMDomainRPCMapType { + Auto, + AddressDVMToEVM, + AddressEVMToDVM, + TxHashDVMToEVM, + TxHashEVMToEVM, + BlockHashDVMToEVM, + BlockHashEVMToDVM +}; + +static int VMDomainRPCMapTypeCount = 7; + +enum class VMDomainIndexType { + BlockHash, + TxHash +}; + + UniValue evmtx(const JSONRPCRequest& request) { auto pwallet = GetWallet(request); @@ -122,11 +140,129 @@ UniValue evmtx(const JSONRPCRequest& request) { return send(MakeTransactionRef(std::move(rawTx)), optAuthTx)->GetHash().ToString(); } + +UniValue vmmap(const JSONRPCRequest& request) { + auto pwallet = GetWallet(request); + RPCHelpMan{"vmmap", + "Give the equivalent of an address, blockhash or transaction from EVM to DVM\n", + { + {"hash", RPCArg::Type::STR, RPCArg::Optional::NO, "DVM address, EVM blockhash, EVM transaction"}, + {"type", RPCArg::Type::NUM, RPCArg::Optional::NO, "Type of mapping: 1 - DFI Address to EVM, 2 - EVM to DFI Address, 3 - DFI Tx to EVM, 4 - EVM Tx to DFI, 5 - DFI Block to EVM, 6 - EVM Block to DFI"} + }, + RPCResult{ + "\"hash\" (string) The hex-encoded string for address, block or transaction\n" + }, + RPCExamples{ + HelpExampleCli("vmmap", R"('""' 1)") + }, + }.Check(request); + const std::string hash = request.params[0].get_str(); + + if (request.params[1].get_int() >= VMDomainRPCMapTypeCount) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameters, argument \"type\" must be less than %d.", VMDomainRPCMapTypeCount)); + } + const auto type = static_cast(request.params[1].get_int()); + switch (type) { + case VMDomainRPCMapType::AddressDVMToEVM: { + const CPubKey key = AddrToPubKey(pwallet, hash); + return EncodeDestination(WitnessV16EthHash(key.GetID())); + } + case VMDomainRPCMapType::AddressEVMToDVM: { + const CPubKey key = AddrToPubKey(pwallet, hash); + return EncodeDestination(PKHash(key.GetID())); + } + default: + break; + } + + LOCK(cs_main); + + ResVal res = ResVal(uint256{}, Res::Ok()); + switch (type) { + case VMDomainRPCMapType::TxHashDVMToEVM: { + res = pcustomcsview->GetVMDomainMapTxHash(VMDomainMapType::DVMToEVM, uint256S(hash)); + break; + } + case VMDomainRPCMapType::TxHashEVMToEVM: { + res = pcustomcsview->GetVMDomainMapTxHash(VMDomainMapType::EVMToDVM, uint256S(hash)); + break; + } + case VMDomainRPCMapType::BlockHashDVMToEVM: { + res = pcustomcsview->GetVMDomainMapBlockHash(VMDomainMapType::DVMToEVM, uint256S(hash)); + break; + } + case VMDomainRPCMapType::BlockHashEVMToDVM: { + res = pcustomcsview->GetVMDomainMapBlockHash(VMDomainMapType::EVMToDVM, uint256S(hash)); + break; + } + default: { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Unknown map type"); + } + } + if (!res) { + throw JSONRPCError(RPC_INVALID_REQUEST, res.msg); + } else { + return res.val->ToString(); + } +} + +UniValue logvmmaps(const JSONRPCRequest& request) { + RPCHelpMan{ + "logvmmaps", + "\nLogs all block or tx indexes for debugging.\n", + { + {"type", RPCArg::Type::NUM, RPCArg::Optional::NO, "Type of log: 0 - Blocks, 1 - Txs"} + }, + RPCResult{ + "{...} (array) Json object with account balances if rpcresult is enabled." + "This is for debugging purposes only.\n"}, + RPCExamples{ + HelpExampleCli("logvmmaps", R"('""' 1)")}, + }.Check(request); + + LOCK(cs_main); + + size_t count{}; + UniValue result{UniValue::VOBJ}; + UniValue indexesJson{UniValue::VOBJ}; + const auto type = static_cast(request.params[0].get_int()); + // TODO: For now, we iterate through the whole list. But this is just a debugging RPC. + // But there's no need to iterate the whole list, we can start at where we need to and + // return false, once we hit the limit and stop the iter. + switch (type) { + case VMDomainIndexType::BlockHash: { + pcustomcsview->ForEachVMDomainMapBlockIndexes([&](const std::pair &index, uint256 blockHash) { + if (index.first == VMDomainMapType::DVMToEVM) { + indexesJson.pushKV(index.second.GetHex(), blockHash.GetHex()); + ++count; + } + return true; + }); + } + case VMDomainIndexType::TxHash: { + pcustomcsview->ForEachVMDomainMapTxIndexes([&](const std::pair &index, uint256 txHash) { + if (index.first == VMDomainMapType::DVMToEVM) { + indexesJson.pushKV(index.second.GetHex(), txHash.GetHex()); + ++count; + } + return true; + }); + } + } + + result.pushKV("indexes", indexesJson); + result.pushKV("count", static_cast(count)); + return result; +} + + static const CRPCCommand commands[] = { // category name actor (function) params // --------------- ---------------------- --------------------- ---------- - {"evm", "evmtx", &evmtx, {"from", "nonce", "gasPrice", "gasLimit", "to", "value", "data"}}, + {"evm", "evmtx", &evmtx, {"from", "nonce", "gasPrice", "gasLimit", "to", "value", "data"}}, + {"evm", "vmmap", &vmmap, {"hash", "type"}}, + {"evm", "logvmmaps", &logvmmaps, {"type"}}, }; void RegisterEVMRPCCommands(CRPCTable& tableRPC) { diff --git a/src/masternodes/validation.cpp b/src/masternodes/validation.cpp index 1c7b0f5e37e..b5fba7340c3 100644 --- a/src/masternodes/validation.cpp +++ b/src/masternodes/validation.cpp @@ -2428,6 +2428,11 @@ static void ProcessEVMQueue(const CBlock &block, const CBlockIndex *pindex, CCus } const auto blockResult = evm_finalize(evmContext, false, block.nBits, beneficiary, block.GetBlockTime()); + auto evmBlockHash = std::vector(blockResult.block_hash.begin(), blockResult.block_hash.end()); + std::reverse(evmBlockHash.begin(), evmBlockHash.end()); + + cache.SetVMDomainMapBlockHash(VMDomainMapType::DVMToEVM, block.GetHash(), uint256(evmBlockHash)); + cache.SetVMDomainMapBlockHash(VMDomainMapType::EVMToDVM, uint256(evmBlockHash), block.GetHash()); if (!blockResult.failed_transactions.empty()) { std::vector failedTransactions; diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 60930cb5477..4be111dd1af 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -365,6 +365,8 @@ static const CRPCConvertParam vRPCConvertParams[] = { "evmtx", 2, "gasPrice" }, { "evmtx", 3, "gasLimit" }, { "evmtx", 5, "value" }, + { "vmmap", 1, "type"}, + { "logvmmaps", 0, "type"}, }; // clang-format on diff --git a/test/functional/feature_evm_rpc.py b/test/functional/feature_evm_rpc.py index 25bce70ce7d..90a2505fee1 100755 --- a/test/functional/feature_evm_rpc.py +++ b/test/functional/feature_evm_rpc.py @@ -153,6 +153,39 @@ def test_block(self): block = self.nodes[0].eth_getBlockByHash(latest_block['hash']) assert_equal(block, latest_block) + def test_vmmap(self): + # TODO: This PR isn't ready just yet without proper tests. + # Current tests are very basic. Need to add proper tests. + # Merging for now, for faster feedback loop. But this is a key to-do. + # Check if xvmmap is working for addresses + eth_address = '0x2E04dbc946c6473DFd318d3bE2BE36E5dfbdACDC' + address = self.nodes[0].vmmap(eth_address, 2) + assert_equal(eth_address, self.nodes[0].vmmap(address, 1)) + + # Check that vmmap is failing on wrong input + eth_address = '0x0000000000000000000000000000000000000000' + assert_raises_rpc_error(-5, "0x0000000000000000000000000000000000000000 does not refer to a key", self.nodes[0].vmmap, eth_address, 2) + assert_raises_rpc_error(-5, "Invalid address: test", self.nodes[0].vmmap, 'test', 1) + + #Check if xvmmap is working for Txs + list_tx = self.nodes[0].logvmmaps(1) + dvm_tx = list(list_tx['indexes'].keys())[0] + evm_tx = self.nodes[0].vmmap(dvm_tx, 3) + assert_equal(dvm_tx, self.nodes[0].vmmap(evm_tx, 4)) + + # Check vmmap fail on wrong tx + evm_tx = '0x0000000000000000000000000000000000000000000000000000000000000000' + assert_raises_rpc_error(-32600, "DB r/w failure: 0000000000000000000000000000000000000000000000000000000000000000", self.nodes[0].vmmap, evm_tx, 4) + + # Check if xvmmap is working for Blocks + latest_block = self.nodes[0].eth_getBlockByNumber("latest", False) + dvm_block = self.nodes[0].vmmap(latest_block['hash'], 6) + assert_equal(latest_block['hash'], "0x" + self.nodes[0].vmmap(dvm_block, 5)) + + # Check vmmap fail on wrong block + evm_block = '0x0000000000000000000000000000000000000000000000000000000000000000' + assert_raises_rpc_error(-32600, "DB r/w failure: 0000000000000000000000000000000000000000000000000000000000000000", self.nodes[0].vmmap, evm_block, 6) + def run_test(self): self.setup() @@ -163,11 +196,12 @@ def run_test(self): self.test_accounts() self.nodes[0].transferdomain([{"src": {"address":self.address, "amount":"100@DFI", "domain": 2}, "dst":{"address":self.ethAddress, "amount":"100@DFI", "domain": 3}}]) - self.nodes[0].generate(1) + self.nodes[0].generate(2) self.test_address_state(self.ethAddress) # TODO test smart contract self.test_block() + self.test_vmmap() if __name__ == '__main__':