From 89896e0d74d557cbeb8dcffd5dcc0ccace7f930c Mon Sep 17 00:00:00 2001 From: Adam Gibson Date: Sat, 15 Jan 2022 12:27:13 +0000 Subject: [PATCH 1/2] Add yieldgen report endpoint to API Fixes #1122. An RPC client can request the list of coinjoins as shown in the file datadir/logs/yigen-statement.csv. Authentication is not required, nor a specific wallet, as this is a global list of all coinjoins done so far. A 404 is returned if the file has not yet been created (no maker operations). --- docs/api/wallet-rpc.yaml | 10 ++++++++++ jmclient/jmclient/wallet_rpc.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/docs/api/wallet-rpc.yaml b/docs/api/wallet-rpc.yaml index afcea56..2419c24 100644 --- a/docs/api/wallet-rpc.yaml +++ b/docs/api/wallet-rpc.yaml @@ -123,6 +123,16 @@ paths: $ref: "#/components/responses/ListWallets-200-OK" '404': $ref: '#/components/responses/404-NotFound' + /wallet/yieldgen/report: + get: + summary: get latest report on yield generating activity + operationId: yieldgenreport + description: get list of coinjoins taken part in as maker (across all wallets) + responses: + '200': + $ref: "#/components/responses/YieldGenReport-200-OK" + '404': + $ref: '#/components/responses/404-NotFound' /wallet/{walletname}/address/new/{mixdepth}: get: security: diff --git a/jmclient/jmclient/wallet_rpc.py b/jmclient/jmclient/wallet_rpc.py index 1247e8d..b6179dc 100644 --- a/jmclient/jmclient/wallet_rpc.py +++ b/jmclient/jmclient/wallet_rpc.py @@ -88,6 +88,11 @@ class TransactionFailed(Exception): class NotEnoughCoinsForMaker(Exception): pass +# raised when we cannot read data from our +# yigen-statement csv file: +class YieldGeneratorDataUnreadable(Exception): + pass + def get_ssl_context(cert_directory): """Construct an SSL context factory from the user's privatekey/cert. TODO: @@ -299,6 +304,11 @@ class JMWalletDaemon(Service): request.setResponseCode(409) return self.err(request, "Maker could not start, no coins.") + @app.handle_errors(YieldGeneratorDataUnreadable) + def yieldgenerator_report_unavailable(self, request, failure): + request.setResponseCode(404) + return self.err(request, "Yield generator report not available.") + def check_cookie(self, request): #part after bearer is what we need try: @@ -600,6 +610,29 @@ class JMWalletDaemon(Service): self.services["maker"].stopService() return make_jmwalletd_response(request, status=202) + def get_json_yigen_report(self): + try: + datadir = os.path.join(jm_single().config.datadir, "logs") + with open(os.path.join(datadir, "yigen-statement.csv"), "rb") as f: + yigen_data_raw = f.readlines() + return json.loads(yigen_data_raw) + except: + raise YieldGeneratorDataUnreadable() + + @app.route('/wallet/yieldgen/report', methods=['GET']) + def yieldgen_report(self, request): + # Note that this is *not* a maker function, and + # not wallet specific (the report aggregates over time, + # even with different wallets), and does not require + # an authenticated session (it reads the filesystem, like + # /all) + # note: can raise, most particularly if file has not been + # created because maker never ran (or deleted): + yigen_data = self.get_json_yigen_report() + # this is the successful case; note the object can + # be an empty list: + return make_jmwalletd_response(request, yigen_data=yigen_data) + @app.route('/wallet//lock', methods=['GET']) def lockwallet(self, request, walletname): print_req(request) From beaa8b0170c1f79506eda295db939973d6892b4f Mon Sep 17 00:00:00 2001 From: Adam Gibson Date: Sun, 30 Jan 2022 16:22:05 +0000 Subject: [PATCH 2/2] fixes to API and return format --- docs/api/wallet-rpc.yaml | 17 ++++++++++++++++- jmclient/jmclient/wallet_rpc.py | 16 +++++++++++----- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/docs/api/wallet-rpc.yaml b/docs/api/wallet-rpc.yaml index 2419c24..56cf11d 100644 --- a/docs/api/wallet-rpc.yaml +++ b/docs/api/wallet-rpc.yaml @@ -127,7 +127,11 @@ paths: get: summary: get latest report on yield generating activity operationId: yieldgenreport - description: get list of coinjoins taken part in as maker (across all wallets) + description: "Get list of coinjoins taken part in as maker (across all wallets). + Data returned as list of strings, each one in the same comma separated format as found in yigen-statement.csv. + Note that this returns all lines in the file, including the lines that are only present to represent the starting + of a bot. Those lines contain the word Connected and can be thus discarded. + The header line is also delivered and so can be ignored as per the client requirements." responses: '200': $ref: "#/components/responses/YieldGenReport-200-OK" @@ -577,6 +581,11 @@ components: items: type: string example: wallet.jmdat + YieldGenReportResponse: + type: array + items: + type: string + example: "2021/10/26 16:40:21,133986791,1,200000000,2680,2680,0.08," SessionResponse: type: object required: @@ -893,6 +902,12 @@ components: application/json: schema: $ref: "#/components/schemas/FreezeResponse" + YieldGenReport-200-OK: + description: "get list of coinjoins taken part in as maker (across all wallets)" + content: + application/json: + schema: + $ref: "#/components/schemas/YieldGenReportResponse" 202-Accepted: description: The request has been submitted successfully for processing, but the processing has not been completed. # Clientside error responses diff --git a/jmclient/jmclient/wallet_rpc.py b/jmclient/jmclient/wallet_rpc.py index b6179dc..f71ba57 100644 --- a/jmclient/jmclient/wallet_rpc.py +++ b/jmclient/jmclient/wallet_rpc.py @@ -611,12 +611,18 @@ class JMWalletDaemon(Service): return make_jmwalletd_response(request, status=202) def get_json_yigen_report(self): + """ Returns a json object whose contents are: + a list of strings, each string is a comma separated record of + a coinjoin event, directly read from yigen-statement.csv without + further processing. + """ try: - datadir = os.path.join(jm_single().config.datadir, "logs") - with open(os.path.join(datadir, "yigen-statement.csv"), "rb") as f: - yigen_data_raw = f.readlines() - return json.loads(yigen_data_raw) - except: + datadir = os.path.join(jm_single().datadir, "logs") + with open(os.path.join(datadir, "yigen-statement.csv"), "r") as f: + yigen_data = f.readlines() + return yigen_data + except Exception as e: + jlog.warn("Yigen report failed to find file: {}".format(repr(e))) raise YieldGeneratorDataUnreadable() @app.route('/wallet/yieldgen/report', methods=['GET'])