Candidate building
How the Lithos client builds each block you mine, where the revenue from its extra transactions goes, and what happens when part of it fails.
Every block you mine starts as a block candidate: the transactions your Ergo node will commit to, turned into the job your mining software hashes. With Lithos, your own client decides what goes into that candidate before your node fills in the rest. This page explains how the client builds a candidate, where the revenue from its extra transactions goes, and how it falls back when something fails.
It stays general. The settings are listed in Block packages, and the ErgoDEX and LithosDex sources are configured in Batching and orders.
What goes into a candidate#
A Lithos candidate is built in layers, in the following order:
- The genesis transaction. It spends a collateral box and creates the holding box for the rollup this block opens. It is what makes the block a Lithos block, so it always comes first.
- The block package. Extra transactions from the client's transaction sources, such as Non-Interactive Share Proof (NISP) submissions and order executions. These are optional, and every source except storage rent ships on.
- The holding top-up. One final transaction that moves the revenue the package earned into the holding box.
- Your node's own selection. Your Ergo node fills the space left with fee-paying transactions from its mempool, as it would for any block.
With the package off, which is the default, a candidate is the genesis transaction followed by your node's selection.
Building the genesis transaction#
The genesis transaction has to exist before the node can hand out a job, so it is the one part of the candidate on the critical path. The client prepares for it in advance:
- The collateral set is held in memory. The client keeps the live collateral boxes, up to 100, loaded ahead of time and refreshes them after every block. Nothing is fetched while a block waits.
- A box is chosen per block. The box offering the finder the largest priority fee wins, and the
oldest breaks a tie. A box that has been live for
clearanceAgeblocks, 7200 by default, is taken ahead of every bid, oldest first, so no box waits forever. SettingcollateralStrategytorandomdraws uniformly instead. Boxes paying into a rollup chain this release does not build are never drawn, as Boxes your block never spends explains. - The coinbase goes to the lender. The client asks the node for a candidate paying the block reward to the chosen box's lender, which is what the collateral box paid for.
The genesis transaction is signed on its own thread, so nothing else the client is doing can delay it.
Building the block package#
When stratum.candidate.blockTransactions is on, the client asks each enabled transaction source
for work as soon as a new block arrives. The sources, in the order they are asked, are:
| Source | What it inserts | Ships |
|---|---|---|
rollups | Your NISP submissions, rollup phase transforms, fraud proofs and payouts | On |
emissions | Maintenance of the collateral queue | On |
rent | Collection of boxes old enough to owe storage rent | Off |
lithosdex | LithosDex order executions and the fee flush | On |
ergodex | ErgoDEX order executions | On |
Every transaction a source offers is inserted without a fee. You are the one mining the block, so there is nobody to pay. Rollup and emission work is protocol work the client also sends to the mempool with a fee, so that it lands whoever finds the block. For rollup work, your own block gets the first chance, as How rollup work reaches the chain explains. Queue Activates get the same first block, and a Clear is always taken, as Queue work in your own blocks explains.
How the package is bounded#
A package is bounded twice. Each source is first held to its own limits on transactions, bytes and
execution cost, so a busy source cannot use space a quieter one needs. The whole package is then held to
blockShare of your node's current block limits, half by default, since the node still needs room for
its own selection.
Sources are admitted in the order above until the package is full. The genesis transaction is counted first, so it is never the part that is dropped. A transaction that depends on an unconfirmed parent is admitted together with that parent or not at all, because a block carrying the child alone is invalid.
Waiting for sources#
Two deadlines apply after a new block, and they protect different things:
- The job waits at most
genesisWaitMs, 1.5 seconds by default. If the package is not ready by then, your mining software gets the genesis transaction alone, and the package follows as a new job when it is. - The sources get at most
blockTxTimeout, 20 seconds by default. A source that has not answered two seconds before then is left out, and what the others built still goes in. A source that answers later is picked up by the next refresh.
Where the revenue goes#
Some sources earn ERG: executor fees from filled orders, and storage rent from collected boxes. That revenue goes to the pool, not to you alone. The holding top-up adds it to the holding box this block's genesis transaction created, and the rollup pays it out by score to every miner who submits to it, you included.
Two limits apply to the top-up:
- Tokens cannot enter the holding box. Any tokens the package earns stay on outputs your wallet owns.
- A top-up that does not fit is skipped. If the package budget has no room left for it, or it would cost more than it adds, the revenue stays on outputs your wallet owns and can be spent later.
Refreshing within a block#
Transactions keep arriving after a block starts, and some pay more than what the package already holds.
Every mempoolRefreshMs, 20 seconds by default, the client rebuilds the package against the current
mempool.
A rebuilt package does not always replace the job. Changing jobs costs your mining software a moment
of work, so the client switches only when the rebuilt package earns at least
minCandidateChangeRevenue more than the one it is serving, 0.001 ERG by default.
Rollup and emission transactions earn no ERG, so the client also switches when the rebuilt package adds
at least minNewProtocolTxs of them, 1 by default. Setting refreshForProtocolTxs to false turns
that rule off.
How rollup work reaches the chain#
Your client sends its rollup transactions to the mempool with a fee, so they land whoever finds the
block. These are your NISP submissions, rollup phase transforms, fraud proofs and payouts. With
blockTransactions on, your own block gets the first chance to carry each one without a fee, in the
following order:
- A new rollup transaction waits for the next package rebuild, up to
mempoolRefreshMs, so your package can take it first. - While your block carries the transaction, the client holds the fee-paying copy back.
- If you find the block, the transaction lands without a fee, and the client sends nothing.
- If someone else finds the block, the client checks the chain within a minute. If the work is still undone, the client sends the fee-paying copy and stops offering the transaction to your blocks.
NISP submissions and fraud proofs must land before a fixed window closes. Any with 10 blocks or fewer left, about 20 minutes at Ergo's 2-minute block time, are sent with a fee at once, whatever your block carries.
Emission transactions are not held back. The client sends them to the mempool on its own timers. The settings that change when rollup work waits are in Rollup work.
When part of a candidate fails#
The client never stops mining over a failed candidate. It falls back one step at a time, and each step is logged at warning or error level with the reason:
| What failed | What the client does |
|---|---|
| A source errors or does not answer in time | Builds the package without that source |
| The node leaves one inserted transaction out of the candidate | Mines the rest of the package and logs which transaction was left out |
| The node refuses the candidate with the package in it | Mines the genesis transaction alone until the next block |
Collecting the package takes longer than blockTxTimeout | Mines the genesis transaction alone until the next block |
| The genesis transaction cannot be built | Tries a different collateral box, up to two more times |
| The node refuses the genesis transaction | Tries a different collateral box, up to three times per block |
No genesis transaction is ready within genesisWaitMs | Serves a solo job until one is ready |
| No genesis transaction can be built at all | Serves a solo job for the rest of the block |
Seeing what your client built#
The Block package tab on the Mining page shows the job your stratum is serving right now: its genesis transaction, the transactions the package inserted, the holding top-up, and the sources that contributed. Refresh reads the newest job without waiting for the page's timer. Below the block, three cards show the following:
- Collateral box: the box the genesis transaction spends, its priority fee above the principal floor, and the part of that fee paid to you.
- Extra transactions: whether
blockTransactionsis on, and for each source whether it is asked, its limits, and whether it made it into this job. - Job refresh: when a rebuilt package replaces the job mid-block.
A source marked not asked, no slots or batcher off is enabled but contributes nothing, and
hovering the status names the setting that stops it. To see the same detail in the log, turn on
stratum.candidate.logTimings and stratum.candidate.logBudgets.
The following log lines show what happened to individual transactions. A stub is the client's record of one rollup transaction waiting to be sent, named as its type and the first 8 characters of its rollup's id:
| Log line | What it means |
|---|---|
Holding N rollup stub(s) back from the funded path… | Your block carries these rollup transactions, so their fee-paying copies wait |
Releasing N rollup stub(s) to the funded path… | The block passed without them, so their fee-paying copies go out now |
Node left N of M requested transaction(s) out of candidate… | Your node left these transactions out of your block. A Not included transaction line in the node's log gives the reason, and no such line means one of the transaction's inputs was missing or already spent |
Node left N transaction(s) out of block H, and the top-up spending their outputs with them… | Your node refused an inserted transaction, which also invalidates the top-up collecting its revenue. The client mines the genesis transaction alone for a moment, then rebuilds the package without that transaction, up to twice per block |
Dropping optional candidate transactions… | The inserted transactions could not be used, after any rebuilds. The rest of this block is mined with the genesis transaction alone |
Next steps#
- Block packages: every setting on this page.
- Batching and orders: the sources that fill other people's orders.
- Mining on Lithos: how the rollup that shares this revenue pays you.