diff --git a/src/supernode/baseclientproxy.cpp b/src/supernode/baseclientproxy.cpp index fec9b6d5f..7fc66c0df 100644 --- a/src/supernode/baseclientproxy.cpp +++ b/src/supernode/baseclientproxy.cpp @@ -26,6 +26,7 @@ // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#include "cryptonote_basic/cryptonote_basic.h" #include "mnemonics/electrum-words.h" #include "common/command_line.h" #include "baseclientproxy.h" @@ -45,6 +46,8 @@ void supernode::BaseClientProxy::Init() m_DAPIServer->ADD_DAPI_HANDLER(CreateAccount, rpc_command::CREATE_ACCOUNT, BaseClientProxy); m_DAPIServer->ADD_DAPI_HANDLER(GetSeed, rpc_command::GET_SEED, BaseClientProxy); m_DAPIServer->ADD_DAPI_HANDLER(RestoreAccount, rpc_command::RESTORE_ACCOUNT, BaseClientProxy); + m_DAPIServer->ADD_DAPI_HANDLER(Transfer, rpc_command::TRANSFER, BaseClientProxy); + m_DAPIServer->ADD_DAPI_HANDLER(GetTransferFee, rpc_command::GET_TRANSFER_FEE, BaseClientProxy); } bool supernode::BaseClientProxy::GetWalletBalance(const supernode::rpc_command::GET_WALLET_BALANCE::request &in, supernode::rpc_command::GET_WALLET_BALANCE::response &out) @@ -176,6 +179,282 @@ bool supernode::BaseClientProxy::RestoreAccount(const supernode::rpc_command::RE return true; } +bool supernode::BaseClientProxy::GetTransferFee(const supernode::rpc_command::GET_TRANSFER_FEE::request &in, supernode::rpc_command::GET_TRANSFER_FEE::response &out) +{ + std::unique_ptr wal = initWallet(base64_decode(in.Account), in.Password); + if (!wal) + { + out.Result = ERROR_OPEN_WALLET_FAILED; + return false; + } + + if (wal->restricted()) + { + // er.code = WALLET_RPC_ERROR_CODE_DENIED; + // er.message = "Command unavailable in restricted mode."; + out.Result = ERROR_OPEN_WALLET_FAILED; + return false; + } + + std::vector dsts; + std::vector extra; + + std::string payment_id = ""; + + uint64_t amount; + std::istringstream iss(in.Amount); + iss >> amount; + + // validate the transfer requested and populate dsts & extra + if (!validate_transfer(in.Account, in.Password, in.Address, amount, payment_id, dsts, extra)) + { + out.Result = ERROR_OPEN_WALLET_FAILED; + return false; + } + + try + { + uint64_t mixin = 4; + uint64_t unlock_time = 0; + uint64_t priority = 0; + std::vector ptx_vector = + wal->create_transactions_2(dsts, mixin, unlock_time, priority, extra, false); + + // reject proposed transactions if there are more than one. see on_transfer_split below. + if (ptx_vector.size() != 1) + { + // er.code = WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR; + // er.message = "Transaction would be too large. try /transfer_split."; + out.Result = ERROR_OPEN_WALLET_FAILED; + return false; + } + + out.Fee = ptx_vector.back().fee; + } + catch (const tools::error::daemon_busy& e) + { + // er.code = WALLET_RPC_ERROR_CODE_DAEMON_IS_BUSY; + // er.message = e.what(); + out.Result = ERROR_OPEN_WALLET_FAILED; + return false; + } + catch (const std::exception& e) + { + // er.code = WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR; + // er.message = e.what(); + out.Result = ERROR_OPEN_WALLET_FAILED; + return false; + } + catch (...) + { + // er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + // er.message = "WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR"; + out.Result = ERROR_OPEN_WALLET_FAILED; + return false; + } + + out.Result = STATUS_OK; + return true; +} + +bool supernode::BaseClientProxy::Transfer(const supernode::rpc_command::TRANSFER::request &in, supernode::rpc_command::TRANSFER::response &out) +{ + std::unique_ptr wal = initWallet(base64_decode(in.Account), in.Password); + if (!wal) + { + out.Result = ERROR_OPEN_WALLET_FAILED; + return false; + } + + if (wal->restricted()) + { + // er.code = WALLET_RPC_ERROR_CODE_DENIED; + // er.message = "Command unavailable in restricted mode."; + out.Result = ERROR_OPEN_WALLET_FAILED; + return false; + } + + std::vector dsts; + std::vector extra; + + std::string payment_id = ""; + + uint64_t amount; + std::istringstream iss(in.Amount); + iss >> amount; + + // validate the transfer requested and populate dsts & extra + if (!validate_transfer(in.Account, in.Password, in.Address, amount, payment_id, dsts, extra)) + { + out.Result = ERROR_OPEN_WALLET_FAILED; + return false; + } + + try + { + uint64_t mixin = 4; + uint64_t unlock_time = 0; + uint64_t priority = 0; + bool do_not_relay = false; + bool get_tx_key = false; + bool get_tx_hex = false; + std::vector ptx_vector = + wal->create_transactions_2(dsts, mixin, unlock_time, priority, extra, false); + + // reject proposed transactions if there are more than one. see on_transfer_split below. + if (ptx_vector.size() != 1) + { + // er.code = WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR; + // er.message = "Transaction would be too large. try /transfer_split."; + out.Result = ERROR_OPEN_WALLET_FAILED; + return false; + } + + if (!do_not_relay) + { + wal->commit_tx(ptx_vector); + storeWalletState(wal.get()); + } + + // populate response with tx hash + // std::string tx_hash = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx_vector.back().tx)); + // if (get_tx_key) + // { + // std::string tx_key = epee::string_tools::pod_to_hex(ptx_vector.back().tx_key); + // } + // uint64_t fee = ptx_vector.back().fee; + + // if (get_tx_hex) + // { + // cryptonote::blobdata blob; + // tx_to_blob(ptx_vector.back().tx, blob); + // res.tx_blob = epee::string_tools::buff_to_hex_nodelimer(blob); + // } + } + catch (const tools::error::daemon_busy& e) + { + // er.code = WALLET_RPC_ERROR_CODE_DAEMON_IS_BUSY; + // er.message = e.what(); + out.Result = ERROR_OPEN_WALLET_FAILED; + return false; + } + catch (const std::exception& e) + { + // er.code = WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR; + // er.message = e.what(); + out.Result = ERROR_OPEN_WALLET_FAILED; + return false; + } + catch (...) + { + // er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + // er.message = "WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR"; + out.Result = ERROR_OPEN_WALLET_FAILED; + return false; + } + + out.Result = STATUS_OK; + return true; +} + +bool supernode::BaseClientProxy::validate_transfer(const string &account, const string &password, + const string &address, uint64_t amount, + const string payment_id, + std::vector &dsts, + std::vector &extra) +{ + std::unique_ptr wal = initWallet(base64_decode(account), password); + if (!wal) + { + return false; + } + + crypto::hash8 integrated_payment_id = cryptonote::null_hash8; + std::string extra_nonce; + + cryptonote::tx_destination_entry de; + bool has_payment_id; + crypto::hash8 new_payment_id; + std::string errorString; + if (!get_account_address_from_str_or_url(de.addr, has_payment_id, new_payment_id, wal->testnet(),address, + [&errorString](const std::string &url, const std::vector &addresses, bool dnssec_valid)->std::string { + if (!dnssec_valid) + { + errorString = std::string("Invalid DNSSEC for ") + url; + return {}; + } + if (addresses.empty()) + { + errorString = std::string("No Monero address found at ") + url; + return {}; + } + return addresses[0]; + })) + { +// er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS; + if (errorString.empty()) + errorString = std::string("WALLET_RPC_ERROR_CODE_WRONG_ADDRESS: ") + address; + return false; + } + de.amount = amount; + dsts.push_back(de); + + if (has_payment_id) + { + if (!payment_id.empty() || integrated_payment_id != cryptonote::null_hash8) + { +// er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; +// er.message = "A single payment id is allowed per transaction"; + return false; + } + integrated_payment_id = new_payment_id; + cryptonote::set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, integrated_payment_id); + + /* Append Payment ID data into extra */ + if (!cryptonote::add_extra_nonce_to_tx_extra(extra, extra_nonce)) + { +// er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; +// er.message = "Something went wrong with integrated payment_id."; + return false; + } + } + + if (!payment_id.empty()) + { + /* Just to clarify */ + const std::string& payment_id_str = payment_id; + + crypto::hash long_payment_id; + crypto::hash8 short_payment_id; + + /* Parse payment ID */ + if (tools::GraftWallet::parse_long_payment_id(payment_id_str, long_payment_id)) + { + cryptonote::set_payment_id_to_tx_extra_nonce(extra_nonce, long_payment_id); + } + /* or short payment ID */ + else if (tools::GraftWallet::parse_short_payment_id(payment_id_str, short_payment_id)) + { + cryptonote::set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, short_payment_id); + } + else + { +// er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; +// er.message = "Payment id has invalid format: \"" + payment_id_str + "\", expected 16 or 64 character string"; + return false; + } + + /* Append Payment ID data into extra */ + if (!cryptonote::add_extra_nonce_to_tx_extra(extra, extra_nonce)) + { +// er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; +// er.message = "Something went wrong with payment_id. Please check its format: \"" + payment_id_str + "\", expected 64-character string"; + return false; + } + } + return true; +} + std::unique_ptr supernode::BaseClientProxy::initWallet(const string &account, const string &password) const { std::unique_ptr wal; diff --git a/src/supernode/baseclientproxy.h b/src/supernode/baseclientproxy.h index a9035dc9d..dc02954ed 100644 --- a/src/supernode/baseclientproxy.h +++ b/src/supernode/baseclientproxy.h @@ -53,9 +53,15 @@ class BaseClientProxy : public BaseRTAProcessor bool GetSeed(const rpc_command::GET_SEED::request &in, rpc_command::GET_SEED::response &out); bool RestoreAccount(const rpc_command::RESTORE_ACCOUNT::request &in, rpc_command::RESTORE_ACCOUNT::response &out); + bool GetTransferFee(const rpc_command::GET_TRANSFER_FEE::request &in, rpc_command::GET_TRANSFER_FEE::response &out); + bool Transfer(const rpc_command::TRANSFER::request &in, rpc_command::TRANSFER::response &out); -protected: - +private: + bool validate_transfer(const string &account, const string &password, + const std::string &address, uint64_t amount, + const std::string payment_id, + std::vector& dsts, + std::vector& extra); }; } diff --git a/src/supernode/graft_defines.h b/src/supernode/graft_defines.h index 28b5f7d5d..92b757f1e 100644 --- a/src/supernode/graft_defines.h +++ b/src/supernode/graft_defines.h @@ -53,5 +53,7 @@ #define ERROR_BALANCE_NOT_AVAILABLE -16 #define ERROR_CANNOT_REJECT_PAY -17 +#define ERROR_NOT_ENOUGH_COINS -20 + #endif // GRAFT_DEFINES_H diff --git a/src/supernode/supernode_rpc_command.cpp b/src/supernode/supernode_rpc_command.cpp index 010976c2f..36b60fcef 100644 --- a/src/supernode/supernode_rpc_command.cpp +++ b/src/supernode/supernode_rpc_command.cpp @@ -63,6 +63,8 @@ DCALL(GetWalletBalance) DCALL(CreateAccount) DCALL(GetSeed) DCALL(RestoreAccount) +DCALL(Transfer) +DCALL(GetTransferFee) DCALL(WalletRejectPay) DCALL(WalletProxyRejectPay) DCALL(AuthWalletRejectPay) diff --git a/src/supernode/supernode_rpc_command.h b/src/supernode/supernode_rpc_command.h index 9260b9911..08e5498ae 100644 --- a/src/supernode/supernode_rpc_command.h +++ b/src/supernode/supernode_rpc_command.h @@ -61,6 +61,8 @@ namespace supernode { extern const string CreateAccount; extern const string GetSeed; extern const string RestoreAccount; + extern const string Transfer; + extern const string GetTransferFee; extern const string WalletRejectPay; extern const string WalletProxyRejectPay; @@ -460,6 +462,55 @@ namespace supernode { }; }; + struct TRANSFER { + struct request { + std::string Account; + std::string Password; + std::string Address; + std::string Amount; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(Account) + KV_SERIALIZE(Password) + KV_SERIALIZE(Address) + KV_SERIALIZE(Amount) + END_KV_SERIALIZE_MAP() + }; + struct response { + int64_t Result; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(Result) + END_KV_SERIALIZE_MAP() + }; + }; + + + struct GET_TRANSFER_FEE { + struct request { + std::string Account; + std::string Password; + std::string Address; + std::string Amount; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(Account) + KV_SERIALIZE(Password) + KV_SERIALIZE(Address) + KV_SERIALIZE(Amount) + END_KV_SERIALIZE_MAP() + }; + struct response { + int64_t Result; + uint64_t Fee; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(Result) + KV_SERIALIZE(Fee) + END_KV_SERIALIZE_MAP() + }; + }; + void ConvertFromTR(RTA_TransactionRecordRequest& dst, const RTA_TransactionRecord& src); void ConvertToTR(RTA_TransactionRecord& dst, const RTA_TransactionRecordRequest& src, const FSN_ServantBase* servant); @@ -565,10 +616,6 @@ namespace supernode { vector List; }; }; - - - - } } diff --git a/tests/supernode_tests/main.cpp b/tests/supernode_tests/main.cpp index a9f3c47e2..daeaf968b 100644 --- a/tests/supernode_tests/main.cpp +++ b/tests/supernode_tests/main.cpp @@ -144,20 +144,20 @@ TEST_F(TestDAPI_Server_And_ClientBase, TestDAPI_Server_And_Client) { ASSERT_TRUE(ret && out.Data==20); -// LOG_PRINT_L5("ret: "<Start(); } - LOG_PRINT_L5("DAPI START on: "<AddFsnAccount(boost::make_shared(FSN_WalletData{address2, viewkey2}, FSN_WalletData{address1, viewkey1}, "127.0.0.1", p2)); - LOG_PRINT_L5("STARTED: "<<(second?2:1)); + LOG_PRINT_L0("STARTED: "<<(second?2:1)); WorkerThread = new boost::thread(&Supernode::Run, this); @@ -296,7 +296,7 @@ struct Test_RTA_FlowBlockChain : public testing::Test { bool Assert(bool bb, const string& str) { if(!Verbose) return bb; - LOG_PRINT_L5(str<<" - "<<(bb?"OK":"Fail")); + LOG_PRINT_L0(str<<" - "<<(bb?"OK":"Fail")); return bb; } @@ -319,7 +319,7 @@ struct Test_RTA_FlowBlockChain : public testing::Test { bb = pos_sale.Invoke("Sale", sale_in, sale_out); if( Assert(bb, "Sale") ) break; - //LOG_PRINT_L5("Sale ret: "< > vv = m_P2P.Seeds(); for(auto& a : vv) str += string("\t") + a.second + string("\n"); str += "\n\n"; - LOG_PRINT_L5(str); + LOG_PRINT_L0(str); } DAPI_RPC_Server* m_DAPIServer = nullptr; @@ -577,10 +577,10 @@ TEST_F(TestHanlerP2P, Test_P2PTest) { bool list1 = node1.m_P2P.Seeds().size()==1 && node0.m_P2P.Seeds().size()==1 && node1.IsHave("7500") && node0.IsHave("8500"); - LOG_PRINT_L5("\n-----------------------------------------------------------\n"); + LOG_PRINT_L0("\n-----------------------------------------------------------\n"); node0.Print(); node1.Print(); - LOG_PRINT_L5("\n-----------------------------------------------------------\n"); + LOG_PRINT_L0("\n-----------------------------------------------------------\n"); @@ -596,11 +596,11 @@ TEST_F(TestHanlerP2P, Test_P2PTest) { /* - LOG_PRINT_L5("\n-----------------------------------------------------------\n"); + LOG_PRINT_L0("\n-----------------------------------------------------------\n"); node0.Print(); node1.Print(); node2.Print(); - LOG_PRINT_L5("\n-----------------------------------------------------------\n"); + LOG_PRINT_L0("\n-----------------------------------------------------------\n"); */ node2.m_P2P.AddHandler("TestP2PHandler", bind(&TestHanlerP2P::TestHandler, this, _1) ); @@ -615,13 +615,13 @@ TEST_F(TestHanlerP2P, Test_P2PTest) { sleep(2); bool call2 = GotData.size()==2 && GotData[0].Port==data.Port; - LOG_PRINT_L5("GotData.size(): "<Port() ); + LOG_PRINT_L0( "\n\n"<Port() ); for(unsigned i=0;iAll_FSN.size();i++) { auto a = Servant->All_FSN[i]; - LOG_PRINT_L5(a->Port<<" : "<Stake.Addr<<" : "<Stake.ViewKey); + LOG_PRINT_L0(a->Port<<" : "<Stake.Addr<<" : "<Stake.ViewKey); }//for } @@ -710,7 +710,7 @@ struct TestActualList { bool FindFSN(const string& port, const string& stakeW, const string& stakeKey) { for(unsigned i=0;iAll_FSN.size();i++) { auto a = Servant->All_FSN[i]; - //LOG_PRINT_L5(a->IP<<":"<Port<<" "<Stake.Addr<<" "<Stake.ViewKey); + //LOG_PRINT_L0(a->IP<<":"<Port<<" "<Stake.Addr<<" "<Stake.ViewKey); if( a->IP=="127.0.0.1" && a->Port==port && a->Stake.Addr==stakeW && a->Stake.ViewKey==stakeKey ) return true; } return false; @@ -756,7 +756,7 @@ TEST_F(Test_ActualFSNList_Strut, Test_ActualFSNList) { bool found1 = node1.FindFSN("7510", "T6SnKmirXp6geLAoB7fn2eV51Ctr1WH1xWDnEGzS9pvQARTJQUXupiRKGR7czL7b5XdDnYXosVJu6Wj3Y3NYfiEA2sU2QiGVa", "8c0ccff03e9f2a9805e200f887731129495ff793dc678db6c5b53df814084f04"); -// LOG_PRINT_L5("=1 FOUND: "<All_FSN.size()==2; bool size2 = node2.Servant->All_FSN.size()==2; - LOG_PRINT_L5("IN "<Port()<<" size: "<All_FSN.size() ); - LOG_PRINT_L5("IN "<Port()<<" size: "<All_FSN.size() ); + LOG_PRINT_L0("IN "<Port()<<" size: "<All_FSN.size() ); + LOG_PRINT_L0("IN "<Port()<<" size: "<All_FSN.size() ); // ------------- node2.Stop(); @@ -799,7 +799,7 @@ TEST_F(Test_ActualFSNList_Strut, Test_ActualFSNList) { ASSERT_TRUE(!found6); ASSERT_TRUE(size3); -// LOG_PRINT_L5("END"); +// LOG_PRINT_L0("END"); } diff --git a/tests/supernode_tests/walletproxy_test.cpp b/tests/supernode_tests/walletproxy_test.cpp index 7619f875a..ad788577d 100644 --- a/tests/supernode_tests/walletproxy_test.cpp +++ b/tests/supernode_tests/walletproxy_test.cpp @@ -100,7 +100,7 @@ struct Supernode objs[i]->Start(); } - LOG_PRINT_L5("DAPI START on: "<AddFsnAccount(boost::make_shared(FSN_WalletData{address2, viewkey2}, FSN_WalletData{address1, viewkey1}, "127.0.0.1", p2)); - LOG_PRINT_L5("STARTED: "<<(second?2:1)); + LOG_PRINT_L0("STARTED: "<<(second?2:1)); WorkerThread = new boost::thread(&Supernode::Run, this); @@ -224,7 +224,7 @@ struct GraftRTATest1 : public testing::Test bool Assert(bool bb, const string& str) { if(!Verbose) return bb; - LOG_PRINT_L5(str<<" - "<<(bb?"OK":"Fail")); + LOG_PRINT_L0(str<<" - "<<(bb?"OK":"Fail")); return bb; } @@ -284,7 +284,7 @@ struct GraftRTATest1 : public testing::Test if (Assert(result, "Pay")) break; //boost::this_thread::sleep_for(boost::chrono::milliseconds(100)); - //LOG_PRINT_L5("Pay ret: "<