diff --git a/jmclient/jmclient/__init__.py b/jmclient/jmclient/__init__.py index 05817ae..e410638 100644 --- a/jmclient/jmclient/__init__.py +++ b/jmclient/jmclient/__init__.py @@ -61,7 +61,7 @@ from .wallet_utils import ( from .wallet_service import WalletService from .maker import Maker from .yieldgenerator import YieldGenerator, YieldGeneratorBasic, ygmain, \ - YieldGeneratorService, YieldGeneratorServiceSetupFailed + YieldGeneratorService from .snicker_receiver import SNICKERError, SNICKERReceiver, SNICKERReceiverService from .payjoin import (parse_payjoin_setup, send_payjoin, JMBIP78ReceiverManager) diff --git a/jmclient/jmclient/wallet-rpc-api.md b/jmclient/jmclient/wallet-rpc-api.md index 2729309..faa789c 100644 --- a/jmclient/jmclient/wallet-rpc-api.md +++ b/jmclient/jmclient/wallet-rpc-api.md @@ -287,7 +287,7 @@ Start the yield generator service. ##### Description -Start the yield generator service with the configuration settings specified in the POST request. Note that if fidelity bonds are enabled in the wallet, and a timelock address has been generated, and then funded, the fidelity bond will automatically be advertised without any specific configuration in this request. +Start the yield generator service with the configuration settings specified in the POST request. Note that if fidelity bonds are enabled in the wallet, and a timelock address has been generated, and then funded, the fidelity bond will automatically be advertised without any specific configuration in this request. Note that if the wallet does not have confirmed coins, or another taker or maker coinjoin service is already running, the maker will not start. ##### Parameters @@ -303,6 +303,7 @@ Start the yield generator service with the configuration settings specified in t | 400 | Bad request format. | | 401 | Unable to authorise the credentials that were supplied. | | 404 | Item not found. | +| 409 | Maker could not start without confirmed balance. | | 503 | The server is not ready to process the request. | ##### Security diff --git a/jmclient/jmclient/wallet-rpc-api.yaml b/jmclient/jmclient/wallet-rpc-api.yaml index 2078a24..00e0373 100644 --- a/jmclient/jmclient/wallet-rpc-api.yaml +++ b/jmclient/jmclient/wallet-rpc-api.yaml @@ -234,7 +234,7 @@ paths: - bearerAuth: [] summary: Start the yield generator service. operationId: startmaker - description: Start the yield generator service with the configuration settings specified in the POST request. Note that if fidelity bonds are enabled in the wallet, and a timelock address has been generated, and then funded, the fidelity bond will automatically be advertised without any specific configuration in this request. + description: Start the yield generator service with the configuration settings specified in the POST request. Note that if fidelity bonds are enabled in the wallet, and a timelock address has been generated, and then funded, the fidelity bond will automatically be advertised without any specific configuration in this request. Note that if the wallet does not have confirmed coins, or another taker or maker coinjoin service is already running, the maker will not start. parameters: - name: walletname in: path @@ -259,6 +259,8 @@ paths: $ref: '#/components/responses/401-Unauthorized' '404': $ref: '#/components/responses/404-NotFound' + '409': + $ref: '#/components/responses/409-No-Coins' '503': $ref: '#/components/responses/503-ServiceUnavailable' /wallet/{walletname}/maker/stop: @@ -863,6 +865,12 @@ components: application/json: schema: $ref: '#/components/schemas/ErrorMessage' + 409-No-Coins: + description: Maker could not start without confirmed balance. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' 404-NotFound: description: Item not found. content: diff --git a/jmclient/jmclient/wallet_rpc.py b/jmclient/jmclient/wallet_rpc.py index df9ee0d..5d82f4d 100644 --- a/jmclient/jmclient/wallet_rpc.py +++ b/jmclient/jmclient/wallet_rpc.py @@ -21,8 +21,7 @@ from jmclient import Taker, jm_single, \ create_wallet, get_max_cj_fee_values, \ StorageError, StoragePasswordError, JmwalletdWebSocketServerFactory, \ JmwalletdWebSocketServerProtocol, RetryableStorageError, \ - SegwitWalletFidelityBonds, wallet_gettimelockaddress, \ - YieldGeneratorServiceSetupFailed + SegwitWalletFidelityBonds, wallet_gettimelockaddress from jmbase.support import get_log jlog = get_log() @@ -84,6 +83,11 @@ class ServiceNotStarted(Exception): class TransactionFailed(Exception): pass +# raised when we tried to start a Maker, +# but the wallet was empty/not enough. +class NotEnoughCoinsForMaker(Exception): + pass + def get_ssl_context(cert_directory): """Construct an SSL context factory from the user's privatekey/cert. TODO: @@ -289,6 +293,12 @@ class JMWalletDaemon(Service): request.setResponseCode(409) return self.err(request, "Transaction failed.") + @app.handle_errors(NotEnoughCoinsForMaker) + def not_enough_coins(self, request, failure): + # as above, 409 may not be ideal + request.setResponseCode(409) + return self.err(request, "Maker could not start, no coins.") + def check_cookie(self, request): #part after bearer is what we need try: @@ -560,14 +570,26 @@ class JMWalletDaemon(Service): self.activate_coinjoin_state(CJ_NOT_RUNNING) def setup(): # note this returns False if we cannot update the state. - return self.activate_coinjoin_state(CJ_MAKER_RUNNING) + if not self.activate_coinjoin_state(CJ_MAKER_RUNNING): + raise ServiceAlreadyStarted() + # don't even start up the service if there aren't any coins + # to offer: + def setup_sanitycheck_balance(): + # note: this will only be non-zero if coins are confirmed. + # note: a call to start_maker necessarily is after a successful + # sync has already happened (this is different from CLI yg). + # note: an edge case of dusty amounts is lost here; it will get + # picked up by Maker.try_to_create_my_orders(). + if not len(self.wallet_service.get_balance_by_mixdepth( + verbose=False, minconfs=1)) > 0: + raise NotEnoughCoinsForMaker() + self.services["maker"].addCleanup(cleanup) self.services["maker"].addSetup(setup) + self.services["maker"].addSetup(setup_sanitycheck_balance) # Service startup now checks and updates coinjoin state: - try: - self.services["maker"].startService() - except YieldGeneratorServiceSetupFailed: - raise ServiceAlreadyStarted() + self.services["maker"].startService() + return make_jmwalletd_response(request, status=202) @app.route('/wallet//maker/stop', methods=['GET']) diff --git a/jmclient/jmclient/yieldgenerator.py b/jmclient/jmclient/yieldgenerator.py index b813133..444483e 100644 --- a/jmclient/jmclient/yieldgenerator.py +++ b/jmclient/jmclient/yieldgenerator.py @@ -268,9 +268,6 @@ class YieldGeneratorBasic(YieldGenerator): cjoutmix = (input_mixdepth + 1) % (self.wallet_service.mixdepth + 1) return self.wallet_service.get_internal_addr(cjoutmix) -class YieldGeneratorServiceSetupFailed(Exception): - pass - class YieldGeneratorService(Service): def __init__(self, wallet_service, daemon_host, daemon_port, yg_config): self.wallet_service = wallet_service @@ -292,8 +289,11 @@ class YieldGeneratorService(Service): no need to check this here. """ for setup in self.setup_fns: - if not setup(): - raise YieldGeneratorServiceSetupFailed + # we do not catch Exceptions in setup, + # deliberately; this must be caught and distinguished + # by whoever started the service. + setup() + # TODO genericise to any YG class: self.yieldgen = YieldGeneratorBasic(self.wallet_service, self.yg_config) self.clientfactory = JMClientProtocolFactory(self.yieldgen, proto_type="MAKER")