diff --git a/docs/rpc-endpoints.md b/docs/rpc-endpoints.md index 585084d4f..01d2c83e3 100644 --- a/docs/rpc-endpoints.md +++ b/docs/rpc-endpoints.md @@ -75,69 +75,7 @@ Reason types without additional information will not have a ### GET /v2/pox -Get current PoX-relevant information. - -Returns JSON data in the form: - -``` -{ - "contract_id": "SP000000000000000000002Q6VF78.pox", - "first_burnchain_block_height": 666050, - "next_cycle_cur_threshold": 70000000000, - "cur_cycle_threshold": 70000000000, - "cur_cycle_stacked_ustx": 202157971547640, - "next_cycle_stacked_ustx": 151807880982060, - "reward_slots": 4000, - "next_rewards_start": 672350, - "next_prepare_phase_start": 672250, - "min_stacking_increment_ustx": 52251700044, - "pox_activation_threshold": 52251700044388, - "prepare_cycle_length": 100, - "rejection_fraction": 25, - "reward_cycle_id": 2, - "reward_cycle_length": 2100, - "rejection_votes_left_required": 261258500221925, - "total_liquid_supply_ustx": 1045034000887772, - "next_reward_cycle_in": 1318, - "next_prepare_phase_in": 1218 -} -``` - -* `contract_id` is the contract identifier for the PoX contract. -* `cur_cycle_threshold` is the threshold amount for obtaining a slot in the - active reward cycle. -* `next_cycle_cur_threshold` is the _current_ microstacks threshold for the - _next_ reward cycle. If more STX is locked for participation in that cycle, - the threshold may increase. -* `first_burnchain_block_height` is the first burn block evaluated in this Stacks - chain. -* `cur_cycle_stacked_ustx` is the total amount of stacked microstacks in the - active reward cycle. -* `next_cycle_stacked_ustx` is the total amount of stacked microstacks in the - next reward cycle. -* `reward_slots` is the number of reward slots in a reward cycle -* `next_rewards_start` is the burn block height when the next reward - cycle begins -* `next_prepare_phase_start` is the burn block height when the next prepare - phase begins. Any eligible stacks _must_ be stacked before this block. -* `min_stacking_increment_ustx` is the minimum amount that can be used to - submit a `stack-stx` call. -* `pox_activation_threshold` is the threshold of stacking participation that - must be reached for PoX to activate in any cycle. -* `prepare_cycle_length` is the length in burn blocks of the prepare phase -* `rejection_fraction` is the fraction of liquid STX that must vote to reject - PoX in order to prevent the next reward cycle from activating. -* `rejection_votes_left_required` is the remaining amount of liquid - STX that must vote to reject the next reward cycle to prevent the next - reward cycle from activating. -* `reward_cycle_id` is the active reward cycle number -* `reward_cycle_length` is the length in burn blocks of a whole PoX - cycle (reward phase and prepare phase) -* `total_liquid_supply_ustx` is the current total amount of liquid microstacks. -* `next_reward_cycle_in` is the number of burn blocks until the next reward - cycle begins. -* `next_prepare_phase_in` is the number of burn blocks until the next prepare - phase starts. +Get current PoX-relevant information. See OpenAPI [spec](./rpc/openapi.yaml) for details. ### GET /v2/accounts/[Principal] diff --git a/docs/rpc/api/contract/post-call-read-only-fn-fail.example.json b/docs/rpc/api/contract/post-call-read-only-fn-fail.example.json new file mode 100644 index 000000000..5680a225c --- /dev/null +++ b/docs/rpc/api/contract/post-call-read-only-fn-fail.example.json @@ -0,0 +1,4 @@ +{ + "okay": false, + "cause": "Unchecked(PublicFunctionNotReadOnly(..." +} diff --git a/docs/rpc/api/contract/post-call-read-only-fn-success.example.json b/docs/rpc/api/contract/post-call-read-only-fn-success.example.json new file mode 100644 index 000000000..cc94dccd1 --- /dev/null +++ b/docs/rpc/api/contract/post-call-read-only-fn-success.example.json @@ -0,0 +1,4 @@ +{ + "okay": true, + "result": "0x111..." +} diff --git a/docs/rpc/api/contract/post-call-read-only-fn.schema.json b/docs/rpc/api/contract/post-call-read-only-fn.schema.json new file mode 100644 index 000000000..652868168 --- /dev/null +++ b/docs/rpc/api/contract/post-call-read-only-fn.schema.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "GET request to get contract source", + "title": "ReadOnlyFunctionSuccessResponse", + "type": "object", + "additionalProperties": false, + "required": ["okay"], + "properties": { + "okay": { + "type": "boolean" + }, + "result": { + "type": "string" + }, + "cause": { + "type": "string" + } + } +} diff --git a/docs/rpc/api/core-node/get-account-data.example.json b/docs/rpc/api/core-node/get-account-data.example.json new file mode 100644 index 000000000..f4cececc9 --- /dev/null +++ b/docs/rpc/api/core-node/get-account-data.example.json @@ -0,0 +1,8 @@ +{ + "balance": "0x0000000000000000000000000007a120", + "locked": "0x0000000000000000000000000007a120", + "unlock_height": 126, + "nonce": 2867, + "balance_proof": "0xabce", + "nonce_proof": "0xabcd" +} diff --git a/docs/rpc/api/core-node/get-account-data.schema.json b/docs/rpc/api/core-node/get-account-data.schema.json new file mode 100644 index 000000000..adddb540c --- /dev/null +++ b/docs/rpc/api/core-node/get-account-data.schema.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "GET request for account data", + "title": "AccountDataResponse", + "type": "object", + "additionalProperties": false, + "required": ["balance", "locked", "unlock_height", "nonce", "balance_proof", "nonce_proof"], + "properties": { + "balance": { + "type": "string" + }, + "locked": { + "type": "string" + }, + "unlock_height": { + "type": "integer" + }, + "nonce": { + "type": "integer" + }, + "balance_proof": { + "type": "string" + }, + "nonce_proof": { + "type": "string" + } + } +} diff --git a/docs/rpc/api/core-node/get-contract-data-map-entry.example.json b/docs/rpc/api/core-node/get-contract-data-map-entry.example.json new file mode 100644 index 000000000..d0e233416 --- /dev/null +++ b/docs/rpc/api/core-node/get-contract-data-map-entry.example.json @@ -0,0 +1,4 @@ +{ + "data": "0x0a0c000000010a6d6f6e737465722d69640100000000000000000000000000000001", + "proof": "0x123..." +} diff --git a/docs/rpc/api/core-node/get-contract-data-map-entry.schema.json b/docs/rpc/api/core-node/get-contract-data-map-entry.schema.json new file mode 100644 index 000000000..04c945eb4 --- /dev/null +++ b/docs/rpc/api/core-node/get-contract-data-map-entry.schema.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Response of get data map entry request", + "title": "MapEntryResponse", + "type": "object", + "required": ["data"], + "properties": { + "data": { + "type": "string", + "description": "Hex-encoded string of clarity value. It is always an optional tuple." + }, + "proof": { + "type": "string", + "description": "Hex-encoded string of the MARF proof for the data" + } + } +} diff --git a/docs/rpc/api/core-node/get-contract-interface.example.json b/docs/rpc/api/core-node/get-contract-interface.example.json new file mode 100644 index 000000000..0e7c12bb7 --- /dev/null +++ b/docs/rpc/api/core-node/get-contract-interface.example.json @@ -0,0 +1,134 @@ +{ + "functions": [ + { + "name": "get-value", + "access": "public", + "args": [ + { + "name": "key", + "type": { + "buffer": { + "length": 32 + } + } + } + ], + "outputs": { + "type": { + "response": { + "ok": { + "buffer": { + "length": 32 + } + }, + "error": "int128" + } + } + } + }, + { + "name": "set-value", + "access": "public", + "args": [ + { + "name": "key", + "type": { + "buffer": { + "length": 32 + } + } + }, + { + "name": "value", + "type": { + "buffer": { + "length": 32 + } + } + } + ], + "outputs": { + "type": { + "response": { + "ok": "uint128", + "error": "none" + } + } + } + }, + { + "name": "test-emit-event", + "access": "public", + "args": [], + "outputs": { + "type": { + "response": { + "ok": "uint128", + "error": "none" + } + } + } + }, + { + "name": "test-event-types", + "access": "public", + "args": [], + "outputs": { + "type": { + "response": { + "ok": "uint128", + "error": "none" + } + } + } + } + ], + "variables": [ + { + "name": "recipient", + "type": "principal", + "access": "constant" + }, + { + "name": "sender", + "type": "principal", + "access": "constant" + } + ], + "maps": [ + { + "name": "store", + "key": [ + { + "name": "key", + "type": { + "buffer": { + "length": 32 + } + } + } + ], + "value": [ + { + "name": "value", + "type": { + "buffer": { + "length": 32 + } + } + } + ] + } + ], + "fungible_tokens": [ + { + "name": "novel-token-19" + } + ], + "non_fungible_tokens": [ + { + "name": "hello-nft", + "type": "uint128" + } + ] +} diff --git a/docs/rpc/api/core-node/get-contract-interface.schema.json b/docs/rpc/api/core-node/get-contract-interface.schema.json new file mode 100644 index 000000000..b2c580f65 --- /dev/null +++ b/docs/rpc/api/core-node/get-contract-interface.schema.json @@ -0,0 +1,44 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "GET request to get contract interface", + "title": "ContractInterfaceResponse", + "type": "object", + "required": ["functions", "variables", "maps", "fungible_tokens", "non_fungible_tokens"], + "properties": { + "functions": { + "type": "array", + "items": { + "type": "object" + }, + "description": "List of defined methods" + }, + "variables": { + "type": "array", + "items": { + "type": "object" + }, + "description": "List of defined variables" + }, + "maps": { + "type": "array", + "items": { + "type": "object" + }, + "description": "List of defined data-maps" + }, + "fungible_tokens": { + "type": "array", + "items": { + "type": "object" + }, + "description": "List of fungible tokens in the contract" + }, + "non_fungible_tokens": { + "type": "array", + "items": { + "type": "object" + }, + "description": "List of non-fungible tokens in the contract" + } + } +} diff --git a/docs/rpc/api/core-node/get-contract-source.example.json b/docs/rpc/api/core-node/get-contract-source.example.json new file mode 100644 index 000000000..b770b5005 --- /dev/null +++ b/docs/rpc/api/core-node/get-contract-source.example.json @@ -0,0 +1,5 @@ +{ + "source": "(define-constant sender 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR)\n(define-constant recipient 'SM2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQVX8X0G)\n\n(define-fungible-token novel-token-19)\n(begin (ft-mint? novel-token-19 u12 sender))\n(begin (ft-transfer? novel-token-19 u2 sender recipient))\n\n(define-non-fungible-token hello-nft uint)\n(begin (nft-mint? hello-nft u1 sender))\n(begin (nft-mint? hello-nft u2 sender))\n(begin (nft-transfer? hello-nft u1 sender recipient))\n\n(define-public (test-emit-event)\n (begin\n (print \"Event! Hello world\")\n (ok u1)))\n(begin (test-emit-event))\n\n(define-public (test-event-types)\n (begin\n (unwrap-panic (ft-mint? novel-token-19 u3 recipient))\n (unwrap-panic (nft-mint? hello-nft u2 recipient))\n (unwrap-panic (stx-transfer? u60 tx-sender 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR))\n (unwrap-panic (stx-burn? u20 tx-sender))\n (ok u1)))\n\n(define-map store ((key (buff 32))) ((value (buff 32))))\n(define-public (get-value (key (buff 32)))\n (begin\n (match (map-get? store ((key key)))\n entry (ok (get value entry))\n (err 0))))\n(define-public (set-value (key (buff 32)) (value (buff 32)))\n (begin\n (map-set store ((key key)) ((value value)))\n (ok u1)))", + "publish_height": 3196, + "proof": "0000001104060000001ec4e..." +} diff --git a/docs/rpc/api/core-node/get-contract-source.schema.json b/docs/rpc/api/core-node/get-contract-source.schema.json new file mode 100644 index 000000000..fe09af4bf --- /dev/null +++ b/docs/rpc/api/core-node/get-contract-source.schema.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "GET request to get contract source", + "title": "ContractSourceResponse", + "type": "object", + "additionalProperties": false, + "required": ["source", "publish_height", "proof"], + "properties": { + "source": { + "type": "string" + }, + "publish_height": { + "type": "integer" + }, + "proof": { + "type": "string" + } + } +} diff --git a/docs/rpc/api/core-node/get-fee-transfer.example.json b/docs/rpc/api/core-node/get-fee-transfer.example.json new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/docs/rpc/api/core-node/get-fee-transfer.example.json @@ -0,0 +1 @@ +1 diff --git a/docs/rpc/api/core-node/get-fee-transfer.schema.json b/docs/rpc/api/core-node/get-fee-transfer.schema.json new file mode 100644 index 000000000..24d9b9876 --- /dev/null +++ b/docs/rpc/api/core-node/get-fee-transfer.schema.json @@ -0,0 +1,7 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "GET fee estimates", + "title": "CoreNodeFeeResponse", + "type": "string", + "additionalProperties": false +} diff --git a/docs/rpc/api/core-node/get-info.example.json b/docs/rpc/api/core-node/get-info.example.json new file mode 100644 index 000000000..19bb6d20f --- /dev/null +++ b/docs/rpc/api/core-node/get-info.example.json @@ -0,0 +1,15 @@ +{ + "peer_version": 385875968, + "pox_consensus": "17f76e597bab45646956f38dd39573085d72cbc0", + "burn_block_height": 16, + "stable_pox_consensus": "8e0561978fc5506b68a589c402dad97e862edb59", + "stable_burn_block_height": 15, + "server_version": "blockstack-core 0.0.1 => 23.0.0.0 (, release build, linux [x86_64])", + "network_id": 2147483648, + "parent_network_id": 3669344250, + "stacks_tip_height": 15, + "stacks_tip": "b1807a2d3f7f8c7922f7c1d60d7c34145ade05d789640dc7dc9ec1021e07bb54", + "stacks_tip_consensus_hash": "17f76e597bab45646956f38dd39573085d72cbc0", + "unanchored_tip": "0000000000000000000000000000000000000000000000000000000000000000", + "exit_at_block_height": null +} diff --git a/docs/rpc/api/core-node/get-info.schema.json b/docs/rpc/api/core-node/get-info.schema.json new file mode 100644 index 000000000..f37cd0893 --- /dev/null +++ b/docs/rpc/api/core-node/get-info.schema.json @@ -0,0 +1,76 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "GET request that core node information", + "title": "CoreNodeInfoResponse", + "type": "object", + "additionalProperties": false, + "required": [ + "peer_version", + "pox_consensus", + "burn_block_height", + "stable_pox_consensus", + "stable_burn_block_height", + "server_version", + "network_id", + "parent_network_id", + "stacks_tip_height", + "stacks_tip", + "stacks_tip_consensus_hash", + "unanchored_tip", + "exit_at_block_height" + ], + "properties": { + "peer_version": { + "type": "integer", + "description": "identifies the version number for the networking communication, this should not change while a node is running, and will only change if there's an upgrade" + }, + "pox_consensus": { + "type": "string", + "description": "is a hash used to identify the burnchain view for a node. it incorporates bitcoin chain information and PoX information. nodes that disagree on this value will appear to each other as forks. this value will change after every block" + }, + "burn_block_height": { + "type": "integer", + "description": "latest bitcoin chain height" + }, + "stable_pox_consensus": { + "type": "string", + "description": "same as burn_consensus, but evaluated at stable_burn_block_height" + }, + "stable_burn_block_height": { + "type": "integer", + "description": "leftover from stacks 1.0, basically always burn_block_height - 1" + }, + "server_version": { + "type": "string", + "description": "is a version descriptor" + }, + "network_id": { + "type": "integer", + "description": "is similar to peer_version and will be used to differentiate between different testnets. this value will be different between mainnet and testnet. once launched, this value will not change" + }, + "parent_network_id": { + "type": "integer", + "description": "same as network_id, but for bitcoin" + }, + "stacks_tip_height": { + "type": "integer", + "description": "the latest Stacks chain height. Stacks forks can occur independent of the Bitcoin chain, that height doesn't increase 1-to-1 with the Bitcoin height" + }, + "stacks_tip": { + "type": "string", + "description": "the best known block hash for the Stack chain (not including any pending microblocks)" + }, + "stacks_tip_consensus_hash": { + "type": "string", + "description": "the burn chain (i.e., bitcoin) consensus hash at the time that stacks_tip was mined" + }, + "unanchored_tip": { + "type": "string", + "description": "the latest microblock hash if any microblocks were processed. if no microblock has been processed for the current block, a 000.., hex array is returned" + }, + "exit_at_block_height": { + "type": "integer", + "description": "the block height at which the testnet network will be reset. not applicable for mainnet" + } + } +} diff --git a/docs/rpc/api/core-node/get-pox.example.json b/docs/rpc/api/core-node/get-pox.example.json new file mode 100644 index 000000000..6be67ccec --- /dev/null +++ b/docs/rpc/api/core-node/get-pox.example.json @@ -0,0 +1,33 @@ +{ + "contract_id": "SP000000000000000000002Q6VF78.pox", + "pox_activation_threshold_ustx": 52329761604388, + "first_burnchain_block_height": 666050, + "prepare_phase_block_length": 100, + "reward_phase_block_length": 2000, + "reward_slots": 4000, + "rejection_fraction": 25, + "total_liquid_supply_ustx": 1046595232087772, + "current_cycle": { + "id": 2, + "min_threshold_ustx": 70000000000, + "stacked_ustx": 202157971547640, + "is_pox_active": true + }, + "next_cycle": { + "id": 3, + "min_threshold_ustx": 70000000000, + "min_increment_ustx": 52329761604, + "stacked_ustx": 162057034977640, + "prepare_phase_start_block_height": 672250, + "blocks_until_prepare_phase": 407, + "reward_phase_start_block_height": 672350, + "blocks_until_reward_phase": 507, + "ustx_until_pox_rejection": 261648808021925 + }, + "min_amount_ustx": 70000000000, + "prepare_cycle_length": 100, + "reward_cycle_id": 2, + "reward_cycle_length": 2100, + "rejection_votes_left_required": 261648808021925, + "next_reward_cycle_in": 507 +} diff --git a/docs/rpc/api/core-node/get-pox.schema.json b/docs/rpc/api/core-node/get-pox.schema.json new file mode 100644 index 000000000..a4856f732 --- /dev/null +++ b/docs/rpc/api/core-node/get-pox.schema.json @@ -0,0 +1,160 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Get Proof of Transfer (PoX) information", + "title": "CoreNodePoxResponse", + "type": "object", + "additionalProperties": false, + "required": [ + "contract_id", + "first_burnchain_block_height", + "pox_activation_threshold_ustx", + "prepare_phase_block_length", + "reward_phase_block_length", + "reward_slots", + "rejection_fraction", + "total_liquid_supply_ustx", + "current_cycle", + "next_cycle", + "reward_cycle_length", + "min_amount_ustx", + "reward_cycle_id", + "prepare_cycle_length", + "rejection_votes_left_required" + ], + "properties": { + "contract_id": { + "type": "string", + "description": "The contract identifier for the PoX contract" + }, + "first_burnchain_block_height": { + "type": "integer", + "description": "The first burn block evaluated in this Stacks chain" + }, + "pox_activation_threshold_ustx": { + "type": "integer", + "description": "The threshold of stacking participation that must be reached for PoX to activate in any cycle" + }, + "rejection_fraction": { + "type": "integer", + "description": "The fraction of liquid STX that must vote to reject PoX in order to prevent the next reward cycle from activating." + }, + "reward_phase_block_length": { + "type": "integer", + "description": "The length in burn blocks of the reward phase" + }, + "prepare_phase_block_length": { + "type": "integer", + "description": "The length in burn blocks of the prepare phase" + }, + "reward_slots": { + "type": "integer", + "description": "The number of reward slots in a reward cycle" + }, + "total_liquid_supply_ustx": { + "type": "integer", + "description": "The current total amount of liquid microstacks." + }, + "reward_cycle_length": { + "type": "integer", + "description": "The length in burn blocks of a whole PoX cycle (reward phase and prepare phase)" + }, + "current_cycle": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "min_threshold_ustx", + "stacked_ustx", + "is_pox_active" + ], + "properties": { + "id": { + "type": "integer", + "description": "The reward cycle number" + }, + "min_threshold_ustx": { + "type": "integer", + "description": "The threshold amount for obtaining a slot in this reward cycle." + }, + "stacked_ustx": { + "type": "integer", + "description": "The total amount of stacked microstacks in this reward cycle." + }, + "is_pox_active": { + "type": "boolean", + "description": "Whether or not PoX is active during this reward cycle." + }, + } + }, + "next_cycle": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "min_threshold_ustx", + "stacked_ustx", + "min_increment_ustx", + "prepare_phase_start_block_height", + "blocks_until_prepare_phase", + "reward_phase_start_block_height", + "blocks_until_reward_phase", + "ustx_until_pox_rejection" + ], + "properties": { + "id": { + "type": "integer", + "description": "The reward cycle number" + }, + "min_threshold_ustx": { + "type": "integer", + "description": "The threshold amount for obtaining a slot in this reward cycle." + }, + "stacked_ustx": { + "type": "integer", + "description": "The total amount of stacked microstacks in this reward cycle." + }, + "min_increment_ustx": { + "type": "integer", + "description": "The minimum amount that can be used to submit a `stack-stx` call." + }, + "prepare_phase_start_block_height": { + "type": "integer", + "description": "The burn block height when the prepare phase for this cycle begins. Any eligible stacks must be stacked before this block." + }, + "blocks_until_prepare_phase": { + "type": "integer", + "description": "The number of burn blocks until the prepare phase for this cycle starts. If the prepare phase for this cycle already started, this value will be negative." + }, + "reward_phase_start_block_height": { + "type": "integer", + "description": "The burn block height when the reward phase for this cycle begins. Any eligible stacks must be stacked before this block." + }, + "blocks_until_reward_phase": { + "type": "integer", + "description": "The number of burn blocks until this reward phase starts." + }, + "ustx_until_pox_rejection": { + "type": "integer", + "description": "The remaining amount of liquid STX that must vote to reject the next reward cycle to prevent the next reward cycle from activating." + } + } + }, + "reward_cycle_id": { + "type": "integer", + "deprecated": true, + "description": "The active reward cycle number" + }, + "min_amount_ustx": { + "type": "integer", + "deprecated": true, + }, + "prepare_cycle_length": { + "type": "integer", + "deprecated": true, + }, + "rejection_votes_left_required": { + "type": "integer", + "deprecated": true, + } + } +} diff --git a/docs/rpc/api/transaction/post-core-node-transactions-error.example.json b/docs/rpc/api/transaction/post-core-node-transactions-error.example.json new file mode 100644 index 000000000..3cab6ce21 --- /dev/null +++ b/docs/rpc/api/transaction/post-core-node-transactions-error.example.json @@ -0,0 +1,11 @@ +{ + "error": "transaction rejected", + "reason": "BadNonce", + "reason_data": { + "actual": 4, + "expected": 0, + "is_origin": true, + "principal": "ST2ZRX0K27GW0SP3GJCEMHD95TQGJMKB7G9Y0X1MH" + }, + "txid": "caf6fd60ae05b0c2d19ef14ab6a7670b1095d117fa7c80224c74e76214d0a791" +} diff --git a/docs/rpc/api/transaction/post-core-node-transactions-error.schema.json b/docs/rpc/api/transaction/post-core-node-transactions-error.schema.json new file mode 100644 index 000000000..ca36fd40a --- /dev/null +++ b/docs/rpc/api/transaction/post-core-node-transactions-error.schema.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "GET request that returns transactions", + "title": "PostCoreNodeTransactionsError", + "type": "object", + "required": ["error", "reason", "reason_data", "txid"], + "properties": { + "error": { + "type": "string", + "description": "The error" + }, + "reason": { + "type": "string", + "description": "The reason for the error" + }, + "reason_data": { + "type": "object", + "description": "More details about the reason" + }, + "txid": { + "type": "string", + "description": "The relevant transaction id" + } + } +} diff --git a/docs/rpc/entities/contracts/read-only-function-args.schema.json b/docs/rpc/entities/contracts/read-only-function-args.schema.json new file mode 100644 index 000000000..ff457fe03 --- /dev/null +++ b/docs/rpc/entities/contracts/read-only-function-args.schema.json @@ -0,0 +1,20 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ReadOnlyFunctionArgs", + "description": "Describes representation of a Type-0 Stacks 2.0 transaction. https://github.com/blockstack/stacks-blockchain/blob/master/sip/sip-005-blocks-and-transactions.md#type-0-transferring-an-asset", + "type": "object", + "required": ["sender", "arguments"], + "properties": { + "sender": { + "type": "string", + "description": "The simulated tx-sender" + }, + "arguments": { + "type": "array", + "description": "An array of hex serialized Clarity values", + "items": { + "type": "string" + } + } + } +} diff --git a/docs/rpc/openapi.yaml b/docs/rpc/openapi.yaml new file mode 100644 index 000000000..d4e2a977d --- /dev/null +++ b/docs/rpc/openapi.yaml @@ -0,0 +1,328 @@ +openapi: 3.0.2 +servers: + - url: http://localhost:20443/ + description: Local +info: + title: Stacks 2.0 RPC API + version: '1.0.0' + description: | + This is the documentation for the `stacks-node` RPC interface. + +paths: + /v2/transactions: + post: + summary: Broadcast raw transaction + tags: + - Transactions + description: Broadcast raw transactions on the network. You can use the [@stacks/transactions](https://github.com/blockstack/stacks.js) project to generate a raw transaction payload. + operationId: post_core_node_transactions + requestBody: + content: + application/octet-stream: + schema: + type: string + format: binary + example: binary format of 00000000010400bed38c2aadffa348931bcb542880ff79d607afec000000000000000000000000000000c800012b0b1fff6cccd0974966dcd665835838f0985be508e1322e09fb3d751eca132c492bda720f9ef1768d14fdabed6127560ba52d5e3ac470dcb60b784e97dc88c9030200000000000516df0ba3e79792be7be5e50a370289accfc8c9e032000000000000303974657374206d656d6f00000000000000000000000000000000000000000000000000 + responses: + 200: + description: Transaction id of successful post of a raw tx to the node's mempool + content: + text/plain: + schema: + type: string + example: '"e161978626f216b2141b156ade10501207ae535fa365a13ef5d7a7c9310a09f2"' + 400: + description: Rejections result in a 400 error + content: + application/json: + schema: + $ref: ./api/transaction/post-core-node-transactions-error.schema.json + example: + $ref: ./api/transaction/post-core-node-transactions-error.example.json + + /v2/contracts/interface/{contract_address}/{contract_name}: + get: + summary: Get contract interface + description: Get contract interface using a `contract_address` and `contract name` + tags: + - Smart Contracts + operationId: get_contract_interface + responses: + 200: + description: Contract interface + content: + application/json: + schema: + $ref: ./api/core-node/get-contract-interface.schema.json + example: + $ref: ./api/core-node/get-contract-interface.example.json + parameters: + - name: contract_address + in: path + required: true + description: Stacks address + schema: + type: string + - name: contract_name + in: path + required: true + description: Contract name + schema: + type: string + - name: tip + in: query + schema: + type: string + description: The Stacks chain tip to query from + + /v2/map_entry/{contract_address}/{contract_name}/{map_name}: + post: + summary: Get specific data-map inside a contract + tags: + - Smart Contracts + operationId: get_contract_data_map_entry + description: | + Attempt to fetch data from a contract data map. The contract is identified with [Stacks Address] and [Contract Name] in the URL path. The map is identified with [Map Name]. + + The key to lookup in the map is supplied via the POST body. This should be supplied as the hex string serialization of the key (which should be a Clarity value). Note, this is a JSON string atom. + + In the response, `data` is the hex serialization of the map response. Note that map responses are Clarity option types, for non-existent values, this is a serialized none, and for all other responses, it is a serialized (some ...) object. + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: ./api/core-node/get-contract-data-map-entry.schema.json + example: + $ref: ./api/core-node/get-contract-data-map-entry.example.json + 400: + description: Failed loading data map + parameters: + - name: contract_address + in: path + required: true + description: Stacks address + schema: + type: string + - name: contract_name + in: path + required: true + description: Contract name + schema: + type: string + - name: map_name + in: path + required: true + description: Map name + schema: + type: string + - name: proof + in: query + description: Returns object without the proof field when set to 0 + schema: + type: integer + - name: tip + in: query + schema: + type: string + description: The Stacks chain tip to query from + x-codegen-request-body-name: key + requestBody: + description: Hex string serialization of the lookup key (which should be a Clarity value) + required: true + content: + application/json: + schema: + type: string + + /v2/contracts/source/{contract_address}/{contract_name}: + get: + summary: Get contract source + tags: + - Smart Contracts + operationId: get_contract_source + description: Returns the Clarity source code of a given contract, along with the block height it was published in, and the MARF proof for the data + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: ./api/core-node/get-contract-source.schema.json + example: + $ref: ./api/core-node/get-contract-source.example.json + parameters: + - name: contract_address + in: path + required: true + description: Stacks address + schema: + type: string + - name: contract_name + in: path + required: true + description: Contract name + schema: + type: string + - name: proof + in: query + description: Returns object without the proof field if set to 0 + schema: + type: integer + - name: tip + in: query + schema: + type: string + description: The Stacks chain tip to query from + required: false + + /v2/contracts/call-read/{contract_address}/{contract_name}/{function_name}: + post: + summary: Call read-only function + tags: + - Smart Contracts + operationId: call_read_only_function + description: | + Call a read-only public function on a given smart contract. + + The smart contract and function are specified using the URL path. The arguments and the simulated tx-sender are supplied via the POST body in the following JSON format: + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: ./api/contract/post-call-read-only-fn.schema.json + examples: + success: + $ref: ./api/contract/post-call-read-only-fn-success.example.json + fail: + $ref: ./api/contract/post-call-read-only-fn-fail.example.json + parameters: + - name: contract_address + in: path + required: true + description: Stacks address + schema: + type: string + - name: contract_name + in: path + required: true + description: Contract name + schema: + type: string + - name: function_name + in: path + required: true + description: Function name + schema: + type: string + - name: tip + in: query + schema: + type: string + description: The Stacks chain tip to query from + required: false + requestBody: + description: map of arguments and the simulated tx-sender where sender is either a Contract identifier or a normal Stacks address, and arguments is an array of hex serialized Clarity values. + required: true + content: + application/json: + schema: + $ref: './entities/contracts/read-only-function-args.schema.json' + example: + sender: 'SP31DA6FTSJX2WGTZ69SFY11BH51NZMB0ZW97B5P0.get-info' + arguments: + - '0x0011...' + - '0x00231...' + + /v2/accounts/{principal}: + get: + summary: Get account info + tags: + - Accounts + operationId: get_account_info + description: | + Get the account data for the provided principal + + Where balance is the hex encoding of a unsigned 128-bit integer (big-endian), nonce is a unsigned 64-bit integer, and the proofs are provided as hex strings. + + For non-existent accounts, this does not 404, rather it returns an object with balance and nonce of 0. + parameters: + - name: principal + in: path + description: Stacks address or a Contract identifier (e.g. `SP31DA6FTSJX2WGTZ69SFY11BH51NZMB0ZW97B5P0.get-info`) + required: true + schema: + type: string + - name: proof + in: query + description: Returns object without the proof field if set to 0 + schema: + type: integer + - name: tip + in: query + schema: + type: string + description: The Stacks chain tip to query from + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: ./api/core-node/get-account-data.schema.json + example: + $ref: ./api/core-node/get-account-data.example.json + + /v2/fees/transfer: + get: + summary: Get estimated fee + tags: + - Fees + operationId: get_fee_transfer + description: Get an estimated fee rate for STX transfer transactions. This a a fee rate / byte, and is returned as a JSON integer + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: ./api/core-node/get-fee-transfer.schema.json + example: + $ref: ./api/core-node/get-fee-transfer.example.json + + /v2/info: + get: + summary: Get Core API info + description: Get Core API information + tags: + - Info + operationId: get_core_api_info + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: ./api/core-node/get-info.schema.json + example: + $ref: ./api/core-node/get-info.example.json + + /v2/pox: + get: + summary: Get PoX details + description: Get Proof of Transfer (PoX) information. Can be used for Stacking. + tags: + - Info + operationId: get_pox_info + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: ./api/core-node/get-pox.schema.json + example: + $ref: ./api/core-node/get-pox.example.json diff --git a/src/chainstate/burn/db/sortdb.rs b/src/chainstate/burn/db/sortdb.rs index 65ca5d337..f54787c9d 100644 --- a/src/chainstate/burn/db/sortdb.rs +++ b/src/chainstate/burn/db/sortdb.rs @@ -1455,6 +1455,15 @@ impl<'a> SortitionHandleConn<'a> { Ok(anchor_block_hash) } + fn get_reward_set_size(&self) -> Result { + self.get_tip_indexed(&db_keys::pox_reward_set_size()) + .map(|x| { + db_keys::reward_set_size_from_string( + &x.expect("CORRUPTION: no current reward set size written"), + ) + }) + } + pub fn get_pox_id(&self) -> Result { let pox_id = self .get_tip_indexed(db_keys::pox_identifier())? @@ -2938,6 +2947,24 @@ impl SortitionDB { } } + pub fn is_pox_active( + &self, + burnchain: &Burnchain, + block: &BlockSnapshot, + ) -> Result { + let reward_start_height = burnchain.reward_cycle_to_block_height( + burnchain + .block_height_to_reward_cycle(block.block_height) + .ok_or_else(|| db_error::NotFoundError)?, + ); + let sort_id_of_start = + get_ancestor_sort_id(&self.index_conn(), reward_start_height, &block.sortition_id)? + .ok_or_else(|| db_error::NotFoundError)?; + + let handle = self.index_handle(&sort_id_of_start); + Ok(handle.get_reward_set_size()? > 0) + } + /// Find out how any burn tokens were destroyed in a given block on a given fork. pub fn get_block_burn_amount( conn: &Connection, diff --git a/src/net/mod.rs b/src/net/mod.rs index f5316c64d..460c895d8 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -1022,28 +1022,48 @@ pub struct RPCPeerInfoData { pub exit_at_block_height: Option, } +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct RPCPoxCurrentCycleInfo { + pub id: u64, + pub min_threshold_ustx: u64, + pub stacked_ustx: u64, + pub is_pox_active: bool, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct RPCPoxNextCycleInfo { + pub id: u64, + pub min_threshold_ustx: u64, + pub min_increment_ustx: u64, + pub stacked_ustx: u64, + pub prepare_phase_start_block_height: u64, + pub blocks_until_prepare_phase: i64, + pub reward_phase_start_block_height: u64, + pub blocks_until_reward_phase: u64, + pub ustx_until_pox_rejection: u64, +} + /// The data we return on GET /v2/pox #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct RPCPoxInfoData { pub contract_id: String, + pub pox_activation_threshold_ustx: u64, pub first_burnchain_block_height: u64, - pub next_cycle_cur_threshold: u64, - pub cur_cycle_threshold: u64, - pub cur_cycle_stacked_ustx: u64, - pub next_cycle_stacked_ustx: u64, + pub prepare_phase_block_length: u64, + pub reward_phase_block_length: u64, pub reward_slots: u64, - pub next_rewards_start: u64, - pub next_prepare_phase_start: u64, - pub min_stacking_increment_ustx: u64, - pub pox_activation_threshold: u64, - pub prepare_cycle_length: u64, pub rejection_fraction: u64, + pub total_liquid_supply_ustx: u64, + pub current_cycle: RPCPoxCurrentCycleInfo, + pub next_cycle: RPCPoxNextCycleInfo, + + // below are included for backwards-compatibility + pub min_amount_ustx: u64, + pub prepare_cycle_length: u64, pub reward_cycle_id: u64, pub reward_cycle_length: u64, pub rejection_votes_left_required: u64, - pub total_liquid_supply_ustx: u64, pub next_reward_cycle_in: u64, - pub next_prepare_phase_in: u64, } #[derive(Debug, Clone, PartialEq, Copy, Hash)] diff --git a/src/net/rpc.rs b/src/net/rpc.rs index f91be5f84..32572084e 100644 --- a/src/net/rpc.rs +++ b/src/net/rpc.rs @@ -17,11 +17,11 @@ along with Blockstack. If not, see . */ -use std::fmt; use std::io; use std::io::prelude::*; use std::io::{Read, Seek, SeekFrom, Write}; use std::net::SocketAddr; +use std::{convert::TryFrom, fmt}; use core::mempool::*; use net::atlas::{AtlasDB, Attachment, MAX_ATTACHMENT_INV_PAGES_PER_REQUEST}; @@ -108,6 +108,8 @@ use vm::{ use rand::prelude::*; use rand::thread_rng; +use super::{RPCPoxCurrentCycleInfo, RPCPoxNextCycleInfo}; + pub const STREAM_CHUNK_SIZE: u64 = 4096; #[derive(Default)] @@ -338,18 +340,13 @@ impl RPCPoxInfoData { let next_reward_cycle_in = reward_cycle_length - (effective_height % reward_cycle_length); let next_rewards_start = burnchain_tip.block_height + next_reward_cycle_in; - let next_reward_cycle_prepare_phase_start = next_rewards_start - prepare_cycle_length; + let next_prepare_phase_start = next_rewards_start - prepare_cycle_length; - let next_prepare_phase_start = - if burnchain_tip.block_height < next_reward_cycle_prepare_phase_start { - next_reward_cycle_prepare_phase_start - } else { - // currently in a prepare phase, so the next prepare phase start is actually the reward cycle after - // next - next_reward_cycle_prepare_phase_start + reward_cycle_length - }; - - let next_prepare_phase_in = next_prepare_phase_start - burnchain_tip.block_height; + let next_prepare_phase_in = i64::try_from(next_prepare_phase_start) + .map_err(|_| net_error::ChainstateError("Burn block height overflowed i64".into()))? + - i64::try_from(burnchain_tip.block_height).map_err(|_| { + net_error::ChainstateError("Burn block height overflowed i64".into()) + })?; let cur_cycle_stacked_ustx = chainstate.get_total_ustx_stacked(&sortdb, tip, reward_cycle_id as u128)?; @@ -370,31 +367,45 @@ impl RPCPoxInfoData { reward_slots as u128, ) as u64; - let pox_activation_threshold = (total_liquid_supply_ustx as u128) + let pox_activation_threshold_ustx = (total_liquid_supply_ustx as u128) .checked_mul(pox_consts.pox_participation_threshold_pct as u128) .map(|x| x / 100) .ok_or_else(|| net_error::DBError(db_error::Overflow))? as u64; + let cur_cycle_pox_active = sortdb.is_pox_active(burnchain, &burnchain_tip)?; + Ok(RPCPoxInfoData { contract_id: boot::boot_code_id("pox", chainstate.mainnet).to_string(), + pox_activation_threshold_ustx, first_burnchain_block_height, - min_stacking_increment_ustx, - next_cycle_cur_threshold: next_threshold, - cur_cycle_threshold, - next_cycle_stacked_ustx: next_cycle_stacked_ustx as u64, - cur_cycle_stacked_ustx: cur_cycle_stacked_ustx as u64, + prepare_phase_block_length: prepare_cycle_length, + reward_phase_block_length: reward_cycle_length - prepare_cycle_length, reward_slots, - pox_activation_threshold, - next_rewards_start, - next_prepare_phase_start, - next_prepare_phase_in, - prepare_cycle_length, rejection_fraction, + total_liquid_supply_ustx, + current_cycle: RPCPoxCurrentCycleInfo { + id: reward_cycle_id, + min_threshold_ustx: cur_cycle_threshold, + stacked_ustx: cur_cycle_stacked_ustx as u64, + is_pox_active: cur_cycle_pox_active, + }, + next_cycle: RPCPoxNextCycleInfo { + id: reward_cycle_id + 1, + min_threshold_ustx: next_threshold, + min_increment_ustx: min_stacking_increment_ustx, + stacked_ustx: next_cycle_stacked_ustx as u64, + prepare_phase_start_block_height: next_prepare_phase_start, + blocks_until_prepare_phase: next_prepare_phase_in, + reward_phase_start_block_height: next_rewards_start, + blocks_until_reward_phase: next_reward_cycle_in, + ustx_until_pox_rejection: rejection_votes_left_required, + }, + min_amount_ustx: next_threshold, + prepare_cycle_length, reward_cycle_id, reward_cycle_length, rejection_votes_left_required, - total_liquid_supply_ustx, next_reward_cycle_in, }) } diff --git a/testnet/stacks-node/src/tests/neon_integrations.rs b/testnet/stacks-node/src/tests/neon_integrations.rs index e63b1f9e5..68ef2f6c0 100644 --- a/testnet/stacks-node/src/tests/neon_integrations.rs +++ b/testnet/stacks-node/src/tests/neon_integrations.rs @@ -2090,14 +2090,15 @@ fn pox_integration_test() { &format!("ST000000000000000000002AMW42H.pox") ); assert_eq!(pox_info.first_burnchain_block_height, 0); - assert_eq!(pox_info.next_cycle_cur_threshold, 125080000000000); - assert_eq!(pox_info.cur_cycle_threshold, 125080000000000); - assert_eq!(pox_info.cur_cycle_stacked_ustx, 0); - assert_eq!(pox_info.next_cycle_stacked_ustx, 0); + assert_eq!(pox_info.next_cycle.min_threshold_ustx, 125080000000000); + assert_eq!(pox_info.current_cycle.min_threshold_ustx, 125080000000000); + assert_eq!(pox_info.current_cycle.stacked_ustx, 0); + assert_eq!(pox_info.current_cycle.is_pox_active, false); + assert_eq!(pox_info.next_cycle.stacked_ustx, 0); assert_eq!(pox_info.reward_slots as u32, pox_constants.reward_slots()); - assert_eq!(pox_info.next_rewards_start, 210); - assert_eq!(pox_info.next_prepare_phase_start, 205); - assert_eq!(pox_info.min_stacking_increment_ustx, 20845173515333); + assert_eq!(pox_info.next_cycle.reward_phase_start_block_height, 210); + assert_eq!(pox_info.next_cycle.prepare_phase_start_block_height, 205); + assert_eq!(pox_info.next_cycle.min_increment_ustx, 20845173515333); assert_eq!( pox_info.prepare_cycle_length as u32, pox_constants.prepare_length @@ -2107,6 +2108,8 @@ fn pox_integration_test() { pox_constants.pox_rejection_fraction ); assert_eq!(pox_info.reward_cycle_id, 0); + assert_eq!(pox_info.current_cycle.id, 0); + assert_eq!(pox_info.next_cycle.id, 1); assert_eq!( pox_info.reward_cycle_length as u32, pox_constants.reward_cycle_length @@ -2155,13 +2158,15 @@ fn pox_integration_test() { &format!("ST000000000000000000002AMW42H.pox") ); assert_eq!(pox_info.first_burnchain_block_height, 0); - assert_eq!(pox_info.next_cycle_cur_threshold, 125080000000000); - assert_eq!(pox_info.cur_cycle_threshold, 125080000000000); - assert_eq!(pox_info.cur_cycle_stacked_ustx, 1000000000000000); - assert_eq!(pox_info.next_cycle_stacked_ustx, 1000000000000000); + assert_eq!(pox_info.next_cycle.min_threshold_ustx, 125080000000000); + assert_eq!(pox_info.current_cycle.min_threshold_ustx, 125080000000000); + assert_eq!(pox_info.current_cycle.stacked_ustx, 1000000000000000); + assert!(pox_info.pox_activation_threshold_ustx > 1500000000000000); + assert_eq!(pox_info.current_cycle.is_pox_active, false); + assert_eq!(pox_info.next_cycle.stacked_ustx, 1000000000000000); assert_eq!(pox_info.reward_slots as u32, pox_constants.reward_slots()); - assert_eq!(pox_info.next_rewards_start, 225); - assert_eq!(pox_info.next_prepare_phase_start, 220); + assert_eq!(pox_info.next_cycle.reward_phase_start_block_height, 225); + assert_eq!(pox_info.next_cycle.prepare_phase_start_block_height, 220); assert_eq!( pox_info.prepare_cycle_length as u32, pox_constants.prepare_length @@ -2171,6 +2176,8 @@ fn pox_integration_test() { pox_constants.pox_rejection_fraction ); assert_eq!(pox_info.reward_cycle_id, 14); + assert_eq!(pox_info.current_cycle.id, 14); + assert_eq!(pox_info.next_cycle.id, 15); assert_eq!( pox_info.reward_cycle_length as u32, pox_constants.reward_cycle_length @@ -2277,18 +2284,21 @@ fn pox_integration_test() { } let pox_info = get_pox_info(&http_origin); + assert_eq!( &pox_info.contract_id, &format!("ST000000000000000000002AMW42H.pox") ); assert_eq!(pox_info.first_burnchain_block_height, 0); - assert_eq!(pox_info.next_cycle_cur_threshold, 125080000000000); - assert_eq!(pox_info.cur_cycle_threshold, 125080000000000); - assert_eq!(pox_info.cur_cycle_stacked_ustx, 1000000000000000); - assert_eq!(pox_info.next_cycle_stacked_ustx, 2000000000000000); + assert_eq!(pox_info.next_cycle.min_threshold_ustx, 125080000000000); + assert_eq!(pox_info.current_cycle.min_threshold_ustx, 125080000000000); + assert_eq!(pox_info.current_cycle.stacked_ustx, 1000000000000000); + assert_eq!(pox_info.current_cycle.is_pox_active, false); + assert_eq!(pox_info.next_cycle.stacked_ustx, 2000000000000000); assert_eq!(pox_info.reward_slots as u32, pox_constants.reward_slots()); - assert_eq!(pox_info.next_rewards_start, 225); - assert_eq!(pox_info.next_prepare_phase_start, 235); + assert_eq!(pox_info.next_cycle.reward_phase_start_block_height, 225); + assert_eq!(pox_info.next_cycle.prepare_phase_start_block_height, 220); + assert_eq!(pox_info.next_cycle.blocks_until_prepare_phase, -4); assert_eq!( pox_info.prepare_cycle_length as u32, pox_constants.prepare_length @@ -2298,6 +2308,8 @@ fn pox_integration_test() { pox_constants.pox_rejection_fraction ); assert_eq!(pox_info.reward_cycle_id, 14); + assert_eq!(pox_info.current_cycle.id, 14); + assert_eq!(pox_info.next_cycle.id, 15); assert_eq!( pox_info.reward_cycle_length as u32, pox_constants.reward_cycle_length @@ -2323,6 +2335,21 @@ fn pox_integration_test() { eprintln!("Sort height: {}", sort_height); } + let pox_info = get_pox_info(&http_origin); + + assert_eq!( + &pox_info.contract_id, + &format!("ST000000000000000000002AMW42H.pox") + ); + assert_eq!(pox_info.first_burnchain_block_height, 0); + assert_eq!(pox_info.current_cycle.min_threshold_ustx, 125080000000000); + assert_eq!(pox_info.current_cycle.stacked_ustx, 2000000000000000); + assert_eq!(pox_info.current_cycle.is_pox_active, true); + assert_eq!(pox_info.next_cycle.reward_phase_start_block_height, 240); + assert_eq!(pox_info.next_cycle.prepare_phase_start_block_height, 235); + assert_eq!(pox_info.next_cycle.blocks_until_prepare_phase, -4); + assert_eq!(pox_info.next_reward_cycle_in, 1); + // we should have received _seven_ Bitcoin commitments, because our commitment was 7 * threshold let utxos = btc_regtest_controller.get_all_utxos(&pox_pubkey);