• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

spesmilo / electrum / 5034298207633408

21 Aug 2025 08:26AM UTC coverage: 61.412% (-0.1%) from 61.516%
5034298207633408

Pull #10161

CirrusCI

ecdsa
txbatcher: be careful when removing local transactions

1. Do not remove local transaction in find_base_tx.

This logic was intended to cleanup claim transactions that are
never broadcast (for example, if the counterparty gets a refund)
(see 1bf1de36c)

However, this code is too unspecific and may result in fund loss,
because the transaction being removed may contain outgoing payments.
For example, if the electrum server is not responsive, the tx will
be seen as local and deleted. In that case, another payment will
be attempted, thus paying twice.

2. Do not remove tx after try_broadcasting returns False.

The server might be lying to us. We can only remove the local tx
if there is a base_tx, because the next tx we create will try to
spend the same output.
Pull Request #10161: txbatcher: be careful when removing local transactions

3 of 6 new or added lines in 1 file covered. (50.0%)

159 existing lines in 48 files now uncovered.

22769 of 37076 relevant lines covered (61.41%)

3.06 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/electrum/utils/stacktracer.py
1
#!/usr/bin/env python
2
#
3
# Copyright (C) 2010 Laszlo Nagy (nagylzs)
4
#
5
# Permission is hereby granted, free of charge, to any person
6
# obtaining a copy of this software and associated documentation files
7
# (the "Software"), to deal in the Software without restriction,
8
# including without limitation the rights to use, copy, modify, merge,
9
# publish, distribute, sublicense, and/or sell copies of the Software,
10
# and to permit persons to whom the Software is furnished to do so,
11
# subject to the following conditions:
12
#
13
# The above copyright notice and this permission notice shall be
14
# included in all copies or substantial portions of the Software.
15
#
16
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
20
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
21
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
# SOFTWARE.
24
#
25
# Taken from: https://code.activestate.com/recipes/577334-how-to-debug-deadlocked-multi-threaded-programs/
26

27

UNCOV
28
"""Stack tracer for multi-threaded applications.
29
Useful for debugging deadlocks and hangs.
30

31
Usage:
32
    import stacktracer
33
    stacktracer.trace_start("trace.html", interval=5)
34
    ...
35
    stacktracer.trace_stop()
36

37
This will create a file named "trace.html" showing the stack traces of all threads,
38
updated every 5 seconds.
39
"""
40

41
import os
×
42
import sys
×
43
import threading
×
44
import time
×
45
import traceback
×
46
from typing import Optional
×
47

48
# 3rd-party dependency:
49
from pygments import highlight
×
50
from pygments.lexers import PythonLexer
×
51
from pygments.formatters import HtmlFormatter
×
52

53

54
def _thread_from_id(ident) -> Optional[threading.Thread]:
×
55
    return threading._active.get(ident)
×
56

57

58
def stacktraces():
×
59
    """Taken from http://bzimmer.ziclix.com/2008/12/17/python-thread-dumps/"""
60
    code = []
×
61
    for thread_id, stack in sys._current_frames().items():
×
62
        thread = _thread_from_id(thread_id)
×
63
        code.append(f"\n# thread_id={thread_id}. thread={thread}")
×
64
        for filename, lineno, name, line in traceback.extract_stack(stack):
×
65
            code.append(f'File: "{filename}", line {lineno}, in {name}')
×
66
            if line:
×
67
                code.append("  %s" % (line.strip()))
×
68

69
    return highlight("\n".join(code), PythonLexer(), HtmlFormatter(
×
70
        full=False,
71
        # style="native",
72
        noclasses=True,
73
    ))
74

75

76
class TraceDumper(threading.Thread):
×
77
    """Dump stack traces into a given file periodically.
78

79
    # written by nagylzs
80
    """
81

82
    def __init__(self, fpath, interval, auto):
×
83
        """
84
        @param fpath: File path to output HTML (stack trace file)
85
        @param auto: Set flag (True) to update trace continuously.
86
            Clear flag (False) to update only if file not exists.
87
            (Then delete the file to force update.)
88
        @param interval: In seconds: how often to update the trace file.
89
        """
90
        assert (interval > 0.1)
×
91
        self.auto = auto
×
92
        self.interval = interval
×
93
        self.fpath = os.path.abspath(fpath)
×
94
        self.stop_requested = threading.Event()
×
95
        threading.Thread.__init__(self)
×
96

97
    def run(self):
×
98
        while not self.stop_requested.is_set():
×
99
            time.sleep(self.interval)
×
100
            if self.auto or not os.path.isfile(self.fpath):
×
101
                self.dump_stacktraces()
×
102

103
    def stop(self):
×
104
        self.stop_requested.set()
×
105
        self.join()
×
106
        try:
×
107
            if os.path.isfile(self.fpath):
×
108
                os.unlink(self.fpath)
×
109
        except OSError:
×
110
            pass
×
111

112
    def dump_stacktraces(self):
×
113
        with open(self.fpath, "w+") as fout:
×
114
            fout.write(stacktraces())
×
115

116

117
_tracer = None  # type: Optional[TraceDumper]
×
118

119

120
def trace_start(fpath, interval=5, *, auto=True):
×
121
    """Start tracing into the given file."""
122
    global _tracer
123
    if _tracer is None:
×
124
        _tracer = TraceDumper(fpath, interval, auto)
×
125
        _tracer.daemon = True
×
126
        _tracer.start()
×
127
    else:
128
        raise Exception("Already tracing to %s" % _tracer.fpath)
×
129

130

131
def trace_stop():
×
132
    """Stop tracing."""
133
    global _tracer
134
    if _tracer is None:
×
135
        raise Exception("Not tracing, cannot stop.")
×
136
    else:
137
        _tracer.stop()
×
138
        _tracer = None
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc