Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
gruve-p committed Jan 17, 2025
2 parents efdd159 + 897327d commit 633883a
Show file tree
Hide file tree
Showing 46 changed files with 642 additions and 219 deletions.
2 changes: 1 addition & 1 deletion .cirrus.yml
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ task:
task:
name: "check submodules"
container:
image: python:3.9
image: python:3.10
cpu: 1
memory: 1G
fetch_script:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
```
Licence: MIT Licence
Author: Groestlcoin Developers
Language: Python (>= 3.8)
Language: Python (>= 3.10)
Homepage: https://groestlcoin.org/
```

Expand Down
3 changes: 1 addition & 2 deletions contrib/build-linux/sdist/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM debian:bullseye@sha256:43ef0c6c3585d5b406caa7a0f232ff5a19c1402aeb415f68bcd1cf9d10180af8
FROM debian:bookworm@sha256:b877a1a3fdf02469440f1768cf69c9771338a875b7add5e80c45b756c92ac20a

ENV LC_ALL=C.UTF-8 LANG=C.UTF-8
ENV DEBIAN_FRONTEND=noninteractive
Expand All @@ -11,7 +11,6 @@ RUN apt-get update -q && \
python3-pip \
python3-setuptools \
python3-venv \
faketime \
&& \
rm -rf /var/lib/apt/lists/* && \
apt-get autoremove -y && \
Expand Down
45 changes: 25 additions & 20 deletions contrib/build-linux/sdist/make_sdist.sh
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,20 @@ PROJECT_ROOT="$(dirname "$(readlink -e "$0")")/../../.."
CONTRIB="$PROJECT_ROOT/contrib"
CONTRIB_SDIST="$CONTRIB/build-linux/sdist"
DISTDIR="$PROJECT_ROOT/dist"
BUILDDIR="$CONTRIB_SDIST/build"
LOCALE="$PROJECT_ROOT/electrum_grs/locale"

. "$CONTRIB"/build_tools_util.sh

git -C "$PROJECT_ROOT" rev-parse 2>/dev/null || fail "Building outside a git clone is not supported."

# note that at least py3.7 is needed, to have https://bugs.python.org/issue30693
rm -rf "$BUILDDIR"
mkdir -p "$BUILDDIR" "$DISTDIR"

python3 --version || fail "python interpreter not found"

break_legacy_easy_install

# upgrade to modern pip so that it knows the flags we need.
# (make_packages.sh will later install a pinned version of pip in a venv)
python3 -m pip install --upgrade pip

rm -rf "$PROJECT_ROOT/packages/"
if ([ "$OMIT_UNCLEAN_FILES" != 1 ]); then
"$CONTRIB"/make_packages.sh || fail "make_packages failed"
Expand Down Expand Up @@ -51,26 +50,32 @@ fi

# note: .zip sdists would not be reproducible due to https://bugs.python.org/issue40963
if ([ "$OMIT_UNCLEAN_FILES" = 1 ]); then
PY_DISTDIR="dist/_sourceonly" # The DISTDIR variable of this script is only used to find where the output is *finally* placed.
PY_DISTDIR="$BUILDDIR/dist1/_sourceonly" # The DISTDIR variable of this script is only used to find where the output is *finally* placed.
else
PY_DISTDIR="$BUILDDIR/dist1"
fi
# build initial tar.gz
python3 setup.py --quiet sdist --format=gztar --dist-dir="$PY_DISTDIR"

VERSION=$("$CONTRIB"/print_electrum_version.py)
if ([ "$OMIT_UNCLEAN_FILES" = 1 ]); then
FINAL_DISTNAME="Electrum-grs-sourceonly-$VERSION.tar.gz"
else
PY_DISTDIR="dist"
FINAL_DISTNAME="Electrum-grs-$VERSION.tar.gz"
fi
TZ=UTC faketime -f '2000-11-11 11:11:11' python3 setup.py --quiet sdist --format=gztar --dist-dir="$PY_DISTDIR"
if ([ "$OMIT_UNCLEAN_FILES" = 1 ]); then
python3 <<EOF
import importlib.util
import os
# load version.py; needlessly complicated alternative to "imp.load_source":
version_spec = importlib.util.spec_from_file_location('version', 'electrum_grs/version.py')
version_module = importlib.util.module_from_spec(version_spec)
version_spec.loader.exec_module(version_module)
VER = version_module.ELECTRUM_VERSION
os.rename(f"dist/_sourceonly/Electrum-grs-{VER}.tar.gz", f"dist/Electrum-grs-sourceonly-{VER}.tar.gz")
EOF
mv "$PY_DISTDIR/Electrum-grs-$VERSION.tar.gz" "$PY_DISTDIR/../$FINAL_DISTNAME"
rmdir "$PY_DISTDIR"
fi

# the initial tar.gz is not reproducible, see https://github.com/pypa/setuptools/issues/2133
# so we untar, fix timestamps, and then re-tar
mkdir -p "$BUILDDIR/dist2"
cd "$BUILDDIR/dist2"
tar -xzf "$BUILDDIR/dist1/$FINAL_DISTNAME"
find -exec touch -h -d '2000-11-11T11:11:11+00:00' {} +
GZIP=-n tar --sort=name -czf "$FINAL_DISTNAME" "Electrum-grs-$VERSION/"
mv "$FINAL_DISTNAME" "$DISTDIR/$FINAL_DISTNAME"
)


Expand Down
4 changes: 4 additions & 0 deletions contrib/deterministic-build/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ certifi==2024.2.2 \
--hash=sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f
dnspython==2.2.1 \
--hash=sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e
electrum-aionostr==0.0.6 \
--hash=sha256:6eead6193edc6ab8455b7ddee1b3f4f5cb3c65d0ea1bdbdadb44506eb8f67092
electrum-ecc==0.0.3 \
--hash=sha256:c8ab69fecb294825367030da532b2d191883fa169789faa2942c256b4043d0a2
electrum-aionostr==0.0.6 \
Expand Down Expand Up @@ -44,6 +46,8 @@ QtPy==2.4.1 \
--hash=sha256:a5a15ffd519550a1361bdc56ffc07fda56a6af7292f17c7b395d4083af632987
setuptools==65.5.1 \
--hash=sha256:e197a19aa8ec9722928f2206f8de752def0e4c9fc6953527360d1c36d94ddb2f
websockets==13.1 \
--hash=sha256:a3b3366087c1bc0a2795111edcadddb8b3b59509d5db5d7ea3fdd69f954a8878
wheel==0.38.4 \
--hash=sha256:965f5259b566725405b05e7cf774052044b1ed30119b5d586b2703aafe8719ac
yarl==1.8.2 \
Expand Down
2 changes: 1 addition & 1 deletion contrib/freeze_packages.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ contrib=$(dirname "$0")

# note: we should not use a higher version of python than what the binaries bundle
if [[ ! "$SYSTEM_PYTHON" ]] ; then
SYSTEM_PYTHON=$(which python3.8) || printf ""
SYSTEM_PYTHON=$(which python3.10) || printf ""
else
SYSTEM_PYTHON=$(which $SYSTEM_PYTHON) || printf ""
fi
Expand Down
4 changes: 4 additions & 0 deletions contrib/make_packages.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ export FROZENLIST_NO_EXTENSIONS=1

export ELECTRUM_ECC_DONT_COMPILE=1

# see https://github.com/python-websockets/websockets/blob/e6d0ea1d6b13a979924329d02fb82f79d82c7236/setup.py#L22
export BUILD_EXTENSION="no"


# if we end up having to compile something, at least give reproducibility a fighting chance
export LC_ALL=C
export TZ=UTC
Expand Down
4 changes: 3 additions & 1 deletion electrum_grs/address_synchronizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from . import bitcoin, util
from .bitcoin import COINBASE_MATURITY
from .util import profiler, bfh, TxMinedInfo, UnrelatedTransactionException, with_lock, OldTaskGroup
from .transaction import Transaction, TxOutput, TxInput, PartialTxInput, TxOutpoint, PartialTransaction
from .transaction import Transaction, TxOutput, TxInput, PartialTxInput, TxOutpoint, PartialTransaction, tx_from_any
from .synchronizer import Synchronizer
from .verifier import SPV
from .blockchain import hash_header, Blockchain
Expand Down Expand Up @@ -275,6 +275,8 @@ def add_transaction(self, tx: Transaction, *, allow_unrelated=False, is_new=True
tx_hash = tx.txid()
if tx_hash is None:
raise Exception("cannot add tx without txid to wallet history")
# For sanity, try to serialize and deserialize tx early:
tx_from_any(str(tx)) # see if raises (no-side-effects)
# we need self.transaction_lock but get_tx_height will take self.lock
# so we need to take that too here, to enforce order of locks
with self.lock, self.transaction_lock:
Expand Down
56 changes: 51 additions & 5 deletions electrum_grs/channel_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import ipaddress
import time
import random
import os
Expand All @@ -41,7 +42,7 @@

from .sql_db import SqlDB, sql
from . import constants, util
from .util import profiler, get_headers_dir, is_ip_address, json_normalize, UserFacingException
from .util import profiler, get_headers_dir, is_ip_address, json_normalize, UserFacingException, is_private_netaddress
from .logging import Logger
from .lntransport import LNPeerAddr
from .lnutil import (format_short_channel_id, ShortChannelID,
Expand Down Expand Up @@ -192,6 +193,48 @@ def from_raw_msg(raw: bytes) -> Tuple['NodeInfo', Sequence['LNPeerAddr']]:
payload_dict = decode_msg(raw)[1]
return NodeInfo.from_msg(payload_dict)

@staticmethod
def to_addresses_field(hostname: str, port: int) -> bytes:
"""Encodes a hostname/port pair into a BOLT-7 'addresses' field."""
if (NodeInfo.invalid_announcement_hostname(hostname)
or port is None or port <= 0 or port > 65535):
return b''
port_bytes = port.to_bytes(2, 'big')
if is_ip_address(hostname): # ipv4 or ipv6
ip_addr = ipaddress.ip_address(hostname)
if ip_addr.version == 4:
return b'\x01' + ip_addr.packed + port_bytes
elif ip_addr.version == 6:
return b'\x02' + ip_addr.packed + port_bytes
elif hostname.endswith('.onion'): # Tor onion v3
onion_addr: bytes = base64.b32decode(hostname[:-6], casefold=True)
return b'\x04' + onion_addr + port_bytes
else:
try:
hostname_ascii: bytes = hostname.encode('ascii')
except UnicodeEncodeError:
# encoding single characters to punycode (according to spec) doesn't make sense
# as you can't differentiate them from regular ascii? encoding the whole string to punycode
# doesn't work either as the receiver would interpret it as regular ascii.
# hostname_ascii: bytes = hostname.encode('punycode')
return b''
if len(hostname_ascii) + 3 > 258: # + 1 byte for length and 2 for port
return b'' # too long
return b'\x05' + len(hostname_ascii).to_bytes(1, "big") + hostname_ascii + port_bytes

@staticmethod
def invalid_announcement_hostname(hostname: Optional[str]) -> bool:
"""Returns True if hostname unsuited for publishing in a NodeAnnouncement."""
if (hostname is None or hostname == ""
or is_private_netaddress(hostname)
or hostname.startswith("http://") # not catching 'http' due to onion addresses
or hostname.startswith("https://")):
return True
if hostname.endswith('.onion'):
if len(hostname) != 62: # not an onion v3 link (probably onion v2)
return True
return False

@staticmethod
def parse_addresses_field(addresses_field):
buf = addresses_field
Expand All @@ -216,15 +259,18 @@ def read(n):
if is_ip_address(ipv6_addr) and port != 0:
addresses.append((ipv6_addr, port))
elif atype == 3: # onion v2
host = base64.b32encode(read(10)) + b'.onion'
host = host.decode('ascii').lower()
port = int.from_bytes(read(2), 'big')
addresses.append((host, port))
read(12) # we skip onion v2 as it is deprecated
elif atype == 4: # onion v3
host = base64.b32encode(read(35)) + b'.onion'
host = host.decode('ascii').lower()
port = int.from_bytes(read(2), 'big')
addresses.append((host, port))
elif atype == 5: # dns hostname
len_hostname = int.from_bytes(read(1), 'big')
host = read(len_hostname).decode('ascii')
port = int.from_bytes(read(2), 'big')
if not NodeInfo.invalid_announcement_hostname(host) and port > 0:
addresses.append((host, port))
else:
# unknown address type
# we don't know how long it is -> have to escape
Expand Down
18 changes: 18 additions & 0 deletions electrum_grs/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,24 @@ async def setconfig(self, key, value):
cv = self.config.cv.from_key(key)
cv.set(value)

@command('')
async def listconfig(self):
"""Returns the list of all configuration variables. """
return self.config.list_config_vars()

@command('')
async def helpconfig(self, key):
"""Returns help about a configuration variable. """
cv = self.config.cv.from_key(key)
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}'"

@command('')
async def make_seed(self, nbits=None, language=None, seed_type=None):
"""Create a seed"""
Expand Down
4 changes: 2 additions & 2 deletions electrum_grs/daemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
from .util import (json_decode, to_bytes, to_string, profiler, standardize_path, constant_time_compare, InvalidPassword)
from .invoices import PR_PAID, PR_EXPIRED
from .util import log_exceptions, ignore_exceptions, randrange, OldTaskGroup, UserFacingException, JsonRPCError
from .util import EventListener, event_listener, traceback_format_exception
from .util import EventListener, event_listener
from .wallet import Wallet, Abstract_Wallet
from .storage import WalletStorage
from .wallet_db import WalletDB, WalletRequiresSplit, WalletRequiresUpgrade, WalletUnfinished
Expand Down Expand Up @@ -264,7 +264,7 @@ async def handle(self, request):
'message': "internal error while executing RPC",
'data': {
"exception": repr(e),
"traceback": "".join(traceback_format_exception(e)),
"traceback": "".join(traceback.format_exception(e)),
},
}
return web.json_response(response)
Expand Down
2 changes: 2 additions & 0 deletions electrum_grs/gui/qml/components/ChannelDetails.qml
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,9 @@ Pane {
}

Label {
Layout.fillWidth: true
text: channeldetails.channelType
wrapMode: Text.Wrap
}

Label {
Expand Down
2 changes: 1 addition & 1 deletion electrum_grs/gui/qml/components/WalletMainView.qml
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,7 @@ Item {
var dialog = app.messageDialog.createObject(app, {
title: qsTr('Error'),
iconSource: Qt.resolvedUrl('../../icons/warning.png'),
text: message
text: message ? message : qsTr('Payment failed')
})
dialog.open()
}
Expand Down
4 changes: 2 additions & 2 deletions electrum_grs/gui/qml/components/main.qml
Original file line number Diff line number Diff line change
Expand Up @@ -613,10 +613,10 @@ ApplicationWindow
}
// TODO: add to notification queue instead of barging through
function onPaymentSucceeded(key) {
notificationPopup.show(Daemon.currentWallet.name, qsTr('Payment Succeeded'))
notificationPopup.show(Daemon.currentWallet.name, qsTr('Payment succeeded'))
}
function onPaymentFailed(key, reason) {
notificationPopup.show(Daemon.currentWallet.name, qsTr('Payment Failed') + ': ' + reason)
notificationPopup.show(Daemon.currentWallet.name, qsTr('Payment failed') + ': ' + reason)
}
}

Expand Down
4 changes: 2 additions & 2 deletions electrum_grs/gui/qml/qedaemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,8 +251,8 @@ def _on_backend_wallet_loaded(self, password=None):
assert wallet is not None
self._current_wallet = QEWallet.getInstanceFor(wallet)
self.availableWallets.updateWallet(self._path)
if wallet.requires_unlock():
wallet.unlock(password or None)
wallet.unlock(password or None) # not conditional on wallet.requires_unlock in qml, as
# the auth wrapper doesn't pass the entered password, but instead we rely on the password in memory
self._loading = False
self.loadingChanged.emit()
self.walletLoaded.emit(self._name, self._path)
Expand Down
4 changes: 2 additions & 2 deletions electrum_grs/gui/qml/qeinvoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -573,15 +573,15 @@ def _validateRecipient_bip21_onchain(self, bip21: Dict[str, Any]) -> None:
def resolve_pi(self):
assert self._pi.need_resolve()

def on_finished(pi):
def on_finished(pi: PaymentIdentifier):
self._busy = False
self.busyChanged.emit()

if pi.is_error():
if pi.type in [PaymentIdentifierType.EMAILLIKE, PaymentIdentifierType.DOMAINLIKE]:
msg = _('Could not resolve address')
elif pi.type == PaymentIdentifierType.LNURLP:
msg = _('Could not resolve LNURL')
msg = _('Could not resolve LNURL') + "\n\n" + pi.get_error()
elif pi.type == PaymentIdentifierType.BIP70:
msg = _('Could not resolve BIP70 payment request: {}').format(pi.error)
else:
Expand Down
6 changes: 5 additions & 1 deletion electrum_grs/gui/qt/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
getOpenFileName, getSaveFileName, font_height)
from .util import ButtonsLineEdit, ShowQRLineEdit
from .util import QtEventListener, qt_event_listener, event_listener
from .util import scan_qr_from_screenshot
from .wizard.wallet import WIF_HELP_TEXT
from .history_list import HistoryList, HistoryModel
from .update_checker import UpdateCheck, UpdateCheckThread
Expand Down Expand Up @@ -1190,6 +1191,8 @@ def run_swap_dialog(self, is_reverse=None, recv_amount_sat=None, channels=None):
except InvalidSwapParameters as e:
self.show_error(str(e))
return
except UserCancelled:
return

def create_sm_transport(self):
sm = self.wallet.lnworker.swap_manager
Expand Down Expand Up @@ -1687,7 +1690,8 @@ def update_console(self):
'util': util,
'bitcoin': bitcoin,
'lnutil': lnutil,
'channels': list(self.wallet.lnworker.channels.values()) if self.wallet.lnworker else []
'channels': list(self.wallet.lnworker.channels.values()) if self.wallet.lnworker else [],
'scan_qr': scan_qr_from_screenshot,
})

c = commands.Commands(
Expand Down
Loading

0 comments on commit 633883a

Please sign in to comment.