Skip to content

Instantly share code, notes, and snippets.

@jcnelson
Last active February 23, 2021 05:46
Show Gist options
  • Save jcnelson/9a527f8b98ea9acb3b9aa7ae0e930617 to your computer and use it in GitHub Desktop.
Save jcnelson/9a527f8b98ea9acb3b9aa7ae0e930617 to your computer and use it in GitHub Desktop.
Decentralized Stacking Delegation

Problem

For various technical reasons, a given PoX reward cycle can have only so many reward addresses registered. This in turn is what necessitates a high minimum Stacking threshold. The design of the PoX smart contract (which controls this) works around this limitation by providing a way to delegate one's STX to a Stacking delegate, who in turn will Stack all of its clients' STX to clinch reward addresses that the clients could not have obtained individually. The implementation of a Stacking delegate is left unspecified.

This document sketches an implementation that allows anyone to be a Stacking delegate, such that the most economically advantageous action for the delegate is to honestly fulfill its role by gathering and Stacking its clients' STX, collecting the BTC sent to its PoX reward addresses, and disbursing the Bitcoin it receives.

Background

Broadly speaking, delegated Stacking works as follows:

  1. The client sends a delegate-stx transaction, indicating a particular Stacking delegate's principal ID, an allowance of STX that may be locked, a maximum Bitcoin block height at which the delegate's stewardship expires, and (optionally) a Bitcoin address into which any PoX payouts must be sent. If no Bitcoin address is specified, then the delegate may choose it.
  2. For each client, the Stacking delegate calls delegate-stack-stx, which locks up some or all of the client's allowance of STX.
  3. For each PoX reward address under its stewardship, the Stacking delegate calls stack-aggregation-commit. This registers the PoX reward address and the aggregated locked STX with the system, so it will be eligible for Bitcoin payouts over the specified reward cycle(s).

A Stacking delegate may be a principal or a contract, but it must be authorized by the STX holder. Similarly, a client can be a principal or a contract.

This document first sketches a procedure for matching clients to delegates in a decentralized manner, such that delegates are incentivized to faithfully execute their duties in a timely fashion. It then sketches a procedure that, if activated through a soft fork (or Stacks 2.1), will further allow clients to find trustworthy delegates that will gather and disburse BTC on their behalf.

Collateral-backed Delegates

In order to Stack via a delegate, a client must have some reason to trust that the delegate will faithfully fulfill at least these three steps. Achieving this could be handled by an intermediate contract called the delegate registry.

To become a delegate, a principal or contract would register itself in the delegate registry, and commit some STX as collateral to prove that it is trustworthy. If they do not execute their duties, then their clients can take some of their collateral as compensation.

This contract holds the collateral STX in escrow, and ensures that delegates cannot take on more clients than they can afford to fail. Delegates can leave with their collateral in hand once they have no ongoing clients.

Delegates make money by charging a fee for their services in STX. The fee that they will require for their services is listed in the registry contract as well, so clients know in advance how much they'll pay for the delegate's services.

Clients would be able to iterate through the list of active delegates and see how much STX they have each put up as collateral, and what kind of fees they charge. They could also gauge how reliable each delegate is by looking at how many previous clients it has helped out successfully, and how many times it has failed.

This contract would provied API wrappers around delegate-stx, delegate-stack-stx, and stacks-aggregate-commit in order to (1) make sure clients put up their fees, (2) make sure delegates put up their collateral, (3) authorize delegates to claim their fees if they successfully carry out their delegation, and (4) enable clients to steal part of the delegate's collateral if the delegate fails to deliver.

Execution Flow

