diff --git a/wallet/api/v6_0/v6_api.cpp b/wallet/api/v6_0/v6_api.cpp index 7f5cb6e8dc..81dbc59662 100644 --- a/wallet/api/v6_0/v6_api.cpp +++ b/wallet/api/v6_0/v6_api.cpp @@ -21,6 +21,7 @@ namespace beam::wallet , _wallet(init.wallet) , _swaps(init.swaps) , _contracts(init.contracts) + , _nodeNetwork(init.nodeNetwork) #ifdef BEAM_IPFS_SUPPORT , _ipfs(init.ipfs) #endif @@ -113,6 +114,15 @@ namespace beam::wallet return _contracts; } + void V6Api::checkNodeConnection() const + { + // nodeNetwork is not provided by every API host, in that case we cannot tell + if (_nodeNetwork && _nodeNetwork->getConnections() == 0) + { + throw jsonrpc_exception(ApiError::ContractError, "No connection to the node"); + } + } + #ifdef BEAM_IPFS_SUPPORT IPFSService::Ptr V6Api::getIPFS() const { diff --git a/wallet/api/v6_0/v6_api.h b/wallet/api/v6_0/v6_api.h index 202db2fee1..d6b2994e1b 100644 --- a/wallet/api/v6_0/v6_api.h +++ b/wallet/api/v6_0/v6_api.h @@ -52,6 +52,10 @@ namespace beam::wallet virtual void fillCoins(json& arr, const std::vector& coins); virtual void fillTransactions(json& arr, const std::vector& txs); + // Throws if the wallet has no connection to a node. Shader calls need to query + // the node and would otherwise wait forever, blocking all subsequent shader calls + void checkNodeConnection() const; + private: void FillAddressData(const AddressData& data, WalletAddress& address); void doTxAlreadyExistsError(const JsonRpcId& id); @@ -82,6 +86,7 @@ namespace beam::wallet Wallet::Ptr _wallet; ISwapsProvider::Ptr _swaps; IShadersManager::Ptr _contracts; + NodeNetwork::Ptr _nodeNetwork; #ifdef BEAM_IPFS_SUPPORT IPFSService::Ptr _ipfs; diff --git a/wallet/api/v6_0/v6_api_handle.cpp b/wallet/api/v6_0/v6_api_handle.cpp index a8a5ca7e76..5a13dae673 100644 --- a/wallet/api/v6_0/v6_api_handle.cpp +++ b/wallet/api/v6_0/v6_api_handle.cpp @@ -836,6 +836,8 @@ namespace beam::wallet throw jsonrpc_exception(ApiError::UnexpectedError, "Previous shader call is still in progress"); } + checkNodeConnection(); + if (data.createTx) { onHandleInvokeContractWithTX(id, std::move(data)); diff --git a/wallet/api/v6_1/v6_1_api_handle.cpp b/wallet/api/v6_1/v6_1_api_handle.cpp index 59ac8a0225..4c78c02d25 100644 --- a/wallet/api/v6_1/v6_1_api_handle.cpp +++ b/wallet/api/v6_1/v6_1_api_handle.cpp @@ -196,6 +196,7 @@ namespace beam::wallet { BEAM_LOG_VERBOSE() << "InvokeContract(id = " << id << ")"; auto contracts = getContracts(); + checkNodeConnection(); if (data.createTx) { diff --git a/wallet/unittests/wallet_test.cpp b/wallet/unittests/wallet_test.cpp index 16559d3beb..0941d0aaac 100644 --- a/wallet/unittests/wallet_test.cpp +++ b/wallet/unittests/wallet_test.cpp @@ -4297,6 +4297,52 @@ namespace WALLET_CHECK(txHistory[0].m_txId == txId); WALLET_CHECK(txHistory[0].m_status == wallet::TxStatus::Completed); } + + void TestInvokeContractNoNodeConnection() + { + cout << "\nTesting invoke_contract without node connection...\n"; + + io::Reactor::Ptr mainReactor{ io::Reactor::create() }; + io::Reactor::Scope scope(*mainReactor); + + TestWalletRig sender(createSenderWalletDB(), Wallet::TxCompletedAction(), TestWalletRig::Type::Offline); + + // node network that is never connected + auto nodeNetwork = std::make_shared(*sender.m_Wallet); + sender.m_Wallet->SetNodeEndpoint(nodeNetwork); + WALLET_CHECK(nodeNetwork->getConnections() == 0); + + ApiInitData data; + data.walletDB = sender.m_WalletDB; + data.wallet = sender.m_Wallet; + data.contracts = IShadersManager::CreateInstance(*sender.m_Wallet, "", "", 0); + data.nodeNetwork = nodeNetwork; + + ApiTest api(data); + + // every call must fail immediately and must not leave the shader manager busy + for (int i = 0; i < 2; ++i) + { + InvokeContract message; + message.contract = { 0, 'a', 's', 'm' }; + message.createTx = false; + + bool thrown = false; + try + { + api.onHandleInvokeContract(1, std::move(message)); + } + catch (const jsonrpc_exception& ex) + { + thrown = true; + WALLET_CHECK(ex.code() == ApiError::ContractError); + } + + WALLET_CHECK(thrown); + WALLET_CHECK(api.m_Messages.empty()); + WALLET_CHECK(data.contracts->IsDone()); + } + } } bool RunNegLoop(beam::Negotiator::IBase& a, beam::Negotiator::IBase& b, const char* szTask) @@ -5890,6 +5936,7 @@ int main() TestCalculateAssetCoinsSelection(); TestContractInvoke(); + TestInvokeContractNoNodeConnection(); assert(g_failureCount == 0); return WALLET_CHECK_RESULT;