2013-02-26 13:56:48 +01:00
#!/usr/bin/env python
#
# Electrum - lightweight Bitcoin client
# Copyright (C) 2011 thomasv@gitorious
#
2016-02-23 11:36:42 +01:00
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
2013-02-26 13:56:48 +01:00
#
2016-02-23 11:36:42 +01:00
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
2013-02-26 13:56:48 +01:00
#
2016-02-23 11:36:42 +01:00
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
2024-11-22 14:46:21 +01:00
import io
2015-05-28 15:22:30 +02:00
import sys
2014-06-24 16:12:43 +03:00
import datetime
import copy
2015-05-28 15:22:30 +02:00
import argparse
2015-05-30 18:49:58 +02:00
import json
2015-05-31 22:42:34 +02:00
import ast
2015-07-14 16:37:04 +02:00
import base64
2019-01-30 09:26:27 +01:00
import operator
2019-08-15 13:17:16 +02:00
import asyncio
2019-09-12 19:22:55 +02:00
import inspect
2021-06-08 16:33:55 +02:00
from collections import defaultdict
2019-09-01 17:52:02 +02:00
from functools import wraps , partial
2019-11-18 01:31:33 +00:00
from itertools import repeat
2022-12-05 18:49:21 +02:00
from decimal import Decimal , InvalidOperation
2019-10-23 17:09:41 +02:00
from typing import Optional , TYPE_CHECKING , Dict , List
commands: add "version_info" cmd
example:
```
$ ./run_electrum -o version_info
{
"aiohttp.version": "3.8.1",
"aiorpcx.version": "0.22.1",
"certifi.version": "2021.10.08",
"cryptodome.version": null,
"cryptography.path": "/home/user/.local/lib/python3.8/site-packages/cryptography",
"cryptography.version": "3.4.6",
"dnspython.version": "2.2.0",
"electrum.path": "/home/user/wspace/electrum/electrum",
"electrum.version": "4.2.1",
"hidapi.version": "0.11.0.post2",
"libsecp256k1.path": "/home/user/wspace/electrum/electrum/libsecp256k1.so.0",
"libusb.path": "libusb-1.0.so",
"libusb.version": "1.0.23.11397",
"libzbar.path": "/home/user/wspace/electrum/electrum/libzbar.so.0",
"pyaes.version": "1.3.0",
"pyqt.path": "/usr/lib/python3/dist-packages/PyQt5",
"pyqt.version": "5.14.1",
"qt.version": "5.12.8"
}
```
2022-04-11 17:05:26 +02:00
import os
2015-05-28 15:22:30 +02:00
2024-06-17 13:38:54 +02:00
import electrum_ecc as ecc
from . import util
2021-03-31 04:33:33 +02:00
from . import keystore
2024-11-22 14:46:21 +01:00
from . lnmsg import OnionWireSerializer
from . logging import Logger
from . onion_message import create_blinded_path , send_onion_message_to
from . util import ( bfh , format_satoshis , json_decode , json_normalize , is_hash256_str , is_hex_str , to_bytes ,
parse_max_spend , to_decimal , UserFacingException , InvalidPassword )
2018-07-11 17:38:47 +02:00
from . import bitcoin
2019-10-23 17:09:41 +02:00
from . bitcoin import is_address , hash_160 , COIN
2019-02-21 22:17:06 +01:00
from . bip32 import BIP32Node
2017-11-12 16:15:06 -06:00
from . i18n import _
2019-10-23 17:09:41 +02:00
from . transaction import ( Transaction , multisig_script , TxOutput , PartialTransaction , PartialTxOutput ,
tx_from_any , PartialTxInput , TxOutpoint )
2023-09-07 13:18:23 +00:00
from . import transaction
2020-05-31 12:49:49 +02:00
from . invoices import PR_PAID , PR_UNPAID , PR_UNKNOWN , PR_EXPIRED
2018-10-03 17:13:46 +02:00
from . synchronizer import Notifier
2021-03-31 04:33:33 +02:00
from . wallet import Abstract_Wallet , create_new_wallet , restore_wallet_from_text , Deterministic_Wallet , BumpFeeStrategy , Imported_Wallet
2019-02-20 18:01:43 +01:00
from . address_synchronizer import TX_HEIGHT_LOCAL
2018-05-23 15:46:30 +02:00
from . mnemonic import Mnemonic
2019-01-29 19:01:04 +01:00
from . lnutil import SENT , RECEIVED
2020-04-16 12:39:12 +02:00
from . lnutil import LnFeatures
2024-10-21 15:04:37 +02:00
from . lntransport import extract_nodeid
2024-11-29 10:38:57 +01:00
from . lnutil import channel_id_from_funding_tx
2023-09-08 14:55:22 +00:00
from . plugin import run_hook , DeviceMgr , Plugins
2019-08-19 12:46:31 +02:00
from . version import ELECTRUM_VERSION
2019-09-01 17:52:02 +02:00
from . simple_config import SimpleConfig
2022-03-15 13:03:34 +01:00
from . invoices import Invoice
2025-02-24 12:20:44 +01:00
from . fee_policy import FeePolicy
2020-05-22 10:39:30 +02:00
from . import submarine_swaps
commands: add "version_info" cmd
example:
```
$ ./run_electrum -o version_info
{
"aiohttp.version": "3.8.1",
"aiorpcx.version": "0.22.1",
"certifi.version": "2021.10.08",
"cryptodome.version": null,
"cryptography.path": "/home/user/.local/lib/python3.8/site-packages/cryptography",
"cryptography.version": "3.4.6",
"dnspython.version": "2.2.0",
"electrum.path": "/home/user/wspace/electrum/electrum",
"electrum.version": "4.2.1",
"hidapi.version": "0.11.0.post2",
"libsecp256k1.path": "/home/user/wspace/electrum/electrum/libsecp256k1.so.0",
"libusb.path": "libusb-1.0.so",
"libusb.version": "1.0.23.11397",
"libzbar.path": "/home/user/wspace/electrum/electrum/libzbar.so.0",
"pyaes.version": "1.3.0",
"pyqt.path": "/usr/lib/python3/dist-packages/PyQt5",
"pyqt.version": "5.14.1",
"qt.version": "5.12.8"
}
```
2022-04-11 17:05:26 +02:00
from . import GuiImportError
from . import crypto
2022-10-30 15:25:12 +11:00
from . import constants
2023-02-26 12:14:25 +00:00
from . import descriptor
2018-10-25 23:01:53 +02:00
if TYPE_CHECKING :
from . network import Network
2019-09-04 20:16:47 +02:00
from . daemon import Daemon
2018-10-22 16:41:25 +02:00
2017-09-04 14:43:31 +02:00
2019-09-09 22:15:11 +02:00
known_commands = { } # type: Dict[str, Command]
2015-06-01 06:10:06 +02:00
2017-02-22 11:23:12 +01:00
cli/rpc: nicer error messages and error-passing
Previously, generally, in case of any error, commands would raise a generic "Exception()" and the CLI/RPC would convert that and return it as `str(e)`.
With this change, we now distinguish "user-facing exceptions" (e.g. "Password required" or "wallet not loaded") and "internal errors" (e.g. bugs).
- for "user-facing exceptions", the behaviour is unchanged
- for "internal errors", we now pass around the traceback (e.g. from daemon server to rpc client) and show it to the user (previously, assuming there was a daemon running, the user could only retrieve the exception from the log of that daemon). These errors use a new jsonrpc error code int (code 2).
As the logic only changes for "internal errors", I deem this change not to be compatibility-breaking.
----------
Examples follow.
Consider the following two commands:
```
@command('')
async def errorgood(self):
from electrum.util import UserFacingException
raise UserFacingException("heyheyhey")
@command('')
async def errorbad(self):
raise Exception("heyheyhey")
```
----------
(before change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9221)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
heyheyhey
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
```
----------
(after change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9254)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
(inside daemon): Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/daemon.py", line 254, in handle
response['result'] = await f(*params)
File "/home/user/wspace/electrum/electrum/daemon.py", line 361, in run_cmdline
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
internal error while executing RPC
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
0.78 | E | __main__ | error running command (without daemon)
Traceback (most recent call last):
File "/home/user/wspace/electrum/./run_electrum", line 534, in handle_cmd
result = fut.result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 458, in result
return self.__get_result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
raise self._exception
File "/home/user/wspace/electrum/./run_electrum", line 255, in run_offline_command
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 2, "message": "internal error while executing RPC", "data": {"exception": "Exception('heyheyhey')", "traceback": "Traceback (most recent call last):\n File \"/home/user/wspace/electrum/electrum/daemon.py\", line 254, in handle\n response['result'] = await f(*params)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 163, in func_wrapper\n return await func(*args, **kwargs)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 217, in errorbad\n raise Exception(\"heyheyhey\")\nException: heyheyhey\n"}}}
```
2024-02-12 19:02:02 +00:00
class NotSynchronizedException ( UserFacingException ) :
2020-04-08 03:09:08 +00:00
pass
2021-04-16 16:03:25 +02:00
def satoshis_or_max ( amount ) :
2021-09-15 20:05:15 +05:30
return satoshis ( amount ) if not parse_max_spend ( amount ) else amount
2021-04-16 16:03:25 +02:00
2025-01-23 12:58:28 +01:00
2017-02-22 11:23:12 +01:00
def satoshis ( amount ) :
# satoshi conversion must not be performed by the parser
commands: fix satoshis decimal conversion in payto cmd and others
When called via jsonrpc (but not via cli) with non-string amounts,
there could be a rounding error resulting in sending 1 sat less.
example:
```
$ ./run_electrum --testnet -w ~/.electrum/testnet/wallets/test_segwit_2 paytomany '[["tb1q6k5h4cz6ra8nzhg90xm9wldvadgh0fpttfthcg", 0.00033389]]' --fee 0
02000000000101b9e6018acb16952e3c9618b069df404dc85544eda8120e5f6e7cd7e94ce5ae8d0100000000fdffffff02fd8100000000000016001410c5b97085ec1637a9f702852f5a81f650fae1566d82000000000000160014d5a97ae05a1f4f315d0579b6577daceb5177a42b024730440220251d2ce83f6e69273de8e9be8602fbcf72b9157e1c0116161fa52f7e04db6e4302202d84045cc6b7056a215d1db3f59884e28dadd5257e1a3960068f90df90b452d1012102b0eff3bf364a2ab5effe952cba33521ebede81dac88c71951a5ed598cb48347b3a022500
$ curl --data-binary '{"id":"curltext","method":"paytomany","params":{"outputs":[["tb1q6k5h4cz6ra8nzhg90xm9wldvadgh0fpttfthcg", 0.00033389]], "fee": 0, "wallet": "/home/user/.electrum/testnet/wallets/test_segwit_2"}}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "result": "02000000000101b9e6018acb16952e3c9618b069df404dc85544eda8120e5f6e7cd7e94ce5ae8d0100000000fdffffff02fe8100000000000016001410c5b97085ec1637a9f702852f5a81f650fae1566c82000000000000160014d5a97ae05a1f4f315d0579b6577daceb5177a42b0247304402206ef66b845ca298c14dc6e8049cba9ed19db1671132194518ce5d521de6f5df8802205ca4b1aee703e3b98331fb9b88210917b385560020c8b2a8a88da38996b101c4012102b0eff3bf364a2ab5effe952cba33521ebede81dac88c71951a5ed598cb48347b39022500"}
```
^ note that first tx has output for 0.00033389, second tx has output for 0.00033388
fixes https://github.com/spesmilo/electrum/issues/8274
2023-03-22 12:22:36 +00:00
return int ( COIN * to_decimal ( amount ) ) if amount is not None else None
2017-02-22 11:23:12 +01:00
2025-01-23 12:58:28 +01:00
2020-05-28 13:11:32 +02:00
def format_satoshis ( x ) :
commands: fix satoshis decimal conversion in payto cmd and others
When called via jsonrpc (but not via cli) with non-string amounts,
there could be a rounding error resulting in sending 1 sat less.
example:
```
$ ./run_electrum --testnet -w ~/.electrum/testnet/wallets/test_segwit_2 paytomany '[["tb1q6k5h4cz6ra8nzhg90xm9wldvadgh0fpttfthcg", 0.00033389]]' --fee 0
02000000000101b9e6018acb16952e3c9618b069df404dc85544eda8120e5f6e7cd7e94ce5ae8d0100000000fdffffff02fd8100000000000016001410c5b97085ec1637a9f702852f5a81f650fae1566d82000000000000160014d5a97ae05a1f4f315d0579b6577daceb5177a42b024730440220251d2ce83f6e69273de8e9be8602fbcf72b9157e1c0116161fa52f7e04db6e4302202d84045cc6b7056a215d1db3f59884e28dadd5257e1a3960068f90df90b452d1012102b0eff3bf364a2ab5effe952cba33521ebede81dac88c71951a5ed598cb48347b3a022500
$ curl --data-binary '{"id":"curltext","method":"paytomany","params":{"outputs":[["tb1q6k5h4cz6ra8nzhg90xm9wldvadgh0fpttfthcg", 0.00033389]], "fee": 0, "wallet": "/home/user/.electrum/testnet/wallets/test_segwit_2"}}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "result": "02000000000101b9e6018acb16952e3c9618b069df404dc85544eda8120e5f6e7cd7e94ce5ae8d0100000000fdffffff02fe8100000000000016001410c5b97085ec1637a9f702852f5a81f650fae1566c82000000000000160014d5a97ae05a1f4f315d0579b6577daceb5177a42b0247304402206ef66b845ca298c14dc6e8049cba9ed19db1671132194518ce5d521de6f5df8802205ca4b1aee703e3b98331fb9b88210917b385560020c8b2a8a88da38996b101c4012102b0eff3bf364a2ab5effe952cba33521ebede81dac88c71951a5ed598cb48347b39022500"}
```
^ note that first tx has output for 0.00033389, second tx has output for 0.00033388
fixes https://github.com/spesmilo/electrum/issues/8274
2023-03-22 12:22:36 +00:00
return str ( to_decimal ( x ) / COIN ) if x is not None else None
2017-02-22 11:23:12 +01:00
2020-02-04 17:56:52 +01:00
2015-06-01 06:10:06 +02:00
class Command :
def __init__ ( self , func , s ) :
self . name = func . __name__
self . requires_network = ' n ' in s
self . requires_wallet = ' w ' in s
self . requires_password = ' p ' in s
2021-07-07 16:44:27 +03:00
self . requires_lightning = ' l ' in s
2015-06-01 06:10:06 +02:00
self . description = func . __doc__
2015-09-07 13:13:04 +02:00
self . help = self . description . split ( ' . ' ) [ 0 ] if self . description else None
2017-01-22 21:25:24 +03:00
varnames = func . __code__ . co_varnames [ 1 : func . __code__ . co_argcount ]
self . defaults = func . __defaults__
2015-06-01 06:10:06 +02:00
if self . defaults :
n = len ( self . defaults )
self . params = list ( varnames [ : - n ] )
self . options = list ( varnames [ - n : ] )
else :
self . params = list ( varnames )
self . options = [ ]
self . defaults = [ ]
commands: make 'wallet'-mangling in decorator less obscure, and fixes
- some commands expect a 'wallet_path' arg, while others expect 'wallet'
- 'wallet_path' in the end is supposed to be a str,
'wallet' in the end is supposed to be an Optional[Abstract_Wallet]
- initially, in the decorator, 'wallet' can be a str, in which case
the decorator replaces it with an Abstract_Wallet (from the daemon)
- Previously the decorator sometimes converted 'wallet_path' to 'wallet'.
This was because when called from the CLI it was always given 'wallet_path' (and never 'wallet),
while when called from JSON-RPC it was given either 'wallet' or 'wallet_path' (depending on command).
Now, the CLI also behaves as JSON-RPC, and hence 'wallet_path' and 'wallet' are fully separate.
- A bug is fixed where, when a command that only optionally takes a 'wallet' (such as gettransaction),
was called from the JSON-RPC with the arg present, it raised; and when called from CLI with the arg present
the arg was not actually passed to the command.
- A bug is fixed where if one command calls another command (that both take a 'wallet'),
it would raise (due to assuming 'wallet' is str and needs to be converted to Abstract_Wallet).
This fixes #6154.
-----
$ ./run_electrum --testnet daemon -d
$ ./run_electrum --testnet load_wallet -w ~/.electrum/testnet/wallets/default_wallet
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"gettransaction","params":{"txid":"9f43ff71ea2594873e4e7d15e61254a3661ff2df1af76325c854d9aa199550ce"}}' http://user:pass@127.0.0.1:7777
{"jsonrpc": "2.0", "result": "0200000001caaac6b5eb916e3067d0224f942fb331ce1dcfb4031cfb479e7941dcf95e409801000000fdfe0000483045022100e2a508bb78c2172eb03f081a342454ba1d24669e959700973b1a742a4fedd0c302203174e06feda265031cf9aa0364d4a4eafb71b0c0a62e76be7795cfbb307b677a01483045022100d0e14564838fac754395158741d64c73da2b86e7900dfdc6a63c7492b232ba130220778e7e7c21d94ebcd340057302aeff7e9a797a3aa3e0ac4884e9ff27339ea6e9014c69522102091f0b4d8ab30016a5d1c088249e02883fad8160f06fa53588ad8598650a3e6221035f2f8263bb3608d6cc4ee03bd4cb8d65c4d70af71049f05fbfee4978832a1fd22103fe42dab58718ea0413f7c8de693cdeee22ce19b1dc34c0bbdd7a48245465c5a253aefdffffff01cb9f0700000000001976a914c13fd6294d1be7b9410a5538f4b4ef10fc594ee788ac802c1800", "id": "curltext"}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"gettransaction","params":{"txid":"9f43ff71ea2594873e4e7d15e61254a3661ff2df1af76325c854d9aa199550ce", "wallet":"~/.electrum/testnet/wallets/default_wallet"}}' http://user:pass@127.0.0.1:7777
{"jsonrpc": "2.0", "error": {"code": -32000, "message": "'str' object has no attribute 'db'"}, "id": "curltext"}
2020-05-14 16:33:02 +02:00
# sanity checks
if self . requires_password :
assert self . requires_wallet
for varname in ( ' wallet_path ' , ' wallet ' ) :
if varname in varnames :
assert varname in self . options
assert not ( ' wallet_path ' in varnames and ' wallet ' in varnames )
if self . requires_wallet :
assert ' wallet ' in varnames
2015-06-01 06:10:06 +02:00
def command ( s ) :
def decorator ( func ) :
global known_commands
name = func . __name__
known_commands [ name ] = Command ( func , s )
@wraps ( func )
2019-09-06 11:06:08 +02:00
async def func_wrapper ( * args , * * kwargs ) :
2019-09-09 22:15:11 +02:00
cmd_runner = args [ 0 ] # type: Commands
cmd = known_commands [ func . __name__ ] # type: Command
2017-10-07 08:19:14 +02:00
password = kwargs . get ( ' password ' )
2019-09-06 11:06:08 +02:00
daemon = cmd_runner . daemon
if daemon :
commands: make 'wallet'-mangling in decorator less obscure, and fixes
- some commands expect a 'wallet_path' arg, while others expect 'wallet'
- 'wallet_path' in the end is supposed to be a str,
'wallet' in the end is supposed to be an Optional[Abstract_Wallet]
- initially, in the decorator, 'wallet' can be a str, in which case
the decorator replaces it with an Abstract_Wallet (from the daemon)
- Previously the decorator sometimes converted 'wallet_path' to 'wallet'.
This was because when called from the CLI it was always given 'wallet_path' (and never 'wallet),
while when called from JSON-RPC it was given either 'wallet' or 'wallet_path' (depending on command).
Now, the CLI also behaves as JSON-RPC, and hence 'wallet_path' and 'wallet' are fully separate.
- A bug is fixed where, when a command that only optionally takes a 'wallet' (such as gettransaction),
was called from the JSON-RPC with the arg present, it raised; and when called from CLI with the arg present
the arg was not actually passed to the command.
- A bug is fixed where if one command calls another command (that both take a 'wallet'),
it would raise (due to assuming 'wallet' is str and needs to be converted to Abstract_Wallet).
This fixes #6154.
-----
$ ./run_electrum --testnet daemon -d
$ ./run_electrum --testnet load_wallet -w ~/.electrum/testnet/wallets/default_wallet
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"gettransaction","params":{"txid":"9f43ff71ea2594873e4e7d15e61254a3661ff2df1af76325c854d9aa199550ce"}}' http://user:pass@127.0.0.1:7777
{"jsonrpc": "2.0", "result": "0200000001caaac6b5eb916e3067d0224f942fb331ce1dcfb4031cfb479e7941dcf95e409801000000fdfe0000483045022100e2a508bb78c2172eb03f081a342454ba1d24669e959700973b1a742a4fedd0c302203174e06feda265031cf9aa0364d4a4eafb71b0c0a62e76be7795cfbb307b677a01483045022100d0e14564838fac754395158741d64c73da2b86e7900dfdc6a63c7492b232ba130220778e7e7c21d94ebcd340057302aeff7e9a797a3aa3e0ac4884e9ff27339ea6e9014c69522102091f0b4d8ab30016a5d1c088249e02883fad8160f06fa53588ad8598650a3e6221035f2f8263bb3608d6cc4ee03bd4cb8d65c4d70af71049f05fbfee4978832a1fd22103fe42dab58718ea0413f7c8de693cdeee22ce19b1dc34c0bbdd7a48245465c5a253aefdffffff01cb9f0700000000001976a914c13fd6294d1be7b9410a5538f4b4ef10fc594ee788ac802c1800", "id": "curltext"}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"gettransaction","params":{"txid":"9f43ff71ea2594873e4e7d15e61254a3661ff2df1af76325c854d9aa199550ce", "wallet":"~/.electrum/testnet/wallets/default_wallet"}}' http://user:pass@127.0.0.1:7777
{"jsonrpc": "2.0", "error": {"code": -32000, "message": "'str' object has no attribute 'db'"}, "id": "curltext"}
2020-05-14 16:33:02 +02:00
if ' wallet_path ' in cmd . options and kwargs . get ( ' wallet_path ' ) is None :
kwargs [ ' wallet_path ' ] = daemon . config . get_wallet_path ( )
if cmd . requires_wallet and kwargs . get ( ' wallet ' ) is None :
kwargs [ ' wallet ' ] = daemon . config . get_wallet_path ( )
if ' wallet ' in cmd . options :
2024-01-02 18:46:02 +03:00
wallet = kwargs . get ( ' wallet ' , None )
if isinstance ( wallet , str ) :
wallet = daemon . get_wallet ( wallet )
commands: make 'wallet'-mangling in decorator less obscure, and fixes
- some commands expect a 'wallet_path' arg, while others expect 'wallet'
- 'wallet_path' in the end is supposed to be a str,
'wallet' in the end is supposed to be an Optional[Abstract_Wallet]
- initially, in the decorator, 'wallet' can be a str, in which case
the decorator replaces it with an Abstract_Wallet (from the daemon)
- Previously the decorator sometimes converted 'wallet_path' to 'wallet'.
This was because when called from the CLI it was always given 'wallet_path' (and never 'wallet),
while when called from JSON-RPC it was given either 'wallet' or 'wallet_path' (depending on command).
Now, the CLI also behaves as JSON-RPC, and hence 'wallet_path' and 'wallet' are fully separate.
- A bug is fixed where, when a command that only optionally takes a 'wallet' (such as gettransaction),
was called from the JSON-RPC with the arg present, it raised; and when called from CLI with the arg present
the arg was not actually passed to the command.
- A bug is fixed where if one command calls another command (that both take a 'wallet'),
it would raise (due to assuming 'wallet' is str and needs to be converted to Abstract_Wallet).
This fixes #6154.
-----
$ ./run_electrum --testnet daemon -d
$ ./run_electrum --testnet load_wallet -w ~/.electrum/testnet/wallets/default_wallet
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"gettransaction","params":{"txid":"9f43ff71ea2594873e4e7d15e61254a3661ff2df1af76325c854d9aa199550ce"}}' http://user:pass@127.0.0.1:7777
{"jsonrpc": "2.0", "result": "0200000001caaac6b5eb916e3067d0224f942fb331ce1dcfb4031cfb479e7941dcf95e409801000000fdfe0000483045022100e2a508bb78c2172eb03f081a342454ba1d24669e959700973b1a742a4fedd0c302203174e06feda265031cf9aa0364d4a4eafb71b0c0a62e76be7795cfbb307b677a01483045022100d0e14564838fac754395158741d64c73da2b86e7900dfdc6a63c7492b232ba130220778e7e7c21d94ebcd340057302aeff7e9a797a3aa3e0ac4884e9ff27339ea6e9014c69522102091f0b4d8ab30016a5d1c088249e02883fad8160f06fa53588ad8598650a3e6221035f2f8263bb3608d6cc4ee03bd4cb8d65c4d70af71049f05fbfee4978832a1fd22103fe42dab58718ea0413f7c8de693cdeee22ce19b1dc34c0bbdd7a48245465c5a253aefdffffff01cb9f0700000000001976a914c13fd6294d1be7b9410a5538f4b4ef10fc594ee788ac802c1800", "id": "curltext"}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"gettransaction","params":{"txid":"9f43ff71ea2594873e4e7d15e61254a3661ff2df1af76325c854d9aa199550ce", "wallet":"~/.electrum/testnet/wallets/default_wallet"}}' http://user:pass@127.0.0.1:7777
{"jsonrpc": "2.0", "error": {"code": -32000, "message": "'str' object has no attribute 'db'"}, "id": "curltext"}
2020-05-14 16:33:02 +02:00
if wallet is None :
cli/rpc: nicer error messages and error-passing
Previously, generally, in case of any error, commands would raise a generic "Exception()" and the CLI/RPC would convert that and return it as `str(e)`.
With this change, we now distinguish "user-facing exceptions" (e.g. "Password required" or "wallet not loaded") and "internal errors" (e.g. bugs).
- for "user-facing exceptions", the behaviour is unchanged
- for "internal errors", we now pass around the traceback (e.g. from daemon server to rpc client) and show it to the user (previously, assuming there was a daemon running, the user could only retrieve the exception from the log of that daemon). These errors use a new jsonrpc error code int (code 2).
As the logic only changes for "internal errors", I deem this change not to be compatibility-breaking.
----------
Examples follow.
Consider the following two commands:
```
@command('')
async def errorgood(self):
from electrum.util import UserFacingException
raise UserFacingException("heyheyhey")
@command('')
async def errorbad(self):
raise Exception("heyheyhey")
```
----------
(before change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9221)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
heyheyhey
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
```
----------
(after change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9254)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
(inside daemon): Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/daemon.py", line 254, in handle
response['result'] = await f(*params)
File "/home/user/wspace/electrum/electrum/daemon.py", line 361, in run_cmdline
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
internal error while executing RPC
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
0.78 | E | __main__ | error running command (without daemon)
Traceback (most recent call last):
File "/home/user/wspace/electrum/./run_electrum", line 534, in handle_cmd
result = fut.result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 458, in result
return self.__get_result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
raise self._exception
File "/home/user/wspace/electrum/./run_electrum", line 255, in run_offline_command
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 2, "message": "internal error while executing RPC", "data": {"exception": "Exception('heyheyhey')", "traceback": "Traceback (most recent call last):\n File \"/home/user/wspace/electrum/electrum/daemon.py\", line 254, in handle\n response['result'] = await f(*params)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 163, in func_wrapper\n return await func(*args, **kwargs)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 217, in errorbad\n raise Exception(\"heyheyhey\")\nException: heyheyhey\n"}}}
```
2024-02-12 19:02:02 +00:00
raise UserFacingException ( ' wallet not loaded ' )
commands: make 'wallet'-mangling in decorator less obscure, and fixes
- some commands expect a 'wallet_path' arg, while others expect 'wallet'
- 'wallet_path' in the end is supposed to be a str,
'wallet' in the end is supposed to be an Optional[Abstract_Wallet]
- initially, in the decorator, 'wallet' can be a str, in which case
the decorator replaces it with an Abstract_Wallet (from the daemon)
- Previously the decorator sometimes converted 'wallet_path' to 'wallet'.
This was because when called from the CLI it was always given 'wallet_path' (and never 'wallet),
while when called from JSON-RPC it was given either 'wallet' or 'wallet_path' (depending on command).
Now, the CLI also behaves as JSON-RPC, and hence 'wallet_path' and 'wallet' are fully separate.
- A bug is fixed where, when a command that only optionally takes a 'wallet' (such as gettransaction),
was called from the JSON-RPC with the arg present, it raised; and when called from CLI with the arg present
the arg was not actually passed to the command.
- A bug is fixed where if one command calls another command (that both take a 'wallet'),
it would raise (due to assuming 'wallet' is str and needs to be converted to Abstract_Wallet).
This fixes #6154.
-----
$ ./run_electrum --testnet daemon -d
$ ./run_electrum --testnet load_wallet -w ~/.electrum/testnet/wallets/default_wallet
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"gettransaction","params":{"txid":"9f43ff71ea2594873e4e7d15e61254a3661ff2df1af76325c854d9aa199550ce"}}' http://user:pass@127.0.0.1:7777
{"jsonrpc": "2.0", "result": "0200000001caaac6b5eb916e3067d0224f942fb331ce1dcfb4031cfb479e7941dcf95e409801000000fdfe0000483045022100e2a508bb78c2172eb03f081a342454ba1d24669e959700973b1a742a4fedd0c302203174e06feda265031cf9aa0364d4a4eafb71b0c0a62e76be7795cfbb307b677a01483045022100d0e14564838fac754395158741d64c73da2b86e7900dfdc6a63c7492b232ba130220778e7e7c21d94ebcd340057302aeff7e9a797a3aa3e0ac4884e9ff27339ea6e9014c69522102091f0b4d8ab30016a5d1c088249e02883fad8160f06fa53588ad8598650a3e6221035f2f8263bb3608d6cc4ee03bd4cb8d65c4d70af71049f05fbfee4978832a1fd22103fe42dab58718ea0413f7c8de693cdeee22ce19b1dc34c0bbdd7a48245465c5a253aefdffffff01cb9f0700000000001976a914c13fd6294d1be7b9410a5538f4b4ef10fc594ee788ac802c1800", "id": "curltext"}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"gettransaction","params":{"txid":"9f43ff71ea2594873e4e7d15e61254a3661ff2df1af76325c854d9aa199550ce", "wallet":"~/.electrum/testnet/wallets/default_wallet"}}' http://user:pass@127.0.0.1:7777
{"jsonrpc": "2.0", "error": {"code": -32000, "message": "'str' object has no attribute 'db'"}, "id": "curltext"}
2020-05-14 16:33:02 +02:00
kwargs [ ' wallet ' ] = wallet
2023-09-15 15:54:25 +02:00
if cmd . requires_password and password is None and wallet . has_password ( ) :
password = wallet . get_unlocked_password ( )
if password :
kwargs [ ' password ' ] = password
else :
cli/rpc: nicer error messages and error-passing
Previously, generally, in case of any error, commands would raise a generic "Exception()" and the CLI/RPC would convert that and return it as `str(e)`.
With this change, we now distinguish "user-facing exceptions" (e.g. "Password required" or "wallet not loaded") and "internal errors" (e.g. bugs).
- for "user-facing exceptions", the behaviour is unchanged
- for "internal errors", we now pass around the traceback (e.g. from daemon server to rpc client) and show it to the user (previously, assuming there was a daemon running, the user could only retrieve the exception from the log of that daemon). These errors use a new jsonrpc error code int (code 2).
As the logic only changes for "internal errors", I deem this change not to be compatibility-breaking.
----------
Examples follow.
Consider the following two commands:
```
@command('')
async def errorgood(self):
from electrum.util import UserFacingException
raise UserFacingException("heyheyhey")
@command('')
async def errorbad(self):
raise Exception("heyheyhey")
```
----------
(before change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9221)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
heyheyhey
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
```
----------
(after change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9254)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
(inside daemon): Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/daemon.py", line 254, in handle
response['result'] = await f(*params)
File "/home/user/wspace/electrum/electrum/daemon.py", line 361, in run_cmdline
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
internal error while executing RPC
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
0.78 | E | __main__ | error running command (without daemon)
Traceback (most recent call last):
File "/home/user/wspace/electrum/./run_electrum", line 534, in handle_cmd
result = fut.result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 458, in result
return self.__get_result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
raise self._exception
File "/home/user/wspace/electrum/./run_electrum", line 255, in run_offline_command
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 2, "message": "internal error while executing RPC", "data": {"exception": "Exception('heyheyhey')", "traceback": "Traceback (most recent call last):\n File \"/home/user/wspace/electrum/electrum/daemon.py\", line 254, in handle\n response['result'] = await f(*params)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 163, in func_wrapper\n return await func(*args, **kwargs)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 217, in errorbad\n raise Exception(\"heyheyhey\")\nException: heyheyhey\n"}}}
```
2024-02-12 19:02:02 +00:00
raise UserFacingException ( ' Password required. Unlock the wallet, or add a --password option to your command ' )
commands: make 'wallet'-mangling in decorator less obscure, and fixes
- some commands expect a 'wallet_path' arg, while others expect 'wallet'
- 'wallet_path' in the end is supposed to be a str,
'wallet' in the end is supposed to be an Optional[Abstract_Wallet]
- initially, in the decorator, 'wallet' can be a str, in which case
the decorator replaces it with an Abstract_Wallet (from the daemon)
- Previously the decorator sometimes converted 'wallet_path' to 'wallet'.
This was because when called from the CLI it was always given 'wallet_path' (and never 'wallet),
while when called from JSON-RPC it was given either 'wallet' or 'wallet_path' (depending on command).
Now, the CLI also behaves as JSON-RPC, and hence 'wallet_path' and 'wallet' are fully separate.
- A bug is fixed where, when a command that only optionally takes a 'wallet' (such as gettransaction),
was called from the JSON-RPC with the arg present, it raised; and when called from CLI with the arg present
the arg was not actually passed to the command.
- A bug is fixed where if one command calls another command (that both take a 'wallet'),
it would raise (due to assuming 'wallet' is str and needs to be converted to Abstract_Wallet).
This fixes #6154.
-----
$ ./run_electrum --testnet daemon -d
$ ./run_electrum --testnet load_wallet -w ~/.electrum/testnet/wallets/default_wallet
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"gettransaction","params":{"txid":"9f43ff71ea2594873e4e7d15e61254a3661ff2df1af76325c854d9aa199550ce"}}' http://user:pass@127.0.0.1:7777
{"jsonrpc": "2.0", "result": "0200000001caaac6b5eb916e3067d0224f942fb331ce1dcfb4031cfb479e7941dcf95e409801000000fdfe0000483045022100e2a508bb78c2172eb03f081a342454ba1d24669e959700973b1a742a4fedd0c302203174e06feda265031cf9aa0364d4a4eafb71b0c0a62e76be7795cfbb307b677a01483045022100d0e14564838fac754395158741d64c73da2b86e7900dfdc6a63c7492b232ba130220778e7e7c21d94ebcd340057302aeff7e9a797a3aa3e0ac4884e9ff27339ea6e9014c69522102091f0b4d8ab30016a5d1c088249e02883fad8160f06fa53588ad8598650a3e6221035f2f8263bb3608d6cc4ee03bd4cb8d65c4d70af71049f05fbfee4978832a1fd22103fe42dab58718ea0413f7c8de693cdeee22ce19b1dc34c0bbdd7a48245465c5a253aefdffffff01cb9f0700000000001976a914c13fd6294d1be7b9410a5538f4b4ef10fc594ee788ac802c1800", "id": "curltext"}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"gettransaction","params":{"txid":"9f43ff71ea2594873e4e7d15e61254a3661ff2df1af76325c854d9aa199550ce", "wallet":"~/.electrum/testnet/wallets/default_wallet"}}' http://user:pass@127.0.0.1:7777
{"jsonrpc": "2.0", "error": {"code": -32000, "message": "'str' object has no attribute 'db'"}, "id": "curltext"}
2020-05-14 16:33:02 +02:00
wallet = kwargs . get ( ' wallet ' ) # type: Optional[Abstract_Wallet]
if cmd . requires_wallet and not wallet :
cli/rpc: nicer error messages and error-passing
Previously, generally, in case of any error, commands would raise a generic "Exception()" and the CLI/RPC would convert that and return it as `str(e)`.
With this change, we now distinguish "user-facing exceptions" (e.g. "Password required" or "wallet not loaded") and "internal errors" (e.g. bugs).
- for "user-facing exceptions", the behaviour is unchanged
- for "internal errors", we now pass around the traceback (e.g. from daemon server to rpc client) and show it to the user (previously, assuming there was a daemon running, the user could only retrieve the exception from the log of that daemon). These errors use a new jsonrpc error code int (code 2).
As the logic only changes for "internal errors", I deem this change not to be compatibility-breaking.
----------
Examples follow.
Consider the following two commands:
```
@command('')
async def errorgood(self):
from electrum.util import UserFacingException
raise UserFacingException("heyheyhey")
@command('')
async def errorbad(self):
raise Exception("heyheyhey")
```
----------
(before change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9221)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
heyheyhey
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
```
----------
(after change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9254)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
(inside daemon): Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/daemon.py", line 254, in handle
response['result'] = await f(*params)
File "/home/user/wspace/electrum/electrum/daemon.py", line 361, in run_cmdline
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
internal error while executing RPC
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
0.78 | E | __main__ | error running command (without daemon)
Traceback (most recent call last):
File "/home/user/wspace/electrum/./run_electrum", line 534, in handle_cmd
result = fut.result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 458, in result
return self.__get_result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
raise self._exception
File "/home/user/wspace/electrum/./run_electrum", line 255, in run_offline_command
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 2, "message": "internal error while executing RPC", "data": {"exception": "Exception('heyheyhey')", "traceback": "Traceback (most recent call last):\n File \"/home/user/wspace/electrum/electrum/daemon.py\", line 254, in handle\n response['result'] = await f(*params)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 163, in func_wrapper\n return await func(*args, **kwargs)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 217, in errorbad\n raise Exception(\"heyheyhey\")\nException: heyheyhey\n"}}}
```
2024-02-12 19:02:02 +00:00
raise UserFacingException ( ' wallet not loaded ' )
2024-10-08 15:15:42 +00:00
if cmd . requires_password and wallet . has_password ( ) :
if password is None :
raise UserFacingException ( ' Password required ' )
try :
wallet . check_password ( password )
except InvalidPassword as e :
raise UserFacingException ( str ( e ) ) from None
2021-07-07 16:44:27 +03:00
if cmd . requires_lightning and ( not wallet or not wallet . has_lightning ( ) ) :
cli/rpc: nicer error messages and error-passing
Previously, generally, in case of any error, commands would raise a generic "Exception()" and the CLI/RPC would convert that and return it as `str(e)`.
With this change, we now distinguish "user-facing exceptions" (e.g. "Password required" or "wallet not loaded") and "internal errors" (e.g. bugs).
- for "user-facing exceptions", the behaviour is unchanged
- for "internal errors", we now pass around the traceback (e.g. from daemon server to rpc client) and show it to the user (previously, assuming there was a daemon running, the user could only retrieve the exception from the log of that daemon). These errors use a new jsonrpc error code int (code 2).
As the logic only changes for "internal errors", I deem this change not to be compatibility-breaking.
----------
Examples follow.
Consider the following two commands:
```
@command('')
async def errorgood(self):
from electrum.util import UserFacingException
raise UserFacingException("heyheyhey")
@command('')
async def errorbad(self):
raise Exception("heyheyhey")
```
----------
(before change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9221)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
heyheyhey
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
```
----------
(after change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9254)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
(inside daemon): Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/daemon.py", line 254, in handle
response['result'] = await f(*params)
File "/home/user/wspace/electrum/electrum/daemon.py", line 361, in run_cmdline
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
internal error while executing RPC
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
0.78 | E | __main__ | error running command (without daemon)
Traceback (most recent call last):
File "/home/user/wspace/electrum/./run_electrum", line 534, in handle_cmd
result = fut.result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 458, in result
return self.__get_result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
raise self._exception
File "/home/user/wspace/electrum/./run_electrum", line 255, in run_offline_command
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 2, "message": "internal error while executing RPC", "data": {"exception": "Exception('heyheyhey')", "traceback": "Traceback (most recent call last):\n File \"/home/user/wspace/electrum/electrum/daemon.py\", line 254, in handle\n response['result'] = await f(*params)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 163, in func_wrapper\n return await func(*args, **kwargs)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 217, in errorbad\n raise Exception(\"heyheyhey\")\nException: heyheyhey\n"}}}
```
2024-02-12 19:02:02 +00:00
raise UserFacingException ( ' Lightning not enabled in this wallet ' )
2019-09-06 12:38:13 +02:00
return await func ( * args , * * kwargs )
2015-06-01 06:10:06 +02:00
return func_wrapper
return decorator
2024-11-22 14:46:21 +01:00
class Commands ( Logger ) :
2013-02-26 13:56:48 +01:00
2019-09-05 18:30:04 +02:00
def __init__ ( self , * , config : ' SimpleConfig ' ,
network : ' Network ' = None ,
2019-09-04 20:16:47 +02:00
daemon : ' Daemon ' = None , callback = None ) :
2024-11-22 14:46:21 +01:00
Logger . __init__ ( self )
2015-05-30 12:35:58 +02:00
self . config = config
2019-08-19 12:46:31 +02:00
self . daemon = daemon
2013-09-15 11:19:48 +02:00
self . network = network
2013-02-27 12:40:16 +01:00
self . _callback = callback
2013-02-26 13:56:48 +01:00
2019-08-15 13:17:16 +02:00
def _run ( self , method , args , password_getter = None , * * kwargs ) :
""" This wrapper is called from unit tests and the Qt python console. """
2013-10-03 12:39:42 +02:00
cmd = known_commands [ method ]
2019-08-06 05:20:53 +02:00
password = kwargs . get ( ' password ' , None )
2019-09-05 18:30:04 +02:00
wallet = kwargs . get ( ' wallet ' , None )
if ( cmd . requires_password and wallet and wallet . has_password ( )
2019-08-06 05:20:53 +02:00
and password is None ) :
2017-08-01 05:22:18 +02:00
password = password_getter ( )
2017-07-02 11:44:48 +02:00
if password is None :
2016-07-01 16:19:26 +02:00
return
2017-08-03 21:32:25 +03:00
2013-11-11 22:03:20 -08:00
f = getattr ( self , method )
2017-07-28 08:04:32 -04:00
if cmd . requires_password :
2019-08-06 05:20:53 +02:00
kwargs [ ' password ' ] = password
2019-08-15 13:17:16 +02:00
2019-09-12 19:22:55 +02:00
if ' wallet ' in kwargs :
sig = inspect . signature ( f )
if ' wallet ' not in sig . parameters :
kwargs . pop ( ' wallet ' )
2019-08-15 13:17:16 +02:00
coro = f ( * args , * * kwargs )
asyncio: stop using get_event_loop(). introduce ~singleton loop.
asyncio.get_event_loop() became deprecated in python3.10. (see https://github.com/python/cpython/issues/83710)
```
.../electrum/electrum/daemon.py:470: DeprecationWarning: There is no current event loop
self.asyncio_loop = asyncio.get_event_loop()
.../electrum/electrum/network.py:276: DeprecationWarning: There is no current event loop
self.asyncio_loop = asyncio.get_event_loop()
```
Also, according to that thread, "set_event_loop() [... is] not deprecated by oversight".
So, we stop using get_event_loop() and set_event_loop() in our own code.
Note that libraries we use (such as the stdlib for python <3.10), might call get_event_loop,
which then relies on us having called set_event_loop e.g. for the GUI thread. To work around
this, a custom event loop policy providing a get_event_loop implementation is used.
Previously, we have been using a single asyncio event loop, created with
util.create_and_start_event_loop, and code in many places got a reference to this loop
using asyncio.get_event_loop().
Now, we still use a single asyncio event loop, but it is now stored as a global in
util._asyncio_event_loop (access with util.get_asyncio_loop()).
I believe these changes also fix https://github.com/spesmilo/electrum/issues/5376
2022-04-29 18:24:49 +02:00
fut = asyncio . run_coroutine_threadsafe ( coro , util . get_asyncio_loop ( ) )
2019-08-15 13:17:16 +02:00
result = fut . result ( )
2017-08-01 05:22:18 +02:00
2013-02-27 12:40:16 +01:00
if self . _callback :
2017-08-01 05:22:18 +02:00
self . _callback ( )
2013-02-26 17:57:48 +01:00
return result
2013-02-26 13:56:48 +01:00
2015-06-01 06:10:06 +02:00
@command ( ' ' )
2019-08-15 13:17:16 +02:00
async def commands ( self ) :
2015-08-06 15:52:38 +02:00
""" List of commands """
return ' ' . join ( sorted ( known_commands . keys ( ) ) )
2015-05-30 19:13:28 +02:00
2019-08-19 12:46:31 +02:00
@command ( ' n ' )
async def getinfo ( self ) :
""" network info """
net_params = self . network . get_parameters ( )
response = {
2022-10-31 15:26:27 +00:00
' network ' : constants . net . NET_NAME ,
2019-08-19 12:46:31 +02:00
' path ' : self . network . config . path ,
2020-04-16 20:30:53 +02:00
' server ' : net_params . server . host ,
2019-08-19 12:46:31 +02:00
' blockchain_height ' : self . network . get_local_height ( ) ,
' server_height ' : self . network . get_server_height ( ) ,
' spv_nodes ' : len ( self . network . get_interfaces ( ) ) ,
' connected ' : self . network . is_connected ( ) ,
' auto_connect ' : net_params . auto_connect ,
' version ' : ELECTRUM_VERSION ,
' default_wallet ' : self . config . get_wallet_path ( ) ,
2025-02-24 12:20:44 +01:00
' fee_estimates ' : self . network . fee_estimates . get_data ( )
2019-08-19 12:46:31 +02:00
}
return response
@command ( ' n ' )
async def stop ( self ) :
""" Stop daemon """
2021-03-17 19:16:07 +01:00
await self . daemon . stop ( )
2019-08-19 12:46:31 +02:00
return " Daemon stopped "
@command ( ' n ' )
async def list_wallets ( self ) :
""" List wallets open in daemon """
2023-10-07 17:42:21 +02:00
return [
{
' path ' : path ,
' synchronized ' : w . is_up_to_date ( ) ,
' unlocked ' : w . has_password ( ) and ( w . get_unlocked_password ( ) is not None ) ,
}
for path , w in self . daemon . get_wallets ( ) . items ( )
]
2019-08-19 12:46:31 +02:00
@command ( ' n ' )
2024-06-08 11:10:27 +02:00
async def load_wallet ( self , wallet_path = None , password = None ) :
2023-10-07 17:42:21 +02:00
"""
Load the wallet in memory
"""
2023-09-22 11:49:53 +02:00
wallet = self . daemon . load_wallet ( wallet_path , password , upgrade = True )
2023-10-07 17:42:21 +02:00
if wallet is None :
cli/rpc: nicer error messages and error-passing
Previously, generally, in case of any error, commands would raise a generic "Exception()" and the CLI/RPC would convert that and return it as `str(e)`.
With this change, we now distinguish "user-facing exceptions" (e.g. "Password required" or "wallet not loaded") and "internal errors" (e.g. bugs).
- for "user-facing exceptions", the behaviour is unchanged
- for "internal errors", we now pass around the traceback (e.g. from daemon server to rpc client) and show it to the user (previously, assuming there was a daemon running, the user could only retrieve the exception from the log of that daemon). These errors use a new jsonrpc error code int (code 2).
As the logic only changes for "internal errors", I deem this change not to be compatibility-breaking.
----------
Examples follow.
Consider the following two commands:
```
@command('')
async def errorgood(self):
from electrum.util import UserFacingException
raise UserFacingException("heyheyhey")
@command('')
async def errorbad(self):
raise Exception("heyheyhey")
```
----------
(before change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9221)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
heyheyhey
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
```
----------
(after change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9254)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
(inside daemon): Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/daemon.py", line 254, in handle
response['result'] = await f(*params)
File "/home/user/wspace/electrum/electrum/daemon.py", line 361, in run_cmdline
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
internal error while executing RPC
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
0.78 | E | __main__ | error running command (without daemon)
Traceback (most recent call last):
File "/home/user/wspace/electrum/./run_electrum", line 534, in handle_cmd
result = fut.result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 458, in result
return self.__get_result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
raise self._exception
File "/home/user/wspace/electrum/./run_electrum", line 255, in run_offline_command
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 2, "message": "internal error while executing RPC", "data": {"exception": "Exception('heyheyhey')", "traceback": "Traceback (most recent call last):\n File \"/home/user/wspace/electrum/electrum/daemon.py\", line 254, in handle\n response['result'] = await f(*params)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 163, in func_wrapper\n return await func(*args, **kwargs)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 217, in errorbad\n raise Exception(\"heyheyhey\")\nException: heyheyhey\n"}}}
```
2024-02-12 19:02:02 +00:00
raise UserFacingException ( ' could not load wallet ' )
2023-10-07 17:42:21 +02:00
run_hook ( ' load_wallet ' , wallet , None )
2019-08-19 12:46:31 +02:00
@command ( ' n ' )
2019-09-06 11:06:08 +02:00
async def close_wallet ( self , wallet_path = None ) :
2019-08-19 12:46:31 +02:00
""" Close wallet """
2021-06-17 12:35:31 +02:00
return await self . daemon . _stop_wallet ( wallet_path )
2019-08-19 12:46:31 +02:00
2015-06-01 06:10:06 +02:00
@command ( ' ' )
2019-09-06 11:06:08 +02:00
async def create ( self , passphrase = None , password = None , encrypt_file = True , seed_type = None , wallet_path = None ) :
2019-03-09 16:47:19 +01:00
""" Create a new wallet.
If you want to be prompted for an argument , type ' ? ' or ' : ' ( concealed )
"""
2019-09-06 11:06:08 +02:00
d = create_new_wallet ( path = wallet_path ,
2019-02-28 20:12:24 +01:00
passphrase = passphrase ,
password = password ,
encrypt_file = encrypt_file ,
2019-09-22 20:46:01 +02:00
seed_type = seed_type ,
config = self . config )
2019-02-28 20:12:24 +01:00
return {
' seed ' : d [ ' seed ' ] ,
' path ' : d [ ' wallet ' ] . storage . path ,
' msg ' : d [ ' msg ' ] ,
}
2015-05-31 23:17:44 +02:00
2018-10-10 20:29:51 +02:00
@command ( ' ' )
2019-09-06 11:06:08 +02:00
async def restore ( self , text , passphrase = None , password = None , encrypt_file = True , wallet_path = None ) :
2015-10-28 09:33:35 +01:00
""" Restore a wallet from text. Text can be a seed phrase, a master
public key , a master private key , a list of bitcoin addresses
2019-03-09 16:47:19 +01:00
or bitcoin private keys .
If you want to be prompted for an argument , type ' ? ' or ' : ' ( concealed )
"""
2019-09-04 20:15:54 +02:00
# TODO create a separate command that blocks until wallet is synced
2019-02-28 20:12:24 +01:00
d = restore_wallet_from_text ( text ,
2019-09-06 11:06:08 +02:00
path = wallet_path ,
2019-02-28 20:12:24 +01:00
passphrase = passphrase ,
password = password ,
2019-09-22 20:46:01 +02:00
encrypt_file = encrypt_file ,
config = self . config )
2019-02-28 20:12:24 +01:00
return {
' path ' : d [ ' wallet ' ] . storage . path ,
' msg ' : d [ ' msg ' ] ,
}
2015-05-31 23:17:44 +02:00
2015-06-01 06:10:06 +02:00
@command ( ' wp ' )
2022-05-26 21:41:29 +02:00
async def password ( self , password = None , new_password = None , encrypt_file = None , wallet : Abstract_Wallet = None ) :
2015-05-31 23:17:44 +02:00
""" Change wallet password. """
2019-09-05 17:57:51 +02:00
if wallet . storage . is_encrypted_with_hw_device ( ) and new_password :
cli/rpc: nicer error messages and error-passing
Previously, generally, in case of any error, commands would raise a generic "Exception()" and the CLI/RPC would convert that and return it as `str(e)`.
With this change, we now distinguish "user-facing exceptions" (e.g. "Password required" or "wallet not loaded") and "internal errors" (e.g. bugs).
- for "user-facing exceptions", the behaviour is unchanged
- for "internal errors", we now pass around the traceback (e.g. from daemon server to rpc client) and show it to the user (previously, assuming there was a daemon running, the user could only retrieve the exception from the log of that daemon). These errors use a new jsonrpc error code int (code 2).
As the logic only changes for "internal errors", I deem this change not to be compatibility-breaking.
----------
Examples follow.
Consider the following two commands:
```
@command('')
async def errorgood(self):
from electrum.util import UserFacingException
raise UserFacingException("heyheyhey")
@command('')
async def errorbad(self):
raise Exception("heyheyhey")
```
----------
(before change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9221)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
heyheyhey
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
```
----------
(after change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9254)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
(inside daemon): Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/daemon.py", line 254, in handle
response['result'] = await f(*params)
File "/home/user/wspace/electrum/electrum/daemon.py", line 361, in run_cmdline
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
internal error while executing RPC
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
0.78 | E | __main__ | error running command (without daemon)
Traceback (most recent call last):
File "/home/user/wspace/electrum/./run_electrum", line 534, in handle_cmd
result = fut.result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 458, in result
return self.__get_result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
raise self._exception
File "/home/user/wspace/electrum/./run_electrum", line 255, in run_offline_command
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 2, "message": "internal error while executing RPC", "data": {"exception": "Exception('heyheyhey')", "traceback": "Traceback (most recent call last):\n File \"/home/user/wspace/electrum/electrum/daemon.py\", line 254, in handle\n response['result'] = await f(*params)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 163, in func_wrapper\n return await func(*args, **kwargs)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 217, in errorbad\n raise Exception(\"heyheyhey\")\nException: heyheyhey\n"}}}
```
2024-02-12 19:02:02 +00:00
raise UserFacingException ( " Can ' t change the password of a wallet encrypted with a hw device. " )
2022-05-26 21:41:29 +02:00
if encrypt_file is None :
if not password and new_password :
# currently no password, setting one now: we encrypt by default
encrypt_file = True
else :
encrypt_file = wallet . storage . is_encrypted ( )
wallet . update_password ( password , new_password , encrypt_storage = encrypt_file )
2020-02-05 15:13:37 +01:00
wallet . save_db ( )
2019-09-05 17:57:51 +02:00
return { ' password ' : wallet . has_password ( ) }
2015-05-31 22:42:34 +02:00
2019-01-26 16:50:51 +01:00
@command ( ' w ' )
2019-09-21 02:14:22 +02:00
async def get ( self , key , wallet : Abstract_Wallet = None ) :
2019-01-26 16:50:51 +01:00
""" Return item from wallet storage """
2020-02-05 15:13:37 +01:00
return wallet . db . get ( key )
2019-01-26 16:50:51 +01:00
2015-06-01 06:10:06 +02:00
@command ( ' ' )
2019-08-15 13:17:16 +02:00
async def getconfig ( self , key ) :
2015-05-31 23:17:44 +02:00
""" Return a configuration variable. """
2023-09-08 14:55:22 +00:00
if Plugins . is_plugin_enabler_config_key ( key ) :
return self . config . get ( key )
else :
cv = self . config . cv . from_key ( key )
return cv . get ( )
2015-05-31 22:42:34 +02:00
2018-04-06 18:53:13 +02:00
@classmethod
def _setconfig_normalize_value ( cls , key , value ) :
2023-05-24 17:41:44 +00:00
if key not in ( SimpleConfig . RPC_USERNAME . key ( ) , SimpleConfig . RPC_PASSWORD . key ( ) ) :
2018-04-06 18:53:13 +02:00
value = json_decode ( value )
2020-11-25 11:47:25 +01:00
# call literal_eval for backward compatibility (see #4225)
2018-04-06 18:53:13 +02:00
try :
value = ast . literal_eval ( value )
2023-04-23 01:33:12 +00:00
except Exception :
2018-04-06 18:53:13 +02:00
pass
return value
2015-06-01 06:10:06 +02:00
@command ( ' ' )
2019-08-15 13:17:16 +02:00
async def setconfig ( self , key , value ) :
2015-06-03 10:02:12 +02:00
""" Set a configuration variable. ' value ' may be a string or a Python expression. """
2018-04-06 18:53:13 +02:00
value = self . _setconfig_normalize_value ( key , value )
2023-05-24 17:41:44 +00:00
if self . daemon and key == SimpleConfig . RPC_USERNAME . key ( ) :
2020-11-25 11:47:25 +01:00
self . daemon . commands_server . rpc_user = value
2023-05-24 17:41:44 +00:00
if self . daemon and key == SimpleConfig . RPC_PASSWORD . key ( ) :
2020-11-25 11:47:25 +01:00
self . daemon . commands_server . rpc_password = value
2023-09-08 14:55:22 +00:00
if Plugins . is_plugin_enabler_config_key ( key ) :
self . config . set_key ( key , value )
else :
cv = self . config . cv . from_key ( key )
cv . set ( value )
2015-05-31 22:42:34 +02:00
2024-04-30 13:22:36 +02:00
@command ( ' ' )
async def listconfig ( self ) :
""" Returns the list of all configuration variables. """
return self . config . list_config_vars ( )
@command ( ' ' )
2025-01-17 11:27:12 +01:00
async def helpconfig ( self , key ) :
2024-04-30 13:22:36 +02:00
""" Returns help about a configuration variable. """
cv = self . config . cv . from_key ( key )
2025-01-17 11:27:12 +01:00
short = cv . get_short_desc ( )
long = cv . get_long_desc ( )
if short and long :
return short + " \n --- \n \n " + long
elif short or long :
return short or long
else :
return f " No description available for ' { key } ' "
2024-04-30 13:22:36 +02:00
2015-06-01 06:10:06 +02:00
@command ( ' ' )
2020-12-08 10:00:34 +01:00
async def make_seed ( self , nbits = None , language = None , seed_type = None ) :
2015-05-31 23:17:44 +02:00
""" Create a seed """
2017-08-27 09:53:22 +02:00
from . mnemonic import Mnemonic
2020-12-08 10:00:34 +01:00
s = Mnemonic ( language ) . make_seed ( seed_type = seed_type , num_bits = nbits )
2017-08-27 09:53:22 +02:00
return s
2014-08-28 15:37:42 +02:00
2015-06-01 06:10:06 +02:00
@command ( ' n ' )
2019-08-15 13:17:16 +02:00
async def getaddresshistory ( self , address ) :
2015-06-03 09:12:38 +02:00
""" Return the transaction history of any address. Note: This is a
walletless server query , results are not checked by SPV .
"""
2018-02-02 23:26:25 +01:00
sh = bitcoin . address_to_scripthash ( address )
2019-08-15 13:17:16 +02:00
return await self . network . get_history_for_scripthash ( sh )
2014-01-23 17:06:47 +01:00
2024-06-08 11:10:27 +02:00
@command ( ' wp ' )
async def unlock ( self , wallet : Abstract_Wallet = None , password = None ) :
""" Unlock the wallet (store the password in memory). """
wallet . unlock ( password )
2015-12-23 15:59:32 +01:00
@command ( ' w ' )
2019-09-21 02:14:22 +02:00
async def listunspent ( self , wallet : Abstract_Wallet = None ) :
2015-06-03 09:12:38 +02:00
""" List unspent outputs. Returns the list of unspent transaction
outputs in your wallet . """
2019-10-23 17:09:41 +02:00
coins = [ ]
for txin in wallet . get_utxos ( ) :
d = txin . to_json ( )
v = d . pop ( " value_sats " )
commands: fix satoshis decimal conversion in payto cmd and others
When called via jsonrpc (but not via cli) with non-string amounts,
there could be a rounding error resulting in sending 1 sat less.
example:
```
$ ./run_electrum --testnet -w ~/.electrum/testnet/wallets/test_segwit_2 paytomany '[["tb1q6k5h4cz6ra8nzhg90xm9wldvadgh0fpttfthcg", 0.00033389]]' --fee 0
02000000000101b9e6018acb16952e3c9618b069df404dc85544eda8120e5f6e7cd7e94ce5ae8d0100000000fdffffff02fd8100000000000016001410c5b97085ec1637a9f702852f5a81f650fae1566d82000000000000160014d5a97ae05a1f4f315d0579b6577daceb5177a42b024730440220251d2ce83f6e69273de8e9be8602fbcf72b9157e1c0116161fa52f7e04db6e4302202d84045cc6b7056a215d1db3f59884e28dadd5257e1a3960068f90df90b452d1012102b0eff3bf364a2ab5effe952cba33521ebede81dac88c71951a5ed598cb48347b3a022500
$ curl --data-binary '{"id":"curltext","method":"paytomany","params":{"outputs":[["tb1q6k5h4cz6ra8nzhg90xm9wldvadgh0fpttfthcg", 0.00033389]], "fee": 0, "wallet": "/home/user/.electrum/testnet/wallets/test_segwit_2"}}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "result": "02000000000101b9e6018acb16952e3c9618b069df404dc85544eda8120e5f6e7cd7e94ce5ae8d0100000000fdffffff02fe8100000000000016001410c5b97085ec1637a9f702852f5a81f650fae1566c82000000000000160014d5a97ae05a1f4f315d0579b6577daceb5177a42b0247304402206ef66b845ca298c14dc6e8049cba9ed19db1671132194518ce5d521de6f5df8802205ca4b1aee703e3b98331fb9b88210917b385560020c8b2a8a88da38996b101c4012102b0eff3bf364a2ab5effe952cba33521ebede81dac88c71951a5ed598cb48347b39022500"}
```
^ note that first tx has output for 0.00033389, second tx has output for 0.00033388
fixes https://github.com/spesmilo/electrum/issues/8274
2023-03-22 12:22:36 +00:00
d [ " value " ] = str ( to_decimal ( v ) / COIN ) if v is not None else None
2019-10-23 17:09:41 +02:00
coins . append ( d )
return coins
2013-02-26 13:56:48 +01:00
2015-06-01 06:10:06 +02:00
@command ( ' n ' )
2019-08-15 13:17:16 +02:00
async def getaddressunspent ( self , address ) :
2015-06-10 23:48:36 +02:00
""" Returns the UTXO list of any address. Note: This
2015-06-03 09:12:38 +02:00
is a walletless server query , results are not checked by SPV .
"""
2018-02-02 23:26:25 +01:00
sh = bitcoin . address_to_scripthash ( address )
2019-08-15 13:17:16 +02:00
return await self . network . listunspent_for_scripthash ( sh )
2014-01-23 17:06:47 +01:00
2016-05-16 14:39:01 +02:00
@command ( ' ' )
2019-08-15 13:17:16 +02:00
async def serialize ( self , jsontx ) :
2023-03-19 18:34:43 +00:00
""" Create a signed raw transaction from a json tx template.
Example value for " jsontx " arg : {
" inputs " : [
{ " prevout_hash " : " 9d221a69ca3997cbeaf5624d723e7dc5f829b1023078c177d37bdae95f37c539 " , " prevout_n " : 1 ,
" value_sats " : 1000000 , " privkey " : " p2wpkh:cVDXzzQg6RoCTfiKpe8MBvmm5d5cJc6JLuFApsFDKwWa6F5TVHpD " }
] ,
" outputs " : [
{ " address " : " tb1q4s8z6g5jqzllkgt8a4har94wl8tg0k9m8kv5zd " , " value_sats " : 990000 }
]
}
2016-05-16 14:39:01 +02:00
"""
keypairs = { }
2019-10-23 17:09:41 +02:00
inputs = [ ] # type: List[PartialTxInput]
2020-08-05 01:47:35 -05:00
locktime = jsontx . get ( ' locktime ' , 0 )
cli/rpc: nicer error messages and error-passing
Previously, generally, in case of any error, commands would raise a generic "Exception()" and the CLI/RPC would convert that and return it as `str(e)`.
With this change, we now distinguish "user-facing exceptions" (e.g. "Password required" or "wallet not loaded") and "internal errors" (e.g. bugs).
- for "user-facing exceptions", the behaviour is unchanged
- for "internal errors", we now pass around the traceback (e.g. from daemon server to rpc client) and show it to the user (previously, assuming there was a daemon running, the user could only retrieve the exception from the log of that daemon). These errors use a new jsonrpc error code int (code 2).
As the logic only changes for "internal errors", I deem this change not to be compatibility-breaking.
----------
Examples follow.
Consider the following two commands:
```
@command('')
async def errorgood(self):
from electrum.util import UserFacingException
raise UserFacingException("heyheyhey")
@command('')
async def errorbad(self):
raise Exception("heyheyhey")
```
----------
(before change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9221)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
heyheyhey
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
```
----------
(after change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9254)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
(inside daemon): Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/daemon.py", line 254, in handle
response['result'] = await f(*params)
File "/home/user/wspace/electrum/electrum/daemon.py", line 361, in run_cmdline
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
internal error while executing RPC
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
0.78 | E | __main__ | error running command (without daemon)
Traceback (most recent call last):
File "/home/user/wspace/electrum/./run_electrum", line 534, in handle_cmd
result = fut.result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 458, in result
return self.__get_result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
raise self._exception
File "/home/user/wspace/electrum/./run_electrum", line 255, in run_offline_command
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 2, "message": "internal error while executing RPC", "data": {"exception": "Exception('heyheyhey')", "traceback": "Traceback (most recent call last):\n File \"/home/user/wspace/electrum/electrum/daemon.py\", line 254, in handle\n response['result'] = await f(*params)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 163, in func_wrapper\n return await func(*args, **kwargs)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 217, in errorbad\n raise Exception(\"heyheyhey\")\nException: heyheyhey\n"}}}
```
2024-02-12 19:02:02 +00:00
for txin_idx , txin_dict in enumerate ( jsontx . get ( ' inputs ' ) ) :
2019-10-23 17:09:41 +02:00
if txin_dict . get ( ' prevout_hash ' ) is not None and txin_dict . get ( ' prevout_n ' ) is not None :
prevout = TxOutpoint ( txid = bfh ( txin_dict [ ' prevout_hash ' ] ) , out_idx = int ( txin_dict [ ' prevout_n ' ] ) )
elif txin_dict . get ( ' output ' ) :
prevout = TxOutpoint . from_str ( txin_dict [ ' output ' ] )
else :
cli/rpc: nicer error messages and error-passing
Previously, generally, in case of any error, commands would raise a generic "Exception()" and the CLI/RPC would convert that and return it as `str(e)`.
With this change, we now distinguish "user-facing exceptions" (e.g. "Password required" or "wallet not loaded") and "internal errors" (e.g. bugs).
- for "user-facing exceptions", the behaviour is unchanged
- for "internal errors", we now pass around the traceback (e.g. from daemon server to rpc client) and show it to the user (previously, assuming there was a daemon running, the user could only retrieve the exception from the log of that daemon). These errors use a new jsonrpc error code int (code 2).
As the logic only changes for "internal errors", I deem this change not to be compatibility-breaking.
----------
Examples follow.
Consider the following two commands:
```
@command('')
async def errorgood(self):
from electrum.util import UserFacingException
raise UserFacingException("heyheyhey")
@command('')
async def errorbad(self):
raise Exception("heyheyhey")
```
----------
(before change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9221)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
heyheyhey
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
```
----------
(after change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9254)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
(inside daemon): Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/daemon.py", line 254, in handle
response['result'] = await f(*params)
File "/home/user/wspace/electrum/electrum/daemon.py", line 361, in run_cmdline
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
internal error while executing RPC
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
0.78 | E | __main__ | error running command (without daemon)
Traceback (most recent call last):
File "/home/user/wspace/electrum/./run_electrum", line 534, in handle_cmd
result = fut.result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 458, in result
return self.__get_result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
raise self._exception
File "/home/user/wspace/electrum/./run_electrum", line 255, in run_offline_command
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 2, "message": "internal error while executing RPC", "data": {"exception": "Exception('heyheyhey')", "traceback": "Traceback (most recent call last):\n File \"/home/user/wspace/electrum/electrum/daemon.py\", line 254, in handle\n response['result'] = await f(*params)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 163, in func_wrapper\n return await func(*args, **kwargs)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 217, in errorbad\n raise Exception(\"heyheyhey\")\nException: heyheyhey\n"}}}
```
2024-02-12 19:02:02 +00:00
raise UserFacingException ( f " missing prevout for txin { txin_idx } " )
2019-10-23 17:09:41 +02:00
txin = PartialTxInput ( prevout = prevout )
2023-03-19 18:34:43 +00:00
try :
txin . _trusted_value_sats = int ( txin_dict . get ( ' value ' ) or txin_dict [ ' value_sats ' ] )
except KeyError :
cli/rpc: nicer error messages and error-passing
Previously, generally, in case of any error, commands would raise a generic "Exception()" and the CLI/RPC would convert that and return it as `str(e)`.
With this change, we now distinguish "user-facing exceptions" (e.g. "Password required" or "wallet not loaded") and "internal errors" (e.g. bugs).
- for "user-facing exceptions", the behaviour is unchanged
- for "internal errors", we now pass around the traceback (e.g. from daemon server to rpc client) and show it to the user (previously, assuming there was a daemon running, the user could only retrieve the exception from the log of that daemon). These errors use a new jsonrpc error code int (code 2).
As the logic only changes for "internal errors", I deem this change not to be compatibility-breaking.
----------
Examples follow.
Consider the following two commands:
```
@command('')
async def errorgood(self):
from electrum.util import UserFacingException
raise UserFacingException("heyheyhey")
@command('')
async def errorbad(self):
raise Exception("heyheyhey")
```
----------
(before change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9221)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
heyheyhey
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
```
----------
(after change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9254)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
(inside daemon): Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/daemon.py", line 254, in handle
response['result'] = await f(*params)
File "/home/user/wspace/electrum/electrum/daemon.py", line 361, in run_cmdline
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
internal error while executing RPC
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
0.78 | E | __main__ | error running command (without daemon)
Traceback (most recent call last):
File "/home/user/wspace/electrum/./run_electrum", line 534, in handle_cmd
result = fut.result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 458, in result
return self.__get_result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
raise self._exception
File "/home/user/wspace/electrum/./run_electrum", line 255, in run_offline_command
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 2, "message": "internal error while executing RPC", "data": {"exception": "Exception('heyheyhey')", "traceback": "Traceback (most recent call last):\n File \"/home/user/wspace/electrum/electrum/daemon.py\", line 254, in handle\n response['result'] = await f(*params)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 163, in func_wrapper\n return await func(*args, **kwargs)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 217, in errorbad\n raise Exception(\"heyheyhey\")\nException: heyheyhey\n"}}}
```
2024-02-12 19:02:02 +00:00
raise UserFacingException ( f " missing ' value_sats ' field for txin { txin_idx } " )
2020-06-28 17:26:52 +02:00
nsequence = txin_dict . get ( ' nsequence ' , None )
if nsequence is not None :
txin . nsequence = nsequence
2019-10-23 17:09:41 +02:00
sec = txin_dict . get ( ' privkey ' )
2017-10-09 11:53:47 +02:00
if sec :
txin_type , privkey , compressed = bitcoin . deserialize_privkey ( sec )
2024-04-29 16:32:19 +00:00
pubkey = ecc . ECPrivkey ( privkey ) . get_public_key_bytes ( compressed = compressed )
keypairs [ pubkey ] = privkey
desc = descriptor . get_singlesig_descriptor_from_legacy_leaf ( pubkey = pubkey . hex ( ) , script_type = txin_type )
2023-02-26 12:14:25 +00:00
txin . script_descriptor = desc
2019-10-23 17:09:41 +02:00
inputs . append ( txin )
2023-03-19 18:34:43 +00:00
outputs = [ ] # type: List[PartialTxOutput]
cli/rpc: nicer error messages and error-passing
Previously, generally, in case of any error, commands would raise a generic "Exception()" and the CLI/RPC would convert that and return it as `str(e)`.
With this change, we now distinguish "user-facing exceptions" (e.g. "Password required" or "wallet not loaded") and "internal errors" (e.g. bugs).
- for "user-facing exceptions", the behaviour is unchanged
- for "internal errors", we now pass around the traceback (e.g. from daemon server to rpc client) and show it to the user (previously, assuming there was a daemon running, the user could only retrieve the exception from the log of that daemon). These errors use a new jsonrpc error code int (code 2).
As the logic only changes for "internal errors", I deem this change not to be compatibility-breaking.
----------
Examples follow.
Consider the following two commands:
```
@command('')
async def errorgood(self):
from electrum.util import UserFacingException
raise UserFacingException("heyheyhey")
@command('')
async def errorbad(self):
raise Exception("heyheyhey")
```
----------
(before change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9221)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
heyheyhey
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
```
----------
(after change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9254)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
(inside daemon): Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/daemon.py", line 254, in handle
response['result'] = await f(*params)
File "/home/user/wspace/electrum/electrum/daemon.py", line 361, in run_cmdline
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
internal error while executing RPC
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
0.78 | E | __main__ | error running command (without daemon)
Traceback (most recent call last):
File "/home/user/wspace/electrum/./run_electrum", line 534, in handle_cmd
result = fut.result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 458, in result
return self.__get_result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
raise self._exception
File "/home/user/wspace/electrum/./run_electrum", line 255, in run_offline_command
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 2, "message": "internal error while executing RPC", "data": {"exception": "Exception('heyheyhey')", "traceback": "Traceback (most recent call last):\n File \"/home/user/wspace/electrum/electrum/daemon.py\", line 254, in handle\n response['result'] = await f(*params)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 163, in func_wrapper\n return await func(*args, **kwargs)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 217, in errorbad\n raise Exception(\"heyheyhey\")\nException: heyheyhey\n"}}}
```
2024-02-12 19:02:02 +00:00
for txout_idx , txout_dict in enumerate ( jsontx . get ( ' outputs ' ) ) :
2023-03-19 18:34:43 +00:00
try :
txout_addr = txout_dict [ ' address ' ]
except KeyError :
cli/rpc: nicer error messages and error-passing
Previously, generally, in case of any error, commands would raise a generic "Exception()" and the CLI/RPC would convert that and return it as `str(e)`.
With this change, we now distinguish "user-facing exceptions" (e.g. "Password required" or "wallet not loaded") and "internal errors" (e.g. bugs).
- for "user-facing exceptions", the behaviour is unchanged
- for "internal errors", we now pass around the traceback (e.g. from daemon server to rpc client) and show it to the user (previously, assuming there was a daemon running, the user could only retrieve the exception from the log of that daemon). These errors use a new jsonrpc error code int (code 2).
As the logic only changes for "internal errors", I deem this change not to be compatibility-breaking.
----------
Examples follow.
Consider the following two commands:
```
@command('')
async def errorgood(self):
from electrum.util import UserFacingException
raise UserFacingException("heyheyhey")
@command('')
async def errorbad(self):
raise Exception("heyheyhey")
```
----------
(before change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9221)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
heyheyhey
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
```
----------
(after change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9254)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
(inside daemon): Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/daemon.py", line 254, in handle
response['result'] = await f(*params)
File "/home/user/wspace/electrum/electrum/daemon.py", line 361, in run_cmdline
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
internal error while executing RPC
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
0.78 | E | __main__ | error running command (without daemon)
Traceback (most recent call last):
File "/home/user/wspace/electrum/./run_electrum", line 534, in handle_cmd
result = fut.result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 458, in result
return self.__get_result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
raise self._exception
File "/home/user/wspace/electrum/./run_electrum", line 255, in run_offline_command
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 2, "message": "internal error while executing RPC", "data": {"exception": "Exception('heyheyhey')", "traceback": "Traceback (most recent call last):\n File \"/home/user/wspace/electrum/electrum/daemon.py\", line 254, in handle\n response['result'] = await f(*params)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 163, in func_wrapper\n return await func(*args, **kwargs)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 217, in errorbad\n raise Exception(\"heyheyhey\")\nException: heyheyhey\n"}}}
```
2024-02-12 19:02:02 +00:00
raise UserFacingException ( f " missing ' address ' field for txout { txout_idx } " )
2023-03-19 18:34:43 +00:00
try :
txout_val = int ( txout_dict . get ( ' value ' ) or txout_dict [ ' value_sats ' ] )
except KeyError :
cli/rpc: nicer error messages and error-passing
Previously, generally, in case of any error, commands would raise a generic "Exception()" and the CLI/RPC would convert that and return it as `str(e)`.
With this change, we now distinguish "user-facing exceptions" (e.g. "Password required" or "wallet not loaded") and "internal errors" (e.g. bugs).
- for "user-facing exceptions", the behaviour is unchanged
- for "internal errors", we now pass around the traceback (e.g. from daemon server to rpc client) and show it to the user (previously, assuming there was a daemon running, the user could only retrieve the exception from the log of that daemon). These errors use a new jsonrpc error code int (code 2).
As the logic only changes for "internal errors", I deem this change not to be compatibility-breaking.
----------
Examples follow.
Consider the following two commands:
```
@command('')
async def errorgood(self):
from electrum.util import UserFacingException
raise UserFacingException("heyheyhey")
@command('')
async def errorbad(self):
raise Exception("heyheyhey")
```
----------
(before change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9221)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
heyheyhey
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
```
----------
(after change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9254)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
(inside daemon): Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/daemon.py", line 254, in handle
response['result'] = await f(*params)
File "/home/user/wspace/electrum/electrum/daemon.py", line 361, in run_cmdline
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
internal error while executing RPC
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
0.78 | E | __main__ | error running command (without daemon)
Traceback (most recent call last):
File "/home/user/wspace/electrum/./run_electrum", line 534, in handle_cmd
result = fut.result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 458, in result
return self.__get_result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
raise self._exception
File "/home/user/wspace/electrum/./run_electrum", line 255, in run_offline_command
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 2, "message": "internal error while executing RPC", "data": {"exception": "Exception('heyheyhey')", "traceback": "Traceback (most recent call last):\n File \"/home/user/wspace/electrum/electrum/daemon.py\", line 254, in handle\n response['result'] = await f(*params)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 163, in func_wrapper\n return await func(*args, **kwargs)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 217, in errorbad\n raise Exception(\"heyheyhey\")\nException: heyheyhey\n"}}}
```
2024-02-12 19:02:02 +00:00
raise UserFacingException ( f " missing ' value_sats ' field for txout { txout_idx } " )
2023-03-19 18:34:43 +00:00
txout = PartialTxOutput . from_address_and_value ( txout_addr , txout_val )
outputs . append ( txout )
2019-10-23 17:09:41 +02:00
tx = PartialTransaction . from_io ( inputs , outputs , locktime = locktime )
2016-05-16 14:39:01 +02:00
tx . sign ( keypairs )
2019-10-23 17:09:41 +02:00
return tx . serialize ( )
2013-02-26 13:56:48 +01:00
2021-06-08 16:33:55 +02:00
@command ( ' ' )
async def signtransaction_with_privkey ( self , tx , privkey ) :
""" Sign a transaction. The provided list of private keys will be used to sign the transaction. """
2020-08-26 19:48:35 +02:00
tx = tx_from_any ( tx )
2021-06-08 16:33:55 +02:00
txins_dict = defaultdict ( list )
for txin in tx . inputs ( ) :
txins_dict [ txin . address ] . append ( txin )
if not isinstance ( privkey , list ) :
privkey = [ privkey ]
for priv in privkey :
txin_type , priv2 , compressed = bitcoin . deserialize_privkey ( priv )
pubkey = ecc . ECPrivkey ( priv2 ) . get_public_key_bytes ( compressed = compressed )
2023-02-26 12:14:25 +00:00
desc = descriptor . get_singlesig_descriptor_from_legacy_leaf ( pubkey = pubkey . hex ( ) , script_type = txin_type )
address = desc . expand ( ) . address ( )
2021-06-08 16:33:55 +02:00
if address in txins_dict . keys ( ) :
for txin in txins_dict [ address ] :
2023-02-26 12:14:25 +00:00
txin . script_descriptor = desc
2024-04-29 16:32:19 +00:00
tx . sign ( { pubkey : priv2 } )
2021-06-08 16:33:55 +02:00
return tx . serialize ( )
@command ( ' wp ' )
2023-11-20 15:23:49 +01:00
async def signtransaction ( self , tx , password = None , wallet : Abstract_Wallet = None , iknowwhatimdoing : bool = False ) :
2021-06-08 16:33:55 +02:00
""" Sign a transaction. The wallet keys will be used to sign the transaction. """
tx = tx_from_any ( tx )
2023-11-20 15:23:49 +01:00
wallet . sign_transaction ( tx , password , ignore_warnings = iknowwhatimdoing )
2019-10-23 17:09:41 +02:00
return tx . serialize ( )
2013-02-26 13:56:48 +01:00
2015-06-01 06:10:06 +02:00
@command ( ' ' )
2019-08-15 13:17:16 +02:00
async def deserialize ( self , tx ) :
2015-06-10 23:21:25 +02:00
""" Deserialize a serialized transaction """
2019-10-23 17:09:41 +02:00
tx = tx_from_any ( tx )
return tx . to_json ( )
2013-02-26 13:56:48 +01:00
2015-06-01 06:10:06 +02:00
@command ( ' n ' )
2019-08-15 13:17:16 +02:00
async def broadcast ( self , tx ) :
2015-05-31 23:17:44 +02:00
""" Broadcast a transaction to the network. """
2016-03-20 19:05:38 +01:00
tx = Transaction ( tx )
2019-08-15 13:17:16 +02:00
await self . network . broadcast_transaction ( tx )
2019-01-18 19:59:12 +01:00
return tx . txid ( )
2013-02-26 13:56:48 +01:00
2015-06-01 06:10:06 +02:00
@command ( ' ' )
2019-08-15 13:17:16 +02:00
async def createmultisig ( self , num , pubkeys ) :
2015-05-31 23:17:44 +02:00
""" Create multisig address """
2015-05-30 18:49:58 +02:00
assert isinstance ( pubkeys , list ) , ( type ( num ) , type ( pubkeys ) )
2017-12-06 18:30:02 +01:00
redeem_script = multisig_script ( pubkeys , num )
2024-04-26 20:09:00 +00:00
address = bitcoin . hash160_to_p2sh ( hash_160 ( redeem_script ) )
return { ' address ' : address , ' redeemScript ' : redeem_script . hex ( ) }
2014-06-24 16:12:43 +03:00
2015-06-01 06:10:06 +02:00
@command ( ' w ' )
2021-01-22 16:47:22 +01:00
async def freeze ( self , address : str , wallet : Abstract_Wallet = None ) :
2015-05-31 23:17:44 +02:00
""" Freeze address. Freeze the funds at one of your wallet \' s addresses """
2019-09-05 17:57:51 +02:00
return wallet . set_frozen_state_of_addresses ( [ address ] , True )
2014-06-24 16:12:43 +03:00
2015-06-01 06:10:06 +02:00
@command ( ' w ' )
2021-01-22 16:47:22 +01:00
async def unfreeze ( self , address : str , wallet : Abstract_Wallet = None ) :
2015-05-31 23:17:44 +02:00
""" Unfreeze address. Unfreeze the funds at one of your wallet \' s address """
2019-09-05 17:57:51 +02:00
return wallet . set_frozen_state_of_addresses ( [ address ] , False )
2013-02-26 13:56:48 +01:00
2021-01-22 16:47:22 +01:00
@command ( ' w ' )
async def freeze_utxo ( self , coin : str , wallet : Abstract_Wallet = None ) :
""" Freeze a UTXO so that the wallet will not spend it. """
wallet . set_frozen_state_of_coins ( [ coin ] , True )
return True
@command ( ' w ' )
async def unfreeze_utxo ( self , coin : str , wallet : Abstract_Wallet = None ) :
""" Unfreeze a UTXO so that the wallet might spend it. """
wallet . set_frozen_state_of_coins ( [ coin ] , False )
return True
2015-06-01 06:10:06 +02:00
@command ( ' wp ' )
2019-09-21 02:14:22 +02:00
async def getprivatekeys ( self , address , password = None , wallet : Abstract_Wallet = None ) :
2015-08-16 16:30:55 +02:00
""" Get private keys of addresses. You may pass a single wallet address, or a list of wallet addresses. """
2017-11-19 23:46:59 +01:00
if isinstance ( address , str ) :
address = address . strip ( )
2016-06-11 16:55:19 +02:00
if is_address ( address ) :
2019-10-23 17:09:41 +02:00
return wallet . export_private_key ( address , password )
2017-03-15 06:12:26 +01:00
domain = address
2019-10-23 17:09:41 +02:00
return [ wallet . export_private_key ( address , password ) for address in domain ]
2013-02-26 16:03:04 +01:00
2020-03-31 05:50:18 +02:00
@command ( ' wp ' )
async def getprivatekeyforpath ( self , path , password = None , wallet : Abstract_Wallet = None ) :
""" Get private key corresponding to derivation path (address index).
' path ' can be either a str such as " m/0/50 " , or a list of ints such as [ 0 , 50 ] .
"""
return wallet . export_private_key_for_path ( path , password )
2015-06-01 06:10:06 +02:00
@command ( ' w ' )
2019-09-21 02:14:22 +02:00
async def ismine ( self , address , wallet : Abstract_Wallet = None ) :
2015-05-31 23:17:44 +02:00
""" Check if address is in wallet. Return true if and only address is in wallet """
2019-09-05 17:57:51 +02:00
return wallet . is_mine ( address )
2015-01-11 20:37:08 +01:00
2015-08-16 16:30:55 +02:00
@command ( ' ' )
2019-08-15 13:17:16 +02:00
async def dumpprivkeys ( self ) :
2015-08-16 16:30:55 +02:00
""" Deprecated. """
return " This command is deprecated. Use a pipe instead: ' electrum listaddresses | electrum getprivatekeys - ' "
2013-02-26 13:56:48 +01:00
2015-06-01 06:10:06 +02:00
@command ( ' ' )
2019-08-15 13:17:16 +02:00
async def validateaddress ( self , address ) :
2015-06-12 10:34:45 +02:00
""" Check that an address is valid. """
2015-05-31 22:42:34 +02:00
return is_address ( address )
2013-10-03 13:31:59 +02:00
2015-06-01 06:10:06 +02:00
@command ( ' w ' )
2019-09-21 02:14:22 +02:00
async def getpubkeys ( self , address , wallet : Abstract_Wallet = None ) :
2015-05-31 23:17:44 +02:00
""" Return the public keys for a wallet address. """
2019-09-05 17:57:51 +02:00
return wallet . get_public_keys ( address )
2013-02-26 13:56:48 +01:00
2015-12-23 15:59:32 +01:00
@command ( ' w ' )
2019-09-21 02:14:22 +02:00
async def getbalance ( self , wallet : Abstract_Wallet = None ) :
2015-12-23 15:59:32 +01:00
""" Return the balance of your wallet. """
2019-09-05 17:57:51 +02:00
c , u , x = wallet . get_balance ( )
l = wallet . lnworker . get_balance ( ) if wallet . lnworker else None
commands: fix satoshis decimal conversion in payto cmd and others
When called via jsonrpc (but not via cli) with non-string amounts,
there could be a rounding error resulting in sending 1 sat less.
example:
```
$ ./run_electrum --testnet -w ~/.electrum/testnet/wallets/test_segwit_2 paytomany '[["tb1q6k5h4cz6ra8nzhg90xm9wldvadgh0fpttfthcg", 0.00033389]]' --fee 0
02000000000101b9e6018acb16952e3c9618b069df404dc85544eda8120e5f6e7cd7e94ce5ae8d0100000000fdffffff02fd8100000000000016001410c5b97085ec1637a9f702852f5a81f650fae1566d82000000000000160014d5a97ae05a1f4f315d0579b6577daceb5177a42b024730440220251d2ce83f6e69273de8e9be8602fbcf72b9157e1c0116161fa52f7e04db6e4302202d84045cc6b7056a215d1db3f59884e28dadd5257e1a3960068f90df90b452d1012102b0eff3bf364a2ab5effe952cba33521ebede81dac88c71951a5ed598cb48347b3a022500
$ curl --data-binary '{"id":"curltext","method":"paytomany","params":{"outputs":[["tb1q6k5h4cz6ra8nzhg90xm9wldvadgh0fpttfthcg", 0.00033389]], "fee": 0, "wallet": "/home/user/.electrum/testnet/wallets/test_segwit_2"}}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "result": "02000000000101b9e6018acb16952e3c9618b069df404dc85544eda8120e5f6e7cd7e94ce5ae8d0100000000fdffffff02fe8100000000000016001410c5b97085ec1637a9f702852f5a81f650fae1566c82000000000000160014d5a97ae05a1f4f315d0579b6577daceb5177a42b0247304402206ef66b845ca298c14dc6e8049cba9ed19db1671132194518ce5d521de6f5df8802205ca4b1aee703e3b98331fb9b88210917b385560020c8b2a8a88da38996b101c4012102b0eff3bf364a2ab5effe952cba33521ebede81dac88c71951a5ed598cb48347b39022500"}
```
^ note that first tx has output for 0.00033389, second tx has output for 0.00033388
fixes https://github.com/spesmilo/electrum/issues/8274
2023-03-22 12:22:36 +00:00
out = { " confirmed " : str ( to_decimal ( c ) / COIN ) }
2015-05-05 20:52:14 +02:00
if u :
commands: fix satoshis decimal conversion in payto cmd and others
When called via jsonrpc (but not via cli) with non-string amounts,
there could be a rounding error resulting in sending 1 sat less.
example:
```
$ ./run_electrum --testnet -w ~/.electrum/testnet/wallets/test_segwit_2 paytomany '[["tb1q6k5h4cz6ra8nzhg90xm9wldvadgh0fpttfthcg", 0.00033389]]' --fee 0
02000000000101b9e6018acb16952e3c9618b069df404dc85544eda8120e5f6e7cd7e94ce5ae8d0100000000fdffffff02fd8100000000000016001410c5b97085ec1637a9f702852f5a81f650fae1566d82000000000000160014d5a97ae05a1f4f315d0579b6577daceb5177a42b024730440220251d2ce83f6e69273de8e9be8602fbcf72b9157e1c0116161fa52f7e04db6e4302202d84045cc6b7056a215d1db3f59884e28dadd5257e1a3960068f90df90b452d1012102b0eff3bf364a2ab5effe952cba33521ebede81dac88c71951a5ed598cb48347b3a022500
$ curl --data-binary '{"id":"curltext","method":"paytomany","params":{"outputs":[["tb1q6k5h4cz6ra8nzhg90xm9wldvadgh0fpttfthcg", 0.00033389]], "fee": 0, "wallet": "/home/user/.electrum/testnet/wallets/test_segwit_2"}}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "result": "02000000000101b9e6018acb16952e3c9618b069df404dc85544eda8120e5f6e7cd7e94ce5ae8d0100000000fdffffff02fe8100000000000016001410c5b97085ec1637a9f702852f5a81f650fae1566c82000000000000160014d5a97ae05a1f4f315d0579b6577daceb5177a42b0247304402206ef66b845ca298c14dc6e8049cba9ed19db1671132194518ce5d521de6f5df8802205ca4b1aee703e3b98331fb9b88210917b385560020c8b2a8a88da38996b101c4012102b0eff3bf364a2ab5effe952cba33521ebede81dac88c71951a5ed598cb48347b39022500"}
```
^ note that first tx has output for 0.00033389, second tx has output for 0.00033388
fixes https://github.com/spesmilo/electrum/issues/8274
2023-03-22 12:22:36 +00:00
out [ " unconfirmed " ] = str ( to_decimal ( u ) / COIN )
2015-05-05 20:52:14 +02:00
if x :
commands: fix satoshis decimal conversion in payto cmd and others
When called via jsonrpc (but not via cli) with non-string amounts,
there could be a rounding error resulting in sending 1 sat less.
example:
```
$ ./run_electrum --testnet -w ~/.electrum/testnet/wallets/test_segwit_2 paytomany '[["tb1q6k5h4cz6ra8nzhg90xm9wldvadgh0fpttfthcg", 0.00033389]]' --fee 0
02000000000101b9e6018acb16952e3c9618b069df404dc85544eda8120e5f6e7cd7e94ce5ae8d0100000000fdffffff02fd8100000000000016001410c5b97085ec1637a9f702852f5a81f650fae1566d82000000000000160014d5a97ae05a1f4f315d0579b6577daceb5177a42b024730440220251d2ce83f6e69273de8e9be8602fbcf72b9157e1c0116161fa52f7e04db6e4302202d84045cc6b7056a215d1db3f59884e28dadd5257e1a3960068f90df90b452d1012102b0eff3bf364a2ab5effe952cba33521ebede81dac88c71951a5ed598cb48347b3a022500
$ curl --data-binary '{"id":"curltext","method":"paytomany","params":{"outputs":[["tb1q6k5h4cz6ra8nzhg90xm9wldvadgh0fpttfthcg", 0.00033389]], "fee": 0, "wallet": "/home/user/.electrum/testnet/wallets/test_segwit_2"}}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "result": "02000000000101b9e6018acb16952e3c9618b069df404dc85544eda8120e5f6e7cd7e94ce5ae8d0100000000fdffffff02fe8100000000000016001410c5b97085ec1637a9f702852f5a81f650fae1566c82000000000000160014d5a97ae05a1f4f315d0579b6577daceb5177a42b0247304402206ef66b845ca298c14dc6e8049cba9ed19db1671132194518ce5d521de6f5df8802205ca4b1aee703e3b98331fb9b88210917b385560020c8b2a8a88da38996b101c4012102b0eff3bf364a2ab5effe952cba33521ebede81dac88c71951a5ed598cb48347b39022500"}
```
^ note that first tx has output for 0.00033389, second tx has output for 0.00033388
fixes https://github.com/spesmilo/electrum/issues/8274
2023-03-22 12:22:36 +00:00
out [ " unmatured " ] = str ( to_decimal ( x ) / COIN )
2019-01-30 19:16:04 +01:00
if l :
commands: fix satoshis decimal conversion in payto cmd and others
When called via jsonrpc (but not via cli) with non-string amounts,
there could be a rounding error resulting in sending 1 sat less.
example:
```
$ ./run_electrum --testnet -w ~/.electrum/testnet/wallets/test_segwit_2 paytomany '[["tb1q6k5h4cz6ra8nzhg90xm9wldvadgh0fpttfthcg", 0.00033389]]' --fee 0
02000000000101b9e6018acb16952e3c9618b069df404dc85544eda8120e5f6e7cd7e94ce5ae8d0100000000fdffffff02fd8100000000000016001410c5b97085ec1637a9f702852f5a81f650fae1566d82000000000000160014d5a97ae05a1f4f315d0579b6577daceb5177a42b024730440220251d2ce83f6e69273de8e9be8602fbcf72b9157e1c0116161fa52f7e04db6e4302202d84045cc6b7056a215d1db3f59884e28dadd5257e1a3960068f90df90b452d1012102b0eff3bf364a2ab5effe952cba33521ebede81dac88c71951a5ed598cb48347b3a022500
$ curl --data-binary '{"id":"curltext","method":"paytomany","params":{"outputs":[["tb1q6k5h4cz6ra8nzhg90xm9wldvadgh0fpttfthcg", 0.00033389]], "fee": 0, "wallet": "/home/user/.electrum/testnet/wallets/test_segwit_2"}}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "result": "02000000000101b9e6018acb16952e3c9618b069df404dc85544eda8120e5f6e7cd7e94ce5ae8d0100000000fdffffff02fe8100000000000016001410c5b97085ec1637a9f702852f5a81f650fae1566c82000000000000160014d5a97ae05a1f4f315d0579b6577daceb5177a42b0247304402206ef66b845ca298c14dc6e8049cba9ed19db1671132194518ce5d521de6f5df8802205ca4b1aee703e3b98331fb9b88210917b385560020c8b2a8a88da38996b101c4012102b0eff3bf364a2ab5effe952cba33521ebede81dac88c71951a5ed598cb48347b39022500"}
```
^ note that first tx has output for 0.00033389, second tx has output for 0.00033388
fixes https://github.com/spesmilo/electrum/issues/8274
2023-03-22 12:22:36 +00:00
out [ " lightning " ] = str ( to_decimal ( l ) / COIN )
2013-04-16 16:05:45 +02:00
return out
2013-02-27 10:24:53 +01:00
2015-06-01 06:10:06 +02:00
@command ( ' n ' )
2019-08-15 13:17:16 +02:00
async def getaddressbalance ( self , address ) :
2015-06-03 09:12:38 +02:00
""" Return the balance of any address. Note: This is a walletless
server query , results are not checked by SPV .
"""
2018-02-02 23:26:25 +01:00
sh = bitcoin . address_to_scripthash ( address )
2019-08-15 13:17:16 +02:00
out = await self . network . get_balance_for_scripthash ( sh )
commands: fix satoshis decimal conversion in payto cmd and others
When called via jsonrpc (but not via cli) with non-string amounts,
there could be a rounding error resulting in sending 1 sat less.
example:
```
$ ./run_electrum --testnet -w ~/.electrum/testnet/wallets/test_segwit_2 paytomany '[["tb1q6k5h4cz6ra8nzhg90xm9wldvadgh0fpttfthcg", 0.00033389]]' --fee 0
02000000000101b9e6018acb16952e3c9618b069df404dc85544eda8120e5f6e7cd7e94ce5ae8d0100000000fdffffff02fd8100000000000016001410c5b97085ec1637a9f702852f5a81f650fae1566d82000000000000160014d5a97ae05a1f4f315d0579b6577daceb5177a42b024730440220251d2ce83f6e69273de8e9be8602fbcf72b9157e1c0116161fa52f7e04db6e4302202d84045cc6b7056a215d1db3f59884e28dadd5257e1a3960068f90df90b452d1012102b0eff3bf364a2ab5effe952cba33521ebede81dac88c71951a5ed598cb48347b3a022500
$ curl --data-binary '{"id":"curltext","method":"paytomany","params":{"outputs":[["tb1q6k5h4cz6ra8nzhg90xm9wldvadgh0fpttfthcg", 0.00033389]], "fee": 0, "wallet": "/home/user/.electrum/testnet/wallets/test_segwit_2"}}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "result": "02000000000101b9e6018acb16952e3c9618b069df404dc85544eda8120e5f6e7cd7e94ce5ae8d0100000000fdffffff02fe8100000000000016001410c5b97085ec1637a9f702852f5a81f650fae1566c82000000000000160014d5a97ae05a1f4f315d0579b6577daceb5177a42b0247304402206ef66b845ca298c14dc6e8049cba9ed19db1671132194518ce5d521de6f5df8802205ca4b1aee703e3b98331fb9b88210917b385560020c8b2a8a88da38996b101c4012102b0eff3bf364a2ab5effe952cba33521ebede81dac88c71951a5ed598cb48347b39022500"}
```
^ note that first tx has output for 0.00033389, second tx has output for 0.00033388
fixes https://github.com/spesmilo/electrum/issues/8274
2023-03-22 12:22:36 +00:00
out [ " confirmed " ] = str ( to_decimal ( out [ " confirmed " ] ) / COIN )
out [ " unconfirmed " ] = str ( to_decimal ( out [ " unconfirmed " ] ) / COIN )
2014-03-02 10:31:34 +01:00
return out
2015-06-01 06:10:06 +02:00
@command ( ' n ' )
2019-08-15 13:17:16 +02:00
async def getmerkle ( self , txid , height ) :
2015-06-12 10:34:45 +02:00
""" Get Merkle branch of a transaction included in a block. Electrum
uses this to verify transactions ( Simple Payment Verification ) . """
2019-08-15 13:17:16 +02:00
return await self . network . get_merkle_for_transaction ( txid , int ( height ) )
2015-05-12 12:30:26 +02:00
2015-06-01 06:10:06 +02:00
@command ( ' n ' )
2019-08-15 13:17:16 +02:00
async def getservers ( self ) :
2020-03-30 22:50:25 +00:00
""" Return the list of known servers (candidates for connecting). """
2013-10-02 13:50:36 +02:00
return self . network . get_servers ( )
2013-02-26 13:56:48 +01:00
2015-06-01 06:10:06 +02:00
@command ( ' ' )
2019-08-15 13:17:16 +02:00
async def version ( self ) :
2018-04-15 20:45:30 +03:00
""" Return the version of Electrum. """
2016-10-11 12:08:56 +02:00
return ELECTRUM_VERSION
2014-06-24 16:12:43 +03:00
commands: add "version_info" cmd
example:
```
$ ./run_electrum -o version_info
{
"aiohttp.version": "3.8.1",
"aiorpcx.version": "0.22.1",
"certifi.version": "2021.10.08",
"cryptodome.version": null,
"cryptography.path": "/home/user/.local/lib/python3.8/site-packages/cryptography",
"cryptography.version": "3.4.6",
"dnspython.version": "2.2.0",
"electrum.path": "/home/user/wspace/electrum/electrum",
"electrum.version": "4.2.1",
"hidapi.version": "0.11.0.post2",
"libsecp256k1.path": "/home/user/wspace/electrum/electrum/libsecp256k1.so.0",
"libusb.path": "libusb-1.0.so",
"libusb.version": "1.0.23.11397",
"libzbar.path": "/home/user/wspace/electrum/electrum/libzbar.so.0",
"pyaes.version": "1.3.0",
"pyqt.path": "/usr/lib/python3/dist-packages/PyQt5",
"pyqt.version": "5.14.1",
"qt.version": "5.12.8"
}
```
2022-04-11 17:05:26 +02:00
@command ( ' ' )
async def version_info ( self ) :
""" Return information about dependencies, such as their version and path. """
ret = {
" electrum.version " : ELECTRUM_VERSION ,
" electrum.path " : os . path . dirname ( os . path . realpath ( __file__ ) ) ,
2023-06-02 21:47:04 +00:00
" python.version " : sys . version ,
" python.path " : sys . executable ,
commands: add "version_info" cmd
example:
```
$ ./run_electrum -o version_info
{
"aiohttp.version": "3.8.1",
"aiorpcx.version": "0.22.1",
"certifi.version": "2021.10.08",
"cryptodome.version": null,
"cryptography.path": "/home/user/.local/lib/python3.8/site-packages/cryptography",
"cryptography.version": "3.4.6",
"dnspython.version": "2.2.0",
"electrum.path": "/home/user/wspace/electrum/electrum",
"electrum.version": "4.2.1",
"hidapi.version": "0.11.0.post2",
"libsecp256k1.path": "/home/user/wspace/electrum/electrum/libsecp256k1.so.0",
"libusb.path": "libusb-1.0.so",
"libusb.version": "1.0.23.11397",
"libzbar.path": "/home/user/wspace/electrum/electrum/libzbar.so.0",
"pyaes.version": "1.3.0",
"pyqt.path": "/usr/lib/python3/dist-packages/PyQt5",
"pyqt.version": "5.14.1",
"qt.version": "5.12.8"
}
```
2022-04-11 17:05:26 +02:00
}
# add currently running GUI
if self . daemon and self . daemon . gui_object :
ret . update ( self . daemon . gui_object . version_info ( ) )
# always add Qt GUI, so we get info even when running this from CLI
try :
from . gui . qt import ElectrumGui as QtElectrumGui
ret . update ( QtElectrumGui . version_info ( ) )
except GuiImportError :
pass
# Add shared libs (.so/.dll), and non-pure-python dependencies.
# Such deps can be installed in various ways - often via the Linux distro's pkg manager,
# instead of using pip, hence it is useful to list them for debugging.
2024-06-17 13:38:54 +02:00
from electrum_ecc import ecc_fast
commands: add "version_info" cmd
example:
```
$ ./run_electrum -o version_info
{
"aiohttp.version": "3.8.1",
"aiorpcx.version": "0.22.1",
"certifi.version": "2021.10.08",
"cryptodome.version": null,
"cryptography.path": "/home/user/.local/lib/python3.8/site-packages/cryptography",
"cryptography.version": "3.4.6",
"dnspython.version": "2.2.0",
"electrum.path": "/home/user/wspace/electrum/electrum",
"electrum.version": "4.2.1",
"hidapi.version": "0.11.0.post2",
"libsecp256k1.path": "/home/user/wspace/electrum/electrum/libsecp256k1.so.0",
"libusb.path": "libusb-1.0.so",
"libusb.version": "1.0.23.11397",
"libzbar.path": "/home/user/wspace/electrum/electrum/libzbar.so.0",
"pyaes.version": "1.3.0",
"pyqt.path": "/usr/lib/python3/dist-packages/PyQt5",
"pyqt.version": "5.14.1",
"qt.version": "5.12.8"
}
```
2022-04-11 17:05:26 +02:00
ret . update ( ecc_fast . version_info ( ) )
from . import qrscanner
ret . update ( qrscanner . version_info ( ) )
ret . update ( DeviceMgr . version_info ( ) )
ret . update ( crypto . version_info ( ) )
# add some special cases
import aiohttp
ret [ " aiohttp.version " ] = aiohttp . __version__
import aiorpcx
ret [ " aiorpcx.version " ] = aiorpcx . _version_str
import certifi
ret [ " certifi.version " ] = certifi . __version__
import dns
ret [ " dnspython.version " ] = dns . __version__
return ret
2015-06-01 06:10:06 +02:00
@command ( ' w ' )
2019-09-21 02:14:22 +02:00
async def getmpk ( self , wallet : Abstract_Wallet = None ) :
2016-06-11 16:10:45 +02:00
""" Get master public key. Return your wallet \' s master public key """
2019-09-05 17:57:51 +02:00
return wallet . get_master_public_key ( )
2013-11-03 14:14:35 +01:00
2015-08-14 15:23:50 +02:00
@command ( ' wp ' )
2019-09-21 02:14:22 +02:00
async def getmasterprivate ( self , password = None , wallet : Abstract_Wallet = None ) :
2015-08-14 15:23:50 +02:00
""" Get master private key. Return your wallet \' s master private key """
2019-09-05 17:57:51 +02:00
return str ( wallet . keystore . get_master_private_key ( password ) )
2015-08-14 15:23:50 +02:00
2019-01-20 15:49:42 +01:00
@command ( ' ' )
2019-08-15 13:17:16 +02:00
async def convert_xkey ( self , xkey , xtype ) :
2019-01-20 15:49:42 +01:00
""" Convert xtype of a master key. e.g. xpub -> ypub """
2019-02-21 22:17:06 +01:00
try :
node = BIP32Node . from_xkey ( xkey )
2023-04-23 01:33:12 +00:00
except Exception :
cli/rpc: nicer error messages and error-passing
Previously, generally, in case of any error, commands would raise a generic "Exception()" and the CLI/RPC would convert that and return it as `str(e)`.
With this change, we now distinguish "user-facing exceptions" (e.g. "Password required" or "wallet not loaded") and "internal errors" (e.g. bugs).
- for "user-facing exceptions", the behaviour is unchanged
- for "internal errors", we now pass around the traceback (e.g. from daemon server to rpc client) and show it to the user (previously, assuming there was a daemon running, the user could only retrieve the exception from the log of that daemon). These errors use a new jsonrpc error code int (code 2).
As the logic only changes for "internal errors", I deem this change not to be compatibility-breaking.
----------
Examples follow.
Consider the following two commands:
```
@command('')
async def errorgood(self):
from electrum.util import UserFacingException
raise UserFacingException("heyheyhey")
@command('')
async def errorbad(self):
raise Exception("heyheyhey")
```
----------
(before change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9221)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
heyheyhey
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
```
----------
(after change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9254)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
(inside daemon): Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/daemon.py", line 254, in handle
response['result'] = await f(*params)
File "/home/user/wspace/electrum/electrum/daemon.py", line 361, in run_cmdline
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
internal error while executing RPC
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
0.78 | E | __main__ | error running command (without daemon)
Traceback (most recent call last):
File "/home/user/wspace/electrum/./run_electrum", line 534, in handle_cmd
result = fut.result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 458, in result
return self.__get_result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
raise self._exception
File "/home/user/wspace/electrum/./run_electrum", line 255, in run_offline_command
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 2, "message": "internal error while executing RPC", "data": {"exception": "Exception('heyheyhey')", "traceback": "Traceback (most recent call last):\n File \"/home/user/wspace/electrum/electrum/daemon.py\", line 254, in handle\n response['result'] = await f(*params)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 163, in func_wrapper\n return await func(*args, **kwargs)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 217, in errorbad\n raise Exception(\"heyheyhey\")\nException: heyheyhey\n"}}}
```
2024-02-12 19:02:02 +00:00
raise UserFacingException ( ' xkey should be a master public/private key ' )
2019-02-21 22:17:06 +01:00
return node . _replace ( xtype = xtype ) . to_xkey ( )
2019-01-20 15:49:42 +01:00
2015-06-01 06:10:06 +02:00
@command ( ' wp ' )
2019-09-21 02:14:22 +02:00
async def getseed ( self , password = None , wallet : Abstract_Wallet = None ) :
2015-05-31 23:17:44 +02:00
""" Get seed phrase. Print the generation seed of your wallet. """
2019-09-05 17:57:51 +02:00
s = wallet . get_seed ( password )
2017-08-27 09:53:22 +02:00
return s
2013-02-26 13:56:48 +01:00
2015-06-01 06:10:06 +02:00
@command ( ' wp ' )
2019-09-21 02:14:22 +02:00
async def importprivkey ( self , privkey , password = None , wallet : Abstract_Wallet = None ) :
2021-03-31 04:33:33 +02:00
""" Import a private key or a list of private keys. """
2019-09-05 17:57:51 +02:00
if not wallet . can_import_privkey ( ) :
2017-06-30 12:20:19 +02:00
return " Error: This type of wallet cannot import private keys. Try to create a new wallet with that key. "
2021-03-31 04:33:33 +02:00
assert isinstance ( wallet , Imported_Wallet )
keys = privkey . split ( )
if not keys :
return " Error: no keys given "
elif len ( keys ) == 1 :
try :
addr = wallet . import_private_key ( keys [ 0 ] , password )
out = " Keypair imported: " + addr
except Exception as e :
out = " Error: " + repr ( e )
return out
else :
good_inputs , bad_inputs = wallet . import_private_keys ( keys , password )
return {
" good_keys " : len ( good_inputs ) ,
" bad_keys " : len ( bad_inputs ) ,
}
2013-02-26 13:56:48 +01:00
cli/rpc: nicer error messages and error-passing
Previously, generally, in case of any error, commands would raise a generic "Exception()" and the CLI/RPC would convert that and return it as `str(e)`.
With this change, we now distinguish "user-facing exceptions" (e.g. "Password required" or "wallet not loaded") and "internal errors" (e.g. bugs).
- for "user-facing exceptions", the behaviour is unchanged
- for "internal errors", we now pass around the traceback (e.g. from daemon server to rpc client) and show it to the user (previously, assuming there was a daemon running, the user could only retrieve the exception from the log of that daemon). These errors use a new jsonrpc error code int (code 2).
As the logic only changes for "internal errors", I deem this change not to be compatibility-breaking.
----------
Examples follow.
Consider the following two commands:
```
@command('')
async def errorgood(self):
from electrum.util import UserFacingException
raise UserFacingException("heyheyhey")
@command('')
async def errorbad(self):
raise Exception("heyheyhey")
```
----------
(before change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9221)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
heyheyhey
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
```
----------
(after change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9254)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
(inside daemon): Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/daemon.py", line 254, in handle
response['result'] = await f(*params)
File "/home/user/wspace/electrum/electrum/daemon.py", line 361, in run_cmdline
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
internal error while executing RPC
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
0.78 | E | __main__ | error running command (without daemon)
Traceback (most recent call last):
File "/home/user/wspace/electrum/./run_electrum", line 534, in handle_cmd
result = fut.result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 458, in result
return self.__get_result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
raise self._exception
File "/home/user/wspace/electrum/./run_electrum", line 255, in run_offline_command
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 2, "message": "internal error while executing RPC", "data": {"exception": "Exception('heyheyhey')", "traceback": "Traceback (most recent call last):\n File \"/home/user/wspace/electrum/electrum/daemon.py\", line 254, in handle\n response['result'] = await f(*params)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 163, in func_wrapper\n return await func(*args, **kwargs)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 217, in errorbad\n raise Exception(\"heyheyhey\")\nException: heyheyhey\n"}}}
```
2024-02-12 19:02:02 +00:00
def _resolver ( self , x , wallet : Abstract_Wallet ) :
2015-06-11 12:08:38 +02:00
if x is None :
return None
2019-09-05 17:57:51 +02:00
out = wallet . contacts . resolve ( x )
2015-06-11 12:08:38 +02:00
if out . get ( ' type ' ) == ' openalias ' and self . nocheck is False and out . get ( ' validated ' ) is False :
cli/rpc: nicer error messages and error-passing
Previously, generally, in case of any error, commands would raise a generic "Exception()" and the CLI/RPC would convert that and return it as `str(e)`.
With this change, we now distinguish "user-facing exceptions" (e.g. "Password required" or "wallet not loaded") and "internal errors" (e.g. bugs).
- for "user-facing exceptions", the behaviour is unchanged
- for "internal errors", we now pass around the traceback (e.g. from daemon server to rpc client) and show it to the user (previously, assuming there was a daemon running, the user could only retrieve the exception from the log of that daemon). These errors use a new jsonrpc error code int (code 2).
As the logic only changes for "internal errors", I deem this change not to be compatibility-breaking.
----------
Examples follow.
Consider the following two commands:
```
@command('')
async def errorgood(self):
from electrum.util import UserFacingException
raise UserFacingException("heyheyhey")
@command('')
async def errorbad(self):
raise Exception("heyheyhey")
```
----------
(before change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9221)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
heyheyhey
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
```
----------
(after change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9254)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
(inside daemon): Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/daemon.py", line 254, in handle
response['result'] = await f(*params)
File "/home/user/wspace/electrum/electrum/daemon.py", line 361, in run_cmdline
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
internal error while executing RPC
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
0.78 | E | __main__ | error running command (without daemon)
Traceback (most recent call last):
File "/home/user/wspace/electrum/./run_electrum", line 534, in handle_cmd
result = fut.result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 458, in result
return self.__get_result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
raise self._exception
File "/home/user/wspace/electrum/./run_electrum", line 255, in run_offline_command
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 2, "message": "internal error while executing RPC", "data": {"exception": "Exception('heyheyhey')", "traceback": "Traceback (most recent call last):\n File \"/home/user/wspace/electrum/electrum/daemon.py\", line 254, in handle\n response['result'] = await f(*params)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 163, in func_wrapper\n return await func(*args, **kwargs)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 217, in errorbad\n raise Exception(\"heyheyhey\")\nException: heyheyhey\n"}}}
```
2024-02-12 19:02:02 +00:00
raise UserFacingException ( f " cannot verify alias: { x } " )
2015-06-11 12:08:38 +02:00
return out [ ' address ' ]
2017-11-29 13:45:02 +01:00
@command ( ' n ' )
2025-02-24 12:20:44 +01:00
async def sweep ( self , privkey , destination , fee = None , feerate = None , nocheck = False , imax = 100 ) :
2015-08-16 16:11:52 +02:00
""" Sweep private keys. Returns a transaction that spends UTXOs from
2015-05-31 23:23:13 +02:00
privkey to a destination address . The transaction is not
broadcasted . """
2017-11-29 13:45:02 +01:00
from . wallet import sweep
2025-02-24 12:20:44 +01:00
fee_policy = self . _get_fee_policy ( fee , feerate )
2017-10-05 20:08:16 +02:00
privkeys = privkey . split ( )
2015-06-11 12:08:38 +02:00
self . nocheck = nocheck
2017-11-29 13:45:02 +01:00
#dest = self._resolver(destination)
2020-12-08 12:21:56 +01:00
tx = await sweep (
privkeys ,
network = self . network ,
to_address = destination ,
2025-02-24 12:20:44 +01:00
fee_policy = fee_policy ,
2020-12-08 12:21:56 +01:00
imax = imax ,
)
2019-10-23 17:09:41 +02:00
return tx . serialize ( ) if tx else None
2014-04-25 17:23:26 +02:00
2015-06-01 06:10:06 +02:00
@command ( ' wp ' )
2019-09-21 02:14:22 +02:00
async def signmessage ( self , address , message , password = None , wallet : Abstract_Wallet = None ) :
2015-05-31 23:23:13 +02:00
""" Sign a message with a key. Use quotes if your message contains
whitespaces """
2019-09-05 17:57:51 +02:00
sig = wallet . sign_message ( address , message , password )
2017-10-17 20:15:33 +02:00
return base64 . b64encode ( sig ) . decode ( ' ascii ' )
2013-02-26 13:56:48 +01:00
2015-06-01 06:10:06 +02:00
@command ( ' ' )
2019-08-15 13:17:16 +02:00
async def verifymessage ( self , address , signature , message ) :
2015-05-31 23:17:44 +02:00
""" Verify a signature. """
2015-07-14 16:37:04 +02:00
sig = base64 . b64decode ( signature )
2017-12-04 17:36:57 +01:00
message = util . to_bytes ( message )
2024-06-17 11:20:37 +02:00
return bitcoin . verify_usermessage_with_address ( address , sig , message )
2013-02-26 13:56:48 +01:00
2025-02-24 12:20:44 +01:00
def _get_fee_policy ( self , fee , feerate ) :
if fee is not None and feerate is not None :
raise Exception ( ' Cannot set both fee and feerate ' )
if fee is not None :
fee_sats = satoshis ( fee )
fee_policy = FeePolicy ( f ' fixed: { fee_sats } ' )
elif feerate is not None :
feerate_per_byte = 1000 * feerate
fee_policy = FeePolicy ( f ' feerate: { feerate_per_byte } ' )
else :
fee_policy = FeePolicy ( self . config . FEE_POLICY )
return fee_policy
2015-12-23 15:59:32 +01:00
@command ( ' wp ' )
2019-09-21 20:07:16 +00:00
async def payto ( self , destination , amount , fee = None , feerate = None , from_addr = None , from_coins = None , change_addr = None ,
2022-12-07 13:08:48 +01:00
nocheck = False , unsigned = False , rbf = True , password = None , locktime = None , addtransaction = False , wallet : Abstract_Wallet = None ) :
2015-05-31 23:17:44 +02:00
""" Create a transaction. """
2020-05-21 10:51:08 +02:00
self . nocheck = nocheck
2025-02-24 12:20:44 +01:00
fee_policy = self . _get_fee_policy ( fee , feerate )
2019-09-21 20:07:16 +00:00
domain_addr = from_addr . split ( ' , ' ) if from_addr else None
domain_coins = from_coins . split ( ' , ' ) if from_coins else None
2020-05-21 10:51:08 +02:00
change_addr = self . _resolver ( change_addr , wallet )
domain_addr = None if domain_addr is None else map ( self . _resolver , domain_addr , repeat ( wallet ) )
2021-04-16 16:03:25 +02:00
amount_sat = satoshis_or_max ( amount )
2020-05-21 10:51:08 +02:00
outputs = [ PartialTxOutput . from_address_and_value ( destination , amount_sat ) ]
tx = wallet . create_transaction (
outputs ,
2025-02-24 12:20:44 +01:00
fee_policy = fee_policy ,
2020-05-21 10:51:08 +02:00
change_addr = change_addr ,
domain_addr = domain_addr ,
domain_coins = domain_coins ,
2023-10-10 17:45:26 +00:00
sign = not unsigned ,
2020-05-21 10:51:08 +02:00
rbf = rbf ,
password = password ,
locktime = locktime )
2020-09-01 22:25:36 +03:00
result = tx . serialize ( )
if addtransaction :
await self . addtransaction ( result , wallet = wallet )
return result
2015-05-30 18:49:58 +02:00
2015-12-23 15:59:32 +01:00
@command ( ' wp ' )
2019-09-21 20:07:16 +00:00
async def paytomany ( self , outputs , fee = None , feerate = None , from_addr = None , from_coins = None , change_addr = None ,
2022-12-11 07:35:17 +01:00
nocheck = False , unsigned = False , rbf = True , password = None , locktime = None , addtransaction = False , wallet : Abstract_Wallet = None ) :
2015-05-31 23:17:44 +02:00
""" Create a multi-output transaction. """
2020-05-21 10:51:08 +02:00
self . nocheck = nocheck
2025-02-24 12:20:44 +01:00
fee_policy = self . _get_fee_policy ( fee , feerate )
2019-09-21 20:07:16 +00:00
domain_addr = from_addr . split ( ' , ' ) if from_addr else None
domain_coins = from_coins . split ( ' , ' ) if from_coins else None
2020-05-21 10:51:08 +02:00
change_addr = self . _resolver ( change_addr , wallet )
domain_addr = None if domain_addr is None else map ( self . _resolver , domain_addr , repeat ( wallet ) )
final_outputs = [ ]
for address , amount in outputs :
address = self . _resolver ( address , wallet )
2021-04-16 16:03:25 +02:00
amount_sat = satoshis_or_max ( amount )
2020-05-21 10:51:08 +02:00
final_outputs . append ( PartialTxOutput . from_address_and_value ( address , amount_sat ) )
tx = wallet . create_transaction (
final_outputs ,
2025-02-24 12:20:44 +01:00
fee_policy = fee_policy ,
2020-05-21 10:51:08 +02:00
change_addr = change_addr ,
domain_addr = domain_addr ,
domain_coins = domain_coins ,
2023-10-10 17:45:26 +00:00
sign = not unsigned ,
2020-05-21 10:51:08 +02:00
rbf = rbf ,
password = password ,
locktime = locktime )
2020-09-01 22:25:36 +03:00
result = tx . serialize ( )
if addtransaction :
await self . addtransaction ( result , wallet = wallet )
return result
2013-04-08 23:40:51 +01:00
2025-02-16 16:59:19 +01:00
def get_year_timestamps ( self , year : int ) :
kwargs = { }
2018-02-09 15:28:28 +01:00
if year :
import time
start_date = datetime . datetime ( year , 1 , 1 )
end_date = datetime . datetime ( year + 1 , 1 , 1 )
kwargs [ ' from_timestamp ' ] = time . mktime ( start_date . timetuple ( ) )
kwargs [ ' to_timestamp ' ] = time . mktime ( end_date . timetuple ( ) )
2025-02-16 16:59:19 +01:00
return kwargs
2021-02-19 12:45:45 +02:00
2025-02-16 16:59:19 +01:00
@command ( ' w ' )
async def onchain_capital_gains ( self , year = None , wallet : Abstract_Wallet = None ) :
"""
Capital gains , using utxo pricing .
This cannot be used with lightning .
"""
kwargs = self . get_year_timestamps ( year )
from . exchange_rate import FxThread
fx = self . daemon . fx if self . daemon else FxThread ( config = self . config )
return json_normalize ( wallet . get_onchain_capital_gains ( fx , * * kwargs ) )
2019-06-14 13:01:23 +02:00
2021-08-11 17:00:26 +05:30
@command ( ' wp ' )
2022-09-26 14:40:28 +02:00
async def bumpfee ( self , tx , new_fee_rate , from_coins = None , decrease_payment = False , password = None , unsigned = False , wallet : Abstract_Wallet = None ) :
2023-09-07 13:18:23 +00:00
""" Bump the fee for an unconfirmed transaction.
' tx ' can be either a raw hex tx or a txid . If txid , the corresponding tx must already be part of the wallet history .
"""
if is_hash256_str ( tx ) : # txid
tx = wallet . db . get_transaction ( tx )
if tx is None :
cli/rpc: nicer error messages and error-passing
Previously, generally, in case of any error, commands would raise a generic "Exception()" and the CLI/RPC would convert that and return it as `str(e)`.
With this change, we now distinguish "user-facing exceptions" (e.g. "Password required" or "wallet not loaded") and "internal errors" (e.g. bugs).
- for "user-facing exceptions", the behaviour is unchanged
- for "internal errors", we now pass around the traceback (e.g. from daemon server to rpc client) and show it to the user (previously, assuming there was a daemon running, the user could only retrieve the exception from the log of that daemon). These errors use a new jsonrpc error code int (code 2).
As the logic only changes for "internal errors", I deem this change not to be compatibility-breaking.
----------
Examples follow.
Consider the following two commands:
```
@command('')
async def errorgood(self):
from electrum.util import UserFacingException
raise UserFacingException("heyheyhey")
@command('')
async def errorbad(self):
raise Exception("heyheyhey")
```
----------
(before change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9221)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
heyheyhey
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
```
----------
(after change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9254)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
(inside daemon): Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/daemon.py", line 254, in handle
response['result'] = await f(*params)
File "/home/user/wspace/electrum/electrum/daemon.py", line 361, in run_cmdline
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
internal error while executing RPC
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
0.78 | E | __main__ | error running command (without daemon)
Traceback (most recent call last):
File "/home/user/wspace/electrum/./run_electrum", line 534, in handle_cmd
result = fut.result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 458, in result
return self.__get_result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
raise self._exception
File "/home/user/wspace/electrum/./run_electrum", line 255, in run_offline_command
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 2, "message": "internal error while executing RPC", "data": {"exception": "Exception('heyheyhey')", "traceback": "Traceback (most recent call last):\n File \"/home/user/wspace/electrum/electrum/daemon.py\", line 254, in handle\n response['result'] = await f(*params)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 163, in func_wrapper\n return await func(*args, **kwargs)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 217, in errorbad\n raise Exception(\"heyheyhey\")\nException: heyheyhey\n"}}}
```
2024-02-12 19:02:02 +00:00
raise UserFacingException ( " Transaction not in wallet. " )
2023-09-07 13:18:23 +00:00
else : # raw tx
try :
tx = Transaction ( tx )
tx . deserialize ( )
except transaction . SerializationError as e :
cli/rpc: nicer error messages and error-passing
Previously, generally, in case of any error, commands would raise a generic "Exception()" and the CLI/RPC would convert that and return it as `str(e)`.
With this change, we now distinguish "user-facing exceptions" (e.g. "Password required" or "wallet not loaded") and "internal errors" (e.g. bugs).
- for "user-facing exceptions", the behaviour is unchanged
- for "internal errors", we now pass around the traceback (e.g. from daemon server to rpc client) and show it to the user (previously, assuming there was a daemon running, the user could only retrieve the exception from the log of that daemon). These errors use a new jsonrpc error code int (code 2).
As the logic only changes for "internal errors", I deem this change not to be compatibility-breaking.
----------
Examples follow.
Consider the following two commands:
```
@command('')
async def errorgood(self):
from electrum.util import UserFacingException
raise UserFacingException("heyheyhey")
@command('')
async def errorbad(self):
raise Exception("heyheyhey")
```
----------
(before change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9221)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
heyheyhey
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
```
----------
(after change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9254)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
(inside daemon): Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/daemon.py", line 254, in handle
response['result'] = await f(*params)
File "/home/user/wspace/electrum/electrum/daemon.py", line 361, in run_cmdline
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
internal error while executing RPC
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
0.78 | E | __main__ | error running command (without daemon)
Traceback (most recent call last):
File "/home/user/wspace/electrum/./run_electrum", line 534, in handle_cmd
result = fut.result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 458, in result
return self.__get_result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
raise self._exception
File "/home/user/wspace/electrum/./run_electrum", line 255, in run_offline_command
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 2, "message": "internal error while executing RPC", "data": {"exception": "Exception('heyheyhey')", "traceback": "Traceback (most recent call last):\n File \"/home/user/wspace/electrum/electrum/daemon.py\", line 254, in handle\n response['result'] = await f(*params)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 163, in func_wrapper\n return await func(*args, **kwargs)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 217, in errorbad\n raise Exception(\"heyheyhey\")\nException: heyheyhey\n"}}}
```
2024-02-12 19:02:02 +00:00
raise UserFacingException ( f " Failed to deserialize transaction: { e } " ) from e
2021-08-11 17:00:26 +05:30
domain_coins = from_coins . split ( ' , ' ) if from_coins else None
coins = wallet . get_spendable_coins ( None )
if domain_coins is not None :
coins = [ coin for coin in coins if ( coin . prevout . to_str ( ) in domain_coins ) ]
wallet: add_input_info to no longer do network requests
- wallet.add_input_info() previously had a fallback to download parent
prev txs from the network (after a lookup in wallet.db failed).
wallet.add_input_info() is not async, so the network request cannot
be done cleanly there and was really just a hack.
- tx.add_info_from_wallet() calls wallet.add_input_info() on each txin,
in which case these network requests were done sequentially, not concurrently
- the network part of wallet.add_input_info() is now split out into new method:
txin.add_info_from_network()
- in addition to tx.add_info_from_wallet(), there is now also tx.add_info_from_network()
- callers of old tx.add_info_from_wallet() should now called either
- tx.add_info_from_wallet(), then tx.add_info_from_network(), preferably in that order
- tx.add_info_from_wallet() alone is sufficient if the tx is complete,
or typically when not in a signing context
- callers of wallet.bump_fee and wallet.dscancel are now expected to have already
called tx.add_info_from_network(), as it cannot be done in a non-async context
(but for the common case of all-inputs-are-ismine, bump_fee/dscancel should work regardless)
- PartialTxInput.utxo was moved to the baseclass, TxInput.utxo
2023-03-10 21:17:05 +00:00
tx . add_info_from_wallet ( wallet )
await tx . add_info_from_network ( self . network )
2021-08-11 17:00:26 +05:30
new_tx = wallet . bump_fee (
tx = tx ,
coins = coins ,
2023-11-20 18:00:23 +00:00
strategy = BumpFeeStrategy . DECREASE_PAYMENT if decrease_payment else BumpFeeStrategy . PRESERVE_PAYMENT ,
2021-08-11 17:00:26 +05:30
new_fee_rate = new_fee_rate )
if not unsigned :
wallet . sign_transaction ( new_tx , password )
return new_tx . serialize ( )
2025-02-16 16:59:19 +01:00
@command ( ' w ' )
async def onchain_history ( self , show_fiat = False , year = None , show_addresses = False , wallet : Abstract_Wallet = None ) :
""" Wallet onchain history. Returns the transaction history of your wallet. """
kwargs = self . get_year_timestamps ( year )
onchain_history = wallet . get_onchain_history ( * * kwargs )
out = [ x . to_dict ( ) for x in onchain_history . values ( ) ]
if show_fiat :
from . exchange_rate import FxThread
fx = self . daemon . fx if self . daemon else FxThread ( config = self . config )
else :
fx = None
for item in out :
if show_addresses :
tx = wallet . db . get_transaction ( item [ ' txid ' ] )
item [ ' inputs ' ] = list ( map ( lambda x : x . to_json ( ) , tx . inputs ( ) ) )
item [ ' outputs ' ] = list ( map ( lambda x : { ' address ' : x . get_ui_address_str ( ) , ' value_sat ' : x . value } ,
tx . outputs ( ) ) )
if fx :
fiat_fields = wallet . get_tx_item_fiat ( tx_hash = item [ ' txid ' ] , amount_sat = item [ ' amount_sat ' ] , fx = fx , tx_fee = item [ ' fee_sat ' ] )
item . update ( fiat_fields )
return json_normalize ( out )
2021-07-07 16:44:27 +03:00
@command ( ' wl ' )
2025-02-16 16:59:19 +01:00
async def lightning_history ( self , wallet : Abstract_Wallet = None ) :
""" lightning history. """
lightning_history = wallet . lnworker . get_lightning_history ( ) if wallet . lnworker else { }
sorted_hist = sorted ( lightning_history . values ( ) , key = lambda x : x . timestamp )
return json_normalize ( [ x . to_dict ( ) for x in sorted_hist ] )
2013-02-26 18:10:29 +01:00
2015-06-01 06:10:06 +02:00
@command ( ' w ' )
2019-09-21 02:14:22 +02:00
async def setlabel ( self , key , label , wallet : Abstract_Wallet = None ) :
2015-05-31 23:23:13 +02:00
""" Assign a label to an item. Item may be a bitcoin address or a
transaction ID """
2019-09-05 17:57:51 +02:00
wallet . set_label ( key , label )
2013-09-08 20:10:43 +02:00
2017-03-06 17:12:27 +01:00
@command ( ' w ' )
2019-09-21 02:14:22 +02:00
async def listcontacts ( self , wallet : Abstract_Wallet = None ) :
2015-05-31 23:17:44 +02:00
""" Show your list of contacts """
2019-09-05 17:57:51 +02:00
return wallet . contacts
2013-02-26 13:56:48 +01:00
2017-03-06 17:12:27 +01:00
@command ( ' w ' )
2019-09-21 02:14:22 +02:00
async def getalias ( self , key , wallet : Abstract_Wallet = None ) :
2015-05-31 23:17:44 +02:00
""" Retrieve alias. Lookup in your list of contacts, and for an OpenAlias DNS record. """
2019-09-05 17:57:51 +02:00
return wallet . contacts . resolve ( key )
2015-05-31 15:06:52 +02:00
2017-03-06 17:12:27 +01:00
@command ( ' w ' )
2019-09-21 02:14:22 +02:00
async def searchcontacts ( self , query , wallet : Abstract_Wallet = None ) :
2015-05-31 23:17:44 +02:00
""" Search through contacts, return matching entries. """
2014-11-05 18:02:44 -05:00
results = { }
2019-09-05 17:57:51 +02:00
for key , value in wallet . contacts . items ( ) :
2015-05-30 12:35:58 +02:00
if query . lower ( ) in key . lower ( ) :
results [ key ] = value
2014-11-05 18:02:44 -05:00
return results
2015-06-01 06:10:06 +02:00
@command ( ' w ' )
2019-09-21 02:14:22 +02:00
async def listaddresses ( self , receiving = False , change = False , labels = False , frozen = False , unused = False , funded = False , balance = False , wallet : Abstract_Wallet = None ) :
2015-09-11 13:07:49 +02:00
""" List wallet addresses. Returns the list of all addresses in your wallet. Use optional arguments to filter the results. """
2013-02-26 18:10:29 +01:00
out = [ ]
2019-09-05 17:57:51 +02:00
for addr in wallet . get_addresses ( ) :
if frozen and not wallet . is_frozen_address ( addr ) :
2015-05-30 18:49:58 +02:00
continue
2019-09-05 17:57:51 +02:00
if receiving and wallet . is_change ( addr ) :
2015-06-11 12:49:14 +02:00
continue
2019-09-05 17:57:51 +02:00
if change and not wallet . is_change ( addr ) :
2015-05-30 18:49:58 +02:00
continue
2022-06-01 23:03:35 +02:00
if unused and wallet . adb . is_used ( addr ) :
2015-05-30 18:49:58 +02:00
continue
2022-06-01 23:03:35 +02:00
if funded and wallet . adb . is_empty ( addr ) :
2015-05-30 18:49:58 +02:00
continue
item = addr
2017-10-07 09:48:20 +02:00
if labels or balance :
item = ( item , )
if balance :
2019-09-05 17:57:51 +02:00
item + = ( format_satoshis ( sum ( wallet . get_addr_balance ( addr ) ) ) , )
2017-10-07 09:48:20 +02:00
if labels :
2022-08-09 17:13:44 +02:00
item + = ( repr ( wallet . get_label_for_address ( addr ) ) , )
2015-05-30 18:49:58 +02:00
out . append ( item )
2013-02-26 18:10:29 +01:00
return out
2014-06-24 16:12:43 +03:00
2017-03-12 12:33:52 +01:00
@command ( ' n ' )
2019-09-21 02:14:22 +02:00
async def gettransaction ( self , txid , wallet : Abstract_Wallet = None ) :
2015-05-31 23:17:44 +02:00
""" Retrieve a transaction. """
2019-02-28 20:21:53 +01:00
tx = None
2019-09-05 17:57:51 +02:00
if wallet :
tx = wallet . db . get_transaction ( txid )
2019-02-28 20:21:53 +01:00
if tx is None :
2019-08-15 13:17:16 +02:00
raw = await self . network . get_transaction ( txid )
2015-05-31 08:20:09 +02:00
if raw :
tx = Transaction ( raw )
else :
cli/rpc: nicer error messages and error-passing
Previously, generally, in case of any error, commands would raise a generic "Exception()" and the CLI/RPC would convert that and return it as `str(e)`.
With this change, we now distinguish "user-facing exceptions" (e.g. "Password required" or "wallet not loaded") and "internal errors" (e.g. bugs).
- for "user-facing exceptions", the behaviour is unchanged
- for "internal errors", we now pass around the traceback (e.g. from daemon server to rpc client) and show it to the user (previously, assuming there was a daemon running, the user could only retrieve the exception from the log of that daemon). These errors use a new jsonrpc error code int (code 2).
As the logic only changes for "internal errors", I deem this change not to be compatibility-breaking.
----------
Examples follow.
Consider the following two commands:
```
@command('')
async def errorgood(self):
from electrum.util import UserFacingException
raise UserFacingException("heyheyhey")
@command('')
async def errorbad(self):
raise Exception("heyheyhey")
```
----------
(before change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9221)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
heyheyhey
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
```
----------
(after change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9254)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
(inside daemon): Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/daemon.py", line 254, in handle
response['result'] = await f(*params)
File "/home/user/wspace/electrum/electrum/daemon.py", line 361, in run_cmdline
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
internal error while executing RPC
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
0.78 | E | __main__ | error running command (without daemon)
Traceback (most recent call last):
File "/home/user/wspace/electrum/./run_electrum", line 534, in handle_cmd
result = fut.result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 458, in result
return self.__get_result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
raise self._exception
File "/home/user/wspace/electrum/./run_electrum", line 255, in run_offline_command
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 2, "message": "internal error while executing RPC", "data": {"exception": "Exception('heyheyhey')", "traceback": "Traceback (most recent call last):\n File \"/home/user/wspace/electrum/electrum/daemon.py\", line 254, in handle\n response['result'] = await f(*params)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 163, in func_wrapper\n return await func(*args, **kwargs)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 217, in errorbad\n raise Exception(\"heyheyhey\")\nException: heyheyhey\n"}}}
```
2024-02-12 19:02:02 +00:00
raise UserFacingException ( " Unknown transaction " )
2019-11-02 05:46:52 +01:00
if tx . txid ( ) != txid :
cli/rpc: nicer error messages and error-passing
Previously, generally, in case of any error, commands would raise a generic "Exception()" and the CLI/RPC would convert that and return it as `str(e)`.
With this change, we now distinguish "user-facing exceptions" (e.g. "Password required" or "wallet not loaded") and "internal errors" (e.g. bugs).
- for "user-facing exceptions", the behaviour is unchanged
- for "internal errors", we now pass around the traceback (e.g. from daemon server to rpc client) and show it to the user (previously, assuming there was a daemon running, the user could only retrieve the exception from the log of that daemon). These errors use a new jsonrpc error code int (code 2).
As the logic only changes for "internal errors", I deem this change not to be compatibility-breaking.
----------
Examples follow.
Consider the following two commands:
```
@command('')
async def errorgood(self):
from electrum.util import UserFacingException
raise UserFacingException("heyheyhey")
@command('')
async def errorbad(self):
raise Exception("heyheyhey")
```
----------
(before change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9221)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
heyheyhey
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
```
----------
(after change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9254)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
(inside daemon): Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/daemon.py", line 254, in handle
response['result'] = await f(*params)
File "/home/user/wspace/electrum/electrum/daemon.py", line 361, in run_cmdline
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
internal error while executing RPC
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
0.78 | E | __main__ | error running command (without daemon)
Traceback (most recent call last):
File "/home/user/wspace/electrum/./run_electrum", line 534, in handle_cmd
result = fut.result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 458, in result
return self.__get_result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
raise self._exception
File "/home/user/wspace/electrum/./run_electrum", line 255, in run_offline_command
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 2, "message": "internal error while executing RPC", "data": {"exception": "Exception('heyheyhey')", "traceback": "Traceback (most recent call last):\n File \"/home/user/wspace/electrum/electrum/daemon.py\", line 254, in handle\n response['result'] = await f(*params)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 163, in func_wrapper\n return await func(*args, **kwargs)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 217, in errorbad\n raise Exception(\"heyheyhey\")\nException: heyheyhey\n"}}}
```
2024-02-12 19:02:02 +00:00
raise UserFacingException ( " Mismatching txid " )
2019-10-23 17:09:41 +02:00
return tx . serialize ( )
2014-01-23 17:06:47 +01:00
2015-06-01 06:10:06 +02:00
@command ( ' ' )
2019-08-15 13:17:16 +02:00
async def encrypt ( self , pubkey , message ) - > str :
2015-05-31 23:17:44 +02:00
""" Encrypt a message with a public key. Use quotes if the message contains whitespaces. """
2019-05-03 03:10:31 +02:00
if not is_hex_str ( pubkey ) :
cli/rpc: nicer error messages and error-passing
Previously, generally, in case of any error, commands would raise a generic "Exception()" and the CLI/RPC would convert that and return it as `str(e)`.
With this change, we now distinguish "user-facing exceptions" (e.g. "Password required" or "wallet not loaded") and "internal errors" (e.g. bugs).
- for "user-facing exceptions", the behaviour is unchanged
- for "internal errors", we now pass around the traceback (e.g. from daemon server to rpc client) and show it to the user (previously, assuming there was a daemon running, the user could only retrieve the exception from the log of that daemon). These errors use a new jsonrpc error code int (code 2).
As the logic only changes for "internal errors", I deem this change not to be compatibility-breaking.
----------
Examples follow.
Consider the following two commands:
```
@command('')
async def errorgood(self):
from electrum.util import UserFacingException
raise UserFacingException("heyheyhey")
@command('')
async def errorbad(self):
raise Exception("heyheyhey")
```
----------
(before change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9221)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
heyheyhey
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
```
----------
(after change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9254)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
(inside daemon): Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/daemon.py", line 254, in handle
response['result'] = await f(*params)
File "/home/user/wspace/electrum/electrum/daemon.py", line 361, in run_cmdline
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
internal error while executing RPC
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
0.78 | E | __main__ | error running command (without daemon)
Traceback (most recent call last):
File "/home/user/wspace/electrum/./run_electrum", line 534, in handle_cmd
result = fut.result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 458, in result
return self.__get_result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
raise self._exception
File "/home/user/wspace/electrum/./run_electrum", line 255, in run_offline_command
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 2, "message": "internal error while executing RPC", "data": {"exception": "Exception('heyheyhey')", "traceback": "Traceback (most recent call last):\n File \"/home/user/wspace/electrum/electrum/daemon.py\", line 254, in handle\n response['result'] = await f(*params)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 163, in func_wrapper\n return await func(*args, **kwargs)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 217, in errorbad\n raise Exception(\"heyheyhey\")\nException: heyheyhey\n"}}}
```
2024-02-12 19:02:02 +00:00
raise UserFacingException ( f " pubkey must be a hex string instead of { repr ( pubkey ) } " )
2019-05-03 03:10:31 +02:00
try :
message = to_bytes ( message )
except TypeError :
cli/rpc: nicer error messages and error-passing
Previously, generally, in case of any error, commands would raise a generic "Exception()" and the CLI/RPC would convert that and return it as `str(e)`.
With this change, we now distinguish "user-facing exceptions" (e.g. "Password required" or "wallet not loaded") and "internal errors" (e.g. bugs).
- for "user-facing exceptions", the behaviour is unchanged
- for "internal errors", we now pass around the traceback (e.g. from daemon server to rpc client) and show it to the user (previously, assuming there was a daemon running, the user could only retrieve the exception from the log of that daemon). These errors use a new jsonrpc error code int (code 2).
As the logic only changes for "internal errors", I deem this change not to be compatibility-breaking.
----------
Examples follow.
Consider the following two commands:
```
@command('')
async def errorgood(self):
from electrum.util import UserFacingException
raise UserFacingException("heyheyhey")
@command('')
async def errorbad(self):
raise Exception("heyheyhey")
```
----------
(before change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9221)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
heyheyhey
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
```
----------
(after change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9254)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
(inside daemon): Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/daemon.py", line 254, in handle
response['result'] = await f(*params)
File "/home/user/wspace/electrum/electrum/daemon.py", line 361, in run_cmdline
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
internal error while executing RPC
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
0.78 | E | __main__ | error running command (without daemon)
Traceback (most recent call last):
File "/home/user/wspace/electrum/./run_electrum", line 534, in handle_cmd
result = fut.result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 458, in result
return self.__get_result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
raise self._exception
File "/home/user/wspace/electrum/./run_electrum", line 255, in run_offline_command
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 2, "message": "internal error while executing RPC", "data": {"exception": "Exception('heyheyhey')", "traceback": "Traceback (most recent call last):\n File \"/home/user/wspace/electrum/electrum/daemon.py\", line 254, in handle\n response['result'] = await f(*params)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 163, in func_wrapper\n return await func(*args, **kwargs)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 217, in errorbad\n raise Exception(\"heyheyhey\")\nException: heyheyhey\n"}}}
```
2024-02-12 19:02:02 +00:00
raise UserFacingException ( f " message must be a string-like object instead of { repr ( message ) } " )
2018-05-24 18:57:13 +02:00
public_key = ecc . ECPubkey ( bfh ( pubkey ) )
2024-06-17 11:20:37 +02:00
encrypted = crypto . ecies_encrypt_message ( public_key , message )
2019-05-03 03:10:31 +02:00
return encrypted . decode ( ' utf-8 ' )
2014-03-03 10:39:10 +01:00
2015-06-01 06:10:06 +02:00
@command ( ' wp ' )
2019-09-21 02:14:22 +02:00
async def decrypt ( self , pubkey , encrypted , password = None , wallet : Abstract_Wallet = None ) - > str :
2015-05-31 23:17:44 +02:00
""" Decrypt a message encrypted with a public key. """
2019-05-03 03:10:31 +02:00
if not is_hex_str ( pubkey ) :
cli/rpc: nicer error messages and error-passing
Previously, generally, in case of any error, commands would raise a generic "Exception()" and the CLI/RPC would convert that and return it as `str(e)`.
With this change, we now distinguish "user-facing exceptions" (e.g. "Password required" or "wallet not loaded") and "internal errors" (e.g. bugs).
- for "user-facing exceptions", the behaviour is unchanged
- for "internal errors", we now pass around the traceback (e.g. from daemon server to rpc client) and show it to the user (previously, assuming there was a daemon running, the user could only retrieve the exception from the log of that daemon). These errors use a new jsonrpc error code int (code 2).
As the logic only changes for "internal errors", I deem this change not to be compatibility-breaking.
----------
Examples follow.
Consider the following two commands:
```
@command('')
async def errorgood(self):
from electrum.util import UserFacingException
raise UserFacingException("heyheyhey")
@command('')
async def errorbad(self):
raise Exception("heyheyhey")
```
----------
(before change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9221)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
heyheyhey
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
```
----------
(after change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9254)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
(inside daemon): Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/daemon.py", line 254, in handle
response['result'] = await f(*params)
File "/home/user/wspace/electrum/electrum/daemon.py", line 361, in run_cmdline
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
internal error while executing RPC
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
0.78 | E | __main__ | error running command (without daemon)
Traceback (most recent call last):
File "/home/user/wspace/electrum/./run_electrum", line 534, in handle_cmd
result = fut.result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 458, in result
return self.__get_result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
raise self._exception
File "/home/user/wspace/electrum/./run_electrum", line 255, in run_offline_command
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 2, "message": "internal error while executing RPC", "data": {"exception": "Exception('heyheyhey')", "traceback": "Traceback (most recent call last):\n File \"/home/user/wspace/electrum/electrum/daemon.py\", line 254, in handle\n response['result'] = await f(*params)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 163, in func_wrapper\n return await func(*args, **kwargs)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 217, in errorbad\n raise Exception(\"heyheyhey\")\nException: heyheyhey\n"}}}
```
2024-02-12 19:02:02 +00:00
raise UserFacingException ( f " pubkey must be a hex string instead of { repr ( pubkey ) } " )
2019-05-03 03:10:31 +02:00
if not isinstance ( encrypted , ( str , bytes , bytearray ) ) :
cli/rpc: nicer error messages and error-passing
Previously, generally, in case of any error, commands would raise a generic "Exception()" and the CLI/RPC would convert that and return it as `str(e)`.
With this change, we now distinguish "user-facing exceptions" (e.g. "Password required" or "wallet not loaded") and "internal errors" (e.g. bugs).
- for "user-facing exceptions", the behaviour is unchanged
- for "internal errors", we now pass around the traceback (e.g. from daemon server to rpc client) and show it to the user (previously, assuming there was a daemon running, the user could only retrieve the exception from the log of that daemon). These errors use a new jsonrpc error code int (code 2).
As the logic only changes for "internal errors", I deem this change not to be compatibility-breaking.
----------
Examples follow.
Consider the following two commands:
```
@command('')
async def errorgood(self):
from electrum.util import UserFacingException
raise UserFacingException("heyheyhey")
@command('')
async def errorbad(self):
raise Exception("heyheyhey")
```
----------
(before change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9221)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
heyheyhey
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
```
----------
(after change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9254)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
(inside daemon): Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/daemon.py", line 254, in handle
response['result'] = await f(*params)
File "/home/user/wspace/electrum/electrum/daemon.py", line 361, in run_cmdline
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
internal error while executing RPC
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
0.78 | E | __main__ | error running command (without daemon)
Traceback (most recent call last):
File "/home/user/wspace/electrum/./run_electrum", line 534, in handle_cmd
result = fut.result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 458, in result
return self.__get_result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
raise self._exception
File "/home/user/wspace/electrum/./run_electrum", line 255, in run_offline_command
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 2, "message": "internal error while executing RPC", "data": {"exception": "Exception('heyheyhey')", "traceback": "Traceback (most recent call last):\n File \"/home/user/wspace/electrum/electrum/daemon.py\", line 254, in handle\n response['result'] = await f(*params)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 163, in func_wrapper\n return await func(*args, **kwargs)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 217, in errorbad\n raise Exception(\"heyheyhey\")\nException: heyheyhey\n"}}}
```
2024-02-12 19:02:02 +00:00
raise UserFacingException ( f " encrypted must be a string-like object instead of { repr ( encrypted ) } " )
2019-09-05 17:57:51 +02:00
decrypted = wallet . decrypt_message ( pubkey , encrypted , password )
2019-05-03 03:10:31 +02:00
return decrypted . decode ( ' utf-8 ' )
2015-05-31 22:42:34 +02:00
2015-12-23 15:59:32 +01:00
@command ( ' w ' )
2022-08-15 14:14:25 +02:00
async def get_request ( self , request_id , wallet : Abstract_Wallet = None ) :
""" Returns a payment request """
r = wallet . get_request ( request_id )
2015-06-07 18:44:33 +02:00
if not r :
cli/rpc: nicer error messages and error-passing
Previously, generally, in case of any error, commands would raise a generic "Exception()" and the CLI/RPC would convert that and return it as `str(e)`.
With this change, we now distinguish "user-facing exceptions" (e.g. "Password required" or "wallet not loaded") and "internal errors" (e.g. bugs).
- for "user-facing exceptions", the behaviour is unchanged
- for "internal errors", we now pass around the traceback (e.g. from daemon server to rpc client) and show it to the user (previously, assuming there was a daemon running, the user could only retrieve the exception from the log of that daemon). These errors use a new jsonrpc error code int (code 2).
As the logic only changes for "internal errors", I deem this change not to be compatibility-breaking.
----------
Examples follow.
Consider the following two commands:
```
@command('')
async def errorgood(self):
from electrum.util import UserFacingException
raise UserFacingException("heyheyhey")
@command('')
async def errorbad(self):
raise Exception("heyheyhey")
```
----------
(before change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9221)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
heyheyhey
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
```
----------
(after change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9254)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
(inside daemon): Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/daemon.py", line 254, in handle
response['result'] = await f(*params)
File "/home/user/wspace/electrum/electrum/daemon.py", line 361, in run_cmdline
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
internal error while executing RPC
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
0.78 | E | __main__ | error running command (without daemon)
Traceback (most recent call last):
File "/home/user/wspace/electrum/./run_electrum", line 534, in handle_cmd
result = fut.result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 458, in result
return self.__get_result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
raise self._exception
File "/home/user/wspace/electrum/./run_electrum", line 255, in run_offline_command
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 2, "message": "internal error while executing RPC", "data": {"exception": "Exception('heyheyhey')", "traceback": "Traceback (most recent call last):\n File \"/home/user/wspace/electrum/electrum/daemon.py\", line 254, in handle\n response['result'] = await f(*params)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 163, in func_wrapper\n return await func(*args, **kwargs)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 217, in errorbad\n raise Exception(\"heyheyhey\")\nException: heyheyhey\n"}}}
```
2024-02-12 19:02:02 +00:00
raise UserFacingException ( " Request not found " )
2020-05-31 12:49:49 +02:00
return wallet . export_request ( r )
2015-06-07 18:44:33 +02:00
2022-08-15 14:14:25 +02:00
@command ( ' w ' )
async def get_invoice ( self , invoice_id , wallet : Abstract_Wallet = None ) :
""" Returns an invoice (request for outgoing payment) """
r = wallet . get_invoice ( invoice_id )
if not r :
cli/rpc: nicer error messages and error-passing
Previously, generally, in case of any error, commands would raise a generic "Exception()" and the CLI/RPC would convert that and return it as `str(e)`.
With this change, we now distinguish "user-facing exceptions" (e.g. "Password required" or "wallet not loaded") and "internal errors" (e.g. bugs).
- for "user-facing exceptions", the behaviour is unchanged
- for "internal errors", we now pass around the traceback (e.g. from daemon server to rpc client) and show it to the user (previously, assuming there was a daemon running, the user could only retrieve the exception from the log of that daemon). These errors use a new jsonrpc error code int (code 2).
As the logic only changes for "internal errors", I deem this change not to be compatibility-breaking.
----------
Examples follow.
Consider the following two commands:
```
@command('')
async def errorgood(self):
from electrum.util import UserFacingException
raise UserFacingException("heyheyhey")
@command('')
async def errorbad(self):
raise Exception("heyheyhey")
```
----------
(before change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9221)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
heyheyhey
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
```
----------
(after change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9254)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
(inside daemon): Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/daemon.py", line 254, in handle
response['result'] = await f(*params)
File "/home/user/wspace/electrum/electrum/daemon.py", line 361, in run_cmdline
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
internal error while executing RPC
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
0.78 | E | __main__ | error running command (without daemon)
Traceback (most recent call last):
File "/home/user/wspace/electrum/./run_electrum", line 534, in handle_cmd
result = fut.result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 458, in result
return self.__get_result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
raise self._exception
File "/home/user/wspace/electrum/./run_electrum", line 255, in run_offline_command
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 2, "message": "internal error while executing RPC", "data": {"exception": "Exception('heyheyhey')", "traceback": "Traceback (most recent call last):\n File \"/home/user/wspace/electrum/electrum/daemon.py\", line 254, in handle\n response['result'] = await f(*params)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 163, in func_wrapper\n return await func(*args, **kwargs)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 217, in errorbad\n raise Exception(\"heyheyhey\")\nException: heyheyhey\n"}}}
```
2024-02-12 19:02:02 +00:00
raise UserFacingException ( " Request not found " )
2022-08-15 14:14:25 +02:00
return wallet . export_invoice ( r )
2015-06-11 10:50:25 +02:00
#@command('w')
2019-08-15 13:17:16 +02:00
#async def ackrequest(self, serialized):
2015-06-11 10:50:25 +02:00
# """<Not implemented>"""
# pass
2015-06-07 18:44:33 +02:00
2022-08-15 14:14:25 +02:00
def _filter_invoices ( self , _list , wallet , pending , expired , paid ) :
2015-06-12 09:46:21 +02:00
if pending :
f = PR_UNPAID
elif expired :
f = PR_EXPIRED
elif paid :
f = PR_PAID
else :
f = None
2015-06-12 20:15:53 +02:00
if f is not None :
2022-08-15 14:14:25 +02:00
_list = [ x for x in _list if f == wallet . get_invoice_status ( x ) ]
return _list
@command ( ' w ' )
async def list_requests ( self , pending = False , expired = False , paid = False , wallet : Abstract_Wallet = None ) :
""" Returns the list of incoming payment requests saved in the wallet. """
l = wallet . get_sorted_requests ( )
l = self . _filter_invoices ( l , wallet , pending , expired , paid )
return [ wallet . export_request ( x ) for x in l ]
@command ( ' w ' )
async def list_invoices ( self , pending = False , expired = False , paid = False , wallet : Abstract_Wallet = None ) :
""" Returns the list of invoices (requests for outgoing payments) saved in the wallet. """
l = wallet . get_invoices ( )
l = self . _filter_invoices ( l , wallet , pending , expired , paid )
return [ wallet . export_invoice ( x ) for x in l ]
2015-05-31 22:42:34 +02:00
2017-01-21 08:04:37 +01:00
@command ( ' w ' )
2019-09-21 02:14:22 +02:00
async def createnewaddress ( self , wallet : Abstract_Wallet = None ) :
2017-10-11 12:07:41 +02:00
""" Create a new receiving address, beyond the gap limit of the wallet """
2019-09-05 17:57:51 +02:00
return wallet . create_new_address ( False )
2017-01-21 08:04:37 +01:00
2020-03-02 19:07:59 +01:00
@command ( ' w ' )
async def changegaplimit ( self , new_limit , iknowwhatimdoing = False , wallet : Abstract_Wallet = None ) :
""" Change the gap limit of the wallet. """
if not iknowwhatimdoing :
cli/rpc: nicer error messages and error-passing
Previously, generally, in case of any error, commands would raise a generic "Exception()" and the CLI/RPC would convert that and return it as `str(e)`.
With this change, we now distinguish "user-facing exceptions" (e.g. "Password required" or "wallet not loaded") and "internal errors" (e.g. bugs).
- for "user-facing exceptions", the behaviour is unchanged
- for "internal errors", we now pass around the traceback (e.g. from daemon server to rpc client) and show it to the user (previously, assuming there was a daemon running, the user could only retrieve the exception from the log of that daemon). These errors use a new jsonrpc error code int (code 2).
As the logic only changes for "internal errors", I deem this change not to be compatibility-breaking.
----------
Examples follow.
Consider the following two commands:
```
@command('')
async def errorgood(self):
from electrum.util import UserFacingException
raise UserFacingException("heyheyhey")
@command('')
async def errorbad(self):
raise Exception("heyheyhey")
```
----------
(before change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9221)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
heyheyhey
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
```
----------
(after change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9254)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
(inside daemon): Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/daemon.py", line 254, in handle
response['result'] = await f(*params)
File "/home/user/wspace/electrum/electrum/daemon.py", line 361, in run_cmdline
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
internal error while executing RPC
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
0.78 | E | __main__ | error running command (without daemon)
Traceback (most recent call last):
File "/home/user/wspace/electrum/./run_electrum", line 534, in handle_cmd
result = fut.result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 458, in result
return self.__get_result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
raise self._exception
File "/home/user/wspace/electrum/./run_electrum", line 255, in run_offline_command
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 2, "message": "internal error while executing RPC", "data": {"exception": "Exception('heyheyhey')", "traceback": "Traceback (most recent call last):\n File \"/home/user/wspace/electrum/electrum/daemon.py\", line 254, in handle\n response['result'] = await f(*params)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 163, in func_wrapper\n return await func(*args, **kwargs)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 217, in errorbad\n raise Exception(\"heyheyhey\")\nException: heyheyhey\n"}}}
```
2024-02-12 19:02:02 +00:00
raise UserFacingException (
" WARNING: Are you SURE you want to change the gap limit? \n "
" It makes recovering your wallet from seed difficult! \n "
" Please do your research and make sure you understand the implications. \n "
" Typically only merchants and power users might want to do this. \n "
" To proceed, try again, with the --iknowwhatimdoing option. " )
2020-03-02 19:07:59 +01:00
if not isinstance ( wallet , Deterministic_Wallet ) :
cli/rpc: nicer error messages and error-passing
Previously, generally, in case of any error, commands would raise a generic "Exception()" and the CLI/RPC would convert that and return it as `str(e)`.
With this change, we now distinguish "user-facing exceptions" (e.g. "Password required" or "wallet not loaded") and "internal errors" (e.g. bugs).
- for "user-facing exceptions", the behaviour is unchanged
- for "internal errors", we now pass around the traceback (e.g. from daemon server to rpc client) and show it to the user (previously, assuming there was a daemon running, the user could only retrieve the exception from the log of that daemon). These errors use a new jsonrpc error code int (code 2).
As the logic only changes for "internal errors", I deem this change not to be compatibility-breaking.
----------
Examples follow.
Consider the following two commands:
```
@command('')
async def errorgood(self):
from electrum.util import UserFacingException
raise UserFacingException("heyheyhey")
@command('')
async def errorbad(self):
raise Exception("heyheyhey")
```
----------
(before change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9221)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
heyheyhey
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
```
----------
(after change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9254)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
(inside daemon): Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/daemon.py", line 254, in handle
response['result'] = await f(*params)
File "/home/user/wspace/electrum/electrum/daemon.py", line 361, in run_cmdline
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
internal error while executing RPC
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
0.78 | E | __main__ | error running command (without daemon)
Traceback (most recent call last):
File "/home/user/wspace/electrum/./run_electrum", line 534, in handle_cmd
result = fut.result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 458, in result
return self.__get_result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
raise self._exception
File "/home/user/wspace/electrum/./run_electrum", line 255, in run_offline_command
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 2, "message": "internal error while executing RPC", "data": {"exception": "Exception('heyheyhey')", "traceback": "Traceback (most recent call last):\n File \"/home/user/wspace/electrum/electrum/daemon.py\", line 254, in handle\n response['result'] = await f(*params)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 163, in func_wrapper\n return await func(*args, **kwargs)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 217, in errorbad\n raise Exception(\"heyheyhey\")\nException: heyheyhey\n"}}}
```
2024-02-12 19:02:02 +00:00
raise UserFacingException ( " This wallet is not deterministic. " )
2020-03-02 19:07:59 +01:00
return wallet . change_gap_limit ( new_limit )
@command ( ' wn ' )
async def getminacceptablegap ( self , wallet : Abstract_Wallet = None ) :
""" Returns the minimum value for gap limit that would be sufficient to discover all
known addresses in the wallet .
"""
if not isinstance ( wallet , Deterministic_Wallet ) :
cli/rpc: nicer error messages and error-passing
Previously, generally, in case of any error, commands would raise a generic "Exception()" and the CLI/RPC would convert that and return it as `str(e)`.
With this change, we now distinguish "user-facing exceptions" (e.g. "Password required" or "wallet not loaded") and "internal errors" (e.g. bugs).
- for "user-facing exceptions", the behaviour is unchanged
- for "internal errors", we now pass around the traceback (e.g. from daemon server to rpc client) and show it to the user (previously, assuming there was a daemon running, the user could only retrieve the exception from the log of that daemon). These errors use a new jsonrpc error code int (code 2).
As the logic only changes for "internal errors", I deem this change not to be compatibility-breaking.
----------
Examples follow.
Consider the following two commands:
```
@command('')
async def errorgood(self):
from electrum.util import UserFacingException
raise UserFacingException("heyheyhey")
@command('')
async def errorbad(self):
raise Exception("heyheyhey")
```
----------
(before change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9221)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
heyheyhey
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
```
----------
(after change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9254)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
(inside daemon): Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/daemon.py", line 254, in handle
response['result'] = await f(*params)
File "/home/user/wspace/electrum/electrum/daemon.py", line 361, in run_cmdline
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
internal error while executing RPC
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
0.78 | E | __main__ | error running command (without daemon)
Traceback (most recent call last):
File "/home/user/wspace/electrum/./run_electrum", line 534, in handle_cmd
result = fut.result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 458, in result
return self.__get_result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
raise self._exception
File "/home/user/wspace/electrum/./run_electrum", line 255, in run_offline_command
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 2, "message": "internal error while executing RPC", "data": {"exception": "Exception('heyheyhey')", "traceback": "Traceback (most recent call last):\n File \"/home/user/wspace/electrum/electrum/daemon.py\", line 254, in handle\n response['result'] = await f(*params)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 163, in func_wrapper\n return await func(*args, **kwargs)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 217, in errorbad\n raise Exception(\"heyheyhey\")\nException: heyheyhey\n"}}}
```
2024-02-12 19:02:02 +00:00
raise UserFacingException ( " This wallet is not deterministic. " )
2020-03-02 19:07:59 +01:00
if not wallet . is_up_to_date ( ) :
2020-04-08 03:09:08 +00:00
raise NotSynchronizedException ( " Wallet not fully synchronized. " )
2020-03-02 19:07:59 +01:00
return wallet . min_acceptable_gap ( )
2017-10-11 12:07:41 +02:00
@command ( ' w ' )
2019-09-21 02:14:22 +02:00
async def getunusedaddress ( self , wallet : Abstract_Wallet = None ) :
2017-10-11 12:07:41 +02:00
""" Returns the first unused address of the wallet, or None if all addresses are used.
An address is considered as used if it has received a transaction , or if it is used in a payment request . """
2019-09-05 17:57:51 +02:00
return wallet . get_unused_address ( )
2017-01-21 08:04:37 +01:00
2015-06-01 13:02:09 +02:00
@command ( ' w ' )
2025-02-23 12:21:41 +01:00
async def add_request ( self , amount , memo = ' ' , expiry = 3600 , lightning = False , force = False , wallet : Abstract_Wallet = None ) :
2017-10-11 12:07:41 +02:00
""" Create a payment request, using the first unused address of the wallet.
2018-04-15 20:45:30 +03:00
The address will be considered as used after this operation .
2017-10-11 12:07:41 +02:00
If no payment is received , the address will be considered as unused if the payment request is deleted from the wallet . """
2017-02-22 11:23:12 +01:00
amount = satoshis ( amount )
2025-02-23 12:21:41 +01:00
if not lightning :
addr = wallet . get_unused_address ( )
if addr is None :
if force :
addr = wallet . create_new_address ( False )
else :
return False
else :
addr = None
2023-03-31 14:25:40 +02:00
expiry = int ( expiry ) if expiry else None
key = wallet . create_request ( amount , memo , expiry , addr )
2022-03-29 14:07:53 +02:00
req = wallet . get_request ( key )
2020-05-31 12:49:49 +02:00
return wallet . export_request ( req )
2015-06-01 13:02:09 +02:00
2018-01-18 11:56:21 +01:00
@command ( ' w ' )
2019-09-21 02:14:22 +02:00
async def addtransaction ( self , tx , wallet : Abstract_Wallet = None ) :
2018-01-18 11:56:21 +01:00
""" Add a transaction to the wallet history """
tx = Transaction ( tx )
2022-06-01 23:03:35 +02:00
if not wallet . adb . add_transaction ( tx ) :
2018-02-06 05:39:26 +01:00
return False
2020-02-05 15:13:37 +01:00
wallet . save_db ( )
2018-01-18 11:56:21 +01:00
return tx . txid ( )
2015-06-01 13:02:09 +02:00
@command ( ' w ' )
2022-08-15 14:14:25 +02:00
async def delete_request ( self , request_id , wallet : Abstract_Wallet = None ) :
""" Remove an incoming payment request """
return wallet . delete_request ( request_id )
@command ( ' w ' )
async def delete_invoice ( self , invoice_id , wallet : Abstract_Wallet = None ) :
""" Remove an outgoing payment invoice """
return wallet . delete_invoice ( invoice_id )
2015-05-31 22:42:34 +02:00
2015-07-22 15:46:53 +02:00
@command ( ' w ' )
2019-09-21 02:14:22 +02:00
async def clear_requests ( self , wallet : Abstract_Wallet = None ) :
2015-07-22 15:46:53 +02:00
""" Remove all payment requests """
2020-06-26 09:47:16 +02:00
wallet . clear_requests ( )
return True
2015-07-22 15:46:53 +02:00
2019-09-08 11:59:03 +02:00
@command ( ' w ' )
2019-09-21 02:14:22 +02:00
async def clear_invoices ( self , wallet : Abstract_Wallet = None ) :
2019-09-08 11:59:03 +02:00
""" Remove all invoices """
wallet . clear_invoices ( )
return True
2015-11-30 10:54:15 +01:00
@command ( ' n ' )
2020-04-24 15:34:55 +02:00
async def notify ( self , address : str , URL : Optional [ str ] ) :
""" Watch an address. Every time the address changes, a http POST is sent to the URL.
Call with an empty URL to stop watching an address .
"""
2018-10-03 17:13:46 +02:00
if not hasattr ( self , " _notifier " ) :
self . _notifier = Notifier ( self . network )
2020-04-24 15:34:55 +02:00
if URL :
await self . _notifier . start_watching_addr ( address , URL )
else :
await self . _notifier . stop_watching_addr ( address )
2015-12-01 08:58:00 +01:00
return True
2015-07-22 15:46:53 +02:00
2016-04-08 16:14:39 +02:00
@command ( ' wn ' )
2019-09-21 02:14:22 +02:00
async def is_synchronized ( self , wallet : Abstract_Wallet = None ) :
2016-04-08 20:29:43 +02:00
""" return wallet synchronization status """
2019-09-05 17:57:51 +02:00
return wallet . is_up_to_date ( )
2016-04-08 16:14:39 +02:00
2025-02-24 12:20:44 +01:00
@command ( ' n ' )
2023-10-08 17:43:42 +02:00
async def getfeerate ( self ) :
2018-06-15 17:02:44 +02:00
"""
2025-02-24 12:20:44 +01:00
Return current fee estimate given network conditions ( in sat / kvByte ) .
To change the fee policy , use ' getconfig/setconfig fee_policy '
"""
fee_policy = FeePolicy ( self . config . FEE_POLICY )
description = fee_policy . get_target_text ( )
feerate = fee_policy . fee_per_kb ( self . network )
tooltip = fee_policy . get_estimate_text ( self . network )
2023-10-08 17:43:42 +02:00
return {
2025-02-24 12:20:44 +01:00
' policy ' : fee_policy . get_descriptor ( ) ,
' description ' : description ,
2023-10-08 17:43:42 +02:00
' sat/kvB ' : feerate ,
' tooltip ' : tooltip ,
}
2019-02-20 18:01:43 +01:00
@command ( ' w ' )
2019-09-21 02:14:22 +02:00
async def removelocaltx ( self , txid , wallet : Abstract_Wallet = None ) :
2019-02-20 18:01:43 +01:00
""" Remove a ' local ' transaction from the wallet, and its dependent
transactions .
"""
if not is_hash256_str ( txid ) :
cli/rpc: nicer error messages and error-passing
Previously, generally, in case of any error, commands would raise a generic "Exception()" and the CLI/RPC would convert that and return it as `str(e)`.
With this change, we now distinguish "user-facing exceptions" (e.g. "Password required" or "wallet not loaded") and "internal errors" (e.g. bugs).
- for "user-facing exceptions", the behaviour is unchanged
- for "internal errors", we now pass around the traceback (e.g. from daemon server to rpc client) and show it to the user (previously, assuming there was a daemon running, the user could only retrieve the exception from the log of that daemon). These errors use a new jsonrpc error code int (code 2).
As the logic only changes for "internal errors", I deem this change not to be compatibility-breaking.
----------
Examples follow.
Consider the following two commands:
```
@command('')
async def errorgood(self):
from electrum.util import UserFacingException
raise UserFacingException("heyheyhey")
@command('')
async def errorbad(self):
raise Exception("heyheyhey")
```
----------
(before change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9221)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
heyheyhey
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
```
----------
(after change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9254)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
(inside daemon): Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/daemon.py", line 254, in handle
response['result'] = await f(*params)
File "/home/user/wspace/electrum/electrum/daemon.py", line 361, in run_cmdline
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
internal error while executing RPC
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
0.78 | E | __main__ | error running command (without daemon)
Traceback (most recent call last):
File "/home/user/wspace/electrum/./run_electrum", line 534, in handle_cmd
result = fut.result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 458, in result
return self.__get_result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
raise self._exception
File "/home/user/wspace/electrum/./run_electrum", line 255, in run_offline_command
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 2, "message": "internal error while executing RPC", "data": {"exception": "Exception('heyheyhey')", "traceback": "Traceback (most recent call last):\n File \"/home/user/wspace/electrum/electrum/daemon.py\", line 254, in handle\n response['result'] = await f(*params)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 163, in func_wrapper\n return await func(*args, **kwargs)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 217, in errorbad\n raise Exception(\"heyheyhey\")\nException: heyheyhey\n"}}}
```
2024-02-12 19:02:02 +00:00
raise UserFacingException ( f " { repr ( txid ) } is not a txid " )
2022-06-01 23:03:35 +02:00
height = wallet . adb . get_tx_height ( txid ) . height
2019-02-20 18:01:43 +01:00
if height != TX_HEIGHT_LOCAL :
cli/rpc: nicer error messages and error-passing
Previously, generally, in case of any error, commands would raise a generic "Exception()" and the CLI/RPC would convert that and return it as `str(e)`.
With this change, we now distinguish "user-facing exceptions" (e.g. "Password required" or "wallet not loaded") and "internal errors" (e.g. bugs).
- for "user-facing exceptions", the behaviour is unchanged
- for "internal errors", we now pass around the traceback (e.g. from daemon server to rpc client) and show it to the user (previously, assuming there was a daemon running, the user could only retrieve the exception from the log of that daemon). These errors use a new jsonrpc error code int (code 2).
As the logic only changes for "internal errors", I deem this change not to be compatibility-breaking.
----------
Examples follow.
Consider the following two commands:
```
@command('')
async def errorgood(self):
from electrum.util import UserFacingException
raise UserFacingException("heyheyhey")
@command('')
async def errorbad(self):
raise Exception("heyheyhey")
```
----------
(before change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9221)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
heyheyhey
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
```
----------
(after change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9254)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
(inside daemon): Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/daemon.py", line 254, in handle
response['result'] = await f(*params)
File "/home/user/wspace/electrum/electrum/daemon.py", line 361, in run_cmdline
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
internal error while executing RPC
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
0.78 | E | __main__ | error running command (without daemon)
Traceback (most recent call last):
File "/home/user/wspace/electrum/./run_electrum", line 534, in handle_cmd
result = fut.result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 458, in result
return self.__get_result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
raise self._exception
File "/home/user/wspace/electrum/./run_electrum", line 255, in run_offline_command
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 2, "message": "internal error while executing RPC", "data": {"exception": "Exception('heyheyhey')", "traceback": "Traceback (most recent call last):\n File \"/home/user/wspace/electrum/electrum/daemon.py\", line 254, in handle\n response['result'] = await f(*params)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 163, in func_wrapper\n return await func(*args, **kwargs)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 217, in errorbad\n raise Exception(\"heyheyhey\")\nException: heyheyhey\n"}}}
```
2024-02-12 19:02:02 +00:00
raise UserFacingException (
f ' Only local transactions can be removed. '
f ' This tx has height: { height } != { TX_HEIGHT_LOCAL } ' )
2022-06-01 23:03:35 +02:00
wallet . adb . remove_transaction ( txid )
2020-02-05 15:13:37 +01:00
wallet . save_db ( )
2019-02-20 18:01:43 +01:00
2019-03-05 17:01:54 +01:00
@command ( ' wn ' )
2019-09-21 02:14:22 +02:00
async def get_tx_status ( self , txid , wallet : Abstract_Wallet = None ) :
2019-03-05 17:01:54 +01:00
""" Returns some information regarding the tx. For now, only confirmations.
The transaction must be related to the wallet .
"""
if not is_hash256_str ( txid ) :
cli/rpc: nicer error messages and error-passing
Previously, generally, in case of any error, commands would raise a generic "Exception()" and the CLI/RPC would convert that and return it as `str(e)`.
With this change, we now distinguish "user-facing exceptions" (e.g. "Password required" or "wallet not loaded") and "internal errors" (e.g. bugs).
- for "user-facing exceptions", the behaviour is unchanged
- for "internal errors", we now pass around the traceback (e.g. from daemon server to rpc client) and show it to the user (previously, assuming there was a daemon running, the user could only retrieve the exception from the log of that daemon). These errors use a new jsonrpc error code int (code 2).
As the logic only changes for "internal errors", I deem this change not to be compatibility-breaking.
----------
Examples follow.
Consider the following two commands:
```
@command('')
async def errorgood(self):
from electrum.util import UserFacingException
raise UserFacingException("heyheyhey")
@command('')
async def errorbad(self):
raise Exception("heyheyhey")
```
----------
(before change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9221)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
heyheyhey
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
```
----------
(after change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9254)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
(inside daemon): Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/daemon.py", line 254, in handle
response['result'] = await f(*params)
File "/home/user/wspace/electrum/electrum/daemon.py", line 361, in run_cmdline
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
internal error while executing RPC
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
0.78 | E | __main__ | error running command (without daemon)
Traceback (most recent call last):
File "/home/user/wspace/electrum/./run_electrum", line 534, in handle_cmd
result = fut.result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 458, in result
return self.__get_result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
raise self._exception
File "/home/user/wspace/electrum/./run_electrum", line 255, in run_offline_command
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 2, "message": "internal error while executing RPC", "data": {"exception": "Exception('heyheyhey')", "traceback": "Traceback (most recent call last):\n File \"/home/user/wspace/electrum/electrum/daemon.py\", line 254, in handle\n response['result'] = await f(*params)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 163, in func_wrapper\n return await func(*args, **kwargs)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 217, in errorbad\n raise Exception(\"heyheyhey\")\nException: heyheyhey\n"}}}
```
2024-02-12 19:02:02 +00:00
raise UserFacingException ( f " { repr ( txid ) } is not a txid " )
2019-09-05 17:57:51 +02:00
if not wallet . db . get_transaction ( txid ) :
cli/rpc: nicer error messages and error-passing
Previously, generally, in case of any error, commands would raise a generic "Exception()" and the CLI/RPC would convert that and return it as `str(e)`.
With this change, we now distinguish "user-facing exceptions" (e.g. "Password required" or "wallet not loaded") and "internal errors" (e.g. bugs).
- for "user-facing exceptions", the behaviour is unchanged
- for "internal errors", we now pass around the traceback (e.g. from daemon server to rpc client) and show it to the user (previously, assuming there was a daemon running, the user could only retrieve the exception from the log of that daemon). These errors use a new jsonrpc error code int (code 2).
As the logic only changes for "internal errors", I deem this change not to be compatibility-breaking.
----------
Examples follow.
Consider the following two commands:
```
@command('')
async def errorgood(self):
from electrum.util import UserFacingException
raise UserFacingException("heyheyhey")
@command('')
async def errorbad(self):
raise Exception("heyheyhey")
```
----------
(before change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9221)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
heyheyhey
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
```
----------
(after change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9254)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
(inside daemon): Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/daemon.py", line 254, in handle
response['result'] = await f(*params)
File "/home/user/wspace/electrum/electrum/daemon.py", line 361, in run_cmdline
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
internal error while executing RPC
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
0.78 | E | __main__ | error running command (without daemon)
Traceback (most recent call last):
File "/home/user/wspace/electrum/./run_electrum", line 534, in handle_cmd
result = fut.result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 458, in result
return self.__get_result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
raise self._exception
File "/home/user/wspace/electrum/./run_electrum", line 255, in run_offline_command
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 2, "message": "internal error while executing RPC", "data": {"exception": "Exception('heyheyhey')", "traceback": "Traceback (most recent call last):\n File \"/home/user/wspace/electrum/electrum/daemon.py\", line 254, in handle\n response['result'] = await f(*params)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 163, in func_wrapper\n return await func(*args, **kwargs)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 217, in errorbad\n raise Exception(\"heyheyhey\")\nException: heyheyhey\n"}}}
```
2024-02-12 19:02:02 +00:00
raise UserFacingException ( " Transaction not in wallet. " )
2019-03-05 17:01:54 +01:00
return {
2022-06-01 23:03:35 +02:00
" confirmations " : wallet . adb . get_tx_height ( txid ) . conf ,
2019-03-05 17:01:54 +01:00
}
2016-02-29 09:57:56 +01:00
@command ( ' ' )
2019-08-15 13:17:16 +02:00
async def help ( self ) :
2016-02-29 09:57:56 +01:00
# for the python console
return sorted ( known_commands . keys ( ) )
2018-05-28 11:55:20 +02:00
# lightning network commands
2021-07-07 16:44:27 +03:00
@command ( ' wnl ' )
2020-04-16 12:39:12 +02:00
async def add_peer ( self , connection_string , timeout = 20 , gossip = False , wallet : Abstract_Wallet = None ) :
lnworker = self . network . lngossip if gossip else wallet . lnworker
await lnworker . add_peer ( connection_string )
2019-07-23 20:14:59 +02:00
return True
2019-06-05 11:08:16 +02:00
2025-03-04 14:33:54 +01:00
@command ( ' wnl ' )
async def gossip_info ( self , wallet : Abstract_Wallet = None ) :
""" Display statistics about lightninig gossip """
lngossip = self . network . lngossip
channel_db = lngossip . channel_db
forwarded = dict ( [ ( key . hex ( ) , p . _num_gossip_messages_forwarded ) for key , p in wallet . lnworker . peers . items ( ) ] ) ,
out = {
' received ' : {
' channel_announcements ' : lngossip . _num_chan_ann ,
' channel_updates ' : lngossip . _num_chan_upd ,
' channel_updates_good ' : lngossip . _num_chan_upd_good ,
' node_announcements ' : lngossip . _num_node_ann ,
} ,
' database ' : {
' nodes ' : channel_db . num_nodes ,
' channels ' : channel_db . num_channels ,
' channel_policies ' : channel_db . num_policies ,
} ,
' forwarded ' : forwarded ,
}
return out
2021-07-07 16:44:27 +03:00
@command ( ' wnl ' )
2020-04-16 12:39:12 +02:00
async def list_peers ( self , gossip = False , wallet : Abstract_Wallet = None ) :
lnworker = self . network . lngossip if gossip else wallet . lnworker
2020-04-12 12:48:44 +02:00
return [ {
' node_id ' : p . pubkey . hex ( ) ,
' address ' : p . transport . name ( ) ,
' initialized ' : p . is_initialized ( ) ,
2020-04-16 12:39:12 +02:00
' features ' : str ( LnFeatures ( p . features ) ) ,
2020-04-12 12:48:44 +02:00
' channels ' : [ c . funding_outpoint . to_str ( ) for c in p . channels . values ( ) ] ,
2020-04-16 12:39:12 +02:00
} for p in lnworker . peers . values ( ) ]
2020-04-12 12:48:44 +02:00
2021-07-07 16:44:27 +03:00
@command ( ' wpnl ' )
2023-08-08 05:09:58 +02:00
async def open_channel ( self , connection_string , amount , push_amount = 0 , public = False , zeroconf = False , password = None , wallet : Abstract_Wallet = None ) :
2019-11-13 09:20:19 +01:00
funding_sat = satoshis ( amount )
push_sat = satoshis ( push_amount )
2023-09-17 13:21:41 +02:00
peer = await wallet . lnworker . add_peer ( connection_string )
2023-10-13 15:49:13 +02:00
chan , funding_tx = await wallet . lnworker . open_channel_with_peer (
peer , funding_sat ,
push_sat = push_sat ,
public = public ,
2023-08-08 05:09:58 +02:00
zeroconf = zeroconf ,
2023-10-13 15:49:13 +02:00
password = password )
2019-08-12 17:54:27 +02:00
return chan . funding_outpoint . to_str ( )
2018-05-28 11:55:20 +02:00
2020-04-11 12:02:38 +02:00
@command ( ' ' )
2020-06-22 22:37:58 +02:00
async def decode_invoice ( self , invoice : str ) :
2022-03-15 13:03:34 +01:00
invoice = Invoice . from_bech32 ( invoice )
2020-06-22 22:37:58 +02:00
return invoice . to_debug_json ( )
2020-04-11 12:02:38 +02:00
2024-10-08 13:29:27 +02:00
@command ( ' wnpl ' )
async def lnpay ( self , invoice , timeout = 120 , password = None , wallet : Abstract_Wallet = None ) :
2020-02-23 12:40:40 +01:00
lnworker = wallet . lnworker
2020-06-22 22:37:58 +02:00
lnaddr = lnworker . _check_invoice ( invoice )
2020-02-23 12:40:40 +01:00
payment_hash = lnaddr . paymenthash
2022-03-15 13:03:34 +01:00
wallet . save_invoice ( Invoice . from_bech32 ( invoice ) )
2022-04-29 12:11:50 +02:00
success , log = await lnworker . pay_invoice ( invoice )
2020-02-23 12:40:40 +01:00
return {
' payment_hash ' : payment_hash . hex ( ) ,
' success ' : success ,
' preimage ' : lnworker . get_preimage ( payment_hash ) . hex ( ) if success else None ,
2020-05-05 18:32:43 +02:00
' log ' : [ x . formatted_tuple ( ) for x in log ]
2020-02-23 12:40:40 +01:00
}
2018-05-28 11:55:20 +02:00
2021-07-07 16:44:27 +03:00
@command ( ' wl ' )
2019-09-21 02:14:22 +02:00
async def nodeid ( self , wallet : Abstract_Wallet = None ) :
2023-05-24 17:41:44 +00:00
listen_addr = self . config . LIGHTNING_LISTEN
2023-02-17 11:35:03 +00:00
return wallet . lnworker . node_keypair . pubkey . hex ( ) + ( ( ' @ ' + listen_addr ) if listen_addr else ' ' )
2018-10-04 14:03:29 +02:00
2021-07-07 16:44:27 +03:00
@command ( ' wl ' )
2019-09-21 02:14:22 +02:00
async def list_channels ( self , wallet : Abstract_Wallet = None ) :
2021-03-12 18:53:09 +01:00
# FIXME: we need to be online to display capacity of backups
2020-02-20 10:30:30 +01:00
from . lnutil import LOCAL , REMOTE , format_short_channel_id
2021-03-12 18:53:09 +01:00
channels = list ( wallet . lnworker . channels . items ( ) )
backups = list ( wallet . lnworker . channel_backups . items ( ) )
2020-02-20 10:30:30 +01:00
return [
{
2021-03-12 18:53:09 +01:00
' type ' : ' CHANNEL ' ,
2020-03-06 09:57:37 +01:00
' short_channel_id ' : format_short_channel_id ( chan . short_channel_id ) if chan . short_channel_id else None ,
2021-03-12 18:53:09 +01:00
' channel_id ' : chan . channel_id . hex ( ) ,
2020-02-20 10:30:30 +01:00
' channel_point ' : chan . funding_outpoint . to_str ( ) ,
' state ' : chan . get_state ( ) . name ,
2020-03-06 09:57:37 +01:00
' peer_state ' : chan . peer_state . name ,
2023-02-17 11:35:03 +00:00
' remote_pubkey ' : chan . node_id . hex ( ) ,
2020-02-20 10:30:30 +01:00
' local_balance ' : chan . balance ( LOCAL ) / / 1000 ,
' remote_balance ' : chan . balance ( REMOTE ) / / 1000 ,
2022-09-01 09:56:46 +02:00
' local_ctn ' : chan . get_latest_ctn ( LOCAL ) ,
' remote_ctn ' : chan . get_latest_ctn ( REMOTE ) ,
2020-04-20 18:48:41 +02:00
' local_reserve ' : chan . config [ REMOTE ] . reserve_sat , # their config has our reserve
' remote_reserve ' : chan . config [ LOCAL ] . reserve_sat ,
2020-03-26 05:43:26 +01:00
' local_unsettled_sent ' : chan . balance_tied_up_in_htlcs_by_direction ( LOCAL , direction = SENT ) / / 1000 ,
' remote_unsettled_sent ' : chan . balance_tied_up_in_htlcs_by_direction ( REMOTE , direction = SENT ) / / 1000 ,
2021-03-12 18:53:09 +01:00
} for channel_id , chan in channels
] + [
{
' type ' : ' BACKUP ' ,
' short_channel_id ' : format_short_channel_id ( chan . short_channel_id ) if chan . short_channel_id else None ,
' channel_id ' : chan . channel_id . hex ( ) ,
' channel_point ' : chan . funding_outpoint . to_str ( ) ,
' state ' : chan . get_state ( ) . name ,
} for channel_id , chan in backups
2020-02-20 10:30:30 +01:00
]
2018-05-28 12:06:37 +02:00
2021-07-07 16:44:27 +03:00
@command ( ' wnl ' )
2020-02-27 20:53:50 +01:00
async def enable_htlc_settle ( self , b : bool , wallet : Abstract_Wallet = None ) :
2021-03-18 07:48:30 +01:00
wallet . lnworker . enable_htlc_settle = b
2020-02-27 20:53:50 +01:00
2018-09-24 18:09:35 +02:00
@command ( ' n ' )
2019-08-15 13:17:16 +02:00
async def clear_ln_blacklist ( self ) :
2021-04-26 08:43:50 +02:00
if self . network . path_finder :
2023-08-15 14:59:10 +00:00
self . network . path_finder . clear_blacklist ( )
2018-09-24 18:09:35 +02:00
2021-04-09 09:40:33 +02:00
@command ( ' n ' )
async def reset_liquidity_hints ( self ) :
2021-04-26 08:43:50 +02:00
if self . network . path_finder :
self . network . path_finder . liquidity_hints . reset_liquidity_hints ( )
2023-08-15 14:59:10 +00:00
self . network . path_finder . clear_blacklist ( )
2018-09-24 18:09:35 +02:00
2024-10-09 18:04:26 +02:00
@command ( ' wnpl ' )
async def close_channel ( self , channel_point , force = False , password = None , wallet : Abstract_Wallet = None ) :
2019-02-01 15:27:50 +01:00
txid , index = channel_point . split ( ' : ' )
chan_id , _ = channel_id_from_funding_tx ( txid , int ( index ) )
2024-11-29 10:38:57 +01:00
if chan_id not in wallet . lnworker . channels :
raise UserFacingException ( f ' Unknown channel { channel_point } ' )
2019-09-05 17:57:51 +02:00
coro = wallet . lnworker . force_close_channel ( chan_id ) if force else wallet . lnworker . close_channel ( chan_id )
2019-08-15 13:17:16 +02:00
return await coro
2019-01-24 18:21:46 +01:00
2024-10-09 18:04:26 +02:00
@command ( ' wnpl ' )
async def request_force_close ( self , channel_point , connection_string = None , password = None , wallet : Abstract_Wallet = None ) :
2021-03-17 09:15:40 +01:00
"""
Requests the remote to force close a channel .
If a connection string is passed , can be used without having state or any backup for the channel .
Assumes that channel was originally opened with the same local peer ( node_keypair ) .
"""
2021-03-12 16:10:02 +01:00
txid , index = channel_point . split ( ' : ' )
chan_id , _ = channel_id_from_funding_tx ( txid , int ( index ) )
2024-11-29 10:38:57 +01:00
if chan_id not in wallet . lnworker . channels and chan_id not in wallet . lnworker . channel_backups :
raise UserFacingException ( f ' Unknown channel { channel_point } ' )
2021-03-17 09:15:40 +01:00
await wallet . lnworker . request_force_close ( chan_id , connect_str = connection_string )
2021-03-12 16:10:02 +01:00
2024-10-09 18:04:26 +02:00
@command ( ' wpl ' )
async def export_channel_backup ( self , channel_point , password = None , wallet : Abstract_Wallet = None ) :
2020-03-13 11:44:29 +01:00
txid , index = channel_point . split ( ' : ' )
chan_id , _ = channel_id_from_funding_tx ( txid , int ( index ) )
2024-11-29 10:38:57 +01:00
if chan_id not in wallet . lnworker . channels :
raise UserFacingException ( f ' Unknown channel { channel_point } ' )
2020-03-13 11:44:29 +01:00
return wallet . lnworker . export_channel_backup ( chan_id )
2021-07-07 16:44:27 +03:00
@command ( ' wl ' )
2020-03-13 11:44:29 +01:00
async def import_channel_backup ( self , encrypted , wallet : Abstract_Wallet = None ) :
2021-03-10 15:26:39 +01:00
return wallet . lnworker . import_channel_backup ( encrypted )
2020-03-13 11:44:29 +01:00
2024-10-08 13:29:27 +02:00
@command ( ' wnpl ' )
async def get_channel_ctx ( self , channel_point , password = None , iknowwhatimdoing = False , wallet : Abstract_Wallet = None ) :
2019-03-14 18:31:41 +01:00
""" return the current commitment transaction of a channel """
2020-03-05 17:27:43 +01:00
if not iknowwhatimdoing :
cli/rpc: nicer error messages and error-passing
Previously, generally, in case of any error, commands would raise a generic "Exception()" and the CLI/RPC would convert that and return it as `str(e)`.
With this change, we now distinguish "user-facing exceptions" (e.g. "Password required" or "wallet not loaded") and "internal errors" (e.g. bugs).
- for "user-facing exceptions", the behaviour is unchanged
- for "internal errors", we now pass around the traceback (e.g. from daemon server to rpc client) and show it to the user (previously, assuming there was a daemon running, the user could only retrieve the exception from the log of that daemon). These errors use a new jsonrpc error code int (code 2).
As the logic only changes for "internal errors", I deem this change not to be compatibility-breaking.
----------
Examples follow.
Consider the following two commands:
```
@command('')
async def errorgood(self):
from electrum.util import UserFacingException
raise UserFacingException("heyheyhey")
@command('')
async def errorbad(self):
raise Exception("heyheyhey")
```
----------
(before change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9221)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
heyheyhey
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
```
----------
(after change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9254)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
(inside daemon): Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/daemon.py", line 254, in handle
response['result'] = await f(*params)
File "/home/user/wspace/electrum/electrum/daemon.py", line 361, in run_cmdline
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
internal error while executing RPC
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
0.78 | E | __main__ | error running command (without daemon)
Traceback (most recent call last):
File "/home/user/wspace/electrum/./run_electrum", line 534, in handle_cmd
result = fut.result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 458, in result
return self.__get_result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
raise self._exception
File "/home/user/wspace/electrum/./run_electrum", line 255, in run_offline_command
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 2, "message": "internal error while executing RPC", "data": {"exception": "Exception('heyheyhey')", "traceback": "Traceback (most recent call last):\n File \"/home/user/wspace/electrum/electrum/daemon.py\", line 254, in handle\n response['result'] = await f(*params)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 163, in func_wrapper\n return await func(*args, **kwargs)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 217, in errorbad\n raise Exception(\"heyheyhey\")\nException: heyheyhey\n"}}}
```
2024-02-12 19:02:02 +00:00
raise UserFacingException (
" WARNING: this command is potentially unsafe. \n "
" To proceed, try again, with the --iknowwhatimdoing option. " )
2019-03-14 18:31:41 +01:00
txid , index = channel_point . split ( ' : ' )
chan_id , _ = channel_id_from_funding_tx ( txid , int ( index ) )
2024-11-29 10:38:57 +01:00
if chan_id not in wallet . lnworker . channels :
raise UserFacingException ( f ' Unknown channel { channel_point } ' )
2019-09-05 17:57:51 +02:00
chan = wallet . lnworker . channels [ chan_id ]
2019-03-14 18:31:41 +01:00
tx = chan . force_close_tx ( )
2019-10-23 17:09:41 +02:00
return tx . serialize ( )
2019-03-14 18:31:41 +01:00
2021-07-07 16:44:27 +03:00
@command ( ' wnl ' )
2020-02-02 14:50:08 +01:00
async def get_watchtower_ctn ( self , channel_point , wallet : Abstract_Wallet = None ) :
""" return the local watchtower ' s ctn of channel. used in regtests """
2024-12-20 14:02:54 +01:00
return wallet . lnworker . get_watchtower_ctn ( channel_point )
2020-02-02 14:50:08 +01:00
2024-10-09 18:04:26 +02:00
@command ( ' wnpl ' )
async def rebalance_channels ( self , from_scid , dest_scid , amount , password = None , wallet : Abstract_Wallet = None ) :
2022-07-13 10:14:34 +02:00
"""
Rebalance channels .
2022-12-06 00:31:16 +02:00
If trampoline is used , channels must be with different trampolines .
2022-07-13 10:14:34 +02:00
"""
from . lnutil import ShortChannelID
from_scid = ShortChannelID . from_str ( from_scid )
dest_scid = ShortChannelID . from_str ( dest_scid )
2023-09-17 11:06:25 +02:00
from_channel = wallet . lnworker . get_channel_by_short_id ( from_scid )
dest_channel = wallet . lnworker . get_channel_by_short_id ( dest_scid )
2022-07-13 10:14:34 +02:00
amount_sat = satoshis ( amount )
2023-06-04 03:07:06 +00:00
success , log = await wallet . lnworker . rebalance_channels (
from_channel ,
dest_channel ,
amount_msat = amount_sat * 1000 ,
)
2022-07-13 10:14:34 +02:00
return {
' success ' : success ,
' log ' : [ x . formatted_tuple ( ) for x in log ]
}
2021-07-07 16:44:27 +03:00
@command ( ' wnpl ' )
2020-05-28 13:11:32 +02:00
async def normal_swap ( self , onchain_amount , lightning_amount , password = None , wallet : Abstract_Wallet = None ) :
"""
Normal submarine swap : send on - chain BTC , receive on Lightning
"""
sm = wallet . lnworker . swap_manager
2024-10-10 12:30:27 +02:00
with sm . create_transport ( ) as transport :
await sm . is_initialized . wait ( )
if lightning_amount == ' dryrun ' :
onchain_amount_sat = satoshis ( onchain_amount )
lightning_amount_sat = sm . get_recv_amount ( onchain_amount_sat , is_reverse = False )
txid = None
elif onchain_amount == ' dryrun ' :
lightning_amount_sat = satoshis ( lightning_amount )
onchain_amount_sat = sm . get_send_amount ( lightning_amount_sat , is_reverse = False )
txid = None
else :
lightning_amount_sat = satoshis ( lightning_amount )
onchain_amount_sat = satoshis ( onchain_amount )
txid = await wallet . lnworker . swap_manager . normal_swap (
transport ,
lightning_amount_sat = lightning_amount_sat ,
expected_onchain_amount_sat = onchain_amount_sat ,
password = password ,
)
2020-05-28 13:11:32 +02:00
return {
' txid ' : txid ,
' lightning_amount ' : format_satoshis ( lightning_amount_sat ) ,
' onchain_amount ' : format_satoshis ( onchain_amount_sat ) ,
}
2020-05-21 10:36:22 +02:00
2024-10-09 18:04:26 +02:00
@command ( ' wnpl ' )
async def reverse_swap ( self , lightning_amount , onchain_amount , password = None , wallet : Abstract_Wallet = None ) :
2020-05-28 13:11:32 +02:00
""" Reverse submarine swap: send on Lightning, receive on-chain
"""
sm = wallet . lnworker . swap_manager
2024-10-10 12:30:27 +02:00
with sm . create_transport ( ) as transport :
await sm . is_initialized . wait ( )
if onchain_amount == ' dryrun ' :
lightning_amount_sat = satoshis ( lightning_amount )
onchain_amount_sat = sm . get_recv_amount ( lightning_amount_sat , is_reverse = True )
funding_txid = None
elif lightning_amount == ' dryrun ' :
onchain_amount_sat = satoshis ( onchain_amount )
lightning_amount_sat = sm . get_send_amount ( onchain_amount_sat , is_reverse = True )
funding_txid = None
else :
lightning_amount_sat = satoshis ( lightning_amount )
2025-02-19 17:39:56 +01:00
claim_fee = sm . get_swap_tx_fee ( )
2024-10-10 12:30:27 +02:00
onchain_amount_sat = satoshis ( onchain_amount ) + claim_fee
funding_txid = await wallet . lnworker . swap_manager . reverse_swap (
transport ,
lightning_amount_sat = lightning_amount_sat ,
expected_onchain_amount_sat = onchain_amount_sat ,
)
2020-05-28 13:11:32 +02:00
return {
2023-06-28 14:23:32 +02:00
' funding_txid ' : funding_txid ,
2020-05-28 13:11:32 +02:00
' lightning_amount ' : format_satoshis ( lightning_amount_sat ) ,
' onchain_amount ' : format_satoshis ( onchain_amount_sat ) ,
}
2020-05-22 10:39:30 +02:00
2022-12-05 18:49:21 +02:00
@command ( ' n ' )
2022-12-06 16:23:59 +02:00
async def convert_currency ( self , from_amount = 1 , from_ccy = ' ' , to_ccy = ' ' ) :
2022-12-05 18:49:21 +02:00
""" Converts the given amount of currency to another using the
configured exchange rate source .
"""
2022-12-06 03:30:24 +02:00
if not self . daemon . fx . is_enabled ( ) :
cli/rpc: nicer error messages and error-passing
Previously, generally, in case of any error, commands would raise a generic "Exception()" and the CLI/RPC would convert that and return it as `str(e)`.
With this change, we now distinguish "user-facing exceptions" (e.g. "Password required" or "wallet not loaded") and "internal errors" (e.g. bugs).
- for "user-facing exceptions", the behaviour is unchanged
- for "internal errors", we now pass around the traceback (e.g. from daemon server to rpc client) and show it to the user (previously, assuming there was a daemon running, the user could only retrieve the exception from the log of that daemon). These errors use a new jsonrpc error code int (code 2).
As the logic only changes for "internal errors", I deem this change not to be compatibility-breaking.
----------
Examples follow.
Consider the following two commands:
```
@command('')
async def errorgood(self):
from electrum.util import UserFacingException
raise UserFacingException("heyheyhey")
@command('')
async def errorbad(self):
raise Exception("heyheyhey")
```
----------
(before change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9221)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
heyheyhey
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
```
----------
(after change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9254)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
(inside daemon): Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/daemon.py", line 254, in handle
response['result'] = await f(*params)
File "/home/user/wspace/electrum/electrum/daemon.py", line 361, in run_cmdline
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
internal error while executing RPC
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
0.78 | E | __main__ | error running command (without daemon)
Traceback (most recent call last):
File "/home/user/wspace/electrum/./run_electrum", line 534, in handle_cmd
result = fut.result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 458, in result
return self.__get_result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
raise self._exception
File "/home/user/wspace/electrum/./run_electrum", line 255, in run_offline_command
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 2, "message": "internal error while executing RPC", "data": {"exception": "Exception('heyheyhey')", "traceback": "Traceback (most recent call last):\n File \"/home/user/wspace/electrum/electrum/daemon.py\", line 254, in handle\n response['result'] = await f(*params)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 163, in func_wrapper\n return await func(*args, **kwargs)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 217, in errorbad\n raise Exception(\"heyheyhey\")\nException: heyheyhey\n"}}}
```
2024-02-12 19:02:02 +00:00
raise UserFacingException ( " FX is disabled. To enable, run: ' electrum setconfig use_exchange_rate true ' " )
2022-12-05 18:49:21 +02:00
# Currency codes are uppercase
2022-12-06 03:27:57 +02:00
from_ccy = from_ccy . upper ( )
2022-12-05 18:49:21 +02:00
to_ccy = to_ccy . upper ( )
2022-12-06 16:23:59 +02:00
# Default currencies
if from_ccy == ' ' :
from_ccy = " BTC " if to_ccy != " BTC " else self . daemon . fx . ccy
if to_ccy == ' ' :
to_ccy = " BTC " if from_ccy != " BTC " else self . daemon . fx . ccy
2022-12-05 18:49:21 +02:00
# Get current rates
rate_from = self . daemon . fx . exchange . get_cached_spot_quote ( from_ccy )
rate_to = self . daemon . fx . exchange . get_cached_spot_quote ( to_ccy )
# Test if currencies exist
if rate_from . is_nan ( ) :
cli/rpc: nicer error messages and error-passing
Previously, generally, in case of any error, commands would raise a generic "Exception()" and the CLI/RPC would convert that and return it as `str(e)`.
With this change, we now distinguish "user-facing exceptions" (e.g. "Password required" or "wallet not loaded") and "internal errors" (e.g. bugs).
- for "user-facing exceptions", the behaviour is unchanged
- for "internal errors", we now pass around the traceback (e.g. from daemon server to rpc client) and show it to the user (previously, assuming there was a daemon running, the user could only retrieve the exception from the log of that daemon). These errors use a new jsonrpc error code int (code 2).
As the logic only changes for "internal errors", I deem this change not to be compatibility-breaking.
----------
Examples follow.
Consider the following two commands:
```
@command('')
async def errorgood(self):
from electrum.util import UserFacingException
raise UserFacingException("heyheyhey")
@command('')
async def errorbad(self):
raise Exception("heyheyhey")
```
----------
(before change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9221)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
heyheyhey
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
```
----------
(after change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9254)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
(inside daemon): Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/daemon.py", line 254, in handle
response['result'] = await f(*params)
File "/home/user/wspace/electrum/electrum/daemon.py", line 361, in run_cmdline
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
internal error while executing RPC
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
0.78 | E | __main__ | error running command (without daemon)
Traceback (most recent call last):
File "/home/user/wspace/electrum/./run_electrum", line 534, in handle_cmd
result = fut.result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 458, in result
return self.__get_result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
raise self._exception
File "/home/user/wspace/electrum/./run_electrum", line 255, in run_offline_command
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 2, "message": "internal error while executing RPC", "data": {"exception": "Exception('heyheyhey')", "traceback": "Traceback (most recent call last):\n File \"/home/user/wspace/electrum/electrum/daemon.py\", line 254, in handle\n response['result'] = await f(*params)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 163, in func_wrapper\n return await func(*args, **kwargs)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 217, in errorbad\n raise Exception(\"heyheyhey\")\nException: heyheyhey\n"}}}
```
2024-02-12 19:02:02 +00:00
raise UserFacingException ( f ' Currency to convert from ( { from_ccy } ) is unknown or rate is unavailable ' )
2022-12-05 18:49:21 +02:00
if rate_to . is_nan ( ) :
cli/rpc: nicer error messages and error-passing
Previously, generally, in case of any error, commands would raise a generic "Exception()" and the CLI/RPC would convert that and return it as `str(e)`.
With this change, we now distinguish "user-facing exceptions" (e.g. "Password required" or "wallet not loaded") and "internal errors" (e.g. bugs).
- for "user-facing exceptions", the behaviour is unchanged
- for "internal errors", we now pass around the traceback (e.g. from daemon server to rpc client) and show it to the user (previously, assuming there was a daemon running, the user could only retrieve the exception from the log of that daemon). These errors use a new jsonrpc error code int (code 2).
As the logic only changes for "internal errors", I deem this change not to be compatibility-breaking.
----------
Examples follow.
Consider the following two commands:
```
@command('')
async def errorgood(self):
from electrum.util import UserFacingException
raise UserFacingException("heyheyhey")
@command('')
async def errorbad(self):
raise Exception("heyheyhey")
```
----------
(before change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9221)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
heyheyhey
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
```
----------
(after change)
CLI with daemon:
```
$ ./run_electrum --testnet daemon -d
starting daemon (PID 9254)
$ ./run_electrum --testnet errorgood
heyheyhey
$ ./run_electrum --testnet errorbad
(inside daemon): Traceback (most recent call last):
File "/home/user/wspace/electrum/electrum/daemon.py", line 254, in handle
response['result'] = await f(*params)
File "/home/user/wspace/electrum/electrum/daemon.py", line 361, in run_cmdline
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
internal error while executing RPC
$ ./run_electrum --testnet stop
Daemon stopped
```
CLI without daemon:
```
$ ./run_electrum --testnet -o errorgood
heyheyhey
$ ./run_electrum --testnet -o errorbad
0.78 | E | __main__ | error running command (without daemon)
Traceback (most recent call last):
File "/home/user/wspace/electrum/./run_electrum", line 534, in handle_cmd
result = fut.result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 458, in result
return self.__get_result()
File "/usr/lib/python3.10/concurrent/futures/_base.py", line 403, in __get_result
raise self._exception
File "/home/user/wspace/electrum/./run_electrum", line 255, in run_offline_command
result = await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 163, in func_wrapper
return await func(*args, **kwargs)
File "/home/user/wspace/electrum/electrum/commands.py", line 217, in errorbad
raise Exception("heyheyhey")
Exception: heyheyhey
```
RPC:
```
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorgood","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 1, "message": "heyheyhey"}}
$ curl --data-binary '{"id":"curltext","jsonrpc":"2.0","method":"errorbad","params":[]}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "error": {"code": 2, "message": "internal error while executing RPC", "data": {"exception": "Exception('heyheyhey')", "traceback": "Traceback (most recent call last):\n File \"/home/user/wspace/electrum/electrum/daemon.py\", line 254, in handle\n response['result'] = await f(*params)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 163, in func_wrapper\n return await func(*args, **kwargs)\n File \"/home/user/wspace/electrum/electrum/commands.py\", line 217, in errorbad\n raise Exception(\"heyheyhey\")\nException: heyheyhey\n"}}}
```
2024-02-12 19:02:02 +00:00
raise UserFacingException ( f ' Currency to convert to ( { to_ccy } ) is unknown or rate is unavailable ' )
2022-12-05 18:49:21 +02:00
# Conversion
try :
commands: fix satoshis decimal conversion in payto cmd and others
When called via jsonrpc (but not via cli) with non-string amounts,
there could be a rounding error resulting in sending 1 sat less.
example:
```
$ ./run_electrum --testnet -w ~/.electrum/testnet/wallets/test_segwit_2 paytomany '[["tb1q6k5h4cz6ra8nzhg90xm9wldvadgh0fpttfthcg", 0.00033389]]' --fee 0
02000000000101b9e6018acb16952e3c9618b069df404dc85544eda8120e5f6e7cd7e94ce5ae8d0100000000fdffffff02fd8100000000000016001410c5b97085ec1637a9f702852f5a81f650fae1566d82000000000000160014d5a97ae05a1f4f315d0579b6577daceb5177a42b024730440220251d2ce83f6e69273de8e9be8602fbcf72b9157e1c0116161fa52f7e04db6e4302202d84045cc6b7056a215d1db3f59884e28dadd5257e1a3960068f90df90b452d1012102b0eff3bf364a2ab5effe952cba33521ebede81dac88c71951a5ed598cb48347b3a022500
$ curl --data-binary '{"id":"curltext","method":"paytomany","params":{"outputs":[["tb1q6k5h4cz6ra8nzhg90xm9wldvadgh0fpttfthcg", 0.00033389]], "fee": 0, "wallet": "/home/user/.electrum/testnet/wallets/test_segwit_2"}}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "result": "02000000000101b9e6018acb16952e3c9618b069df404dc85544eda8120e5f6e7cd7e94ce5ae8d0100000000fdffffff02fe8100000000000016001410c5b97085ec1637a9f702852f5a81f650fae1566c82000000000000160014d5a97ae05a1f4f315d0579b6577daceb5177a42b0247304402206ef66b845ca298c14dc6e8049cba9ed19db1671132194518ce5d521de6f5df8802205ca4b1aee703e3b98331fb9b88210917b385560020c8b2a8a88da38996b101c4012102b0eff3bf364a2ab5effe952cba33521ebede81dac88c71951a5ed598cb48347b39022500"}
```
^ note that first tx has output for 0.00033389, second tx has output for 0.00033388
fixes https://github.com/spesmilo/electrum/issues/8274
2023-03-22 12:22:36 +00:00
from_amount = to_decimal ( from_amount )
2022-12-06 03:27:57 +02:00
to_amount = from_amount / rate_from * rate_to
2022-12-05 18:49:21 +02:00
except InvalidOperation :
raise Exception ( " from_amount is not a number " )
return {
locale amounts: consistently use "." as dec point, and " " as thou sep
Always use "." as decimal point, and " " as thousands separator.
Previously,
- for decimal point, we were using
- "." in some places (e.g. AmountEdit, most fiat amounts), and
- `locale.localeconv()['decimal_point']` in others.
- for thousands separator, we were using
- "," in some places (most fiat amounts), and
- " " in others (format_satoshis)
I think it is better to be consistent even if whatever we pick differs from the locale.
Using whitespace for thousands separator (vs comma) is probably less confusing for people
whose locale would user "." for ts and "," for dp (as in e.g. German).
The alternative option would be to always use the locale. Even if we decide to do that later,
this refactoring should be useful.
closes https://github.com/spesmilo/electrum/issues/2629
2023-01-10 14:45:35 +00:00
" from_amount " : self . daemon . fx . ccy_amount_str ( from_amount , add_thousands_sep = False , ccy = from_ccy ) ,
" to_amount " : self . daemon . fx . ccy_amount_str ( to_amount , add_thousands_sep = False , ccy = to_ccy ) ,
2022-12-05 18:49:21 +02:00
" from_ccy " : from_ccy ,
" to_ccy " : to_ccy ,
" source " : self . daemon . fx . exchange . name ( ) ,
}
2024-11-22 14:46:21 +01:00
@command ( ' wnl ' )
async def send_onion_message ( self , node_id_or_blinded_path_hex : str , message : str , wallet : Abstract_Wallet = None ) :
"""
Send an onion message with onionmsg_tlv . message payload to node_id .
"""
assert wallet
assert wallet . lnworker
assert node_id_or_blinded_path_hex
assert message
node_id_or_blinded_path = bfh ( node_id_or_blinded_path_hex )
assert len ( node_id_or_blinded_path ) > = 33
destination_payload = {
' message ' : { ' text ' : message . encode ( ' utf-8 ' ) }
}
try :
2024-12-03 15:58:10 +01:00
send_onion_message_to ( wallet . lnworker , node_id_or_blinded_path , destination_payload )
2024-11-22 14:46:21 +01:00
return { ' success ' : True }
except Exception as e :
msg = str ( e )
return {
' success ' : False ,
' msg ' : msg
}
@command ( ' wnl ' )
async def get_blinded_path_via ( self , node_id : str , dummy_hops : int = 0 , wallet : Abstract_Wallet = None ) :
"""
Create a blinded path with node_id as introduction point . Introduction point must be direct peer of me .
"""
# TODO: allow introduction_point to not be a direct peer and construct a route
assert wallet
assert node_id
pubkey = bfh ( node_id )
assert len ( pubkey ) == 33 , ' invalid node_id '
peer = wallet . lnworker . peers [ pubkey ]
assert peer , ' node_id not a peer '
path = [ pubkey , wallet . lnworker . node_keypair . pubkey ]
session_key = os . urandom ( 32 )
blinded_path = create_blinded_path ( session_key , path = path , final_recipient_data = { } , dummy_hops = dummy_hops )
with io . BytesIO ( ) as blinded_path_fd :
2025-02-12 14:28:30 +01:00
OnionWireSerializer . write_field (
2025-01-22 16:43:33 +01:00
fd = blinded_path_fd ,
field_type = ' blinded_path ' ,
count = 1 ,
value = blinded_path )
2024-11-22 14:46:21 +01:00
encoded_blinded_path = blinded_path_fd . getvalue ( )
return encoded_blinded_path . hex ( )
2020-02-02 14:50:08 +01:00
2018-10-10 20:29:51 +02:00
def eval_bool ( x : str ) - > bool :
if x == ' false ' : return False
if x == ' true ' : return True
try :
return bool ( ast . literal_eval ( x ) )
2023-04-23 01:33:12 +00:00
except Exception :
2018-10-10 20:29:51 +02:00
return bool ( x )
2025-01-23 12:58:28 +01:00
2015-05-31 22:42:34 +02:00
param_descriptions = {
' privkey ' : ' Private key. Type \' ? \' to get a prompt. ' ,
' destination ' : ' Bitcoin address, contact or alias ' ,
' address ' : ' Bitcoin address ' ,
' seed ' : ' Seed phrase ' ,
' txid ' : ' Transaction ID ' ,
' pos ' : ' Position ' ,
2015-06-11 04:55:08 -04:00
' height ' : ' Block height ' ,
2015-05-31 22:42:34 +02:00
' tx ' : ' Serialized transaction (hexadecimal) ' ,
' key ' : ' Variable name ' ,
' pubkey ' : ' Public key ' ,
' message ' : ' Clear text message. Use quotes if it contains spaces. ' ,
' encrypted ' : ' Encrypted message ' ,
' amount ' : ' Amount to be sent (in BTC). Type \' ! \' to send the maximum available. ' ,
2015-09-09 08:47:30 +02:00
' outputs ' : ' list of [ " address " , amount] ' ,
2017-09-25 21:35:14 +02:00
' redeem_script ' : ' redeem script (hexadecimal) ' ,
2020-05-28 13:11:32 +02:00
' lightning_amount ' : " Amount sent or received in a submarine swap. Set it to ' dryrun ' to receive a value " ,
' onchain_amount ' : " Amount sent or received in a submarine swap. Set it to ' dryrun ' to receive a value " ,
2024-11-22 14:46:21 +01:00
' node_id ' : " Node pubkey in hex format "
2015-05-31 22:42:34 +02:00
}
command_options = {
2023-10-07 17:42:21 +02:00
' password ' : ( " -W " , " Password. Use ' --password : ' if you want a prompt. " ) ,
2017-10-07 09:48:20 +02:00
' new_password ' : ( None , " New Password " ) ,
2018-10-10 20:29:51 +02:00
' encrypt_file ' : ( None , " Whether the file on disk should be encrypted with the provided password " ) ,
2017-10-07 09:48:20 +02:00
' receiving ' : ( None , " Show only receiving addresses " ) ,
' change ' : ( None , " Show only change addresses " ) ,
' frozen ' : ( None , " Show only frozen addresses " ) ,
' unused ' : ( None , " Show only unused addresses " ) ,
' funded ' : ( None , " Show only funded addresses " ) ,
' balance ' : ( " -b " , " Show the balances of listed addresses " ) ,
' labels ' : ( " -l " , " Show the labels of listed addresses " ) ,
' nocheck ' : ( None , " Do not verify aliases " ) ,
' imax ' : ( None , " Maximum number of inputs " ) ,
2019-09-01 17:52:02 +02:00
' fee ' : ( " -f " , " Transaction fee (absolute, in BTC) " ) ,
2024-02-03 05:13:09 +00:00
' feerate ' : ( None , f " Transaction fee rate (in { util . UI_UNIT_NAME_FEERATE_SAT_PER_VBYTE } ) " ) ,
2017-11-29 13:45:02 +01:00
' from_addr ' : ( " -F " , " Source address (must be a wallet address; use sweep to spend from non-wallet address). " ) ,
2019-09-21 20:07:16 +00:00
' from_coins ' : ( None , " Source coins (must be in wallet; use sweep to spend from non-wallet address). " ) ,
2017-10-07 09:48:20 +02:00
' change_addr ' : ( " -c " , " Change address. Default is a spare address, or the source address if it ' s not in the wallet " ) ,
' nbits ' : ( None , " Number of bits of entropy " ) ,
2019-08-09 22:02:01 +02:00
' seed_type ' : ( None , " The type of seed to create, e.g. ' standard ' or ' segwit ' " ) ,
2017-10-07 09:48:20 +02:00
' language ' : ( " -L " , " Default language for wordlist " ) ,
2018-10-10 20:29:51 +02:00
' passphrase ' : ( None , " Seed extension " ) ,
2017-10-07 09:48:20 +02:00
' privkey ' : ( None , " Private key. Set to ' ? ' to get a prompt. " ) ,
' unsigned ' : ( " -u " , " Do not sign transaction " ) ,
2019-11-22 16:09:42 +01:00
' rbf ' : ( None , " Whether to signal opt-in Replace-By-Fee in the transaction (true/false) " ) ,
2022-09-26 14:40:28 +02:00
' decrease_payment ' : ( None , " Whether payment amount will be decreased (true/false) " ) ,
2017-10-07 09:48:20 +02:00
' locktime ' : ( None , " Set locktime block number " ) ,
2020-09-01 22:25:36 +03:00
' addtransaction ' : ( None , ' Whether transaction is to be used for broadcasting afterwards. Adds transaction to the wallet ' ) ,
2017-10-07 09:48:20 +02:00
' domain ' : ( " -D " , " List of addresses " ) ,
' memo ' : ( " -m " , " Description of the request " ) ,
2025-02-23 12:21:41 +01:00
' amount ' : ( None , " Requested amount (in btc) " ) ,
' lightning ' : ( None , " Create lightning request " ) ,
2023-03-31 14:25:40 +02:00
' expiry ' : ( None , " Time in seconds " ) ,
2017-10-07 09:48:20 +02:00
' timeout ' : ( None , " Timeout in seconds " ) ,
' force ' : ( None , " Create new address beyond gap limit, if no more addresses are available. " ) ,
' pending ' : ( None , " Show only pending requests. " ) ,
2019-11-13 09:20:19 +01:00
' push_amount ' : ( None , ' Push initial amount (in BTC) ' ) ,
2023-08-08 05:09:58 +02:00
' zeroconf ' : ( None , ' request zeroconf channel ' ) ,
2017-10-07 09:48:20 +02:00
' expired ' : ( None , " Show only expired requests. " ) ,
' paid ' : ( None , " Show only paid requests. " ) ,
2018-02-09 15:28:28 +01:00
' show_addresses ' : ( None , " Show input and output addresses " ) ,
' show_fiat ' : ( None , " Show fiat value of transactions " ) ,
2018-12-04 16:17:22 +01:00
' show_fees ' : ( None , " Show miner fees paid by transactions " ) ,
2018-02-09 15:28:28 +01:00
' year ' : ( None , " Show history for a given year " ) ,
2019-02-12 18:38:35 +01:00
' from_height ' : ( None , " Only show transactions that confirmed after given block height " ) ,
' to_height ' : ( None , " Only show transactions that confirmed before given block height " ) ,
2020-03-02 19:07:59 +01:00
' iknowwhatimdoing ' : ( None , " Acknowledge that I understand the full implications of what I am about to do " ) ,
2020-04-16 12:39:12 +02:00
' gossip ' : ( None , " Apply command to gossip node instead of wallet " ) ,
2021-03-17 09:15:40 +01:00
' connection_string ' : ( None , " Lightning network node ID or network address " ) ,
2024-02-03 05:13:09 +00:00
' new_fee_rate ' : ( None , f " The Updated/Increased Transaction fee rate (in { util . UI_UNIT_NAME_FEERATE_SAT_PER_VBYTE } ) " ) ,
2022-12-06 03:27:57 +02:00
' from_amount ' : ( None , " Amount to convert (default: 1) " ) ,
2022-12-05 18:49:21 +02:00
' from_ccy ' : ( None , " Currency to convert from " ) ,
2022-12-06 03:27:57 +02:00
' to_ccy ' : ( None , " Currency to convert to " ) ,
2023-10-13 15:49:13 +02:00
' public ' : ( None , ' Channel will be announced ' ) ,
2024-11-22 14:46:21 +01:00
' dummy_hops ' : ( None , ' Number of dummy hops to add ' ) ,
2015-05-31 22:42:34 +02:00
}
2015-12-15 11:33:04 +01:00
# don't use floats because of rounding errors
2019-11-08 15:01:18 +01:00
from . transaction import convert_raw_tx_to_hex
commands: fix satoshis decimal conversion in payto cmd and others
When called via jsonrpc (but not via cli) with non-string amounts,
there could be a rounding error resulting in sending 1 sat less.
example:
```
$ ./run_electrum --testnet -w ~/.electrum/testnet/wallets/test_segwit_2 paytomany '[["tb1q6k5h4cz6ra8nzhg90xm9wldvadgh0fpttfthcg", 0.00033389]]' --fee 0
02000000000101b9e6018acb16952e3c9618b069df404dc85544eda8120e5f6e7cd7e94ce5ae8d0100000000fdffffff02fd8100000000000016001410c5b97085ec1637a9f702852f5a81f650fae1566d82000000000000160014d5a97ae05a1f4f315d0579b6577daceb5177a42b024730440220251d2ce83f6e69273de8e9be8602fbcf72b9157e1c0116161fa52f7e04db6e4302202d84045cc6b7056a215d1db3f59884e28dadd5257e1a3960068f90df90b452d1012102b0eff3bf364a2ab5effe952cba33521ebede81dac88c71951a5ed598cb48347b3a022500
$ curl --data-binary '{"id":"curltext","method":"paytomany","params":{"outputs":[["tb1q6k5h4cz6ra8nzhg90xm9wldvadgh0fpttfthcg", 0.00033389]], "fee": 0, "wallet": "/home/user/.electrum/testnet/wallets/test_segwit_2"}}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "result": "02000000000101b9e6018acb16952e3c9618b069df404dc85544eda8120e5f6e7cd7e94ce5ae8d0100000000fdffffff02fe8100000000000016001410c5b97085ec1637a9f702852f5a81f650fae1566c82000000000000160014d5a97ae05a1f4f315d0579b6577daceb5177a42b0247304402206ef66b845ca298c14dc6e8049cba9ed19db1671132194518ce5d521de6f5df8802205ca4b1aee703e3b98331fb9b88210917b385560020c8b2a8a88da38996b101c4012102b0eff3bf364a2ab5effe952cba33521ebede81dac88c71951a5ed598cb48347b39022500"}
```
^ note that first tx has output for 0.00033389, second tx has output for 0.00033388
fixes https://github.com/spesmilo/electrum/issues/8274
2023-03-22 12:22:36 +00:00
json_loads = lambda x : json . loads ( x , parse_float = lambda x : str ( to_decimal ( x ) ) )
2015-05-31 22:42:34 +02:00
arg_types = {
2015-12-16 20:17:20 +01:00
' num ' : int ,
' nbits ' : int ,
2016-10-08 11:40:03 +02:00
' imax ' : int ,
2018-02-09 15:28:28 +01:00
' year ' : int ,
2019-02-12 18:38:35 +01:00
' from_height ' : int ,
' to_height ' : int ,
2019-11-08 15:01:18 +01:00
' tx ' : convert_raw_tx_to_hex ,
2015-12-16 20:17:20 +01:00
' pubkeys ' : json_loads ,
2016-05-18 14:33:00 +02:00
' jsontx ' : json_loads ,
2015-12-16 20:17:20 +01:00
' inputs ' : json_loads ,
' outputs ' : json_loads ,
commands: fix satoshis decimal conversion in payto cmd and others
When called via jsonrpc (but not via cli) with non-string amounts,
there could be a rounding error resulting in sending 1 sat less.
example:
```
$ ./run_electrum --testnet -w ~/.electrum/testnet/wallets/test_segwit_2 paytomany '[["tb1q6k5h4cz6ra8nzhg90xm9wldvadgh0fpttfthcg", 0.00033389]]' --fee 0
02000000000101b9e6018acb16952e3c9618b069df404dc85544eda8120e5f6e7cd7e94ce5ae8d0100000000fdffffff02fd8100000000000016001410c5b97085ec1637a9f702852f5a81f650fae1566d82000000000000160014d5a97ae05a1f4f315d0579b6577daceb5177a42b024730440220251d2ce83f6e69273de8e9be8602fbcf72b9157e1c0116161fa52f7e04db6e4302202d84045cc6b7056a215d1db3f59884e28dadd5257e1a3960068f90df90b452d1012102b0eff3bf364a2ab5effe952cba33521ebede81dac88c71951a5ed598cb48347b3a022500
$ curl --data-binary '{"id":"curltext","method":"paytomany","params":{"outputs":[["tb1q6k5h4cz6ra8nzhg90xm9wldvadgh0fpttfthcg", 0.00033389]], "fee": 0, "wallet": "/home/user/.electrum/testnet/wallets/test_segwit_2"}}' http://user:pass@127.0.0.1:7777
{"id": "curltext", "jsonrpc": "2.0", "result": "02000000000101b9e6018acb16952e3c9618b069df404dc85544eda8120e5f6e7cd7e94ce5ae8d0100000000fdffffff02fe8100000000000016001410c5b97085ec1637a9f702852f5a81f650fae1566c82000000000000160014d5a97ae05a1f4f315d0579b6577daceb5177a42b0247304402206ef66b845ca298c14dc6e8049cba9ed19db1671132194518ce5d521de6f5df8802205ca4b1aee703e3b98331fb9b88210917b385560020c8b2a8a88da38996b101c4012102b0eff3bf364a2ab5effe952cba33521ebede81dac88c71951a5ed598cb48347b39022500"}
```
^ note that first tx has output for 0.00033389, second tx has output for 0.00033388
fixes https://github.com/spesmilo/electrum/issues/8274
2023-03-22 12:22:36 +00:00
' fee ' : lambda x : str ( to_decimal ( x ) ) if x is not None else None ,
' amount ' : lambda x : str ( to_decimal ( x ) ) if not parse_max_spend ( x ) else x ,
2017-07-31 09:25:10 +07:00
' locktime ' : int ,
2020-09-01 22:25:36 +03:00
' addtransaction ' : eval_bool ,
2018-10-10 20:29:51 +02:00
' encrypt_file ' : eval_bool ,
2019-11-22 16:09:42 +01:00
' rbf ' : eval_bool ,
2019-08-15 19:31:31 +02:00
' timeout ' : float ,
2024-11-22 14:46:21 +01:00
' dummy_hops ' : int ,
2015-05-31 22:42:34 +02:00
}
2015-06-03 11:34:52 +02:00
config_variables = {
' addrequest ' : {
' ssl_privkey ' : ' Path to your SSL private key, needed to sign the request. ' ,
' ssl_chain ' : ' Chain of SSL certificates, needed for signed requests. Put your certificate at the top and the root CA at the end ' ,
2015-06-09 09:58:40 +02:00
' url_rewrite ' : ' Parameters passed to str.replace(), in order to create the r= part of bitcoin: URIs. Example: \" ( \' file:///var/www/ \' , \' https://electrum.org/ \' ) \" ' ,
2015-06-03 11:34:52 +02:00
} ,
' listrequests ' : {
' url_rewrite ' : ' Parameters passed to str.replace(), in order to create the r= part of bitcoin: URIs. Example: \" ( \' file:///var/www/ \' , \' https://electrum.org/ \' ) \" ' ,
}
}
2015-05-31 22:42:34 +02:00
2025-01-23 12:58:28 +01:00
2015-05-31 22:42:34 +02:00
def set_default_subparser ( self , name , args = None ) :
""" see http://stackoverflow.com/questions/5176691/argparse-how-to-specify-a-default-subcommand """
subparser_found = False
for arg in sys . argv [ 1 : ] :
2022-02-15 19:51:20 +01:00
if arg in [ ' -h ' , ' --help ' , ' --version ' ] : # global help/version if no subparser
2015-05-31 22:42:34 +02:00
break
else :
for x in self . _subparsers . _actions :
if not isinstance ( x , argparse . _SubParsersAction ) :
continue
for sp_name in x . _name_parser_map . keys ( ) :
if sp_name in sys . argv [ 1 : ] :
subparser_found = True
if not subparser_found :
# insert default in first position, this implies no
# global options without a sub_parsers specified
if args is None :
sys . argv . insert ( 1 , name )
else :
args . insert ( 0 , name )
2025-01-23 12:58:28 +01:00
2015-05-31 22:42:34 +02:00
argparse . ArgumentParser . set_default_subparser = set_default_subparser
2015-06-07 18:44:33 +02:00
2017-02-22 09:32:35 +01:00
# workaround https://bugs.python.org/issue23058
# see https://github.com/nickstenning/honcho/pull/121
def subparser_call ( self , parser , namespace , values , option_string = None ) :
from argparse import ArgumentError , SUPPRESS , _UNRECOGNIZED_ARGS_ATTR
parser_name = values [ 0 ]
arg_strings = values [ 1 : ]
# set the parser name if requested
if self . dest is not SUPPRESS :
setattr ( namespace , self . dest , parser_name )
# select the parser
try :
parser = self . _name_parser_map [ parser_name ]
except KeyError :
tup = parser_name , ' , ' . join ( self . _name_parser_map )
2018-02-04 07:26:55 +01:00
msg = _ ( ' unknown parser {!r} (choices: {} ) ' ) . format ( * tup )
2017-02-22 09:32:35 +01:00
raise ArgumentError ( self , msg )
# parse all the remaining options into the namespace
# store any unrecognized options on the object, so that the top
# level parser can decide what to do with them
namespace , arg_strings = parser . parse_known_args ( arg_strings , namespace )
if arg_strings :
vars ( namespace ) . setdefault ( _UNRECOGNIZED_ARGS_ATTR , [ ] )
getattr ( namespace , _UNRECOGNIZED_ARGS_ATTR ) . extend ( arg_strings )
2025-01-23 12:58:28 +01:00
2017-02-22 09:32:35 +01:00
argparse . _SubParsersAction . __call__ = subparser_call
2015-06-07 18:44:33 +02:00
2015-05-31 22:42:34 +02:00
def add_network_options ( parser ) :
2023-05-24 17:41:44 +00:00
parser . add_argument ( " -f " , " --serverfingerprint " , dest = SimpleConfig . NETWORK_SERVERFINGERPRINT . key ( ) , default = None ,
help = " only allow connecting to servers with a matching SSL certificate SHA256 fingerprint. " +
" To calculate this yourself: ' $ openssl x509 -noout -fingerprint -sha256 -inform pem -in mycertfile.crt ' . Enter as 64 hex chars. " )
2023-11-28 14:11:48 +01:00
parser . add_argument ( " -1 " , " --oneserver " , action = " store_true " , dest = SimpleConfig . NETWORK_ONESERVER . key ( ) , default = None ,
help = " connect to one server only " )
parser . add_argument ( " -s " , " --server " , dest = SimpleConfig . NETWORK_SERVER . key ( ) , default = None ,
help = " set server host:port:protocol, where protocol is either t (tcp) or s (ssl) " )
parser . add_argument ( " -p " , " --proxy " , dest = SimpleConfig . NETWORK_PROXY . key ( ) , default = None ,
help = " set proxy [type:]host:port (or ' none ' to disable proxy), where type is socks4 or socks5 " )
parser . add_argument ( " --proxyuser " , dest = SimpleConfig . NETWORK_PROXY_USER . key ( ) , default = None ,
help = " set proxy username " )
parser . add_argument ( " --proxypassword " , dest = SimpleConfig . NETWORK_PROXY_PASSWORD . key ( ) , default = None ,
help = " set proxy password " )
parser . add_argument ( " --noonion " , action = " store_true " , dest = SimpleConfig . NETWORK_NOONION . key ( ) , default = None ,
help = " do not try to connect to onion servers " )
parser . add_argument ( " --skipmerklecheck " , action = " store_true " , dest = SimpleConfig . NETWORK_SKIPMERKLECHECK . key ( ) , default = None ,
help = " Tolerate invalid merkle proofs from server " )
2015-05-31 22:42:34 +02:00
2025-01-23 12:58:28 +01:00
2017-02-22 09:32:35 +01:00
def add_global_options ( parser ) :
group = parser . add_argument_group ( ' global options ' )
2019-05-07 21:07:18 +02:00
group . add_argument ( " -v " , dest = " verbosity " , help = " Set verbosity (log levels) " , default = ' ' )
group . add_argument ( " -V " , dest = " verbosity_shortcuts " , help = " Set verbosity (shortcut-filter list) " , default = ' ' )
2016-05-30 08:58:10 +02:00
group . add_argument ( " -D " , " --dir " , dest = " electrum_path " , help = " electrum directory " )
2015-06-12 09:58:29 +02:00
group . add_argument ( " -P " , " --portable " , action = " store_true " , dest = " portable " , default = False , help = " Use local ' electrum_data ' directory " )
2017-01-07 16:58:23 +01:00
group . add_argument ( " --testnet " , action = " store_true " , dest = " testnet " , default = False , help = " Use Testnet " )
2024-09-14 01:47:49 +09:00
group . add_argument ( " --testnet4 " , action = " store_true " , dest = " testnet4 " , default = False , help = " Use Testnet4 " )
2018-04-11 20:10:14 +03:00
group . add_argument ( " --regtest " , action = " store_true " , dest = " regtest " , default = False , help = " Use Regtest " )
2018-06-22 17:07:07 +02:00
group . add_argument ( " --simnet " , action = " store_true " , dest = " simnet " , default = False , help = " Use Simnet " )
2021-05-02 05:54:58 +09:00
group . add_argument ( " --signet " , action = " store_true " , dest = " signet " , default = False , help = " Use Signet " )
2023-05-24 17:41:44 +00:00
group . add_argument ( " -o " , " --offline " , action = " store_true " , dest = SimpleConfig . NETWORK_OFFLINE . key ( ) , default = None , help = " Run offline " )
group . add_argument ( " --rpcuser " , dest = SimpleConfig . RPC_USERNAME . key ( ) , default = argparse . SUPPRESS , help = " RPC user " )
group . add_argument ( " --rpcpassword " , dest = SimpleConfig . RPC_PASSWORD . key ( ) , default = argparse . SUPPRESS , help = " RPC password " )
2017-02-22 09:32:35 +01:00
2025-01-23 12:58:28 +01:00
2019-09-09 06:28:54 +02:00
def add_wallet_option ( parser ) :
parser . add_argument ( " -w " , " --wallet " , dest = " wallet_path " , help = " wallet path " )
2023-05-24 17:41:44 +00:00
parser . add_argument ( " --forgetconfig " , action = " store_true " , dest = SimpleConfig . CONFIG_FORGET_CHANGES . key ( ) , default = False , help = " Forget config on exit " )
2019-09-09 06:28:54 +02:00
2025-01-23 12:58:28 +01:00
2017-02-22 09:32:35 +01:00
def get_parser ( ) :
2015-05-31 22:42:34 +02:00
# create main parser
parser = argparse . ArgumentParser (
epilog = " Run ' electrum help <command> ' to see the help for a command " )
2022-02-15 19:51:20 +01:00
parser . add_argument ( " --version " , dest = " cmd " , action = ' store_const ' , const = ' version ' , help = " Return the version of Electrum. " )
2017-02-22 09:32:35 +01:00
add_global_options ( parser )
2020-10-05 18:02:37 +02:00
add_wallet_option ( parser )
2015-05-31 22:42:34 +02:00
subparsers = parser . add_subparsers ( dest = ' cmd ' , metavar = ' <command> ' )
# gui
2017-02-21 12:41:24 +01:00
parser_gui = subparsers . add_parser ( ' gui ' , description = " Run Electrum ' s Graphical User Interface. " , help = " Run GUI (default) " )
2015-05-31 22:42:34 +02:00
parser_gui . add_argument ( " url " , nargs = ' ? ' , default = None , help = " bitcoin URI (or bip70 file) " )
2023-08-30 13:11:33 +00:00
parser_gui . add_argument ( " -g " , " --gui " , dest = SimpleConfig . GUI_NAME . key ( ) , help = " select graphical user interface " , choices = [ ' qt ' , ' text ' , ' stdio ' , ' qml ' ] )
2023-05-24 17:41:44 +00:00
parser_gui . add_argument ( " -m " , action = " store_true " , dest = SimpleConfig . GUI_QT_HIDE_ON_STARTUP . key ( ) , default = False , help = " hide GUI on startup " )
parser_gui . add_argument ( " -L " , " --lang " , dest = SimpleConfig . LOCALIZATION_LANGUAGE . key ( ) , default = None , help = " default language used in GUI " )
2018-09-13 16:25:56 +02:00
parser_gui . add_argument ( " --daemon " , action = " store_true " , dest = " daemon " , default = False , help = " keep daemon running after GUI is closed " )
2023-05-24 17:41:44 +00:00
parser_gui . add_argument ( " --nosegwit " , action = " store_true " , dest = SimpleConfig . WIZARD_DONT_CREATE_SEGWIT . key ( ) , default = False , help = " Do not create segwit wallets " )
2019-09-09 06:28:54 +02:00
add_wallet_option ( parser_gui )
2015-05-31 22:42:34 +02:00
add_network_options ( parser_gui )
2017-02-22 09:32:35 +01:00
add_global_options ( parser_gui )
2015-05-31 22:42:34 +02:00
# daemon
2017-02-21 12:41:24 +01:00
parser_daemon = subparsers . add_parser ( ' daemon ' , help = " Run Daemon " )
2019-09-02 19:04:08 +02:00
parser_daemon . add_argument ( " -d " , " --detached " , action = " store_true " , dest = " detach " , default = False , help = " run daemon in detached mode " )
2022-05-13 16:48:50 +02:00
# FIXME: all these options are rpc-server-side. The CLI client-side cannot use e.g. --rpcport,
# instead it reads it from the daemon lockfile.
2023-05-24 17:41:44 +00:00
parser_daemon . add_argument ( " --rpchost " , dest = SimpleConfig . RPC_HOST . key ( ) , default = argparse . SUPPRESS , help = " RPC host " )
parser_daemon . add_argument ( " --rpcport " , dest = SimpleConfig . RPC_PORT . key ( ) , type = int , default = argparse . SUPPRESS , help = " RPC port " )
parser_daemon . add_argument ( " --rpcsock " , dest = SimpleConfig . RPC_SOCKET_TYPE . key ( ) , default = None , help = " what socket type to which to bind RPC daemon " , choices = [ ' unix ' , ' tcp ' , ' auto ' ] )
parser_daemon . add_argument ( " --rpcsockpath " , dest = SimpleConfig . RPC_SOCKET_FILEPATH . key ( ) , help = " where to place RPC file socket " )
2015-05-31 22:42:34 +02:00
add_network_options ( parser_daemon )
2017-02-22 09:32:35 +01:00
add_global_options ( parser_daemon )
2015-05-31 22:42:34 +02:00
# commands
for cmdname in sorted ( known_commands . keys ( ) ) :
cmd = known_commands [ cmdname ]
2017-02-21 12:41:24 +01:00
p = subparsers . add_parser ( cmdname , help = cmd . help , description = cmd . description )
2015-06-01 00:17:50 +02:00
for optname , default in zip ( cmd . options , cmd . defaults ) :
2019-09-06 11:06:08 +02:00
if optname in [ ' wallet_path ' , ' wallet ' ] :
2019-09-09 06:28:54 +02:00
add_wallet_option ( p )
2019-09-05 17:57:51 +02:00
continue
2017-10-07 09:48:20 +02:00
a , help = command_options [ optname ]
b = ' -- ' + optname
2018-10-10 20:29:51 +02:00
action = " store_true " if default is False else ' store '
2015-05-31 22:42:34 +02:00
args = ( a , b ) if a else ( b , )
if action == ' store ' :
_type = arg_types . get ( optname , str )
p . add_argument ( * args , dest = optname , action = action , default = default , help = help , type = _type )
else :
p . add_argument ( * args , dest = optname , action = action , default = default , help = help )
2019-09-09 06:28:54 +02:00
add_global_options ( p )
2015-05-31 22:42:34 +02:00
for param in cmd . params :
2019-09-06 11:06:08 +02:00
if param in [ ' wallet_path ' , ' wallet ' ] :
2019-09-05 17:57:51 +02:00
continue
2015-05-31 22:42:34 +02:00
h = param_descriptions . get ( param , ' ' )
_type = arg_types . get ( param , str )
p . add_argument ( param , help = h , type = _type )
2015-06-03 11:34:52 +02:00
cvh = config_variables . get ( cmdname )
if cvh :
group = p . add_argument_group ( ' configuration variables ' , ' (set with setconfig/getconfig) ' )
for k , v in cvh . items ( ) :
group . add_argument ( k , nargs = ' ? ' , help = v )
2015-05-31 22:42:34 +02:00
# 'gui' is the default command
parser . set_default_subparser ( ' gui ' )
return parser