Collateral-based Pool for Ergo
After multiple iterations on the design, the following is a final version of the collateral scheme for an upcoming Ergo mining pool, for public review.
Perpetual Token
This is a singleton token, which is intended to last “forever”, with an additional restriction that prevents it from being spent more than once per block.
{
val createdPreviousBlock = {(b: Box) => b.creationInfo._1 < HEIGHT}
val createdCurrentBlock = {(b: Box) => b.creationInfo._1 == HEIGHT}
val isPerpetualWithHeight = {(b: Box) =>
b.propositionBytes == SELF.propositionBytes &&
b.tokens == SELF.tokens &&
createdCurrentBlock(b)
}
sigmaProp(OUTPUTS.exists(isPerpetualWithHeight) &&
createdPreviousBlock(SELF))
}
Without this technique, I was concerned that users might accidentally create more than one unspent collateral box for the same miner public key, which would then allow a pool to claim more than one reward in the same block.
Collateral
This is the main pool collateral script, which allows a pool to take a reward from the collateral box when the miner finds a block.
{
val minersReward = fixedRate - foundersInitialReward
val minersFixedRatePeriod = fixedRatePeriod + 2 * epochLength
val epoch = 1 + ((HEIGHT - fixedRatePeriod) / epochLength)
val coinsToIssue = if (HEIGHT < minersFixedRatePeriod) {
minersReward
} else {
fixedRate - oneEpochReduction * epoch
}
val totalSelf = INPUTS.fold(0L, {(a: Long, b:Box) =>
if (b.propositionBytes == SELF.propositionBytes) {
a + b.value
} else {
a
}
})
val remainder = totalSelf - coinsToIssue
val remainderToSelf = {(b: Box) =>
(remainder <= 0) || (
b.propositionBytes == SELF.propositionBytes &&
b.value == remainder
)
}
val perpetualToken = {(b: Box) =>
b.tokens(0)._1 == perpetualTokenId
}
val aliceFoundBlock = sigmaProp(
CONTEXT.preHeader.minerPk == alice &&
perpetualToken(INPUTS(0)) &&
remainderToSelf(OUTPUTS(0))
)
val aliceWithdraw = proveDlog(alice)
aliceFoundBlock || aliceWithdraw
}
Pool Reward Transaction
Let’s call the spending transaction involving one or more collateral boxes as inputs the pool reward transaction. The following properties should hold for this transaction in the case of aliceFoundBlock
:
- The block was mined by Alice.
- The first input contains the special perpetual token.
- After the pool has taken its reward from the total value of the input collateral boxes, if the remainder is non-zero it is sent in full to a new collateral box (protected by the same script).
The following properties hold for the special perpetual token (and for the pool reward transaction, since it is forced to spend this token):
- There is only one indivisible token (a “singleton” token), hence there is a maximum of one unspent token box at any time. This property is guaranteed when we issue the token with
amount == 1
. - When the token box is spent, it must have
box.creationHeight < HEIGHT
andnewBox.creationHeight == HEIGHT
. This guarantees that only one perpetual token box can be created per block, sincenewBox
can only be spent in a future block.
Since the pool reward transaction is forced to spend this token, then by extension, there can only be one pool reward transaction per block.
Note that there is currently no restriction on where the pool might send its reward, or indeed which pool might spend this reward transaction.
Final notes:
- This transaction will have zero mining fees, since it will be mined by the pool anyway.
- The token box must contain the minimum value required. If miners modify the minimum value for a box by voting, then the pool must contribute a small amount of ERG to the new token box to achieve the correct minimum value.