Skip to content

Instantly share code, notes, and snippets.

@jcnelson
Last active October 17, 2021 12:23
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save jcnelson/bb67cdd6b3c6ea0f5f447b33dee0425e to your computer and use it in GitHub Desktop.
Save jcnelson/bb67cdd6b3c6ea0f5f447b33dee0425e to your computer and use it in GitHub Desktop.
appchain.clar
;; Appchain MVP mining contract! Appchain peers scan the host chain (the chain that stores this contract and its state)
;; to discover one another and the appchain block metadata. From there, the appchain's miners proceed to work on their
;; chain by sending contract-call's to this contract to store more block metadata.
;;
;; This contract essentially mimics the way Bitcoin behaves towards Stacks -- it's just a dumb data storage contract that
;; holds onto the same data that would go into Bitcoin transactions for mining. The only difference is that the appchain
;; uses STX as its underlying base currency for mining and PoX, instead of BTC.
(define-constant ERR_NO_RECIPIENTS u0)
(define-constant ERR_NO_COMMIT_SPEND u1)
(define-constant ERR_INSUFFICIENT_BALANCE u2)
(define-constant ERR_BLOCK_FULL u3)
;; Schema version. Required by the appchain client.
(define-data-var appchain-version uint u1)
;; List of app chain block ops, grouped by host chain block
(define-map appchain
;; host chain chain block height
uint
;; list of burnchain operations at that height
(list 128 {
;; miner
sender: principal,
;; is this operation chained to the last one? Only applies to block-commits
chained?: bool,
;; burnchain op payload (serialized)
data: (buff 80),
;; amount of parent tokens destroyed
burnt: uint,
;; total amount of tokens transferred
transferred: uint,
;; PoX recipients on parent chain
recipients: (list 2 principal)
})
)
;; Appchain configuration variable
(define-data-var appchain-config
{
;; 32-bit unique chain identifier (goes into the chain's transactions)
chain-id: uint,
;; height on the host chain at which the appchain's blocks start
start-height: uint,
;; list of boot nodes' public keys, p2p addresses, and rpc addresses
boot-nodes: (list 16 { public-key: (buff 33), host: (buff 16), port: (buff 2), data-host: (buff 16), data-port: (buff 2) }),
;; PoX config for the appchain
pox: {
reward-cycle-length: uint,
prepare-length: uint,
anchor-threshold: uint,
pox-rejection-fraction: uint,
pox-participation-threshold-pct: uint,
sunset-start: uint,
sunset-end: uint
},
;; Block limit for the app chain
block-limit: {
write-length: uint,
write-count: uint,
read-length: uint,
read-count: uint,
runtime: uint
},
;; List of contract names that will execute as part of the appchain boot code.
boot-code: (list 128 (string-ascii 128)),
;; List of initial balances to be allocated in the appchain genesis block
initial-balances: (list 128 { recipient: principal, amount: uint })
}
{
;; chain ID: 0x80000002
chain-id: u2147483650,
;; mining starts now -- as soon as the transaction containing this contract is mined
start-height: block-height,
;; one initial boot node.
;; In practice, this contract would have a function for updating this list.
boot-nodes: (list
{
public-key: 0x025f2a3e20527805a5bc57539b4a127764738510480bc5545458d4a3d1375ba135,
;; 44.199.104.134:14300
host: 0x00000000000000000000ffff2cc76886,
port: 0x37dc,
;; 44.199.104.134:14301
data-host: 0x00000000000000000000ffff2cc76886,
data-port: 0x37dd
}
),
;; PoX config
pox: {
;; 100-block reward cycle
reward-cycle-length: u100,
;; 20-block prepare phase
prepare-length: u20,
;; 16 out of 20 prepare phase blocks must confirm an anchor block
anchor-threshold: u16,
;; at least 25% of all liquid appchain tokens must vote to turn off PoX per reward cycle
pox-rejection-fraction: u25,
;; at least 5% of all liquid appchain tokens must stack in order for PoX to activate in a reward cycle
pox-participation-threshold-pct: u5,
;; basically never sunset -- use the largest allowed values by the node
sunset-start: u18446744073709551615,
sunset-end: u18446744073709551615
},
;; Block size -- 2x what Stacks mainnet is
block-limit: {
write-length: u30000000,
write-count: u15500,
read-length: u200000000,
read-count: u15500,
runtime: u10000000000
},
;; One custom boot code contract that prints "hello appchains!" (just to prove that this feature works)
boot-code: (list
"hello-world"
),
;; Initial appchain tokens -- allocate a faucet
initial-balances: (list
{
recipient: 'ST310DN3W42RNTYDQBVFYPA1ZBE40ENNG49ZM58X1,
amount: u1000000000000
}
)
}
)
;; When did a miner last mine? Used to determine whether or not a miner has been mining frequently enough
;; that the sortition weight penalty for *not* doing so does not apply.
(define-map last-mined-heights
;; sender
principal
;; height at which a block-commit was last sent
uint
)
;; Private method to process most host chain operations (everything but block-commits)
(define-private (add-nonmining-block-op (payload (buff 80)) (recipients (list 2 principal)))
(let (
(op-list (default-to (list ) (map-get? appchain block-height)))
)
(asserts! (< (len op-list) u128)
(err ERR_BLOCK_FULL))
(map-set appchain block-height
(unwrap-panic
(as-max-len? (append op-list {
sender: tx-sender,
chained?: true,
data: payload,
burnt: u0,
transferred: u0,
recipients: recipients
})
u128)
)
)
(ok true)
)
)
;; Register a VRF public key. The payload key-op is identical to the VRF public key
;; registration that would go into a Bitcoin OP_RETURN.
(define-public (register-vrf-key (key-op (buff 80)))
(add-nonmining-block-op key-op (list ))
)
;; Private method to pay PoX recipients in STX.
(define-private (send-to-recipient (recipient principal) (amount uint))
(begin
(unwrap-panic
(if (not (is-eq tx-sender recipient))
(stx-transfer? amount tx-sender recipient)
(ok true)
)
)
amount
)
)
;; Mine a block -- register a block-commit and pay out to the PoX recipients.
;; The block-op buff is identical to the block-commit data that goes into a Bitcoin OP_RETURN.
;; The to-burn value is the PoX sunset burn to be burnt.
;; The recipients list is the list of PoX payouts, who will receive uSTX.
;; The recipient-amount is the amount of uSTX to be sent to each recipient.
(define-public (mine-block (block-op (buff 80)) (to-burn uint) (recipients (list 2 principal)) (recipient-amount uint))
(let (
(op-list (default-to (list ) (map-get? appchain block-height)))
;; pessimistic take: consider block-commits chained only if the miner mined in the last two blocks
(chained? (<= block-height (+ u2 (default-to u0 (map-get? last-mined-heights tx-sender)))))
)
(asserts! (> (len recipients) u0)
(err ERR_NO_RECIPIENTS))
(asserts! (> recipient-amount u0)
(err ERR_NO_COMMIT_SPEND))
(asserts! (>= (stx-get-balance tx-sender) (+ to-burn (* (len recipients) recipient-amount)))
(err ERR_INSUFFICIENT_BALANCE))
(asserts! (< (len op-list) u128)
(err ERR_BLOCK_FULL))
;; everything from here on out should just work
(if (> to-burn u0)
(unwrap-panic (stx-burn? to-burn tx-sender))
true
)
(fold send-to-recipient recipients recipient-amount)
(map-set appchain block-height
(unwrap-panic
(as-max-len? (append op-list {
sender: tx-sender,
chained?: chained?,
data: block-op,
burnt: to-burn,
transferred: (* (len recipients) recipient-amount),
recipients: recipients
})
u128)
)
)
(map-set last-mined-heights tx-sender block-height)
(ok true)
)
)
;; Register a PreSTX operation in order to carry out the appchain-equivalent STX-transfer or Stack-STX operation on the host chain.
;; The payload argument is identical to the payload that would be sent in a Bitcoin OP_RETURN for PreSTX
(define-public (prestx (payload (buff 80)))
(add-nonmining-block-op payload (list ))
)
;; Stack tokens on the appchain, bypassing the appchain miners.
;; The stack-pyaload argument is identical to the StackStx Bitcoin OP_RETURN.
;; The pox-addr argument is the address to which uSTX payouts will be sent.
(define-public (stack-appchain-stx (stack-payload (buff 80)) (pox-addr principal))
(add-nonmining-block-op stack-payload (list pox-addr))
)
;; Transfer tokens on the appchain, bypassing the appchain miners.
;; The transfer-payload argument is identical to the TransferStx Bitcoin OP_RETURN.
;; The recipient argument is the address on the appchain that will receive the tokens.
(define-public (transfer-appchain-stx (transfer-payload (buff 80)) (recipient principal))
(add-nonmining-block-op transfer-payload (list recipient))
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment