diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 86a606fb1..24fda9532 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -3080,6 +3080,21 @@ bool wallet2::add_address_book_row(const cryptonote::account_public_address &add return false; } +bool wallet2::set_address_book_row(size_t row_id, const cryptonote::account_public_address &address, const crypto::hash &payment_id, const std::string &description, bool is_subaddress) +{ + wallet2::address_book_row a; + a.m_address = address; + a.m_payment_id = payment_id; + a.m_description = description; + a.m_is_subaddress = is_subaddress; + + const auto size = m_address_book.size(); + if (row_id >= size) + return false; + m_address_book[row_id] = a; + return true; +} + bool wallet2::delete_address_book_row(std::size_t row_id) { if(m_address_book.size() <= row_id) return false; diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 95f6f507a..1469b4c00 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -1098,6 +1098,7 @@ private: */ std::vector get_address_book() const { return m_address_book; } bool add_address_book_row(const cryptonote::account_public_address &address, const crypto::hash &payment_id, const std::string &description, bool is_subaddress); + bool set_address_book_row(size_t row_id, const cryptonote::account_public_address &address, const crypto::hash &payment_id, const std::string &description, bool is_subaddress); bool delete_address_book_row(std::size_t row_id); uint64_t get_num_rct_outputs(); diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 765a6c24e..0e0221c03 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -2824,6 +2824,108 @@ namespace tools return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_edit_address_book(const wallet_rpc::COMMAND_RPC_EDIT_ADDRESS_BOOK_ENTRY::request& req, wallet_rpc::COMMAND_RPC_EDIT_ADDRESS_BOOK_ENTRY::response& res, epee::json_rpc::error& er, const connection_context *ctx) + { + if (!m_wallet) return not_open(er); + if (m_restricted) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + + const auto ab = m_wallet->get_address_book(); + if (req.index >= ab.size()) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_INDEX; + er.message = "Index out of range: " + std::to_string(req.index); + return false; + } + + tools::wallet2::address_book_row entry = ab[req.index]; + + cryptonote::address_parse_info info; + crypto::hash payment_id = crypto::null_hash; + if (req.set_address) + { + er.message = ""; + if(!get_account_address_from_str_or_url(info, m_wallet->nettype(), req.address, + [&er](const std::string &url, const std::vector &addresses, bool dnssec_valid)->std::string { + if (!dnssec_valid) + { + er.message = std::string("Invalid DNSSEC for ") + url; + return {}; + } + if (addresses.empty()) + { + er.message = std::string("No Monero address found at ") + url; + return {}; + } + return addresses[0]; + })) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS; + if (er.message.empty()) + er.message = std::string("WALLET_RPC_ERROR_CODE_WRONG_ADDRESS: ") + req.address; + return false; + } + entry.m_address = info.address; + entry.m_is_subaddress = info.is_subaddress; + if (info.has_payment_id) + { + memcpy(entry.m_payment_id.data, info.payment_id.data, 8); + memset(entry.m_payment_id.data + 8, 0, 24); + } + } + + if (req.set_payment_id) + { + if (req.payment_id.empty()) + { + payment_id = crypto::null_hash; + } + else + { + if (req.set_address && info.has_payment_id) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; + er.message = "Separate payment ID given with integrated address"; + return false; + } + + if (!wallet2::parse_long_payment_id(req.payment_id, payment_id)) + { + crypto::hash8 spid; + if (!wallet2::parse_short_payment_id(req.payment_id, spid)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; + er.message = "Payment id has invalid format: \"" + req.payment_id + "\", expected 64 character string"; + return false; + } + else + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; + er.message = "Payment id has invalid format: standalone short payment IDs are forbidden, they must be part of an integrated address"; + return false; + } + } + } + + entry.m_payment_id = payment_id; + } + + if (req.set_description) + entry.m_description = req.description; + + if (!m_wallet->set_address_book_row(req.index, entry.m_address, entry.m_payment_id, entry.m_description, entry.m_is_subaddress)) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Failed to edit address book entry"; + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_delete_address_book(const wallet_rpc::COMMAND_RPC_DELETE_ADDRESS_BOOK_ENTRY::request& req, wallet_rpc::COMMAND_RPC_DELETE_ADDRESS_BOOK_ENTRY::response& res, epee::json_rpc::error& er, const connection_context *ctx) { if (!m_wallet) return not_open(er); diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index a327ed908..b2b5e7116 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -127,6 +127,7 @@ namespace tools MAP_JON_RPC_WE("parse_uri", on_parse_uri, wallet_rpc::COMMAND_RPC_PARSE_URI) MAP_JON_RPC_WE("get_address_book", on_get_address_book, wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY) MAP_JON_RPC_WE("add_address_book", on_add_address_book, wallet_rpc::COMMAND_RPC_ADD_ADDRESS_BOOK_ENTRY) + MAP_JON_RPC_WE("edit_address_book", on_edit_address_book, wallet_rpc::COMMAND_RPC_EDIT_ADDRESS_BOOK_ENTRY) MAP_JON_RPC_WE("delete_address_book",on_delete_address_book,wallet_rpc::COMMAND_RPC_DELETE_ADDRESS_BOOK_ENTRY) MAP_JON_RPC_WE("refresh", on_refresh, wallet_rpc::COMMAND_RPC_REFRESH) MAP_JON_RPC_WE("auto_refresh", on_auto_refresh, wallet_rpc::COMMAND_RPC_AUTO_REFRESH) @@ -212,6 +213,7 @@ namespace tools bool on_parse_uri(const wallet_rpc::COMMAND_RPC_PARSE_URI::request& req, wallet_rpc::COMMAND_RPC_PARSE_URI::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); bool on_get_address_book(const wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY::request& req, wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); bool on_add_address_book(const wallet_rpc::COMMAND_RPC_ADD_ADDRESS_BOOK_ENTRY::request& req, wallet_rpc::COMMAND_RPC_ADD_ADDRESS_BOOK_ENTRY::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); + bool on_edit_address_book(const wallet_rpc::COMMAND_RPC_EDIT_ADDRESS_BOOK_ENTRY::request& req, wallet_rpc::COMMAND_RPC_EDIT_ADDRESS_BOOK_ENTRY::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); bool on_delete_address_book(const wallet_rpc::COMMAND_RPC_DELETE_ADDRESS_BOOK_ENTRY::request& req, wallet_rpc::COMMAND_RPC_DELETE_ADDRESS_BOOK_ENTRY::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); bool on_refresh(const wallet_rpc::COMMAND_RPC_REFRESH::request& req, wallet_rpc::COMMAND_RPC_REFRESH::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); bool on_auto_refresh(const wallet_rpc::COMMAND_RPC_AUTO_REFRESH::request& req, wallet_rpc::COMMAND_RPC_AUTO_REFRESH::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index d70de68be..0c86f404d 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -47,7 +47,7 @@ // advance which version they will stop working with // Don't go over 32767 for any of these #define WALLET_RPC_VERSION_MAJOR 1 -#define WALLET_RPC_VERSION_MINOR 15 +#define WALLET_RPC_VERSION_MINOR 16 #define MAKE_WALLET_RPC_VERSION(major,minor) (((major)<<16)|(minor)) #define WALLET_RPC_VERSION MAKE_WALLET_RPC_VERSION(WALLET_RPC_VERSION_MAJOR, WALLET_RPC_VERSION_MINOR) namespace tools @@ -1845,6 +1845,38 @@ namespace wallet_rpc typedef epee::misc_utils::struct_init response; }; + struct COMMAND_RPC_EDIT_ADDRESS_BOOK_ENTRY + { + struct request_t + { + uint64_t index; + bool set_address; + std::string address; + bool set_payment_id; + std::string payment_id; + bool set_description; + std::string description; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(index) + KV_SERIALIZE(set_address) + KV_SERIALIZE(address) + KV_SERIALIZE(set_payment_id) + KV_SERIALIZE(payment_id) + KV_SERIALIZE(set_description) + KV_SERIALIZE(description) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init request; + + struct response_t + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init response; + }; + struct COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY { struct request_t diff --git a/tests/functional_tests/address_book.py b/tests/functional_tests/address_book.py index 77a2d200b..e6c57896b 100755 --- a/tests/functional_tests/address_book.py +++ b/tests/functional_tests/address_book.py @@ -29,10 +29,6 @@ # 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. -import os -import time -import errno - """Test wallet address book RPC """ @@ -69,6 +65,10 @@ class AddressBookTest(): try: wallet.delete_address_book(0) except: ok = True assert ok + ok = False + try: wallet.edit_address_book(0, description = '') + except: ok = True + assert ok # add one res = wallet.add_address_book('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', description = 'self') @@ -220,13 +220,78 @@ class AddressBookTest(): assert len(res.entries) == 1 assert res.entries[0].address == '87KfgTZ8ER5D3Frefqnrqif11TjVsTPaTcp37kqqKMrdDRUhpJRczeR7KiBmSHF32UJLP3HHhKUDmEQyJrv2mV8yFDCq8eB' + # edit + res = wallet.get_address_book([1]) + assert len(res.entries) == 1 + e = res.entries[0] + assert e.index == 1 + assert e.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' + assert e.payment_id == '0' * 64 + assert e.description == u'あまやかす' + res = wallet.edit_address_book(1, payment_id = '1' * 64) + res = wallet.get_address_book([1]) + assert len(res.entries) == 1 + e = res.entries[0] + assert e.index == 1 + assert e.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' + assert e.payment_id == '1' * 64 + assert e.description == u'あまやかす' + res = wallet.edit_address_book(1, description = '') + res = wallet.get_address_book([1]) + assert len(res.entries) == 1 + e = res.entries[0] + assert e.index == 1 + assert e.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' + assert e.payment_id == '1' * 64 + assert e.description == '' + res = wallet.edit_address_book(1, description = 'えんしゅう') + res = wallet.get_address_book([1]) + assert len(res.entries) == 1 + e = res.entries[0] + assert e.index == 1 + assert e.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' + assert e.payment_id == '1' * 64 + assert e.description == u'えんしゅう' + res = wallet.edit_address_book(1, address = '44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A') + res = wallet.get_address_book([1]) + assert len(res.entries) == 1 + e = res.entries[0] + assert e.index == 1 + assert e.address == '44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A' + assert e.payment_id == '1' * 64 + assert e.description == u'えんしゅう' + res = wallet.edit_address_book(1, payment_id = '') + res = wallet.get_address_book([1]) + assert len(res.entries) == 1 + e = res.entries[0] + assert e.index == 1 + assert e.address == '44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A' + assert e.payment_id == '0' * 64 + assert e.description == u'えんしゅう' + ok = False + try: res = wallet.edit_address_book(1, address = '') + except: ok = True + assert ok + ok = False + try: res = wallet.edit_address_book(1, payment_id = 'asdnd') + except: ok = True + assert ok + ok = False + try: res = wallet.edit_address_book(1, address = 'address') + except: ok = True + assert ok + res = wallet.edit_address_book(1) + res = wallet.get_address_book([1]) + assert len(res.entries) == 1 + assert e == res.entries[0] + # empty wallet.delete_address_book(4) wallet.delete_address_book(0) res = wallet.get_address_book([0]) # entries above the deleted one collapse one slot up assert len(res.entries) == 1 - assert res.entries[0].address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' - assert res.entries[0].description == u'あまやかす' + assert res.entries[0].address == '44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A' + assert res.entries[0].description == u'えんしゅう' wallet.delete_address_book(2) wallet.delete_address_book(0) wallet.delete_address_book(0) diff --git a/utils/python-rpc/framework/wallet.py b/utils/python-rpc/framework/wallet.py index c67fefc87..e42b99239 100644 --- a/utils/python-rpc/framework/wallet.py +++ b/utils/python-rpc/framework/wallet.py @@ -903,6 +903,23 @@ class Wallet(object): } return self.rpc.send_json_rpc_request(add_address_book) + def edit_address_book(self, index, address = None, payment_id = None, description = None): + edit_address_book = { + 'method': 'edit_address_book', + 'jsonrpc': '2.0', + 'params': { + 'index': index, + 'set_address': address != None, + 'address': address or '', + 'set_payment_id': payment_id != None, + 'payment_id': payment_id or '', + 'set_description': description != None, + 'description': description or '', + }, + 'id': '0' + } + return self.rpc.send_json_rpc_request(edit_address_book) + def get_address_book(self, entries = []): get_address_book = { 'method': 'get_address_book',