tumbler.plan
tumbler.plan
Tumbler plan data model.
A Plan is an ordered list of Phase objects. Each phase is one of:
- :class:
TakerCoinjoinPhase- a single taker CoinJoin (optionally sweep). - :class:
MakerSessionPhase- run a maker bot for a bounded time or until a target number of CoinJoins complete.
The plan and its phases form the single source of truth for a running
tumble. Progress is persisted to a YAML file (see :mod:tumbler.persistence).
Attributes
MIN_DESTINATIONS = 3
module-attribute
Phase = Annotated[TakerCoinjoinPhase | MakerSessionPhase, Field(discriminator='kind')]
module-attribute
Classes
MakerSessionPhase
Bases: _PhaseBase
Run a maker bot as part of the tumble.
The session ends when any configured bound is reached: duration_seconds
elapses, target_cj_count CoinJoins have been served, or no CoinJoin has
been served for idle_timeout_seconds (whichever comes first). At least
one of duration_seconds or target_cj_count must be set;
idle_timeout_seconds is an optional safety fallback so the phase does
not hang forever when the maker is never chosen as counterparty.
Source code in tumbler/src/tumbler/plan.py
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | |
Attributes
cj_served: int = Field(default=0, ge=0, description='CoinJoins served so far.')
class-attribute
instance-attribute
duration_seconds: float | None = Field(default=None, gt=0.0)
class-attribute
instance-attribute
idle_timeout_seconds: float | None = Field(default=None, gt=0.0)
class-attribute
instance-attribute
kind: Literal[PhaseKind.MAKER_SESSION] = PhaseKind.MAKER_SESSION
class-attribute
instance-attribute
target_cj_count: int | None = Field(default=None, ge=1)
class-attribute
instance-attribute
PhaseKind
Bases: StrEnum
Discriminator for the phase variants.
Source code in tumbler/src/tumbler/plan.py
24 25 26 27 28 | |
Attributes
MAKER_SESSION = 'maker_session'
class-attribute
instance-attribute
TAKER_COINJOIN = 'taker_coinjoin'
class-attribute
instance-attribute
PhaseStatus
Bases: StrEnum
Lifecycle of an individual phase.
Source code in tumbler/src/tumbler/plan.py
31 32 33 34 35 36 37 38 | |
Attributes
CANCELLED = 'cancelled'
class-attribute
instance-attribute
COMPLETED = 'completed'
class-attribute
instance-attribute
FAILED = 'failed'
class-attribute
instance-attribute
PENDING = 'pending'
class-attribute
instance-attribute
RUNNING = 'running'
class-attribute
instance-attribute
Plan
Bases: BaseModel
A tumble plan with per-phase progress tracking.
Source code in tumbler/src/tumbler/plan.py
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 | |
Attributes
created_at: datetime = Field(default_factory=(lambda: datetime.now(UTC)))
class-attribute
instance-attribute
current_phase: int = Field(default=0, ge=0, description='Index of the next phase to run (0 == plan not started).')
class-attribute
instance-attribute
destinations: list[str] = Field(..., min_length=1, description=f'External destination addresses. The CLI enforces at least {MIN_DESTINATIONS} to avoid pairwise re-aggregation heuristics; library consumers may pass fewer.')
class-attribute
instance-attribute
error: str | None = None
class-attribute
instance-attribute
parameters: PlanParameters = Field(default_factory=PlanParameters)
class-attribute
instance-attribute
phases: list[Phase] = Field(default_factory=list)
class-attribute
instance-attribute
plan_id: str = Field(default_factory=(lambda: uuid4().hex))
class-attribute
instance-attribute
status: PlanStatus = PlanStatus.PENDING
class-attribute
instance-attribute
updated_at: datetime = Field(default_factory=(lambda: datetime.now(UTC)))
class-attribute
instance-attribute
wallet_name: str
instance-attribute
Methods:
current() -> Phase | None
Return the phase at current_phase, or None if the plan is done.
Source code in tumbler/src/tumbler/plan.py
227 228 229 230 231 | |
touch() -> None
Update updated_at to now (UTC).
Source code in tumbler/src/tumbler/plan.py
233 234 235 | |
PlanParameters
Bases: BaseModel
User-facing knobs captured for audit and resume. The builder records what it was told; the runner does not re-derive phases from these.
Source code in tumbler/src/tumbler/plan.py
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 | |
Attributes
include_maker_sessions: bool = True
class-attribute
instance-attribute
maker_count_max: int = Field(default=9, ge=1, le=20)
class-attribute
instance-attribute
maker_count_min: int = Field(default=5, ge=1, le=20)
class-attribute
instance-attribute
max_phase_retries: int = Field(default=3, ge=0, le=20, description='Maximum number of re-tries for a failed taker CoinJoin phase. Exhausting retries fails the entire plan.')
class-attribute
instance-attribute
mincjamount_sats: int = Field(default=100000, ge=0)
class-attribute
instance-attribute
seed: int | None = None
class-attribute
instance-attribute
time_lambda_seconds: float = Field(default=(6.0 * 60.0 * 60.0), gt=0.0)
class-attribute
instance-attribute
PlanStatus
Bases: StrEnum
Lifecycle of the overall plan.
Source code in tumbler/src/tumbler/plan.py
41 42 43 44 45 46 47 48 | |
Attributes
CANCELLED = 'cancelled'
class-attribute
instance-attribute
COMPLETED = 'completed'
class-attribute
instance-attribute
FAILED = 'failed'
class-attribute
instance-attribute
PENDING = 'pending'
class-attribute
instance-attribute
RUNNING = 'running'
class-attribute
instance-attribute
TakerCoinjoinPhase
Bases: _PhaseBase
A single taker CoinJoin.
Source code in tumbler/src/tumbler/plan.py
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | |
Attributes
amount: int | None = Field(default=None, ge=0)
class-attribute
instance-attribute
amount_fraction: float | None = Field(default=None, ge=0.0, le=1.0)
class-attribute
instance-attribute
counterparty_count: int = Field(..., ge=1, le=20)
class-attribute
instance-attribute
destination: str = Field(..., description="A bitcoin address, or the sentinel 'INTERNAL' to pick the next mixdepth's internal address at execution time.")
class-attribute
instance-attribute
is_sweep: bool
property
A sweep empties the mixdepth: amount==0 or amount_fraction==0.
kind: Literal[PhaseKind.TAKER_COINJOIN] = PhaseKind.TAKER_COINJOIN
class-attribute
instance-attribute
mixdepth: int = Field(..., ge=0, le=9)
class-attribute
instance-attribute
rounding_sigfigs: int | None = Field(default=None, ge=1, le=8, description="If set, round the resolved sat amount to this many significant figures before dispatching to the taker. Mirrors the reference implementation's ``rounding`` schedule entry: a sub-BTC amount like 0.13256 BTC rounded to 2 sigfigs becomes 0.13 BTC, which obfuscates the relationship between the wallet balance and the CoinJoin amount. Sweeps (amount==0 / amount_fraction==0) ignore this field.")
class-attribute
instance-attribute
txid: str | None = Field(default=None, description='Broadcast txid, set once the CoinJoin confirms.')
class-attribute
instance-attribute
Functions:
round_to_significant_figures(value: int, sigfigs: int) -> int
Round value to sigfigs significant figures in base 10.
Mirrors round_to_significant_figures in the reference
jmclient.taker: the smallest power of ten greater than value is
used as the scale, then the value is rounded to sigfigs sigfigs
around it. Examples (sigfigs=2)::
13_256_421 -> 13_000_000
9_876 -> 9_900
1_000_000 -> 1_000_000
0 -> 0
Raises ValueError if value is negative or sigfigs is not in
[1, 8] (matching the model bounds).
Source code in tumbler/src/tumbler/plan.py
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 | |