This is likely missing a lot of subtle details, but I'm sure they could be worked out. Think of this as a first approximation of how this could work.

  1. Delegates register themselves with the delegate registry. The put up their collateral, list their fees and Stacking time-tables, and they call allow-contract-caller to authorize the contract to call PoX delegation functions on its behalf.
  2. The client calls allow-contract-caller to permit the delegate registry to call PoX delegation functions on its behalf.
  3. When a client has chosen a delegate, it will call into the delegate registry to:
    1. Verify that the delegate is still active and is taking clients (and can afford to take this client on).
    2. Lock up its STX fee for the chosen delegate, to be held in escrow until the end of the reward cycle(s)
    3. Set the block height deadline by which the delegate must complete its part of the delegation process (note that the contract will calculate this deadline based on the delegate's advertised time-tables, which the client has accepted already as part of selecting this delegate).
    4. Call delegate-stx to permit their chosen delegate to Stack for them.
  4. If the delegate wants to take on this client, it will call into the delegate registry to do so. The contract will:
    1. Ensure that the delegate has enough collateral to pay this client if it fails to fulfill its duties.
    2. Call delegate-stack-stx for the delegate, thereby locking up the client's STX.
    3. Lock the requisite portion of its collateral, should it fail to complete the delegation duties (this ensures a delegate can't take on more clients than it can afford to pay out to).
    4. Record the block height at which the delegate has locked the client's STX.
  5. When the delegate is ready to commit its clients' STX, it will call into the delegate registry to:
    1. Ensure that step (4) completed above before the deadline.
    2. Call stack-aggregation-commit for its clients' STX.
    3. If done on time, record the block height at which the delegate has committed its clients' STX. Otherwise, make a portion of the STX collateral claimable by the client for failure to carry out its delegation duties.
  6. When the client's reward cycles complete, the delegate can call into the contract to claim its fees. The contract will:
    1. Ensure that the delegate completed its delegation duties on-time.
    2. Disburse the STX fees from its client.
  7. If a delegate missed the deadline, then the contract retains a portion of the delegate's collateral, which the client can claim at any time. The client will also get its fee back.
  8. If the delegate wishes to leave the contract, it does the following:
    1. It unlists itself from the registry. Future clients will be unable to select it in step 3.
    2. Once all ongoing client reward cycles have finished, the delegate can exit the contract with its remaining collateral and any due fees.

Delegates can pick the clients they want in step (4) above -- a client is not guaranteed to receive a delegate. But, the act of taking on a client in step (4) commits the delegate to either seeing the delegation process through to completion (and getting a service fee), or losing part of their collateral. If no delegate takes a client's offer in the allotted time, then the client can exit with their STX fees returned to them.

Remarks

  • A client or delegate can pay or receive payment in an alternative token besides STX. Using STX is just an example here.
  • A delegate can pay out to a client an amount of STX (or equivalent) proportional to how much BTC a client expects to miss out on. This is all negotiable -- as part of a client's request for delegate, it can both propose a fee to its delegate as well as a collateral payment penalty. The negotiation itself can happen off-chain; the term parameters only need to be written into the smart contract beforehand.
  • This procedure does not describe how the BTC gets disbursed. If the client chooses the BTC address, then the delegate is not responsible for disbursal. But if the client does not choose the BTC address, the delegate would be responsible for disbursal. Doing this in a trust-minimized manner is discussed below.

Delegates as Disbursers

It is possible to make it so that a delegate's most profitable course of action is to collect and disburse its clients' BTC back to the clients. However, doing this in a machine-verifiable way is currently not supported because Clarity does not have access to how much BTC miners paid to each reward address. But, this could be added through a soft fork -- there could be a designated Clarity contract that makes this information available, and Stacks miners could be required through a soft fork's new consensus rules to record this information to this smart contract. This would likely be coupled by a cost-vote to make doing this extra work "free" (i.e. not counted towards the block's compute budget). This functionality could also be added directly to the Clarity VM in the Stacks 2.1 network upgrade.

Supposing that there existed a contract or Clarity built-in to determine how much BTC has been sent to a PoX reward address, it would be possible to write a Clarity function parse-delegate-payout that could do the following:

  • Take a serialized Bitcoin transaction as input (e.g. as a {tx: (buff 512), len: uint} or something), as well as a Merkle proof (e.g. the block header and intermediate Merkle tree hashes) that showed that this transaction was mined in the given block.
  • Verify that the Merkle proof does indeed link the transaction to the Bitcoin chain within that block
  • Parse the transaction to extract an OP_RETURN memo field and the first scriptPubKey and its value
  • Return the memo field, the scriptPubKey, and the BTC value. If the transaction doesn't contain a memo field or can't be parsed for whatever reason, return an error.

Using this Clarity function, we can modify step 6 above to do the following:

  1. When the clients' reward cycles complete, the delegate can call into the contract to claim its fees. The contract will:
    1. Ensure that the delegate completed its delegation duties on-time.
    2. Require the delegate to post the transaction they sent to the client to disburse their BTC
    3. Verify that the delegate's transaction does indeed pay the client the required amount of BTC
    4. Verify that the delegate's transaction has indeed been mined before the disbursal deadline
    5. Verify that the delegate's transaction wasn't a duplicate of some other transaction sent to the client by verifying that the transaction's memo field contained an agreed-upon serial number that the client expects to see (the client would specify this in step 3, as part of choosing a delegate).
    6. Disburse the client's STX fee

This way, the delegate can only get its STX fee if it actually pays out the BTC. Otherwise, the client will be able to claim part of the delegate's collateral as compensation (and of course get its fee back).

Limitations

Trying to make it so delegates can disburse BTC is risky if the price of BTC suddenly swings wildly upward relative to the cost of STX. It might become more profitable for the delegate to forefeit its collateral and keep the BTC.

This approach necessarily requires each client to send at least one transaction to authorize the delegate registry contract to call the Stacking functions on its behalf, and at least one transaction to select a delegate. This could get expensive if transaction fees go up. Stacking via an exchange in traunches is likely to be cheaper for small-scale Stackers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment