Last active
October 17, 2021 12:23
-
-
Save jcnelson/bb67cdd6b3c6ea0f5f447b33dee0425e to your computer and use it in GitHub Desktop.
appchain.clar
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;; 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