mirror of
https://github.com/alexgo-io/stacks-puppet-node.git
synced 2026-04-24 03:45:38 +08:00
Merge branch 'develop' into feat/rpc-pox-updates
This commit is contained in:
37
README.md
37
README.md
@@ -20,18 +20,20 @@ Stacks 2.0 is a layer-1 blockchain that connects to Bitcoin for security and ena
|
||||
|
||||
## Roadmap
|
||||
|
||||
- [x] [SIP 001: Burn Election](https://github.com/blockstack/stacks-blockchain/blob/master/sip/sip-001-burn-election.md)
|
||||
- [x] [SIP 002: Clarity, a language for predictable smart contracts](https://github.com/blockstack/stacks-blockchain/blob/master/sip/sip-002-smart-contract-language.md)
|
||||
- [X] [SIP 003: Peer Network](https://github.com/blockstack/stacks-blockchain/blob/master/sip/sip-003-peer-network.md)
|
||||
- [x] [SIP 004: Cryptographic Committment to Materialized Views](https://github.com/blockstack/stacks-blockchain/blob/master/sip/sip-004-materialized-view.md)
|
||||
- [x] [SIP 005: Blocks, Transactions, and Accounts](https://github.com/blockstack/stacks-blockchain/blob/master/sip/sip-005-blocks-and-transactions.md)
|
||||
- [ ] SIP 006: Clarity Execution Cost Assessment
|
||||
- [x] [SIP 007: Stacking Consensus](https://github.com/blockstack/stacks-blockchain/blob/master/sip/sip-007-stacking-consensus.md)
|
||||
- [ ] SIP 008: Clarity Parsing and Analysis Cost Assessment
|
||||
- [x] [SIP 001: Burn Election](https://github.com/stacksgov/sips/blob/main/sips/sip-001/sip-001-burn-election.md)
|
||||
- [x] [SIP 002: Clarity, a language for predictable smart contracts](https://github.com/stacksgov/sips/blob/main/sips/sip-002/sip-002-smart-contract-language.md)
|
||||
- [X] [SIP 003: Peer Network](https://github.com/stacksgov/sips/blob/main/sips/sip-003/sip-003-peer-network.md)
|
||||
- [x] [SIP 004: Cryptographic Committment to Materialized Views](https://github.com/stacksgov/sips/blob/main/sips/sip-004/sip-004-materialized-view.md)
|
||||
- [x] [SIP 005: Blocks, Transactions, and Accounts](https://github.com/stacksgov/sips/blob/main/sips/sip-005/sip-005-blocks-and-transactions.md)
|
||||
- [X] [SIP 006: Clarity Execution Cost Assessment](https://github.com/stacksgov/sips/blob/main/sips/sip-006/sip-006-runtime-cost-assessment.md)
|
||||
- [x] [SIP 007: Stacking Consensus](https://github.com/stacksgov/sips/blob/main/sips/sip-007/sip-007-stacking-consensus.md)
|
||||
- [X] [SIP 008: Clarity Parsing and Analysis Cost Assessment](https://github.com/stacksgov/sips/blob/main/sips/sip-008/sip-008-analysis-cost-assessment.md)
|
||||
|
||||
Stacks improvement proposals (SIPs) are aimed at describing the implementation of the Stacks blockchain, as well as proposing improvements. They should contain concise technical specifications of features or standards and the rationale behind it. SIPs are intended to be the primary medium for proposing new features, for collecting community input on a system-wide issue, and for documenting design decisions.
|
||||
|
||||
See [SIP 000](https://github.com/blockstack/stacks-blockchain/blob/master/sip/sip-000-stacks-improvement-proposal-process.md) for more details.
|
||||
See [SIP 000](https://github.com/stacksgov/sips/blob/main/sips/sip-000/sip-000-stacks-improvement-proposal-process.md) for more details.
|
||||
|
||||
The SIPs are now located in the [stacksgov/sips](https://github.com/stacksgov/sips) repository as part of the [Stacks Community Governance organization](https://github.com/stacksgov).
|
||||
|
||||
### Testnet versions
|
||||
|
||||
@@ -43,9 +45,9 @@ See [SIP 000](https://github.com/blockstack/stacks-blockchain/blob/master/sip/si
|
||||
|
||||
- [X] **Krypton** is the third version of our public testnet, which incorporates a partial implementation of SIP 007. It allows developers to test a simple version of Stacking and PoX consensus.
|
||||
|
||||
- [ ] **Xenon** is the upcoming version of our public testnet, which will run on the Bitcoin testnet. It will include SIP 006 and SIP 008, and will contain bugfixes and improvements to the implementation of SIP 007.
|
||||
- [X] **Xenon** is the upcoming version of our public testnet, which will run on the Bitcoin testnet. It will include SIP 006 and SIP 008, and will contain bugfixes and improvements to the implementation of SIP 007.
|
||||
|
||||
- [ ] **Mainnet** is the fully functional version, that is estimated for Q4 2020.
|
||||
- [X] **Mainnet** is the fully functional version, that is estimated for Q4 2020.
|
||||
|
||||
See the [testnet website](https://testnet.blockstack.org) and ["when mainnet?" FAQ](https://github.com/blockstack/stacks/blob/master/whenmainnet.md) for details.
|
||||
|
||||
@@ -83,7 +85,7 @@ cargo test testnet -- --test-threads=1
|
||||
|
||||
### Encode and sign transactions
|
||||
|
||||
Let's start by generating a keypair, that will be used for signing the upcoming transactions:
|
||||
Here, we have generated a keypair that will be used for signing the upcoming transactions:
|
||||
|
||||
```bash
|
||||
cargo run --bin blockstack-cli generate-sk --testnet
|
||||
@@ -95,6 +97,7 @@ cargo run --bin blockstack-cli generate-sk --testnet
|
||||
# stacksAddress: "ST2ZRX0K27GW0SP3GJCEMHD95TQGJMKB7G9Y0X1MH"
|
||||
# }
|
||||
```
|
||||
This keypair is already registered in the `Stacks.toml` file, so it can be used as presented here.
|
||||
|
||||
We will interact with the following simple contract `kv-store`. In our examples, we will assume this contract is saved to `./kv-store.clar`:
|
||||
|
||||
@@ -124,17 +127,18 @@ cargo run --bin blockstack-cli publish --help
|
||||
With the following arguments:
|
||||
|
||||
```bash
|
||||
cargo run --bin blockstack-cli publish b8d99fd45da58038d630d9855d3ca2466e8e0f89d3894c4724f0efc9ff4b51f001 500 0 kv-store ./kv-store.clar --testnet
|
||||
cargo run --bin blockstack-cli publish b8d99fd45da58038d630d9855d3ca2466e8e0f89d3894c4724f0efc9ff4b51f001 515 0 kv-store ./kv-store.clar --testnet
|
||||
```
|
||||
|
||||
The `500` is the transaction fee, denominated in microSTX. Right now, the
|
||||
The `515` is the transaction fee, denominated in microSTX. Right now, the
|
||||
testnet requires one microSTX per byte minimum, and this transaction should be
|
||||
less than 500 bytes.
|
||||
less than 515 bytes.
|
||||
The third argument `0` is a nonce, that must be increased monotonically with each new transaction.
|
||||
|
||||
This command will output the **binary format** of the transaction. In our case, we want to pipe this output and dump it to a file that will be used later in this tutorial.
|
||||
|
||||
```bash
|
||||
cargo run --bin blockstack-cli publish b8d99fd45da58038d630d9855d3ca2466e8e0f89d3894c4724f0efc9ff4b51f001 500 0 kv-store ./kv-store.clar --testnet | xxd -r -p > tx1.bin
|
||||
cargo run --bin blockstack-cli publish b8d99fd45da58038d630d9855d3ca2466e8e0f89d3894c4724f0efc9ff4b51f001 515 0 kv-store ./kv-store.clar --testnet | xxd -r -p > tx1.bin
|
||||
```
|
||||
|
||||
### Run the testnet
|
||||
@@ -187,7 +191,6 @@ cargo run --bin blockstack-cli contract-call b8d99fd45da58038d630d9855d3ca2466e8
|
||||
```
|
||||
|
||||
`contract-call` generates and signs a contract-call transaction.
|
||||
Note: the third argument `1` is a nonce, that must be increased monotonically with each new transaction.
|
||||
|
||||
We can submit the transaction by moving it to the mempool path:
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ Example:
|
||||
"reward_recipients": [
|
||||
{
|
||||
"recipient": "1C56LYirKa3PFXFsvhSESgDy2acEHVAEt6",
|
||||
"amount": 5000
|
||||
"amt": 5000
|
||||
}
|
||||
],
|
||||
"burn_amount": 12000
|
||||
|
||||
5
sip/README.md
Normal file
5
sip/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Stacks Improvement Proposals (SIPs)
|
||||
|
||||
This directory formerly contained all of the in-progress Stacks Improvement Proposals before the Stacks 2.0 mainnet launched.
|
||||
|
||||
The SIPs are now located in the [stacksgov/sips repository](https://github.com/stacksgov/sips) as part of the [Stacks Community Governance organization](https://github.com/stacksgov).
|
||||
@@ -1,848 +1,5 @@
|
||||
# SIP-000 Stacks Improvement Proposal Process
|
||||
|
||||
# Preamble
|
||||
This document formerly contained SIP-000 before the Stacks 2.0 mainnet launched.
|
||||
|
||||
Title: Stacks Improvement Proposal Process
|
||||
|
||||
Author: Ken Liao <yukanliao@gmail.com>, Jude Nelson <jude@blockstack.com>
|
||||
|
||||
Status: Draft
|
||||
|
||||
Consideration: Governance
|
||||
|
||||
Type: Meta
|
||||
|
||||
Created: 2020-06-23
|
||||
|
||||
License: BSD-2-Clause
|
||||
|
||||
Sign-off:
|
||||
|
||||
# Abstract
|
||||
|
||||
A Stacks Improvement Proposal (SIP) is a design document that provides
|
||||
information to the greater Stacks ecosystem's participants concerning the design
|
||||
of the Stacks blockchain and its ongoing operation. Each SIP shall provide a
|
||||
clear and concise description of features, processes, and/or standards for the
|
||||
Stacks blockchain and its operators to adopt, with sufficient details provided
|
||||
such that a reasonable practitioner may use the document to create an
|
||||
independent but compatible implementation of the proposed improvement.
|
||||
|
||||
SIPs are the canonical medium by which new features are proposed and described,
|
||||
and by which input from the Stacks ecosystem participants is collected. The SIP
|
||||
Ratification Process is also described in this document, and provides the means
|
||||
by which SIPs may be proposed, vetted, edited, accepted, rejected, implemented,
|
||||
and finally incorporated into the Stacks blockchain's design, governance, and
|
||||
operational procedures. The set of SIPs that have been ratified shall
|
||||
sufficiently describe the design, governance, and operationalization of the
|
||||
Stacks blockchain, as well as the means by which future changes to its official
|
||||
design, implementation, operation, and governance may be incorporated.
|
||||
|
||||
# License and Copyright
|
||||
|
||||
This SIP is made available under the terms of the BSD-2-Clause license,
|
||||
available at https://opensource.org/licenses/BSD-2-Clause. This SIP’s copyright
|
||||
is held by the Stacks Open Internet Foundation.
|
||||
|
||||
# Specification
|
||||
|
||||
Each SIP shall adhere to the same general formatting and shall be ratified
|
||||
through the processes described by this document.
|
||||
|
||||
## Introduction
|
||||
|
||||
Blockchains are unique among distributed systems in that they also
|
||||
happen to encode a social contract. By running a blockchain node, a user
|
||||
implicitly agrees to be bound to the social contract's terms embedded within the
|
||||
blockchain's software. These social contracts are elaborate constructions that
|
||||
contain not only technical terms (e.g. "a block may be at most 1MB"), but also
|
||||
economic terms (e.g. "only 21 million tokens may exist") and social terms (e.g.
|
||||
"no money can leave this account" or "this transaction type was supported
|
||||
before, but will now be ignored by the system") which the user agrees to uphold
|
||||
by running a blockchain node.
|
||||
|
||||
It stands to reason that the Stacks blockchain is made of more than just
|
||||
software; it is also made of the people who run it. As such, the act of
|
||||
developing and managing the Stacks blockchain network includes the act of
|
||||
helping its people coordinate and agree on what the blockchain is and what it
|
||||
should do. To this end, this document proposes a process by which the Stacks
|
||||
blockchain's users can conduct themselves to be the stewards of the blockchain
|
||||
network in perpetuity.
|
||||
|
||||
The goals of this process are to ensure that anyone may submit a SIP in good
|
||||
faith, that each SIP will receive fair and speedy good-faith consideration by
|
||||
other people with the relevant expertise, and that any discussions and
|
||||
decision-making on each SIP's ratification shall happen in public. To achieve
|
||||
these ends, this document proposes a standard way of presenting a Stacks
|
||||
Improvement Proposal (SIP), and a standard way of ratifying one.
|
||||
|
||||
Each SIP document contains all of the information needed to propose a
|
||||
non-trivial change to the way in which the Stacks blockchain operates. This
|
||||
includes both technical considerations, as well as operational and governance
|
||||
considerations. This document proposes a formal document structure based on both
|
||||
request-for-comments (RFC) practices in the Internet Engineering Task Force
|
||||
(IETF), as well as existing blockchain networks.
|
||||
|
||||
SIPs must be ratified in order to be incorporated into the definition of what
|
||||
the Stacks blockchain is, what it does, and how it operates. This document
|
||||
proposes a ratification process based on existing governance processes from
|
||||
existing open source projects (including Python, Bitcoin, Ethereum, and Zcash),
|
||||
and makes provisions for creating and staffing various roles that people must
|
||||
take on to carry out ratification (e.g. committees, editors, working groups and
|
||||
so on).
|
||||
|
||||
This document uses the word “users” to refer specifically to people who
|
||||
participate in the greater Stacks ecosystem. This includes, but is not limited
|
||||
to, people who mine blocks, people who contribute code, people who run nodes,
|
||||
people who develop applications that rely on the Stacks blockchain, people who
|
||||
use such applications, people involved in the project governance, and people
|
||||
involved in operating software deployments.
|
||||
|
||||
## SIP Format
|
||||
|
||||
All SIPs shall be formatted as markdown files. Each section shall be
|
||||
annotated as a 2nd-level header (e.g. `##`). Subsections may be added with
|
||||
lower-level headers.
|
||||
|
||||
Each SIP shall contain the following sections, in the given order:
|
||||
|
||||
- _Preamble_. This section shall provide fields useful for categorizing the SIP.
|
||||
The required fields in all cases shall be:
|
||||
- _SIP Number_. Each SIP receives a unique number once it has been accepted
|
||||
for consideration for ratification (see below). This number is assigned to
|
||||
a SIP; its author does not provide it.
|
||||
- _Title_. A concise description of the SIP, no more than 20 words long.
|
||||
- _Author_. A list of names and email addresses of the SIP's author(s).
|
||||
- _Consideration_. What class of SIP this is (see below).
|
||||
- _Type_. The SIP track for consideration (see below).
|
||||
- _Status_. This SIP's point in the SIP workflow (see below).
|
||||
- _Created_. The ISO 8601 date when this SIP was created.
|
||||
- _License_. The content license for the SIP (see below for permitted
|
||||
licenses).
|
||||
- _Sign-off_. The list of relevant persons and their titles who have worked to
|
||||
ratify the SIP. This field is not filled in entirely until ratification,
|
||||
but is incrementally filled in as the SIP progresses through the ratification
|
||||
process.
|
||||
- Additional SIP fields, which are sometimes required, include:
|
||||
- _Layer_. The logical layer of the Stacks blockchain affected. Must be one
|
||||
- of the following:
|
||||
- _Consensus (soft fork)_. For backwards-compatible proposals for
|
||||
transaction-processing.
|
||||
- _Consensus (hard fork)_. For backwards-incompatible proposals for
|
||||
transaction-processing.
|
||||
- _Peer Services_. For proposals to the peer-to-peer network protocol
|
||||
stack.
|
||||
- _API/RPC_. For proposals to the Stacks blockchain's official
|
||||
programmatic interfaces.
|
||||
- _Traits_. For proposals for new standardized Clarity trait definitions.
|
||||
- _Applications_. For proposals for standardized application protocols
|
||||
that interface with the Stacks blockchain.
|
||||
- _Discussions-To_. A mailing list where ongoing discussion of the SIP takes
|
||||
place.
|
||||
- _Comments-Summary_. The comments summary tone.
|
||||
- _Comments-URI_. A link to the Stacks blockchain wiki for comments.
|
||||
- _License-Code_. Abbreviation for code under a different license than the SIP
|
||||
proposal.
|
||||
- _Post-History_. Dates of posting the SIP to the Stacks mailing list, or a
|
||||
link to a thread with the mailing list.
|
||||
- _Requires_. A list of SIPs that must be implemented prior to this SIP.
|
||||
- _Replaces_. A list of SIPs that this SIP replaces.
|
||||
- _Superceded-By_. A list of SIPs that replace this SIP.
|
||||
|
||||
- _Abstract_. This section shall provide a high-level summary of the proposed
|
||||
improvement. It shall not exceed 5000 words.
|
||||
- _Copyright_. This section shall provide the copyright license that governs the
|
||||
use of the SIP content. It must be one of the approved set of licenses (see
|
||||
below).
|
||||
- _Introduction_. This section shall provide a high-level summary of the
|
||||
problem(s) that this SIP proposes to solve, as well as a high-level
|
||||
description of how the proposal solves them. This section shall emphasize its
|
||||
novel contributions, and briefly describe how they address the problem(s). Any
|
||||
motivational arguments and example problems and solutions belong in this
|
||||
section.
|
||||
- _Specification_. This section shall provide the detailed technical
|
||||
specification. It may include code snippits, diagrams, performance
|
||||
evaluations, and other supplemental data to justify particular design decisions.
|
||||
However, a copy of all external supplemental data (such as links to research
|
||||
papers) must be included with the SIP, and must be made available under an
|
||||
approved copyright license.
|
||||
- _Related Work_. This section shall summarize alternative solutions that address
|
||||
the same or similar problems, and briefly describe why they are not adequate
|
||||
solutions. This section may reference alternative solutions in other blockchain
|
||||
projects, in research papers from academia and industry, other open-source
|
||||
projects, and so on. This section must be accompanied by a bibliography of
|
||||
sufficient detail such that someone reading the SIP can find and evaluate the
|
||||
related works.
|
||||
- _Backwards Compatibility_. This section shall address any
|
||||
backwards-incompatiblity concerns that may arise with the implementation of
|
||||
this SIP, as well as describe (or reference) technical mitigations for breaking
|
||||
changes. This section may be left blank for non-technical SIPs.
|
||||
- _Activation_. This section shall describe the timeline, falsifiable criteria,
|
||||
and process for activating the SIP once it is ratified. This applies to both
|
||||
technical and non-technical SIPs. This section is used to unambiguously
|
||||
determine whether or not the SIP has been accepted by the Stacks users once it
|
||||
has been submitted for ratification (see below).
|
||||
- _Reference Implementations_. This section shall include one or more references
|
||||
to one or more production-quality implementations of the SIP, if applicable.
|
||||
This section is only informative — the SIP ratification process is independent
|
||||
of any engineering processes (or other processes) that would be followed to
|
||||
produce implementations. If a particular implementation process is desired,
|
||||
then a detailed description of the process must be included in the Activation
|
||||
section. This section may be updated after a SIP is ratified in order to
|
||||
include an up-to-date listing of any implementations or embodiments of the SIP.
|
||||
|
||||
Additional sections may be included as appropriate.
|
||||
|
||||
### Supplemental Materials
|
||||
|
||||
A SIP may include any supplemental materials as
|
||||
appropriate (within reason), but all materials must have an open format
|
||||
unencumbered by legal restrictions. For example, an LibreOffice `.odp`
|
||||
slide-deck file may be submitted as supplementary material, but not a Keynote
|
||||
`.key` file.
|
||||
|
||||
When submitting the SIP, supplementary materials must be present within the same
|
||||
directory, and must be named as `SIP-XXXX-YYY.ext`, where:
|
||||
|
||||
- `XXXX` is the SIP number,
|
||||
- `YYY` is the serial number of the file, starting with 1,
|
||||
- `.ext` is the file extension.
|
||||
|
||||
## SIP Types
|
||||
|
||||
The types of SIPs are as follows:
|
||||
|
||||
- _Consensus_. This SIP type means that all Stacks blockchain implementations
|
||||
would need to adopt this SIP to remain compatible with one another. If this is
|
||||
the SIP type, then the SIP preamble must have the Layer field set to either
|
||||
_Consensus (soft fork)_ or _Consensus (hard fork)_.
|
||||
- _Standard_. This SIP type means that the proposed change affects one or more
|
||||
implementations, but does not affect network consensus. If this is the SIP
|
||||
type, then the SIP preamble must have the Layer field set to indicate which
|
||||
aspect(s) of the Stacks blockchain are affected by the proposal.
|
||||
- _Operation_. This SIP type means that the proposal concerns the operation of the
|
||||
Stacks blockchain -- in particular, it concerns node operators and miners.
|
||||
The difference between this SIP type and the Standard type is that this type
|
||||
does not change any existing protocols.
|
||||
- _Meta_. This SIP type means that the proposal concerns the SIP ratification
|
||||
process. Such a SIP is a proposal to change the way SIPs are handled.
|
||||
- _Informational_. This is a SIP type that provides useful information, but does
|
||||
not require any action to be taken on the part of any user.
|
||||
|
||||
New types of SIPs may be created with the ratification of a Meta-type SIP under
|
||||
the governance consideration (see below). SIP types may not be removed.
|
||||
|
||||
## SIP Considerations
|
||||
|
||||
A SIP's consideration determines the particular steps needed to ratify the SIP
|
||||
and incorporate it into the Stacks blockchain. Different SIP considerations have
|
||||
different criteria for ratification. A SIP can have more than one consideration,
|
||||
since a SIP may need to be vetted by different users with different domains of
|
||||
expertise.
|
||||
|
||||
|
||||
- _Technical_. The SIP is technical in nature, and must be vetted by users with
|
||||
the relevant technical expertise.
|
||||
- _Economic_. The SIP concerns the blockchain's token economics. This not only
|
||||
includes the STX token, but also any on-chain tokens created within smart
|
||||
contracts. SIPs that are concerned with fundraising methods, grants, bounties,
|
||||
and so on also belong in this SIP track.
|
||||
- _Governance_. The SIP concerns the governance of the Stacks blockchain,
|
||||
including the SIP process. This includes amendments to the SIP Ratification
|
||||
Process, as well as structural considerations such as the creation (or removal)
|
||||
of various committees, editorial bodies, and formally recognized special
|
||||
interest groups. In addition, governance SIPs may propose changes to the way by
|
||||
which committee members are selected.
|
||||
- _Ethics_. This SIP concerns the behaviors of office-holders in the SIP
|
||||
Ratification Process that can affect its widespread adoption. Such SIPs
|
||||
describe what behaviors shall be deemed acceptable, and which behaviors shall be
|
||||
considered harmful to this end (including any remediation or loss of privileges
|
||||
that misbehavior may entail). SIPs that propose formalizations of ethics like
|
||||
codes of conduct, procedures for conflict resolution, criteria for involvement
|
||||
in governance, and so on would belong in this SIP consideration.
|
||||
- _Diversity_. This SIP concerns proposals to grow the set of users, with an
|
||||
emphasis on including users who are traditionally not involved with
|
||||
open-source software projects. SIPs that are concerned with evangelism,
|
||||
advertising, outreach, and so on must have this consideration.
|
||||
|
||||
Each SIP consideration shall have a dedicated Advisory Board that ultimately
|
||||
vets SIPs under their consideration for possible ratification in a timely
|
||||
fashion (see below). New considerations may be created via the ratification of
|
||||
a Meta-type SIP under the governance consideration.
|
||||
|
||||
## SIP Workflow
|
||||
|
||||
As a SIP is considered for ratification, it passes through multiple statuses as
|
||||
determined by one or more committees (see next section). A SIP may have exactly
|
||||
one of the following statuses at any given time:
|
||||
|
||||
- _Draft_. The SIP is still being prepared for formal submission. It does not yet
|
||||
have a SIP number.
|
||||
- _Accepted_. The SIP text is sufficiently complete that it constitutes a
|
||||
well-formed SIP, and is of sufficient quality that it may be considered for
|
||||
ratification. A SIP receives a SIP number when it is moved into the Accepted
|
||||
state by SIP Editors.
|
||||
- _Recommended_. The people responsible for vetting the SIPs under the
|
||||
consideration(s) in which they have expertise have agreed that this SIP should
|
||||
be implemented. A SIP must be Accepted before it can be Recommended.
|
||||
- _Activation-In-Progress_. The SIP has been tentatively approved by the Steering
|
||||
Committee for ratification. However, not all of the criteria for ratification
|
||||
have been met according to the SIP’s Activation section. For example, the
|
||||
Activation section might require miners to vote on activating the SIPs’
|
||||
implementations, which would occur after the SIP has been transferred into
|
||||
Activation-In-Progress status but before it is transferred to Ratified status.
|
||||
- _Ratified._ The SIP has been activated according to the procedures described in
|
||||
its Activation section. Once ratified, a SIP remains ratified in perpetuity,
|
||||
but a subsequent SIP may supersede it. If the SIP is a Consensus-type SIP, and
|
||||
then all Stacks blockchain implementations must implement it. A SIP must be
|
||||
Recommended before it can be Ratified. Moving a SIP into this state may be done
|
||||
retroactively, once the SIP has been activated according to the terms in its
|
||||
Activation section.
|
||||
- _Rejected_. The SIP does not meet at least one of the criteria for ratification
|
||||
in its current form. A SIP can become Rejected from any state, except
|
||||
Ratified. If a SIP is moved to the Rejected state, then it may be re-submitted
|
||||
as a Draft.
|
||||
- _Obsolete_. The SIP is deprecated, but its candidacy for ratification has not
|
||||
been officially withdrawn (e.g. it may warrant further discussion). An
|
||||
Obsolete SIP may not be ratified, and will ultimately be Withdrawn.
|
||||
- _Replaced_. The SIP has been superseded by a different SIP. Its preamble must
|
||||
have a Superseded-By field. A Replaced SIP may not be ratified, nor may it be
|
||||
re-submitted as a Draft-status SIP. It must be transitioned to a Withdrawn
|
||||
state once the SIP(s) that replace it have been processed.
|
||||
- _Withdrawn_. The SIP's authors have ceased working on the SIP. A Withdrawn SIP
|
||||
may not be ratified, and may not be re-submitted as a Draft. It must be
|
||||
re-assigned a SIP number if taken up again.
|
||||
|
||||
|
||||
The act of ratifying a SIP is the act of transitioning it to the Ratified status
|
||||
-- that is, moving it from Draft to Accepted, from Accepted to Recommended, and
|
||||
Recommended to Activation-In-Progress, and from Activation-In-Progress to
|
||||
Ratified, all without the SIP being transitioned to Rejected, Obsolete,
|
||||
Replaced, or Withdrawn status. A SIP's current status is recorded in its Status
|
||||
field in its preamble.
|
||||
|
||||
## SIP Committees
|
||||
|
||||
The act of deciding the status of a SIP is handled by a set of designated
|
||||
committees. These committees are composed of users who dedicate their time and
|
||||
expertise to curate the blockchain, ratifying SIPs on behalf of the rest of the
|
||||
ecosystem’s users.
|
||||
|
||||
There are three types of committee:
|
||||
|
||||
- _Steering Committee (SC)_. The roles of the SC are to select Recommended-status
|
||||
SIPs to be activated, to determine whether or not a SIP has been activated and
|
||||
thus ratified, and to formally recognize Consideration Advisory Boards (see
|
||||
below).
|
||||
- _Consideration Advisory Boards_. The roles of the Consideration Advisory Boards
|
||||
are to provide expert feedback on SIPs that have been moved to Accepted status
|
||||
in a timely manner, and to transition SIPs to Recommended status if they meet
|
||||
the Board's consideration criteria, and Rejected status otherwise.
|
||||
- _SIP Editors_. The role of the SIP Editors is to identify SIPs in the Draft
|
||||
status that can be transitioned to Accepted status. A SIP editor must be able
|
||||
to vet a SIP to ensure that it is well-formed, that it follows the ratification
|
||||
workflow faithfully, and that it does not overlap with any already-Accepted SIPs
|
||||
or SIPs that have since become Recommended or Ratified.
|
||||
|
||||
Any user may serve on a committee. However, all Stacks committee members must
|
||||
abide by the SIP Code of Conduct and must have a history of adhering to it.
|
||||
Failure to adhere to the Code of Conduct shall be grounds for immediate removal
|
||||
from a committee, and a prohibition against serving on any future committees.
|
||||
|
||||
### Compensation
|
||||
|
||||
Compensation for carrying out committee duties is outside of the scope of this
|
||||
document. This document does not create a provision for compensation for
|
||||
committee participation, but it does not forbid it either.
|
||||
|
||||
### Steering Committee Duties
|
||||
|
||||
The Steering Committee's overarching duty is to oversee the evolution of the
|
||||
Stacks blockchain’s design, operation, and governance, in a way that is
|
||||
technically sound and feasible, according to the rules and procedures described
|
||||
in this document. The SC shall be guided by and held accountable by the greater
|
||||
community of users, and shall make all decisions with the advice of the relevant
|
||||
Consideration Advisory Boards.
|
||||
|
||||
The SC’s role is that of a steward. The SC shall select SIPs for ratification
|
||||
based on how well they serve the greater good of the Stacks users. Given the
|
||||
nature of blockchains, the SC's particular responsibilities pertaining to
|
||||
upgrading the blockchain network are meant to ensure that upgrades happen in a
|
||||
backwards-compatible fashion if at all possible. While this means that more
|
||||
radical SIPs may be rejected or may spend a long amount of time in Recommended
|
||||
status, it also minimizes the chances of an upgrade leading to widespread
|
||||
disruption (the minimization of which itself serves the greater good).
|
||||
|
||||
#### Membership
|
||||
|
||||
The initial Steering Committee shall be comprised of at least three members:
|
||||
two from the Stacks Open Internet Foundation, and one
|
||||
from the greater Stacks blockchain community (independent of the Stacks
|
||||
Foundation).
|
||||
|
||||
A provisional Steering Committee will be appointed by the Stacks Open Internet Foundation Board
|
||||
before the launch of the Stacks blockchain’s mainnet (see the "Activation" section).
|
||||
Once this SIP activates, the Stacks Open Internet Foundation shall select its
|
||||
representatives in a manner of their choosing within 90 days after activation.
|
||||
The committee may be expanded later to include more seats. Once this SIP
|
||||
activates, the provisional SC will consult with the community to
|
||||
ratify a SIP that implements a voting procedure whereby
|
||||
Stacks community members can select the individual who will serve on the
|
||||
community SC seat.
|
||||
|
||||
#### Qualifications
|
||||
|
||||
Members of this committee must have deep domain expertise
|
||||
pertinent to blockchain development, and must have excellent written
|
||||
communication skills. It is highly recommended that members should have authored
|
||||
at least one ratified technical-consideration SIP before joining this committee.
|
||||
|
||||
#### Responsibilities
|
||||
|
||||
The Steering Committee shall be responsible for the following
|
||||
tasks.
|
||||
|
||||
##### Recognizing Consideration Advisory Boards.
|
||||
|
||||
The members of the Steering Committee
|
||||
must bear in mind that they are not infallible, and that they do not know everything
|
||||
there is to know about what is best for the broader user community. To the
|
||||
greatest extent practical, the SC shall create and foster the development of
|
||||
Consideration Advisory Boards in order make informed decisions on subjects that
|
||||
in which they may not be experts.
|
||||
|
||||
Any group of users can form an unofficial working group to help provide feedback
|
||||
to SIPs, but the SC shall have the power to recognize such groups formally as a
|
||||
Consideration Advisory Board via at least a two-thirds majority vote. The SC
|
||||
shall simultaneously recognize one of it’s member to serve as the interim
|
||||
chairperson while the Advisory Board forms. A SC member cannot normally serve on
|
||||
a Consideration Advisory Board concurrently with serving on the SC, unless
|
||||
granted a limited exception by a unanimous vote by the SC (e.g. in order to
|
||||
address the Board’s business while a suitable chairperson is found). Formally
|
||||
recognizing Consideration Advisory Boards shall occur in Public Meetings (see
|
||||
below) no more than once per quarter.
|
||||
|
||||
Once recognized, Consideration Advisory Boards may not be dissolved or
|
||||
dismissed, unless there are no Accepted or Recommended SIPs that request their
|
||||
consideration. If this is the case, then the SC may vote to rescind recognition
|
||||
of a Consideration Advisory Board with a two-thirds majority at one of its
|
||||
Public Meetings.
|
||||
|
||||
In order to identify users who would form a Consideration Advisory Board, users
|
||||
should organize into an unofficial working group and submit a SIP to petition
|
||||
that SC recognize the working group as a Consideration Advisory Board. This
|
||||
petition must take the form of a Meta-type SIP, and may be used to select the
|
||||
initial chairperson and define the Board's domain(s) of expertise, bylaws,
|
||||
membership, meeting procedures, communication channels, and so on, independent
|
||||
of the SC. The SC would only be able to ratify or reject the SIP.
|
||||
|
||||
The SC shall maintain a public index of all Consideration Advisory Boards that
|
||||
are active, including contact information for the Board and a summary of what
|
||||
kinds of expertise the Board can offer. This index is meant to be used by SIP
|
||||
authors to help route their SIPs towards the appropriate reviewers before being
|
||||
taken up by the SC.
|
||||
|
||||
##### Voting on Technical SIPs
|
||||
|
||||
The Steering Committee shall select Recommended SIPs
|
||||
for ratification by moving them to Activation-In-Progress status. All
|
||||
technical-consideration SIPs shall require an 80% vote. If it is a
|
||||
Consensus-type SIP for a hard fork, then a unanimous vote shall be required. If
|
||||
a SIP is voted on and is not moved to Activation-in-Progress, then it shall be
|
||||
moved to Rejected status, and the SC shall provide a detailed explanation as to
|
||||
why they made their decision (see below).
|
||||
|
||||
##### Voting on Non-technical SIPs
|
||||
|
||||
Not all SIPs are technical in nature. All
|
||||
non-technical SIPs shall require only a two-thirds majority vote to transition
|
||||
it to Activation-In-Progress status. The SC members must provide a public
|
||||
explanation for the way it voted as supplementary materials with the ratified
|
||||
non-technical SIP (see below). If the SC votes to move a non-technical SIP to
|
||||
Activation-In-Progress status, but does not receive the requisite number of
|
||||
votes, then the SIP shall be transferred to Rejected status, and the SC shall
|
||||
provide a detailed explanation as to why they made their decision (see below).
|
||||
|
||||
##### Overseeing SIP Activation and Ratification
|
||||
|
||||
Once a SIP is in Activation-In-Progress status,
|
||||
the SC shall be responsible for overseeing the procedures and criteria in the
|
||||
SIP’s Activation section. The Activation section of a SIP can be thought of as
|
||||
an “instruction manual” and/or “checklist” for the SC to follow to determine if
|
||||
the SIP has been accepted by the Stacks users. The SC shall strictly adhere to
|
||||
the process set forth in the Activation section. If the procedure and/or
|
||||
criteria of the Activation section cannot be met, then the SC may transfer the
|
||||
SIP to Rejected status and ask the authors to re-submit the SIP with an updated
|
||||
Activation section.
|
||||
|
||||
Once all criteria have been unambiguously meet and all activation procedures
|
||||
have been followed, the SC shall transition the SIP to Ratified status. The SC
|
||||
shall keep a log and provide a record of the steps they took in following a
|
||||
SIP’s Activation section once the SIP is in Activation-In-Progress status, and
|
||||
publish them alongside the Ratified SIP as supplemental material.
|
||||
|
||||
Due to the hands-on nature of the Activation section, the SC may deem it
|
||||
appropriate to reject a SIP solely on the quality of its Activation section.
|
||||
Reasonable grounds for rejection include, but are not limited to, ambiguous
|
||||
instructions, insufficiently-informative activation criteria, too much work on
|
||||
the SC members’ parts, the lack of a prescribed activation timeout, and so on.
|
||||
|
||||
Before the Stacks mainnet launches, the SC shall ratify a SIP that, when
|
||||
activated according to the procedures outlined in its Activation section, will
|
||||
allow Stacks blockchain miners to signal their preferences for the activation of
|
||||
particular SIPs within the blocks that they mine. This will enable the greater
|
||||
Stacks community of users to have the final say as to which SIPs activate and
|
||||
become ratified.
|
||||
|
||||
##### Feedback on Recommended SIPs
|
||||
|
||||
The Steering Committee shall give a full, fair,
|
||||
public, and timely evaluation to each SIP transitioned to Recommended status by
|
||||
Consideration Advisory Boards. A SIP shall only be considered by the SC if the
|
||||
Consideration Advisory Board chairpeople for each of the SIP's considerations
|
||||
have signed-off on the SIP (by indicating as such on the SIP's preamble).
|
||||
|
||||
The SC may transition a SIP to Rejected status if it disagrees with the
|
||||
Consideration Advisory Boards' recommendation. The SC may transition a SIP to
|
||||
Obsolete status if it finds that the SIP no longer addresses a relevant concern.
|
||||
It may transition the SIP to a Replaced status if it considers a similar,
|
||||
alternative SIP that is more likely to succeed. In all cases, the SC shall
|
||||
ensure that a SIP does not remain in Recommended status for an unreasonable
|
||||
amount of time.
|
||||
|
||||
The SC shall maintain a public record of all feedbacks provided for each SIP it
|
||||
reviews.
|
||||
|
||||
If a SIP is moved to Rejected, Obsolete, or Replaced status, the SIP authors may
|
||||
appeal the process by re-submitting it in Draft status once the feedback has
|
||||
been addressed. The appealed SIP must cite the SC’s feedback as supplemental
|
||||
material, so that SIP Editors and Consideration Advisory Boards are able to
|
||||
verify that the feedback has, in fact, been addressed.
|
||||
|
||||
##### Public Meetings
|
||||
|
||||
The Steering Committee shall hold and record regular public
|
||||
meetings at least once per month. The SC may decide the items of business for
|
||||
these meetings at its sole discretion, but it shall prioritize business
|
||||
pertaining to the ratification of SIPs, the recognition of Consideration
|
||||
Advisory Boards, and the needs of all outstanding committees. That said, any
|
||||
user may join these meetings as an observer, and the SC shall make a good-faith
|
||||
effort to address public comments from observers as time permits.
|
||||
|
||||
The SC shall appoint up to two dedicated moderators from the user community for
|
||||
its engineering meetings, who shall vet questions and commentary from observers
|
||||
in advance (possibly well before the meeting begins). If there is more than one
|
||||
moderator, then the moderators may take turns. In addition, the SC shall appoint
|
||||
a dedicated note-taker to record the minutes of the meetings. All of these
|
||||
appointees shall be eligible to receive a fixed, regular bounty for their work.
|
||||
|
||||
### Consideration Advisory Board Duties
|
||||
|
||||
There is an Advisory Board for each SIP consideration, with a designated
|
||||
chairperson responsible for maintaining copies of all discussion and feedback on
|
||||
the SIPs under consideration.
|
||||
|
||||
#### Membership
|
||||
|
||||
All Consideration Advisory Boards begin their life as unofficial
|
||||
working groups of users who wish to review inbound SIPs according to their
|
||||
collective expertise. If they wish to be recognized as an official
|
||||
Consideration Advisory Board, they shall submit a SIP to the Steering Committee
|
||||
per the procedure described in the Steering Committee’s duties. Each
|
||||
Consideration Advisory Board shall be formally created by the SC with a
|
||||
designated member serving as its first interim chairperson. After this, the
|
||||
Consideration Advisory Board may adopt its own bylaws for selecting members and
|
||||
chairpeople. However, members should have domain expertise relevant to the
|
||||
consideration.
|
||||
|
||||
#### Members
|
||||
|
||||
shall serve on their respective Consideration Advisory Boards so long as
|
||||
they are in good standing with the SIP Code of Conduct and in accordance to the
|
||||
individual Board’s bylaws. A user may serve on at most three Consideration
|
||||
Advisory Boards concurrently.
|
||||
|
||||
#### Qualifications
|
||||
|
||||
Each Consideration Advisory Board member shall have sufficient
|
||||
domain expertise to provide the Steering Committee with feedback pertaining to a
|
||||
SIP's consideration. Members shall possess excellent written communication
|
||||
skills.
|
||||
|
||||
#### Responsibilities
|
||||
|
||||
Each Consideration Advisory Board shall be responsible for the
|
||||
following.
|
||||
|
||||
##### Chairperson
|
||||
|
||||
Each Consideration Advisory Board shall appoint a chairperson, who
|
||||
shall serve as the point of contact between the rest of the Board and the
|
||||
Steering Committee. If the chairperson becomes unresponsive, the SC may ask the
|
||||
Board to appoint a new chairperson (alternatively, the Board may appoint a new
|
||||
chairperson on its own and inform the SC). The chairperson shall be responsible
|
||||
for maintaining the Board’s public list of members’ names and contact
|
||||
information as a supplementary document to the SIP that the SC ratified to
|
||||
recognize the Board.
|
||||
|
||||
##### Consideration Track
|
||||
|
||||
Each Consideration Advisory Board shall provide a clear and
|
||||
concise description of what expertise it can offer, so that SIP authors may
|
||||
solicit it with confidence that it will be helpful. The chairperson shall make
|
||||
this description available to the Steering Committee and to the SIP Editors, so
|
||||
that both committees can help SIP authors ensure that they receive the most
|
||||
appropriate feedback.
|
||||
|
||||
The description shall be provided and updated by the chairperson to the SC so
|
||||
that the SC can provide a public index of all considerations a SIP may possess.
|
||||
|
||||
##### Feedback
|
||||
|
||||
to SIP Authors Each Consideration Advisory Board shall provide a full,
|
||||
fair, public, and timely evaluation of any Accepted-status SIP that lists the
|
||||
Board's consideration in its preamble. The Board may decide to move each SIP to
|
||||
a Recommended status or a Rejected status based on whether or not the Board
|
||||
believes that the SIP is feasible, practical, and beneficial to the greater
|
||||
Stacks ecosystem.
|
||||
|
||||
Any feedback created shall be made public. It is the responsibility of the Board
|
||||
to store and publish all feedbacks for the SIPs it reviews. It shall forward
|
||||
copies of this feedback to both the SIP authors.
|
||||
|
||||
##### Consultation with the Steering Committee
|
||||
|
||||
The Steering Committee may need to
|
||||
follow up with the Consideration Advisory Board in order to clarify its position
|
||||
or solicit its advice on a particular SIP. For example, the SC may determine
|
||||
that a Recommended SIP needs to be considered by one or more additional Boards
|
||||
that have not yet been consulted by the SIP authors.
|
||||
|
||||
The Board shall respond to the SC's request for advice in a timely manner, and
|
||||
shall prioritize feedback on SIPs that are under consideration for ratification.
|
||||
|
||||
### SIP Editor Duties
|
||||
|
||||
By far the largest committee in the SIP process is the SIP Editor Committee.
|
||||
The SIP Editors are responsible for maintaining the "inbound funnel" for SIPs
|
||||
from the greater Stacks community. SIP Editors ensure that all inbound SIPs are
|
||||
well-formed, relevant, and do not duplicate prior work (including rejected
|
||||
SIPs).
|
||||
|
||||
#### Membership
|
||||
|
||||
Anyone may become a SIP Editor by recommendation from an existing SIP
|
||||
Editor, subject to the “Recruitment” section below.
|
||||
|
||||
#### Qualifications
|
||||
|
||||
A SIP Editor must demonstrate proficiency in the SIP process and
|
||||
formatting requirements. A candidate SIP Editor must demonstrate to an existing
|
||||
SIP Editor that they can independently vet SIPs.
|
||||
|
||||
#### Responsibilities
|
||||
|
||||
SIP Editors are concerned with shepherding SIPs from Draft
|
||||
status to Accepted status, and for mentoring community members who want to get
|
||||
involved with the SIP processes (as applicable).
|
||||
|
||||
##### Getting Users Started
|
||||
|
||||
SIP Editors should be open and welcoming towards
|
||||
enthusiastic users who want to help improve the greater Stacks ecosystem. As
|
||||
such, SIP Editors should encourage users to submit SIPs if they have good ideas
|
||||
that may be worth implementing.
|
||||
|
||||
In addition, SIP Editors should respond to public requests for help from
|
||||
community members who want to submit a SIP. They may point them towards this
|
||||
document, or towards other supplemental documents and tools to help them get
|
||||
started.
|
||||
|
||||
##### Feedback
|
||||
|
||||
When a SIP is submitted in Draft status, a SIP Editor that takes the
|
||||
SIP into consideration should provide fair and full feedback on how to make the
|
||||
SIP ready for its transition to Accepted status.
|
||||
|
||||
To do this, the SIP Editor should:
|
||||
|
||||
- Verify that the SIP is well-formed according to the criteria in this document
|
||||
- Verify that the SIP has not been proposed before
|
||||
- Verify as best that they can that the SIP is original work
|
||||
- Verify that the SIP is appropriate for its type and consideration
|
||||
- Recommend additional Considerations if appropriate
|
||||
- Ensure that the text is clear, concise, and grammatically-correct English
|
||||
- Ensure that there are appropriate avenues for discussion of the SIP listed in
|
||||
the preamble.
|
||||
|
||||
The SIP Editor does not need to provide public feedback to the SIP authors, but
|
||||
should add their name(s) to the Signed-off field in the SIP preamble once the
|
||||
SIP is ready to be Accepted.
|
||||
|
||||
##### Acceptance
|
||||
|
||||
Once a SIP is moved to Accepted, the SIP Editor shall assign it the
|
||||
smallest positive number not currently used to identify any other SIP. Once that
|
||||
number is known, the SIP Editor shall set the SIP's status to Accepted, set the
|
||||
number, and commit the SIP to the SIP repository in order to make it visible to
|
||||
other SIP Editors and to the Consideration Advisory Boards.
|
||||
|
||||
##### Recruitment
|
||||
|
||||
Each SIP Editor must list their name and contact information in an
|
||||
easy-to-find location in the SIP repository, as well list of each SIP Editor
|
||||
they recommended. In so doing, the SIP Editors shall curate an “invite tree”
|
||||
that shows which Editors recommended which other Editors.
|
||||
|
||||
A SIP Editor may recommend another user to be a SIP Editor no more than once per
|
||||
month, and only if they have faithfully moved at least one SIP to Accepted
|
||||
status in the last quarter. If a SIP Editor does not participate in editing a
|
||||
SIP for a full year and a day, then they may be removed from the SIP Editor
|
||||
list. The SC may remove a SIP Editor (and some or all of the users he or she
|
||||
recommended) if they find that the SIP Editor has violated the SIP Code of
|
||||
Conduct.
|
||||
|
||||
Newly-Accepted SIPs, new SIP Editor recruitment, and SIP Editor retirement shall
|
||||
be submitted as pull requests by SIP Editors to the SIP repository.
|
||||
|
||||
## SIP Workflow
|
||||
|
||||
The lifecycle of a SIP is summarized in the flow-chart below:
|
||||
|
||||
```
|
||||
------------------
|
||||
| Draft | <-------------------------. Revise and resubmit
|
||||
------------------ |
|
||||
| --------------------
|
||||
Submit to SIP Editor -------------> | Rejected |
|
||||
| --------------------
|
||||
| ^
|
||||
V |
|
||||
------------------ |
|
||||
| Accepted | -------------------------/ | /--------------------------------.
|
||||
------------------ | |
|
||||
| -------------------- |
|
||||
Review by Consideration ----------> | Rejected | |
|
||||
Advisory Board(s) -------------------- |
|
||||
| ^ |
|
||||
V | |
|
||||
------------------------- | |
|
||||
| Recommended | -----------------/ | /------------------------------->|
|
||||
------------------------- | |
|
||||
| -------------------- |
|
||||
Vote by the Steering -----------> | Rejected | |
|
||||
Committee for activation -------------------- |
|
||||
| ^ |
|
||||
V | |
|
||||
-------------------------- | |
|
||||
| Activation-in-Progress | -----------------/ | /------------------------------->|
|
||||
-------------------------- | |
|
||||
| --------------------- |
|
||||
All activation ------------------> | Rejected | |
|
||||
criteria are met | --------------------- ------------------ |
|
||||
| |----------------------------------> | Obsolete | |
|
||||
V | --------------------- ------------------ |
|
||||
------------------ *---> | Replaced | --------------->|<-----------*
|
||||
| Ratified | --------------------- |
|
||||
------------------ V
|
||||
-------------------
|
||||
| Withdrawn |
|
||||
-------------------
|
||||
```
|
||||
|
||||
When a SIP is transitioned to Rejected, it is not deleted, but is preserved in
|
||||
the SIP repository so that it can be referenced as related or prior work by
|
||||
other SIPs. Once a SIP is Rejected, it may be re-submitted as a Draft at a later
|
||||
date. SIP Editors may decide how often to re-consider rejected SIPs as an
|
||||
anti-spam measure, but the Steering Committee and Consideration Advisory Boards
|
||||
may opt to independently re-consider rejected SIPs at their own discretion.
|
||||
|
||||
## Public Venues for Conducting Business
|
||||
|
||||
The canonical set of SIPs in all state shall be recorded in the same medium that
|
||||
the canonical copy of this SIP is. Right now, this is in the Github repository
|
||||
https://github.com/stacksorg/sips, but may be changed before this SIP is
|
||||
ratified. New SIPs, edits to SIPs, comments on SIPs, and so on shall be
|
||||
conducted through Github's facilities for the time being.
|
||||
|
||||
In addition, individual committees may set up and use public mailing lists for
|
||||
conducting business. The Stacks Open Internet Foundation shall provide a means
|
||||
for doing so. Any discussions on the mailing lists that lead to non-trivial
|
||||
contributions to SIPs should be referenced by these SIPs as supplemental
|
||||
material.
|
||||
|
||||
### Github-specific Considerations
|
||||
|
||||
All SIPs shall be submitted as pull requests, and all SIP edits (including status
|
||||
updates) shall be submitted as pull requests. The SC, or one or more
|
||||
individuals or entities appointed by the SC, shall be responsible for merging
|
||||
pull requests to the main branch.
|
||||
|
||||
## SIP Copyright & Licensing
|
||||
|
||||
Each SIP must identify at least one acceptable license in its preamble. Source
|
||||
code in the SIP can be licensed differently than the text. SIPs whose reference
|
||||
implementation(s) touch existing reference implementation(s) must use the same
|
||||
license as the existing implementation(s) in order to be considered. Below is a
|
||||
list of recommended licenses.
|
||||
|
||||
- BSD-2-Clause: OSI-approved BSD 2-clause license
|
||||
- BSD-3-Clause: OSI-approved BSD 3-clause license
|
||||
- CC0-1.0: Creative Commons CC0 1.0 Universal
|
||||
- GNU-All-Permissive: GNU All-Permissive License
|
||||
- GPL-2.0+: GNU General Public License (GPL), version 2 or newer
|
||||
- LGPL-2.1+: GNU Lesser General Public License (LGPL), version 2.1 or newer
|
||||
|
||||
# Related Work
|
||||
|
||||
The governance process proposed in this SIP is inspired by the Python PEP
|
||||
process [1], the Bitcoin BIP2 process [2], the Ethereum Improvement Proposal [3]
|
||||
processes, the Zcash governance process [4], and the Debian GNU/Linux
|
||||
distribution governance process [5]. This SIP describes a governance process
|
||||
where top-level decision-making power is vested in a committee of elected
|
||||
representatives, which distinguishes it from Debian (which has a single elected
|
||||
project leader), Python (which has a benevolent dicator for life), and Bitcoin
|
||||
and ZCash (which vest all decision ratification power solely in the blockchain
|
||||
miners). The reason for a top-level steering committee is to ensure that
|
||||
decision-making power is not vested in a single individual, but also to ensure
|
||||
that the individuals responsible for decisions are accountable to the community
|
||||
that elects them (as opposed to only those who have the means to participate
|
||||
in mining). This SIP differs from Ethereum's governance
|
||||
process in that the top-level decision-making body (the "Core Devs" in Ethereum,
|
||||
and the Steering Committee in Stacks) is not only technically proficient to evaluate
|
||||
SIPs, but also held accountable through an official governance
|
||||
process.
|
||||
|
||||
[1] https://www.python.org/dev/peps/pep-0001/
|
||||
|
||||
[2] https://github.com/bitcoin/bips/blob/master/bip-0002.mediawiki
|
||||
|
||||
[3] https://eips.ethereum.org/
|
||||
|
||||
[4] https://www.zfnd.org/governance/
|
||||
|
||||
[5] https://debian-handbook.info/browse/stable/sect.debian-internals.html
|
||||
|
||||
# Activation
|
||||
|
||||
This SIP activates once following tasks have been carried out:
|
||||
|
||||
- The provisional Steering Committee must be appointed by the Stacks Open Internet
|
||||
Foundation Board.
|
||||
- Mailing lists for the initial committees must be created.
|
||||
- The initial Consideration Advisory Boards must be formed, if there is interest
|
||||
in doing so before this SIP activates.
|
||||
- A public, online SIP repository must be created to hold all non-Draft SIPs, their edit
|
||||
histories, and their feedbacks.
|
||||
- A directory of Consideration Advisory Boards must be established (e.g. within
|
||||
the SIP repository).
|
||||
- A SIP Code of Conduct should be added as a supplemental document
|
||||
- The Stacks blockchain mainnet must launch.
|
||||
|
||||
# Reference Implementation
|
||||
|
||||
Not applicable.
|
||||
|
||||
# Frequently Asked Questions
|
||||
|
||||
NOTE: this section will be expanded as necessary before ratification
|
||||
This SIP is now located in the [stacksgov/sips repository](https://github.com/stacksgov/sips/blob/main/sips/sip-000/sip-000-stacks-improvement-proposal-process.md) as part of the [Stacks Community Governance organization](https://github.com/stacksgov).
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,747 +1,5 @@
|
||||
# Abstract
|
||||
# SIP-002 Smart Contract Language
|
||||
|
||||
In order to support applications which require validation of some
|
||||
pieces of their logic, we present a smart contracting language for use
|
||||
with the Stacks blockchain. This smart contracting language can be
|
||||
used on the Stacks blockchain to support programatic control over
|
||||
digital assets within the Stacks blockchain (e.g., BNS names, Stacks
|
||||
tokens, etc.)
|
||||
This document formerly contained SIP-002 before the Stacks 2.0 mainnet launched.
|
||||
|
||||
While application-chains may use any smart-contract language that they
|
||||
like, this smart contracting language's VM will be a part of
|
||||
blockstack-core, and, as such, any blockstack-core node will be able to
|
||||
validate application chains using this smart contracting language with
|
||||
a simple configuration change.
|
||||
|
||||
This smart contracting language permits static analysis of any legal
|
||||
smart contract to determine runtime costs. This smart contracting
|
||||
language is not only Turing-incomplete (a requirement for such static
|
||||
analysis to be guaranteed successful), but readily permits other kinds
|
||||
of proofs to be made about the code as well.
|
||||
|
||||
# Design
|
||||
|
||||
A smart contract is composed of two parts:
|
||||
|
||||
1. A data-space, which is a set of tables of data which only the
|
||||
smart contract may modify
|
||||
2. A set of functions which operate within the data-space of the
|
||||
smart contract, though they may call public functions from other smart
|
||||
contracts.
|
||||
|
||||
Users call smart contracts' public functions by broadcasting a
|
||||
transaction on the blockchain which invokes the public function.
|
||||
|
||||
This smart contracting language differs from most other smart
|
||||
contracting languages in two important ways:
|
||||
|
||||
1. The language _is not_ intended to be compiled. The LISP language
|
||||
described in this document is the specification for correctness.
|
||||
2. The language _is not_ Turing complete. This allows us to guarantee
|
||||
that static analysis of programs to determine properties like
|
||||
runtime cost and data usage can complete successfully.
|
||||
|
||||
## Specifying Contracts
|
||||
|
||||
A smart contract definition is specified in a LISP language with the
|
||||
following limitations:
|
||||
|
||||
1. Recursion is illegal and there is no `lambda` function.
|
||||
2. Looping may only be performed via `map`, `filter`, or `fold`
|
||||
3. The only atomic types are booleans, integers, fixed length
|
||||
buffers, and principals
|
||||
4. There is additional support for lists of the atomic types, however
|
||||
the only variable length lists in the language appear as function
|
||||
inputs (i.e., there is no support for list operations like append
|
||||
or join).
|
||||
5. Variables may only be created via `let` binding and there
|
||||
is no support for mutating functions like `set`.
|
||||
6. Defining of constants and functions are allowed for simplifying
|
||||
code using `define-private` statement. However, these are purely
|
||||
syntactic. If a definition cannot be inlined, the contract will be
|
||||
rejected as illegal. These definitions are also _private_, in that
|
||||
functions defined this way may only be called by other functions
|
||||
defined in the given smart contract.
|
||||
7. Functions specified via `define-public` statements are _public_
|
||||
functions.
|
||||
8. Functions specified via `define-read-only` statements are _public_
|
||||
functions and perform _no_ state mutations. Any attempts to
|
||||
modify contract state by these functions or functions called by
|
||||
these functions will result in an error.
|
||||
|
||||
Public functions return a Response type result. If the function returns
|
||||
an `ok` type, then the function call is considered valid, and any changes
|
||||
made to the blockchain state will be materialized. If the function
|
||||
returns an `err` type, it will be considered invalid, and will have _no
|
||||
effect_ on the smart contract's state. So if function `foo.A` calls
|
||||
`bar.B`, and `bar.B` returns an `ok`, but `foo.A` returns an `err`, no
|
||||
effects from calling `foo.A` materialize--- including effects from
|
||||
`bar.B`. If, however, `bar.B` returns an `err` and `foo.A` returns an `ok`,
|
||||
there may be some database effects which are materialized from
|
||||
`foo.A`, but _no_ effects from calling `bar.B` will materialize.
|
||||
|
||||
Unlike functions created by `define-public`, which may only return
|
||||
Response types, functions created with `define-read-only` may return
|
||||
any type.
|
||||
|
||||
## List Operations
|
||||
|
||||
* Lists may be multi-dimensional (i.e., lists may contain other lists), however each
|
||||
entry of this list must be of the same type.
|
||||
* `filter` `map` and `fold` functions may only be called with user-defined functions
|
||||
(i.e., functions defined with `(define-private ...)`, `(define-read-only ...)`, or
|
||||
`(define-public ...)`) or simple native functions (e.g., `+`, `-`, `not`).
|
||||
* Functions that return lists of a different size than the input size
|
||||
(e.g., `(append-item ...)`) take a required _constant_ parameter that indicates
|
||||
the maximum output size of the function. This is enforced with a runtime check.
|
||||
|
||||
## Inter-Contract Calls
|
||||
|
||||
A smart contract may call functions from other smart contracts using a
|
||||
`(contract-call?)` function.
|
||||
|
||||
This function returns a Response type result-- the return value of the called smart
|
||||
contract function. Note that if a called smart contract returns an
|
||||
`err` type, it is guaranteed to not alter any smart contract state
|
||||
whatsoever. Of course, any transaction fees paid for the execution
|
||||
of that function will not be returned.
|
||||
|
||||
We distinguish 2 different types of `contract-call?`:
|
||||
|
||||
* Static dispatch: the callee is a known, invariant contract available
|
||||
on-chain when the caller contract is being deployed. In this case, the
|
||||
callee's principal is provided as first argument, followed by the name
|
||||
of the method and its arguments:
|
||||
|
||||
```scheme
|
||||
(contract-call?
|
||||
'SC3H92H297DX3YDPFHZGH90G8Z4NPH4VE8E83YWAQ.registrar
|
||||
register-name
|
||||
name-to-register)
|
||||
```
|
||||
|
||||
This approach must always be preferred, when adequate.
|
||||
It makes static analysis easier, and eliminates the
|
||||
potential for reentrancy bugs when the contracts are
|
||||
being published (versus when being used).
|
||||
|
||||
* Dynamic dispatch: the callee is passed as an argument, and typed
|
||||
as a trait reference (<A>).
|
||||
|
||||
```scheme
|
||||
(define-public (swap (token-a <can-transfer-tokens>)
|
||||
(amount-a uint)
|
||||
(owner-a principal)
|
||||
(token-b <can-transfer-tokens>)
|
||||
(amount-b uint)
|
||||
(owner-b principal)))
|
||||
(begin
|
||||
(unwrap! (contract-call? token-a transfer-from? owner-a owner-b amount-a))
|
||||
(unwrap! (contract-call? token-b transfer-from? owner-b owner-a amount-b))))
|
||||
```
|
||||
|
||||
Traits can either be locally defined:
|
||||
|
||||
```scheme
|
||||
(define-trait can-transfer-tokens (
|
||||
(transfer-from? (principal principal uint) (response uint)))
|
||||
```
|
||||
|
||||
Or imported from an existing contract:
|
||||
|
||||
```scheme
|
||||
(use-trait can-transfer-tokens
|
||||
'SC3H92H297DX3YDPFHZGH90G8Z4NPH4VE8E83YWAQ.contract-defining-trait.can-transfer-tokens)
|
||||
```
|
||||
|
||||
Looking at trait conformance, callee contracts have two different paths.
|
||||
They can either be "compatible" with a trait by defining methods
|
||||
matching some of the methods defined in a trait, or explicitely declare
|
||||
conformance using the `impl-trait` statement:
|
||||
|
||||
```scheme
|
||||
(impl-trait 'SC3H92H297DX3YDPFHZGH90G8Z4NPH4VE8E83YWAQ.contract-defining-trait.can-transfer-tokens)
|
||||
```
|
||||
|
||||
Explicit conformance should be prefered when adequate.
|
||||
It acts as a safeguard by helping the static analysis system to detect
|
||||
deviations in method signatures before contract deployment.
|
||||
|
||||
The following limitations are imposed on contract calls:
|
||||
|
||||
1. On static dispatches, callee smart contracts _must_ exist at the time of creation.
|
||||
2. No cycles may exist in the call graph of a smart contract. This
|
||||
prevents recursion (and re-entrancy bugs). Such structures can
|
||||
be detected with static analysis of the call graph, and will be
|
||||
rejected by the network.
|
||||
3. `contract-call?` are for inter-contract calls only. Situations
|
||||
where the caller is also the callee will result in abortion of
|
||||
the ongoing transaction.
|
||||
|
||||
## Principals and Owner Verification
|
||||
|
||||
The language provides a primitive for checking whether or not the
|
||||
smart contract transaction was signed by a particular
|
||||
_principal_. Principals are a specific type in the smart contracting
|
||||
language which represent a spending entity (roughly equivalent to a
|
||||
Stacks address). The signature itself is not checked by the smart
|
||||
contract, but by the VM. A smart contract function can use a globally
|
||||
defined variable to obtain the current principal:
|
||||
|
||||
```scheme
|
||||
tx-sender
|
||||
```
|
||||
|
||||
The `tx-sender` variable does not change during inter-contract
|
||||
calls. This means that if a transaction invokes a function in a given
|
||||
smart contract, that function is able to make calls into other smart
|
||||
contracts without that variable changing. This enables a wide variety
|
||||
of applications, but it comes with some dangers for users of smart
|
||||
contracts. However, as mentioned before, the static analysis
|
||||
guarantees of our smart contracting language allow clients to know a
|
||||
priori which functions a given smart contract will ever call.
|
||||
|
||||
Another global variable, `contract-caller`, _does_ change during
|
||||
inter-contract calls. In particular, `contract-caller` is the contract
|
||||
principal corresponding to the most recent invocation of `contract-call?`.
|
||||
In the case of a "top-level" invocation, this variable is equal to `tx-sender`.
|
||||
|
||||
Assets in the smart contracting language and blockchain are
|
||||
"owned" by objects of the principal type, meaning that any object of
|
||||
the principal type may own an asset. For the case of public-key hash
|
||||
and multi-signature Stacks addresses, a given principal can operate on
|
||||
their assets by issuing a signed transaction on the blockchain. _Smart
|
||||
contracts_ may also be principals (reprepresented by the smart
|
||||
contract's identifier), however, there is no private key associated
|
||||
with the smart contract, and it cannot broadcast a signed transaction
|
||||
on the blockchain.
|
||||
|
||||
In order to allow smart contracts to operate on assets it owns, smart
|
||||
contracts may use the special function:
|
||||
|
||||
```scheme
|
||||
(as-contract (...))
|
||||
```
|
||||
|
||||
This function will execute the closure (passed as an argument) with
|
||||
the `tx-sender` and `contract-caller` set to the _contract's_
|
||||
principal, rather than the current sender. It returns the return value
|
||||
of the provided closure. A smart contract may use the special variable
|
||||
`contract-principal` to refer to its own principal.
|
||||
|
||||
For example, a smart contract that implements something like a "token
|
||||
faucet" could be implemented as so:
|
||||
|
||||
```scheme
|
||||
(define-public (claim-from-faucet)
|
||||
(if (is-none? (map-get claimed-before (tuple (sender tx-sender))))
|
||||
(let ((requester tx-sender)) ;; set a local variable requester = tx-sender
|
||||
(map-insert! claimed-before (tuple (sender requester)) (tuple (claimed true)))
|
||||
(as-contract (stacks-transfer! requester 1))))
|
||||
(err 1))
|
||||
```
|
||||
|
||||
Here, the public function `claim-from-faucet`:
|
||||
|
||||
1. Checks if the sender has claimed from the faucet before
|
||||
2. Assigns the tx sender to a requester variable
|
||||
3. Adds an entry to the tracking map
|
||||
4. Uses `as-contract` to send 1 microstack
|
||||
|
||||
The primitive function `is-contract?` can be used to determine
|
||||
whether a given principal corresponds to a smart contract.
|
||||
|
||||
## Stacks Transfer Primitives
|
||||
|
||||
To interact with Stacks balances, smart contracts may call the
|
||||
`(stacks-transfer!)` function. This function will attempt to transfer
|
||||
from the current principal to another principal:
|
||||
|
||||
|
||||
```scheme
|
||||
(stacks-transfer!
|
||||
to-send-amount
|
||||
recipient-principal)
|
||||
```
|
||||
|
||||
This function itself _requires_ that the operation have been signed by
|
||||
the transferring principal. The `integer` type in our smart contracting
|
||||
language is an 16-byte signed integer, which allows it to specify the
|
||||
maximum amount of microstacks spendable in a single Stacks transfer.
|
||||
|
||||
Like any other public smart contract function, this function call
|
||||
returns an `ok` if the transfer was successful, and `err` otherwise.
|
||||
|
||||
## Data-Space Primitives
|
||||
|
||||
Data within a smart contract's data-space is stored within
|
||||
`maps`. These stores relate a typed-tuple to another typed-tuple
|
||||
(almost like a typed key-value store). As opposed to a table data
|
||||
structure, a map will only associate a given key with exactly one
|
||||
value. Values in a given mapping are set or fetched using:
|
||||
|
||||
1. `(map-get map-name key-tuple)` - This fetches the value
|
||||
associated with a given key in the map, or returns `none` if there
|
||||
is no such value.
|
||||
2. `(map-set! map-name key-tuple value-tuple)` - This will set the
|
||||
value of `key-tuple` in the data map
|
||||
3. `(map-insert! map-name key-tuple value-tuple)` - This will set
|
||||
the value of `key-tuple` in the data map if and only if an entry
|
||||
does not already exist.
|
||||
4. `(map-delete! map-name key-tuple)` - This will delete `key-tuple`
|
||||
from the data map
|
||||
|
||||
We chose to use data maps as opposed to other data structures for two
|
||||
reasons:
|
||||
|
||||
1. The simplicity of data maps allows for both a simple implementation
|
||||
within the VM, and easier reasoning about functions. By inspecting a
|
||||
given function definition, it is clear which maps will be modified and
|
||||
even within those maps, which keys are affected by a given invocation.
|
||||
2. The interface of data maps ensures that the return types of map
|
||||
operations are _fixed length_, which is a requirement for static
|
||||
analysis of smart contracts' runtime, costs, and other properties.
|
||||
|
||||
A smart contract defines the data schema of a data map with the
|
||||
`define-map` call. The `define-map` function may only be called in the
|
||||
top-level of the smart-contract (similar to `define-private`). This
|
||||
function accepts a name for the map, and a definition of the structure
|
||||
of the key and value types. Each of these is a list of `(name, type)`
|
||||
pairs, and they specify the input and output type of `map-get`.
|
||||
Types are either the values `'principal`, `'integer`, `'bool` or
|
||||
the output of a call to `(buffer n)`, which defines an n-byte
|
||||
fixed-length buffer.
|
||||
|
||||
This interface, as described, disallows range-queries and
|
||||
queries-by-prefix on data maps. Within a smart contract function,
|
||||
you cannot iterate over an entire map.
|
||||
|
||||
### Record Type Syntax
|
||||
|
||||
To support the use of _named_ fields in keys and values, our language
|
||||
allows the construction of named tuples using a function `(tuple ...)`,
|
||||
e.g.,
|
||||
|
||||
```
|
||||
(define-constant imaginary-number-a (tuple (real 1) (i 2)))
|
||||
(define-constant imaginary-number-b (tuple (real 2) (i 3)))
|
||||
|
||||
```
|
||||
|
||||
This allows for creating named tuples on the fly, which is useful for
|
||||
data maps where the keys and values are themselves named tuples. To
|
||||
access a named value of a given tuple, the function `(get #name
|
||||
tuple)` will return that item from the tuple.
|
||||
|
||||
### Time-shifted Evaluations
|
||||
|
||||
The Stacks language supports _historical_ data queries using the
|
||||
`(at-block)` function:
|
||||
|
||||
```
|
||||
(at-block 0x0101010101010101010101010101010101010101010101010101010101010101
|
||||
; returns owner principal of name represented by integer 12013
|
||||
; at the time of block 0x010101...
|
||||
(map-get name-map 12013))
|
||||
```
|
||||
|
||||
This function evaluates the supplied closure as if evaluated at the end of
|
||||
the supplied block, returning the resulting value. The supplied
|
||||
closure _must_ be read-only (is checked by the analysis).
|
||||
|
||||
The supplied block hash must correspond to a known block in the same
|
||||
fork as the current block, otherwise a runtime error will occur and the
|
||||
containing transaction will _fail_. Note that if the supplied block
|
||||
pre-dates any of the data structures being read within the closure (i.e.,
|
||||
the block is before the block that constructed a data map), a runtime
|
||||
error will occur and the transaction will _fail_.
|
||||
|
||||
## Library Support and Syntactic Sugar
|
||||
|
||||
There are a number of ways that the developer experience can be
|
||||
improved through the careful addition of improved syntax. For example,
|
||||
while the only atomic types supported by the smart contract language
|
||||
are integers, buffers, booleans, and principals, so if a developer
|
||||
wishes to use a buffer to represent a fixed length string, we should
|
||||
support syntax for representing a buffer literal using something like
|
||||
an ASCII string. Such support should also be provided by transaction
|
||||
generation libraries, where buffer arguments may be supplied strings
|
||||
which are then automatically converted to buffers. There are many
|
||||
possible syntactic improvements and we expect that over the course
|
||||
of developing the prototype, we will have a better sense for which
|
||||
of those improvements we should support. Any such synactic changes
|
||||
will appear in an eventual language specification, but we believe
|
||||
them to be out of scope for this proposal.
|
||||
|
||||
# Static Analysis
|
||||
|
||||
One of the design goals of our smart contracting language was the
|
||||
ability to statically analyze smart contracts to obtain accurate
|
||||
upper-bound estimates of transaction costs (i.e., runtime and storage
|
||||
requirements) as a function of input lengths. By limiting the types
|
||||
supported, the ability to recurse, and the ability to iterate, we
|
||||
believe that the language as presented is amenable to such static
|
||||
analysis based on initial investigations.
|
||||
|
||||
The essential step in demonstrating the possibility of accurate and
|
||||
useful analysis of our smart contract definitions is demonstrating
|
||||
that any function within the language specification has an output
|
||||
length bounded by a constant factor of the input length. If we can
|
||||
demonstrate this, then statically computing runtime or space
|
||||
requirements involves merely associating each function in the language
|
||||
specification with a way to statically determine cost as a function of
|
||||
input length.
|
||||
|
||||
Notably, the fact that the cost functions produced by static analysis
|
||||
are functions of _input length_ means the following things:
|
||||
|
||||
1. The cost of a cross-contract call can be "memoized", such
|
||||
that a static analyzer _does not_ need to recompute any
|
||||
static analysis on the callee when analyzing a caller.
|
||||
2. The cost of a given public function on a given input size
|
||||
_is always the same_, meaning that smart contract developers
|
||||
do not need to reason about different cases in which a given
|
||||
function may cost more or less to execute.
|
||||
|
||||
## Bounding Function Output Length
|
||||
|
||||
Importantly, our smart contracting language does not allow the
|
||||
creation of variable length lists: there are no `list` or
|
||||
`cons` constructors, and buffer lengths must be statically
|
||||
defined. Under such requirements (and given that recursion is
|
||||
illegal), determining the output lengths of functions is rather
|
||||
directly achievable. To see this, we'll examine trying to compute the
|
||||
output lengths for the only functions allowed to iterate in the
|
||||
language:
|
||||
|
||||
```
|
||||
outputLen(map f list<t>) := Len(list<t>) * outputLen(f t)
|
||||
outputLen(filter f list<t>) := Len(list<t>)
|
||||
outputLen(fold f list<t> s) := Len(s)
|
||||
```
|
||||
|
||||
Many functions within the language will output values larger than the
|
||||
function's input, _however_, these outputs will be bound by
|
||||
statically inferable constants. For example, the data function
|
||||
_map-get_ will always return an object whose size is equal
|
||||
to the specified value type of the map.
|
||||
|
||||
A complete proof for the static runtime analysis of smart contracts
|
||||
will be included with the implementation of the language.
|
||||
|
||||
# Deploying the Smart Contract
|
||||
|
||||
Smart contracts on the Stacks blockchain will be deployed directly as
|
||||
source code. The goal of the smart contracting language is that the
|
||||
code of the contract defines the _ground truth_ about the intended
|
||||
functionality of the contract. While seemingly banal, many systems
|
||||
chose instead to use a compiler to translate from a friendly
|
||||
high-level language to a lower-level language deployed on the
|
||||
blockchain. Such an architecture is needlessly dangerous. A bug in
|
||||
such a compiler could lead to a bug in a deployed smart contract when
|
||||
no such bug exists in the original source. This is problematic for
|
||||
recovery --- a hard fork to "undo" any should-have-been invalid
|
||||
transactions would be contentious and potentially create a rift in the
|
||||
community, especially as it will not be easy to deduce which contracts
|
||||
exactly were affected and for how long. In contrast, bugs in the VM
|
||||
itself present a more clear case for a hard fork: the smart contract
|
||||
was defined correctly, as everyone can see directly on the chain, but
|
||||
illegal transactions were incorrectly marked as valid.
|
||||
|
||||
# Virtual Machine API
|
||||
|
||||
From the perspective of other components of `blockstack-core`, the
|
||||
smart contracting VM will provide the following interface:
|
||||
|
||||
```
|
||||
connect-to-database(db)
|
||||
|
||||
publish-contract(
|
||||
contract-source-code)
|
||||
|
||||
returns: contract-identifier
|
||||
|
||||
execute-contract(
|
||||
contract-identifier,
|
||||
transaction-name,
|
||||
sender-principal,
|
||||
transaction-arguments)
|
||||
|
||||
returns: true or false if the transaction executed successfully
|
||||
```
|
||||
|
||||
## Invocation and Static Analysis
|
||||
|
||||
When processing a client transaction, a `blockstack-core` node will do
|
||||
one of two things, depending on whether that transaction is a contract
|
||||
function invocation, or is attempting to publish a new smart contract.
|
||||
|
||||
### Contract function invocation
|
||||
|
||||
Any transaction which invokes a smart contract will be included in the
|
||||
blockchain. This is true even for transactions which are
|
||||
_invalid_. This is because _validating_ an invalid transaction is not
|
||||
a free operation. The only exceptions to this are transactions which
|
||||
do not pay more than either a minimum fee or a storage fee
|
||||
corresponding to the length of the transaction. Transactions which do
|
||||
not pay a storage fee and clear the minimum transaction fee are
|
||||
dropped from the mempool.
|
||||
|
||||
To process a function invocation, `blockstack-core` does the following:
|
||||
|
||||
1. Get the balance of the sender's account. If it's less than the tx fee,
|
||||
then `RETURN INVALID`.
|
||||
2. Otherwise, debit the user's account by the tx fee.
|
||||
3. Look up the contract by hash. If it does not exist, then `RETURN
|
||||
INVALID`.
|
||||
4. Look up the contract's `define-public` function and compare the
|
||||
tx's arguments against it. If the tx does not call an existing
|
||||
method, or supplies invalid arguments, then `RETURN INVALID`.
|
||||
5. Look up the cost to execute the given function, and if it is greater
|
||||
than the paid tx fee, `RETURN INVALID`.
|
||||
6. Execute the public function code and commit the effects of running
|
||||
the code and `RETURN OK`
|
||||
|
||||
### Publish contract
|
||||
|
||||
A transaction which creates a new smart contract must pay a fee which
|
||||
funds the static analysis required to determine the cost of the new
|
||||
smart contract's public functions. To process such a transaction,
|
||||
`blockstack-core` will:
|
||||
|
||||
1. Check the sender's account balance. If zero, then `RETURN INVALID`
|
||||
2. Check the tx fee against the user's balance. If it's higher, then `RETURN INVALID`
|
||||
3. Debit the tx fee from the user's balance.
|
||||
4. Check the syntax, calculating the fee of verifying each code
|
||||
item. If the cost of checking the next item exceeds the tx fee, or
|
||||
if the syntax is invalid, then `RETURN INVALID`.
|
||||
5. Build the AST, and assign a fee for adding each AST item. If the
|
||||
cost of adding the next item to the tree exceeds the tx fee (or if
|
||||
the AST gets too big), then `RETURN INVALID`.
|
||||
6. Walk the AST. Each step in the walk incurs a small fee. Do the
|
||||
following while the tx fee is higher than the total cost incurred
|
||||
by walking to the next node in the AST:
|
||||
a. If the next node calls a contract method, then verify that
|
||||
the contract exists and the method arguments match the contract's
|
||||
`define-public` signature. If not, then `RETURN INVALID`.
|
||||
b. Compute the runtime cost of each node in the AST, adding it
|
||||
to the function's cost analysis.
|
||||
7. Find all `define-map` calls to find all tables that need to
|
||||
exist. Each step in this incurs a small fee.
|
||||
8. Create all the tables if the cost of creating them is smaller than
|
||||
the remaining tx fee. If not, then RETURN INVALID.
|
||||
9. `RETURN OK`
|
||||
|
||||
## Database Requirements and Transaction Accounting
|
||||
|
||||
The smart contract VM needs to interact with a database somewhat
|
||||
directly: the effects of an `map-insert!` or `map-set!` call are
|
||||
realized later in the execution of the same transaction. The database
|
||||
will need to support fairly fine-grained rollbacks as some contract
|
||||
calls within a transaction's execution may fail, triggering a
|
||||
rollback, while the transaction execution continues and successfully
|
||||
completes other database operations.
|
||||
|
||||
The database API provided to the smart contract VM, therefore, must be
|
||||
capable of (1) quickly responding to `map-get` queries, which are
|
||||
essentially simply key-value _gets_ on the materialized view of the
|
||||
operation log. The operation log itself is simply a log of the
|
||||
`map-insert!` and `map-set!` calls. In addition to these
|
||||
operations, the smart contract VM will be making token transfer calls.
|
||||
The databasse log should track those operations as well.
|
||||
|
||||
In order to aid in accounting for the database operations created by a
|
||||
given transaction, the underlying database should store, with each
|
||||
operation entry, the corresponding transaction identifier. This will
|
||||
be expanded in a future SIP to require the database to store enough
|
||||
information to reconstruct each block, such that the blocks can be
|
||||
relayed to bootstrapping peers.
|
||||
|
||||
# Clarity Type System
|
||||
|
||||
## Types
|
||||
|
||||
The Clarity language uses a strong static type system. Function arguments
|
||||
and database schemas require specified types, and use of types is checked
|
||||
during contract launch. The type system does _not_ have a universal
|
||||
super type. The type system contains the following types:
|
||||
|
||||
* `(tuple (key-name-0 key-type-0) (key-name-1 key-type-1) ...)` -
|
||||
a typed tuple with named fields.
|
||||
* `(list max-len entry-type)` - a list of maximum length `max-len`, with
|
||||
entries of type `entry-type`
|
||||
* `(response ok-type err-type)` - object used by public functions to commit
|
||||
their changes or abort. May be returned or used by other functions as
|
||||
well, however, only public functions have the commit/abort behavior.
|
||||
* `(optional some-type)` - an option type for objects that can either be
|
||||
`(some value)` or `none`
|
||||
* `(buff max-len)` := byte buffer or maximum length `max-len`.
|
||||
* `principal` := object representing a principal (whether a contract principal
|
||||
or standard principal).
|
||||
* `bool` := boolean value (`true` or `false`)
|
||||
* `int` := signed 128-bit integer
|
||||
* `uint` := unsigned 128-bit integer
|
||||
|
||||
## Type Admission
|
||||
|
||||
**UnknownType**. The Clarity type system does not allow for specifying
|
||||
an "unknown" type, however, in type analysis, unknown types may be
|
||||
constructed and used by the analyzer. Such unknown types are used
|
||||
_only_ in the admission rules for `response` and `optional` types
|
||||
(i.e., the variant types).
|
||||
|
||||
Type admission in Clarity follows the following rules:
|
||||
|
||||
* Types will only admit objects of the same type, i.e., lists will only
|
||||
admit lists, tuples only admit tuples, bools only admit bools.
|
||||
* A tuple type `A` admits another tuple type `B` iff they have the exact same
|
||||
key names, and every key type of `A` admits the corresponding key type of `B`.
|
||||
* A list type `A` admits another list type `B` iff `A.max-len >= B.max-len` and
|
||||
`A.entry-type` admits `B.entry-type`.
|
||||
* A buffer type `A` admits another buffer type `B` iff `A.max-len >= B.max-len`.
|
||||
* An optional type `A` admits another optional type `B` iff:
|
||||
* `A.some-type` admits `B.some-type` _OR_ `B.some-type` is an unknown type:
|
||||
this is the case if `B` only ever corresponds to `none`
|
||||
* A response type `A` admits another response type `B` if one of the following is true:
|
||||
* `A.ok-type` admits `B.ok-type` _AND_ `A.err-type` admits `B.err-type`
|
||||
* `B.ok-type` is unknown _AND_ `A.err-type` admits `B.err-type`
|
||||
* `B.err-type` is unknown _AND_ `A.ok-type` admits `B.ok-type`
|
||||
* Principals, bools, ints, and uints only admit types of the exact same type.
|
||||
|
||||
Type admission is used for determining whether an object is a legal argument for
|
||||
a function, or for insertion into the database. Type admission is _also_ used
|
||||
during type analysis to determine the return types of functions. In particular,
|
||||
a function's return type is the least common supertype of each type returned from any
|
||||
control path in the function. For example:
|
||||
|
||||
```
|
||||
(define-private (if-types (input bool))
|
||||
(if input
|
||||
(ok 1)
|
||||
(err false)))
|
||||
```
|
||||
|
||||
The return type of `if-types` is the least common supertype of `(ok
|
||||
1)` and `(err false)` (i.e., the most restrictive type that contains
|
||||
all returns). In this case, that type `(response int bool)`. Because
|
||||
Clarity _does not_ have a universal supertype, it may be impossible to
|
||||
determine such a type. In these cases, the functions are illegal, and
|
||||
will be rejected during type analysis.
|
||||
|
||||
# Measuring Transaction Costs for Fee Collection
|
||||
|
||||
Our smart contracting language admits static analysis to determine
|
||||
many properties of transactions _before_ executing those
|
||||
transactions. In particular, it allows for the VM to count the total
|
||||
number of runtime operations required, the maximum amount of database
|
||||
writes, and the maximum number of calls to any expensive primitive
|
||||
functions like database reads or hash computations. Translating that
|
||||
information into transaction costs, however, requires more than simply
|
||||
counting those operations. It requires translating the operations into
|
||||
a single cost metric (something like gas in Ethereum). Then, clients
|
||||
can set the fee rate for that metric, and pay the corresponding
|
||||
transaction fee. Notably, unlike Turing-complete smart contracting
|
||||
languages, any such fees are known _before_ executing the transaction,
|
||||
such that clients will no longer need to estimate gas fees. They will,
|
||||
however, still need to estimate fee rates (much like Bitcoin clients
|
||||
do today).
|
||||
|
||||
Developing such a cost metric is an important task that has
|
||||
significant consequences. If the metric is a bad one, it could open up
|
||||
the possibility of denial-of-service attacks against nodes in the
|
||||
Stacks network. We leave the development of a cost metric to another
|
||||
Stacks Improvement Proposal, as we believe that such a metric should
|
||||
be designed by collecting real benchmarking data from something close
|
||||
to a real system (such measurements will likely be collected through
|
||||
a combination of hand-crafted benchmarks and fuzzing test suites).
|
||||
|
||||
### Maximum Operation Costs and Object Sizes
|
||||
|
||||
Even with a cost metric, it is a good idea to set maximums for the
|
||||
cost of an operation, and the size of objects (like
|
||||
buffers). Developing good values for constants such as maximum number
|
||||
of database reads or writes per transaction, maximum size of buffers,
|
||||
maximum number of arguments to a tuple, maximum size of a smart
|
||||
contract definition, etc. is a process much like developing a
|
||||
cost metric--- this is something best done in tandem with the
|
||||
production of a prototype. However, we should note that we do intend
|
||||
to set such limits.
|
||||
|
||||
|
||||
# Example: Simple Naming System
|
||||
|
||||
To demonstrate the expressiveness of this smart contracting language,
|
||||
let's look at an example smart contract which implements a simple
|
||||
naming system with just two kinds of transactions: _preorder_ and
|
||||
_register_. The requirements of the system are as follows:
|
||||
|
||||
1. Names may only be owned by one principal
|
||||
2. A register is only allowed if there is a corresponding preorder
|
||||
with a matching hash
|
||||
3. A register transaction must be signed by the same principal who
|
||||
paid for the preorder
|
||||
4. A preorder must have paid at least the price of the name. Names
|
||||
are represented as integers, and any name less than 100000 costs
|
||||
1000 microstacks, while all other names cost 100 microstacks.
|
||||
5. Preorder hashs are _globally_ unique.
|
||||
|
||||
In this simple scheme, names are represented by integers, but in
|
||||
practice, a buffer would probably be used.
|
||||
|
||||
```scheme
|
||||
(define-constant burn-address '1111111111111111111114oLvT2)
|
||||
(define-private (price-function name)
|
||||
(if (< name 1e5) 1000 100))
|
||||
|
||||
(define-map name-map
|
||||
{ name: uint } { buyer: principal })
|
||||
(define-map preorder-map
|
||||
{ name-hash: (buff 160) }
|
||||
{ buyer: principal, paid: uint })
|
||||
|
||||
(define-public (preorder
|
||||
(name-hash (buffer 20))
|
||||
(name-price integer))
|
||||
(if (and (is-ok? (stacks-transfer!
|
||||
name-price burn-address))
|
||||
(map-insert! preorder-map
|
||||
(tuple (name-hash name-hash))
|
||||
(tuple (paid name-price)
|
||||
(buyer tx-sender))))
|
||||
(ok 0)
|
||||
(err 1)))
|
||||
|
||||
(define-public (register
|
||||
(recipient-principal principal)
|
||||
(name integer)
|
||||
(salt integer))
|
||||
(let ((preorder-entry
|
||||
(map-get preorder-map
|
||||
(tuple (name-hash (hash160 name salt)))))
|
||||
(name-entry
|
||||
(map-get name-map (tuple (name name)))))
|
||||
(if (and
|
||||
;; must be preordered
|
||||
(not (is-none? preorder-entry))
|
||||
;; name shouldn't *already* exist
|
||||
(is-none? name-entry)
|
||||
;; preorder must have paid enough
|
||||
(<= (price-funcion name)
|
||||
(default-to 0 (get paid preorder-entry)))
|
||||
;; preorder must have been the current principal
|
||||
(eq? tx-sender
|
||||
(expects! (get buyer preorder-entry) (err 1)))
|
||||
(map-insert! name-table
|
||||
(tuple (name name))
|
||||
(tuple (owner recipient))))
|
||||
(ok 0)
|
||||
(err 1))))
|
||||
```
|
||||
|
||||
|
||||
Note that Blockstack PBC intends to supply a full BNS (Blockstack
|
||||
Naming System) smart contract, as well as formal proofs that certain
|
||||
desirable properties hold (e.g. "names are globally unique", "a
|
||||
revoked name cannot be updated or transferred", "names cost stacks
|
||||
based on their namespace price function", "only the principal can
|
||||
reveal a name on registration", etc.).
|
||||
This SIP is now located in the [stacksgov/sips repository](https://github.com/stacksgov/sips/blob/main/sips/sip-002/sip-002-smart-contract-language.md) as part of the [Stacks Community Governance organization](https://github.com/stacksgov).
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,750 +1,5 @@
|
||||
# SIP 004 Cryptographic Committment to Materialized Views
|
||||
# SIP-004 Cryptographic Committment to Materialized Views
|
||||
|
||||
## Preamble
|
||||
This document formerly contained SIP-004 before the Stacks 2.0 mainnet launched.
|
||||
|
||||
Title: Cryptograhpic Commitment to Materialized Views
|
||||
|
||||
Author: Jude Nelson <jude@blockstack.com>
|
||||
|
||||
Status: Draft
|
||||
|
||||
Type: Standard
|
||||
|
||||
Created: 7/15/2019
|
||||
|
||||
License: BSD 2-Clause
|
||||
|
||||
## Abstract
|
||||
|
||||
Blockchain peers are replicated state machines, and as such, must maintain a
|
||||
materialized view of all of the state the transaction log represents in order to
|
||||
validate a subsequent transaction. The Stacks blockchain in particular not only
|
||||
maintains a materialized view of the state of every fork, but also requires
|
||||
miners to cryptographically commit to that view whenever they mine a block.
|
||||
This document describes a **Merklized Adaptive Radix Forest** (MARF), an
|
||||
authenticated index data structure for efficiently encoding a
|
||||
cryptographic commitment to blockchain state.
|
||||
|
||||
The MARF's structure is part of the consensus logic in the Stacks blockchain --
|
||||
every Stacks peer must process the MARF the same way. Stacks miners announce
|
||||
a cryptographic hash of their chain tip's MARF in the blocks they produce, and in
|
||||
doing so, demonstrate to each peer and each light client that they have
|
||||
applied the block's transactions to the peer's state correctly.
|
||||
|
||||
The MARF represents blockchain state as an authenticated directory. State is
|
||||
represented as key/value pairs. The MARF structure gives a peer the ability to
|
||||
prove to a light client that a particular key has a particular value, given the
|
||||
MARF's cryptographic hash. The proof has _O(log B)_ space for _B_ blocks, and
|
||||
takes _O(log B)_ time complexity to produce and verify. In addition, it offers
|
||||
_O(1)_ expected time and space complexity for inserts and queries.
|
||||
The MARF proof allows a light client to determine:
|
||||
|
||||
* What the value of a particular key is,
|
||||
* How much cumulative energy has been spent to produce the key/value pair,
|
||||
* How many confirmations the key/value pair has.
|
||||
|
||||
## Rationale
|
||||
|
||||
In order to generate a valid transaction, a blockchain client needs to be able
|
||||
to query the current state of the blockchain. For example, in Bitcoin, a client
|
||||
needs to query its unspent transaction outputs (UTXOs) in order to satisfy their
|
||||
spending conditions in a new transaction. As another example, in Ethereum, a
|
||||
client needs to query its accounts' current nonces in order to generate a valid
|
||||
transaction to spend their tokens.
|
||||
|
||||
Whether or not a blockchain's peers are required to commit to the current state
|
||||
in the blocks themselves (i.e. as part of the consensus logic) is a
|
||||
philosophical decision. We argue that it is a highly desirable in Blockstack's
|
||||
case, since it affords light clients more security when querying the blockchain state than
|
||||
not. This is because a client often queries state that was last updated several
|
||||
blocks in the past (i.e. and is "confirmed"). If a blockchain peer can prove to
|
||||
a client that a particular key in the state has a particular value, and was last
|
||||
updated a certain number of blocks in the past, then the client can determine
|
||||
whether or not to trust the peer's proof based on factors beyond simply trusting
|
||||
the remote peer to be honest. In particular, the client can determine how
|
||||
difficult it would be to generate a dishonest proof, in terms of the number of
|
||||
blocks that would need to be maliciously crafted and accepted by the network.
|
||||
This offers clients some protection against peers that would lie to them -- a
|
||||
lying peer would need to spend a large amount of energy (and money) in order to
|
||||
do so.
|
||||
|
||||
Specific to Blockstack, we envision that many applications will run
|
||||
their own Stacks-based blockchain peer networks that operate "on top" of the
|
||||
Stacks blockchain through proof-of-burn. This means that the Blockstack
|
||||
application ecosystem will have many parallel "app chains" that users may wish
|
||||
to interact with. While a cautious power user may run validator nodes for each
|
||||
app chain they are interested in, we expect that most users will not do so,
|
||||
especially if they are just trying out the application or are casual users. In
|
||||
order to afford these users better security than simply telling them to find a
|
||||
trusted validating peer, it is essential that each Stacks peer commits to its
|
||||
materialized view in each block.
|
||||
|
||||
On top of providing better security to light clients, committing to the materialized
|
||||
state view in each block has the additional benefit of helping the peer network
|
||||
detect malfunctioning miners early on. A malfunctioning miner will calculate a
|
||||
different materialized view using the same transactions, and with overwhelmingly
|
||||
high probability, will also calculate a different state view hash. This makes
|
||||
it easy for a blockchain's peers to reject a block produced in this manner
|
||||
outright, without having to replay its transactions.
|
||||
|
||||
### Design Considerations
|
||||
|
||||
Committing to the materialized view in each block has a non-zero cost in terms
|
||||
of time and space complexity. Given that Stacks miners use PoW to increase
|
||||
their chances of winning a block race, the time required to calculate
|
||||
the materialized view necessarily cuts into the time
|
||||
required to solve the PoW puzzle -- it is part of the block validation logic.
|
||||
While this is a cost borne by each miner, the fact that PoW mining is a zero-sum game
|
||||
means that miners that are able to calculate the materialized view the fastest will have a
|
||||
better chance of winning a block race than those who do not. This means that it
|
||||
is of paramount importance to keep the materialized view digest calculation as
|
||||
fast as possible, just as it is of paramount importance to make block
|
||||
validation as fast and cheap as possible.
|
||||
|
||||
The following considerations have a non-trivial impact on the design of the
|
||||
MARF:
|
||||
|
||||
**A transaction can read or write any prior state in the same fork.** This
|
||||
means that the index must support fast random-access reads and fast
|
||||
random writes.
|
||||
|
||||
**The Stacks blockchain can fork, and a miner can produce a fork at any block
|
||||
height in the past.** As argued in SIP 001, a Stacks blockchain peer must process
|
||||
all forks and keep their blocks around. This also means that a peer needs to
|
||||
calculate and validate the materialized view of each fork, no matter where it
|
||||
occurs. This is also necessary because a client may request a proof for some
|
||||
state in any fork -- in order to service such requests, the peer must calculate
|
||||
the materialized view for all forks.
|
||||
|
||||
**Forks can occur in any order, and blocks can arrive in any order.** As such,
|
||||
the runtime cost of calculating the materialized view must be _independent_ of the
|
||||
order in which forks are produced, as well as the order in which their blocks
|
||||
arrive. This is required in order to avoid denial-of-service vulnerabilities,
|
||||
whereby a network attacker can control the schedules of both
|
||||
forks and block arrivals in a bid to force each peer to expend resources
|
||||
validating the fork. It must be impossible for an attacker to
|
||||
significantly slow down the peer network by maliciously varying either schedule.
|
||||
This has non-trivial consequences for the design of the data structures for
|
||||
encoding materialized views.
|
||||
|
||||
## Specification
|
||||
|
||||
The Stacks peer's materialized view is realized as a flat key/value store.
|
||||
Transactions encode zero or more creates, inserts, updates, and deletes on this
|
||||
key/value store. As a consequence of needing to support forks from any prior block,
|
||||
no data is ever removed; instead, a "delete" on a particular key is encoded
|
||||
by replacing the value with a tombstone record. The materialized view is the
|
||||
subset of key/value pairs that belong to a particular fork in the blockchain.
|
||||
|
||||
The Stacks blockchain separates the concern of maintaining _authenticated
|
||||
index_ over data from storing a copy of the data itself. The blockchain peers
|
||||
commit to the digest of the authenticated index, but can store the data however
|
||||
they want. The authenticated index is realized as a _Merklized Adaptive Radix
|
||||
Forest_ (MARF). The MARF gives Stacks peers the ability to prove that a
|
||||
particular key in the materialized view maps to a particular value in a
|
||||
particular fork.
|
||||
|
||||
A MARF has two principal data structures: a _merklized adaptive radix trie_
|
||||
for each block and a _merklized skip-list_ that
|
||||
cryptographically links merklized adaptive radix tries in prior blocks to the
|
||||
current block.
|
||||
|
||||
### Merklized Adaptive Radix Tries (ARTs)
|
||||
|
||||
An _adaptive radix trie_ (ART) is a prefix tree where each node's branching
|
||||
factor varies with the number of children. In particular, a node's branching
|
||||
factor increases according to a schedule (0, 4, 16, 48, 256) as more and more
|
||||
children are added. This behavior, combined with the usual sparse trie
|
||||
optimizations of _lazy expansion_ and _path compression_, produce a tree-like
|
||||
index over a set key/value pairs that is _shallower_ than a perfectly-balanced
|
||||
binary search tree over the same values. Details on the analysis of ARTs can
|
||||
be found in [1].
|
||||
|
||||
To produce an _index_ over new state introduced in this block, the Stacks peer
|
||||
will produce an adaptive radix trie that describes each key/value pair modified.
|
||||
In particular, for each key affected by the block, the Stacks peer will:
|
||||
* Calculate the hash of the key to get a fixed-length trie path,
|
||||
* Store the new value and this hash into its data store,
|
||||
* Insert or update the associated value hash in the block's ART at the trie path,
|
||||
* Calculate the new Merkle root of the ART by hashing all modified intermediate
|
||||
nodes along the path.
|
||||
|
||||
In doing so, the Stacks peer produces an authenticated index for all key/value
|
||||
pairs affected by a block. The leaves of the ART are the hashes of the values,
|
||||
and the hashes produced in each intermediate node and root give the peer a
|
||||
way to cryptographically prove that a particular value is present in the ART
|
||||
(given the root hash and the key).
|
||||
|
||||
The Stacks blockchain employs _path compression_ and _lazy expansion_
|
||||
to efficiently represent all key/value pairs while minimizing the number of trie
|
||||
nodes. That is, if two children share a common prefix, the prefix bytes are
|
||||
stored in a single intermediate node instead of being spread across multiple
|
||||
intermediate nodes (path compression). In the special case where a path suffix
|
||||
uniquely identifies the leaf, the path suffix will be stored alongside the leaf
|
||||
instead as a sequence of intermediate nodes (lazy expansion). As more and more
|
||||
key/value pairs are inserted, intermediate nodes and leaves with multi-byte
|
||||
paths will be split into more nodes.
|
||||
|
||||
**Trie Structure**
|
||||
|
||||
A trie is made up of nodes with radix 4, 16, 48, or 256, as well as leaves. In
|
||||
the documentation below, these are called `node4`, `node16`, `node48`,
|
||||
`node256`, and `leaf` nodes. An empty trie has a single `node256` as its root.
|
||||
Child pointers occupy one byte.
|
||||
|
||||
**Notation**
|
||||
|
||||
The notation `(ab)node256` means "a `node256` who descends from its parent via
|
||||
byte 0xab".
|
||||
|
||||
The notation `node256[path=abcd]` means "a `node256` that has a shared prefix
|
||||
with is children `abcd`".
|
||||
|
||||
**Lazy Expansion**
|
||||
|
||||
If a leaf has a non-zero-byte path suffix, and another leaf is inserted that
|
||||
shares part of the suffix, the common bytes will be split off of the existing
|
||||
leaf to form a `node4`, whose two immediate children are the two leaves. Each
|
||||
of the two leaves will store the path bytes that are unique to them. For
|
||||
example, consider this trie with a root `node256` and a single leaf, located at
|
||||
path `aabbccddeeff00112233` and having value hash `123456`:
|
||||
|
||||
```
|
||||
node256
|
||||
\
|
||||
(aa)leaf[path=bbccddeeff00112233]=123456
|
||||
```
|
||||
|
||||
If the peer inserts the value hash `98765` at path `aabbccddeeff998877`, the
|
||||
single leaf's path will be split into a shared prefix and two distinct suffixes,
|
||||
as follows:
|
||||
|
||||
```
|
||||
insert (aabbccddeeff998877, 98765)
|
||||
|
||||
node256 (00)leaf[path=112233]=123456
|
||||
\ /
|
||||
(aa)node4[path-bbccddeeff]
|
||||
\
|
||||
(99)leaf[path=887766]=98765
|
||||
```
|
||||
|
||||
Now, the trie encodes both `aabbccddeeff00112233=123456` and
|
||||
`aabbccddeeff99887766=98765`.
|
||||
|
||||
**Node Promotion**
|
||||
|
||||
As a node with a small radix gains children, it will eventually need to be
|
||||
promoted to a node with a higher radix. A `node4` will become a `node16` when
|
||||
it receives its 5th child; a `node16` will become a `node48` when it receives
|
||||
its 17th child, and a `node48` will become a `node256` when it receives its 49th
|
||||
child. A `node256` will never need to be promoted, because it has slots for
|
||||
child pointers with all possible byte values.
|
||||
|
||||
For example, consider this trie with a `node4` and 4 children:
|
||||
|
||||
```
|
||||
node256 (00)leaf[path=112233]=123456
|
||||
\ /
|
||||
\ / (01)leaf[path=445566]=67890
|
||||
\ / /
|
||||
(aa)node4[path=bbccddeeff]---
|
||||
\ \
|
||||
\ (02)leaf[path=778899]=abcdef
|
||||
\
|
||||
(99)leaf[path=887766]=98765
|
||||
```
|
||||
|
||||
This trie encodes the following:
|
||||
* `aabbccddeeff00112233=123456`
|
||||
* `aabbccddeeff01445566=67890`
|
||||
* `aabbccddeeff02778899=abcdef`
|
||||
* `aabbccddeeff99887766=9876`
|
||||
|
||||
Inserting one more node with a prefix `aabbccddeeff` will promote the
|
||||
intermediate `node4` to a `node16`:
|
||||
|
||||
```
|
||||
insert (aabbccddeeff03aabbcc, 314159)
|
||||
|
||||
node256 (00)leaf[path=112233]=123456
|
||||
\ /
|
||||
\ / (01)leaf[path=445566]=67890
|
||||
\ / /
|
||||
(aa)node16[path=bbccddeeff]-----(03)leaf[path=aabbcc]=314159
|
||||
\ \
|
||||
\ (02)leaf[path=778899]=abcdef
|
||||
\
|
||||
(99)leaf[path=887766]=98765
|
||||
```
|
||||
|
||||
The trie now encodes the following:
|
||||
* `aabbccddeeff00112233=123456`
|
||||
* `aabbccddeeff01445566=67890`
|
||||
* `aabbccddeeff03aabbcc=314159`
|
||||
* `aabbccddeeff02778899=abcdef`
|
||||
* `aabbccddeeff99887766=9876`
|
||||
|
||||
**Path Compression**
|
||||
|
||||
Intermediate nodes, such as the `node16` in the previous example, store path
|
||||
prefixes shared by all of their children. If a node is inserted that shares
|
||||
some of this prefix, but not all of it, the path is "decompressed" -- a new
|
||||
leaf is "spliced" into the compressed path, and attached to a `node4` whose two
|
||||
children are the leaf and the existing node (i.e. the `node16` in this case)
|
||||
whose shared path now contains the suffix unique to its children, but distinct
|
||||
from the newly-spliced leaf.
|
||||
|
||||
For example, consider this trie with the intermediate `node16` sharing a path
|
||||
prefix `bbccddeeff` with its 5 children:
|
||||
|
||||
```
|
||||
node256 (00)leaf[path=112233]=123456
|
||||
\ /
|
||||
\ / (01)leaf[path=445566]=67890
|
||||
\ / /
|
||||
(aa)node16[path=bbccddeeff]-----(03)leaf[path=aabbcc]=314159
|
||||
\ \
|
||||
\ (02)leaf[path=778899]=abcdef
|
||||
\
|
||||
(99)leaf[path=887766]=98765
|
||||
```
|
||||
|
||||
This trie encodes the following:
|
||||
* `aabbccddeeff00112233=123456`
|
||||
* `aabbccddeeff01445566=67890`
|
||||
* `aabbccddeeff03aabbcc=314159`
|
||||
* `aabbccddeeff02778899=abcdef`
|
||||
* `aabbccddeeff99887766=9876`
|
||||
|
||||
If we inserted `(aabbcc001122334455, 21878)`, the `node16`'s path would be
|
||||
decompressed to `eeff`, a leaf with the distinct suffix `1122334455` would be spliced
|
||||
in via a `node4`, and the `node4` would have the shared path prefix `bbcc` with
|
||||
its now-child `node16` and leaf.
|
||||
|
||||
```
|
||||
insert (aabbcc00112233445566, 21878)
|
||||
|
||||
(00)leaf[path=112233445566]=21878
|
||||
/
|
||||
node256 / (00)leaf[path=112233]=123456
|
||||
\ / /
|
||||
(aa)node4[path=bbcc] / (01)leaf[path=445566]=67890
|
||||
\ / /
|
||||
(dd)node16[path=eeff]-----(03)leaf[path=aabbcc]=314159
|
||||
\ \
|
||||
\ (02)leaf[path=778899]=abcdef
|
||||
\
|
||||
(99)leaf[path=887766]=98765
|
||||
```
|
||||
|
||||
The resulting trie now encodes the following:
|
||||
* `aabbcc00112233445566=21878`
|
||||
* `aabbccddeeff00112233=123456`
|
||||
* `aabbccddeeff01445566=67890`
|
||||
* `aabbccddeeff03aabbcc=314159`
|
||||
* `aabbccddeeff02778899=abcdef`
|
||||
* `aabbccddeeff99887766=9876`
|
||||
|
||||
### Back-pointers
|
||||
|
||||
The materialized view of a fork will hold key/value pairs for data produced by
|
||||
applying _all transactions_ in that fork, not just the ones in the last block. As such,
|
||||
the index over all key/value pairs in a fork is encoded in the sequence of
|
||||
its block's merklized ARTs.
|
||||
|
||||
To ensure that random reads and writes on the a fork's materialized view remain
|
||||
fast no matter which block added them, a child pointer in an ART can point to
|
||||
either a node in the same ART, or a node with the same path in a prior ART. For
|
||||
example, if the ART at block _N_ has a `node16` whose path is `aabbccddeeff`, and 10
|
||||
blocks ago a leaf was inserted at path `aabbccddeeff99887766`, it will
|
||||
contain a child pointer to the intermediate node from 10 blocks ago whose path is
|
||||
`aabbccddeeff` and who has a child node in slot `0x99`. This information is encoded
|
||||
as a _back-pointer_. To see it visually:
|
||||
|
||||
```
|
||||
At block N
|
||||
|
||||
|
||||
node256 (00)leaf[path=112233]=123456
|
||||
\ /
|
||||
\ / (01)leaf[path=445566]=67890
|
||||
\ / /
|
||||
(aa)node16[path=bbccddeeff]-----(03)leaf[path=aabbcc]=314159
|
||||
\ \
|
||||
\ (02)leaf[path=778899]=abcdef
|
||||
\
|
||||
|
|
||||
|
|
||||
|
|
||||
At block N-10 - - - - - - - - - - - - - | - - - - - - - - - - - - - - - - - - -
|
||||
|
|
||||
node256 | /* back-pointer to block N-10 */
|
||||
\ |
|
||||
\ |
|
||||
\ |
|
||||
(aa)node4[path=bbccddeeff] |
|
||||
\ |
|
||||
\ |
|
||||
\ |
|
||||
(99)leaf[path=887766]=98765
|
||||
```
|
||||
|
||||
By maintaining trie child pointers this way, the act of looking up a path to a value in
|
||||
a previous block is a matter of following back-pointers to previous tries.
|
||||
This back-pointer uses the _block-hash_ of the previous block to uniquely identify
|
||||
the block. In order to keep the in-memory and on-disk representations of trie nodes succint,
|
||||
the MARF structure uses a locally defined unsigned 32-bit integer to identify the previous
|
||||
block, along with a local mapping of such integers to the respective block header hash.
|
||||
|
||||
Back-pointers are calculated in a copy-on-write fashion when calculating the ART
|
||||
for the next block. When the root node for the ART at block N+1 is created, all
|
||||
of its children are set to back-pointers that point to the immediate children of
|
||||
the root of block N's ART. Then, when inserting a key/value pair, the peer
|
||||
walks the current ART to the insertion point, but whenever a
|
||||
back-pointer is encountered, it copies the node it points to into the current
|
||||
ART, and sets all of its non-empty child pointers to back-pointers. The peer
|
||||
then continues traversing the ART until the insertion point is found (i.e. a
|
||||
node has an unallocated child pointer where the leaf should go), copying
|
||||
over intermediate nodes lazily.
|
||||
|
||||
For example, consider the act of inserting `aabbccddeeff00112233=123456` into an
|
||||
ART where a previous ART contains the key/value pair
|
||||
`aabbccddeeff99887766=98765`:
|
||||
|
||||
```
|
||||
At block N
|
||||
|
||||
|
||||
node256 (00)leaf[path=112233]=123456
|
||||
^ \ /
|
||||
| \ /
|
||||
| \ /
|
||||
| (aa)node4[path=bbccddeeff]
|
||||
| ^ \
|
||||
| | \
|
||||
| /* 1. @root. */ | /* 2. @node4. */ \ /* 3. 00 is empty, so insert */
|
||||
| /* copy up, &*/ | /* copy up, & */ |
|
||||
| /* make back-*/ | /* make back- */ |
|
||||
| /* ptr to aa */ | /* ptr to 99 */ |
|
||||
| | |
|
||||
|- At block N-10 -|- - - - - - - - - - | - - - - - - - - - - - - - - - - - -
|
||||
| | |
|
||||
node256 | |
|
||||
\ | |
|
||||
\ | |
|
||||
\ | |
|
||||
(aa)node4[path=bbccddeeff] |
|
||||
\ |
|
||||
\ |
|
||||
\|
|
||||
(99)leaf[path=887766]=98765
|
||||
```
|
||||
|
||||
In step 1, the `node256` in block _N_ would have a back-pointer to the `node4` in
|
||||
block _N - 10_ in child slot `0xaa`. While walking path `aabbccddeeff00112233`,
|
||||
the peer would follow slot `0xaa` to the `node4` in block _N - 10_ and copy it
|
||||
into block _N_, and would set its child pointer at `0x99` to be a back-pointer
|
||||
to the `leaf` in block _N - 10_. It would then step to the `node4` it copied,
|
||||
and walk path bytes `bbccddeeff`. When it reaches child slot `0x00`, the peer
|
||||
sees that it is unallocated, and attaches the leaf with the unexpanded path
|
||||
suffix `112233`. The back-pointer to `aabbccddeeff99887766=98765` is thus
|
||||
preserved in block _N_'s ART.
|
||||
|
||||
**Calculating the Root Hash with Back-pointers**
|
||||
|
||||
For reasons that will be explained in a moment, the hash of a child node that is a
|
||||
back-pointer is not calculated the usual way when calculating the root hash of
|
||||
the Merklized ART. Instead of taking the hash of the child node (as would be
|
||||
done for a child in the same ART), the hash of the _block header_ is used
|
||||
instead. In the above example, the hash of the `leaf` node whose path is
|
||||
`aabbccddeeff99887766` would be the hash of block _N - 10_'s header, whereas the
|
||||
hash of the `leaf` node whose path is `aabbccddeeff00112233` would be the hash
|
||||
of the value hash `123456`.
|
||||
|
||||
The main reason for doing this is to keep block validation time down by a
|
||||
significant constant factor. The block header hash is always kept in RAM,
|
||||
but at least one disk seek is required to read the hash of a child in a separate
|
||||
ART (and it often takes more than one seek). This does not sacrifice the security
|
||||
of a Merkle proof of `aabbccddeeff99887766=98765`, but it does alter the mechanics
|
||||
of calculating and verifying it.
|
||||
|
||||
### Merklized Skip-list
|
||||
|
||||
The second principal data structure in a MARF is a Merklized skip-list encoded
|
||||
from the block header hashes and ART root hashes in each block. The hash of the
|
||||
root node in the ART for block _N_ is derived not only from the hash of the
|
||||
root's children, but also from the hashes of the block headers from blocks
|
||||
`N - 1`, `N - 2`, `N - 4`, `N - 8`, `N - 16`, and so on. This constitutes
|
||||
a _Merklized skip-list_ over the sequence of ARTs.
|
||||
|
||||
The reason for encoding the root node's hash this way is to make it possible for
|
||||
peers to create a cryptographic proof that a particular key maps to a particular
|
||||
value when the value lives in a prior block, and can only be accessed by
|
||||
following one or more back-pointers. In addition, the Merkle skip-list affords
|
||||
a client _two_ ways to verify key-value pairs: the client only needs either (1)
|
||||
a known-good root hash, or (2) the sequence of block headers for the Stacks
|
||||
chain and its underlying burn chain. Having (2) allows the client to determine
|
||||
(1), but calculating (2) is expensive for a client doing a small number of
|
||||
queries. For this reason, both options are supported.
|
||||
|
||||
#### Resolving Block Height Queries
|
||||
|
||||
For a variety of reasons, the MARF structure must be able to resolve
|
||||
queries mapping from block heights (or relative block heights) to
|
||||
block header hashes and vice-versa --- for example, the Clarity VM
|
||||
allows contracts to inspect this information. Most applicable to the
|
||||
MARF, though, is that in order to find the ancestor hashes to include
|
||||
in the Merklized Skip-list, the data structure must be able to find
|
||||
the block headers which are 1, 2, 4, 8, 16, ... blocks prior in the
|
||||
same fork. This could be discovered by walking backwards from the
|
||||
current block, using the previous block header to step back through
|
||||
the fork's history. However, such a process would require _O(N)_ steps
|
||||
(where _N_ is the current block height). But, if a mapping exists for
|
||||
discovering the block at a given block height, this process would instead
|
||||
be _O(1)_ (because a node will have at most 32 such ancestors).
|
||||
|
||||
But correctly implementing such a mapping is not trivial: a given
|
||||
height could resolve to different blocks in different forks. However,
|
||||
the MARF itself is designed to handle exactly these kinds of
|
||||
queries. As such, at the beginning of each new block, the MARF inserts
|
||||
into the block's trie two entries:
|
||||
|
||||
1. This block's block header hash -> this block's height.
|
||||
2. This block's height -> this block's block header hash.
|
||||
|
||||
This mapping allows the ancestor hash calculation to proceed.
|
||||
|
||||
### MARF Merkle Proofs
|
||||
|
||||
A Merkle proof for a MARF is constructed using a combination of two types of
|
||||
sub-proofs: _segment proofs_, and _shunt proofs_. A _segment proof_ is a proof
|
||||
that a node belongs to a particular Merklized ART. It is simply a Merkle tree
|
||||
proof. A _shunt proof_ is a proof that the ART for block _N_ is exactly _K_
|
||||
blocks away from the ART at block _N - K_. It is generated as a Merkle proof
|
||||
from the Merkle skip-list.
|
||||
|
||||
Calculating a MARF Merkle proof is done by first calculating a segment proof for a
|
||||
sequence of path prefixes, such that all the nodes in a single prefix are in the
|
||||
same ART. To do so, the node walks from the current block's ART's root node
|
||||
down to the leaf in question, and each time it encounters a back-pointer, it
|
||||
generates a segment proof from the _currently-visited_ ART to the intermediate
|
||||
node whose child is the back-pointer to follow. If a path contains _i_
|
||||
back-pointers, then there will be _i+1_ segment proofs.
|
||||
|
||||
Once the peer has calculated each segment proof, it calculates a shunt proof
|
||||
that shows that the _i+1_th segment was reached by walking back a given number
|
||||
of blocks from the _i_th segment by following the _i_th segment's back-pointer.
|
||||
The final shunt proof for the ART that contains the leaf node includes all of
|
||||
the prior block header hashes that went into producing its root node's hash.
|
||||
Each shunt proof is a sequence of sequences of block header hashes and ART root
|
||||
hashes, such that the hash of the next ART root node can be calculated from the
|
||||
previous sequence.
|
||||
|
||||
For example, consider the following ARTs:
|
||||
|
||||
```
|
||||
At block N
|
||||
|
||||
|
||||
node256 (00)leaf[path=112233]=123456
|
||||
\ /
|
||||
\ / (01)leaf[path=445566]=67890
|
||||
\ / /
|
||||
(aa)node16[path=bbccddeeff]-----(03)leaf[path=aabbcc]=314159
|
||||
\ \
|
||||
\ (02)leaf[path=778899]=abcdef
|
||||
\
|
||||
|
|
||||
|
|
||||
|
|
||||
At block N-10 - - - - - - - - - - - - - | - - - - - - - - - - - - - - - - - - -
|
||||
|
|
||||
node256 | /* back-pointer to N - 10 */
|
||||
\ |
|
||||
\ |
|
||||
\ |
|
||||
(aa)node4[path=bbccddeeff] |
|
||||
\ |
|
||||
\ |
|
||||
\ |
|
||||
(99)leaf[path=887766]=98765
|
||||
```
|
||||
|
||||
To generate a MARF Merkle proof, the client queries a Stacks peer for a
|
||||
particular value hash, and then requests the peer generate a proof that the key
|
||||
and value must have been included in the calculation of the current block's ART
|
||||
root hash (i.e. the digest of the materialized view of this fork).
|
||||
|
||||
For example, given the key/value pair `aabbccddeeff99887766=98765` and the hash
|
||||
of the ART at block _N_, the peer would generate two segment proofs for the
|
||||
following paths: `aabbccddeeff` in block _N_, and `aabbccddeeff99887766` in
|
||||
block `N - 10`.
|
||||
|
||||
```
|
||||
At block N
|
||||
|
||||
|
||||
node256
|
||||
\ /* this segment proof would contain the hashes of all other */
|
||||
\ /* children of the root, except for the one at 0xaa. */
|
||||
\
|
||||
(aa)node16[path=bbccddeeff]
|
||||
|
||||
At block N-10 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
node256 /* this segment proof would contain two sequences of hashes: */
|
||||
\ /* the hashes for all children of the root besides 0xaa, and */
|
||||
\ /* the hashes of all children of the node4, except 0x99. */
|
||||
\
|
||||
(aa)node4[path=bbccddeeff]
|
||||
\
|
||||
\
|
||||
\
|
||||
(99)leaf[path=887766]=98765
|
||||
```
|
||||
|
||||
Then, it would calculate two shunt proofs. The first proof, called the "head shunt proof,"
|
||||
supplies the sequence of block hashes for blocks _N - 11, N - 12, N - 14, N - 18, N - 26, ..._ and the
|
||||
hash of the children of the root node of the ART for block _N - 10_. This lets the
|
||||
client calculate the hash of the root of the ART at block _N - 10_. The second
|
||||
shunt proof (and all subsequent shunt proofs, if there are more back-pointers to
|
||||
follow) is comprised of the hashes that "went into" calculating the hashes on the
|
||||
skip-list from the next segment proof's root hash.
|
||||
|
||||
In detail, the second shunt proof would have two parts:
|
||||
|
||||
* the block header hashes for block _N - 9_ _N - 12_, _N - 16_, _N - 24_, ...
|
||||
* the block header hashes for _N - 1_, _N - 2_, _N - 4_, _N - 16_, _N - 32_, ...
|
||||
|
||||
The reason there are two sequences in this shunt proof is because "walking back"
|
||||
from block _N_ to block _N - 10_ requires walking first to block _N - 8_ (i.e.
|
||||
following the skip-list column for 2 ** 3), and then walking to block _N - 10_
|
||||
from _N - 8_ (i.e. following its skip-list column for 2 ** 1). The first segment
|
||||
proof (i.e. with the leaf) lets the client calculate the hash of the children of
|
||||
the ART root node in block _N - 10_, which when combined with the first part of
|
||||
this shunt proof yields the ART root hash for _N - 8_. Then, the client
|
||||
uses the hash of the children of the root node in the ART of block _N_ (calculated from the second segment
|
||||
proof), combined with the root hash from node _N - 8_ and with the hashes
|
||||
in the second piece of this shunt proof, to calculate the ART root hash for
|
||||
block _N_. The proof is valid if this calculated root hash matches the root
|
||||
hash for which it requested the proof.
|
||||
|
||||
In order to fully verify the MARF Merkle proof, the client would verify that:
|
||||
|
||||
* The first segment proof's path's bytes are equal to the hash of the key for
|
||||
which the proof was requested.
|
||||
* The first segment proof ends in a leaf node, and the leaf node contains the
|
||||
hash of the value for which the proof was requested.
|
||||
* Each segment proof is valid -- the root hash could only be calculated from the
|
||||
deepest intermediate node in the segment,
|
||||
* Each subsequent segment proof was generated from a prefix of the path
|
||||
represented by the current segment proof,
|
||||
* Each back-pointer at the tail of each segment (except the one that terminates
|
||||
in the leaf -- i.e. the first one) was a number of blocks back that is equal
|
||||
to the number of blocks skipped over in the shunt proof linking it to the next
|
||||
segment.
|
||||
* Each block header was included in the fork the client is querying,
|
||||
* Each block header was generated from its associated ART root hash,
|
||||
* (Optional, but encouraged): The burn chain block headers demonstrate that the
|
||||
correct difficulty rules were followed. This step can be skipped if the
|
||||
client somehow already knows that the hash of block _N_ is valid.
|
||||
|
||||
Note that to verify the proof, the client would need to substitute the
|
||||
_block header hash_ for each intermediate node at the tail of each segment
|
||||
proof. The block header hash can either be obtained by fetching the block
|
||||
headers for both the Stacks chain and burn chain _a priori_ and verifying that
|
||||
they are valid, or by fetching them on-the-fly. The second strategy should only
|
||||
be used if the client's root hash it submits to the peer is known out-of-band to
|
||||
be the correct hash.
|
||||
|
||||
The security of the proof is similar to SPV proofs in Bitcoin -- the proof is
|
||||
valid assuming the client is able to either verify that the final header hash
|
||||
represents the true state of the network, or the client is able to fetch the
|
||||
true burn chain block header sequence. The client has some assurance that a
|
||||
_given_ header sequence is the _true_ header sequence, because the header
|
||||
sequence encodes the proof-of-work that went into producing it. A header
|
||||
sequence with a large amount of proof-of-work is assumed to be infeasible for an
|
||||
attacker to produce -- i.e. only the majority of the burn chain's network hash
|
||||
power could have produced the header chain. Regardless of which data the client
|
||||
has, the usual security assumptions about confirmation depth apply -- a proof
|
||||
that a key maps to a given value is valid only if the transaction that set
|
||||
it is unlikely to be reversed by a chain reorg.
|
||||
|
||||
### Performance
|
||||
|
||||
The time and space complexity of a MARF is as follows:
|
||||
|
||||
* **Reads are _O(1)_** While reads may traverse multiple tries, they are always
|
||||
descending the radix trie, and resolving back pointers is constant time.
|
||||
* **Inserts and updates are _O(1)._** Inserts have the same complexity
|
||||
as reads, though they require more work by constant factors (in
|
||||
particular, hash recalculations).
|
||||
* **Creating a new block is _O(log B)_.** Inserting a block requires
|
||||
including the Merkle skip-list hash in the root node of the new
|
||||
ART. This is _log B_ work, where _B_ is chain length.
|
||||
* **Creating a new fork is _O(log B)_.** Forks do not incur any overhead relative
|
||||
to appending a block to a prior chain-tip.
|
||||
* **Generating a proof is _O(log B)_ for B blocks**. This is the cost of
|
||||
reading a fixed number of nodes, combined with walking the Merkle skip-list.
|
||||
* **Verifying a proof is _O(log B)_**. This is the cost of verifying a fixed
|
||||
number of fixed-length segments, and verifying a fixed number of _O(log B)_
|
||||
shunt proof hashes.
|
||||
* **Proof size is _O(log B)_**. A proof has a fixed number of segment proofs,
|
||||
where each node has a constant size. It has _O(log B)_ hashes across all of
|
||||
its shunt proofs.
|
||||
|
||||
### Consensus Details
|
||||
|
||||
The hash function used to generate a path from a key, as well as the hash
|
||||
function used to generate a node hash, is SHA2-512/256. This was chosen because
|
||||
it is extremely fast on 64-bit architectures, and is immune to length extension
|
||||
attacks.
|
||||
|
||||
The hash of an intermediate node is the hash over the following data:
|
||||
|
||||
* a 1-byte node ID,
|
||||
* the sequence of child pointer data (dependent on the type of node),
|
||||
* the 1-byte length of the path prefix this node contains,
|
||||
* the 0-to-32-byte path prefix
|
||||
|
||||
A single child pointer contains:
|
||||
* a 1-byte node ID,
|
||||
* a 1-byte path character,
|
||||
* the 32-byte block header hash of the pointed-to block
|
||||
|
||||
A `node4`, `node16`, `node48`, and `node256` each have an array of 4,
|
||||
16, 48, and 256 child pointers each.
|
||||
|
||||
Children are listed in a `node4`, `node16`, and `node48`'s child pointer arrays in the
|
||||
order in which they are inserted. While searching for a child in a `node4` or
|
||||
`node16` requires a linear scan of the child pointer array, searching a `node48` is done
|
||||
by looking up the child's index in its child pointer array using the
|
||||
path character byte as an index into the `node48`'s 256-byte child pointer
|
||||
index, and then using _that_ index to look up the child pointer. Children are
|
||||
inserted into the child pointer array of a `node256` by using the 1-byte
|
||||
path character as the index.
|
||||
|
||||
The disk pointer stored in a child pointer, as well as the storage mechanism for
|
||||
mapping hashes of values (leaves in the MARF) to the values themselves, are both
|
||||
unspecified by the consensus rules. Any mechanism or representation is
|
||||
permitted.
|
||||
|
||||
## Implementation
|
||||
|
||||
The implementation is in Rust, and is about 4,400 lines of code. It stores each
|
||||
ART in a separate file, where each ART file contains the hash of the previous
|
||||
block's ART's root hash and the locally-defined block identifiers.
|
||||
|
||||
The implementation is crash-consistent. It builds up the ART for block _N_ in
|
||||
RAM, dumps it to disk, and then `rename(2)`s it into place.
|
||||
|
||||
The implementation uses a Sqlite3 database to map values to their hashes. A
|
||||
read on a given key will first pass through the ART to find hash(value), and
|
||||
then query the Sqlite3 database for the value. Similarly, a write will first
|
||||
insert hash(value) and value into the Sqlite3 database, and then insert
|
||||
hash(key) to hash(value) in the MARF.
|
||||
|
||||
## References
|
||||
|
||||
[1] https://db.in.tum.de/~leis/papers/ART.pdf
|
||||
This SIP is now located in the [stacksgov/sips repository](https://github.com/stacksgov/sips/blob/main/sips/sip-004/sip-004-materialized-view.md) as part of the [Stacks Community Governance organization](https://github.com/stacksgov).
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,611 +1,5 @@
|
||||
# SIP-007: Stacking Consensus
|
||||
# SIP-007 Stacking Consensus
|
||||
|
||||
# Preamble
|
||||
This document formerly contained SIP-007 before the Stacks 2.0 mainnet launched.
|
||||
|
||||
Title: Stacking Consensus
|
||||
|
||||
Authors:
|
||||
|
||||
Muneeb Ali <muneeb@blockstack.com>,
|
||||
Aaron Blankstein <aaron@blockstack.com>,
|
||||
Michael J. Freedman <mfreed@cs.princeton.edu>,
|
||||
Diwaker Gupta <diwaker@blockstack.com>,
|
||||
Jude Nelson <jude@blockstack.com>,
|
||||
Jesse Soslow <jesse@blockstack.com>,
|
||||
Patrick Stanley <patrick@blockstack.com>
|
||||
|
||||
Status: Draft
|
||||
|
||||
Type: Standard
|
||||
|
||||
Created: 01/14/2020
|
||||
|
||||
# Abstract
|
||||
|
||||
This SIP proposes a new consensus algorithm, called Stacking, that
|
||||
uses the proof-of-work cryptocurrency of an established blockchain to
|
||||
secure a new blockchain. An economic benefit of the Stacking consensus
|
||||
algorithm is that the holders of the new cryptocurrency can earn a
|
||||
reward in a base cryptocurrency by actively participating in the
|
||||
consensus algorithm.
|
||||
|
||||
This SIP proposes to change the mining mechanism of the Stacks
|
||||
blockchain. [SIP-001](./sip-001-burn-election.md) introduced
|
||||
proof-of-burn (PoB) where a base cryptocurrency is destroyed to
|
||||
participate in mining of a new cryptocurrency. This proposal argues
|
||||
that a new mining mechanism called proof-of-transfer (PoX) will be an
|
||||
improvement over proof-of-burn.
|
||||
|
||||
With proof-of-transfer, instead of destroying the base cryptocurrency,
|
||||
miners are required to distribute the base cryptocurrency to existing
|
||||
holders of the new cryptocurrency who participate in the consensus
|
||||
algorithm. Therefore, existing holders of the new cryptocurrency have
|
||||
an economic incentive to participate, do useful work for the network,
|
||||
and receive rewards.
|
||||
|
||||
Proof-of-transfer avoids burning of the base cryptocurrency which
|
||||
destroys some supply of the base cryptocurrency. Stacking in general
|
||||
can be viewed as a more "efficient" algorithm where instead of
|
||||
destroying a valuable resource (like electricity or base
|
||||
cryptocurrency), the valuable resource is distributed to holders of
|
||||
the new cryptocurrency.
|
||||
|
||||
The SIP describes one potential implementation of the Stacking
|
||||
consensus algorithm for the Stacks blockchain using Bitcoin as the
|
||||
base cryptocurrency.
|
||||
|
||||
# Introduction
|
||||
|
||||
Consensus algorithms for public blockchains require computational or
|
||||
financial resources to secure the blockchain state. Mining mechanisms
|
||||
used by these algorithms are broadly divided into proof-of-work (PoW),
|
||||
in which nodes dedicate computational resources, and proof-of-stake
|
||||
(PoS), in which nodes dedicate financial resources. The intention
|
||||
behind both proof-of-work and proof-of-stake is to make it practically
|
||||
infeasible for any single malicious actor to have enough computational
|
||||
power or ownership stake to attack the network.
|
||||
|
||||
With proof-of-work, a miner does some "work" that consumes electricity
|
||||
and is rewarded with digital currency. The miner is, theoretically,
|
||||
converting electricity and computing power into the newly minted
|
||||
digital currency. Bitcoin is an example of this and is by far the
|
||||
largest and most secure PoW blockchain.
|
||||
|
||||
With proof-of-stake, miners stake their holdings of a new digital
|
||||
currency to participate in the consensus algorithm and bad behavior
|
||||
can be penalized by "slashing" the funds of the miner. PoS requires
|
||||
less energy/electricity to be consumed and can give holders of the new
|
||||
cryptocurrency who participate in staking a reward on their holdings
|
||||
in the new cryptocurrency.
|
||||
|
||||
In this SIP we introduce a new consensus algorithm called
|
||||
Stacking. The Stacking consensus algorithm uses a new type of mining
|
||||
mechanism called *proof-of-transfer* (PoX). With PoX, miners are not
|
||||
converting electricity and computing power to newly minted tokens, nor
|
||||
are they staking their cryptocurrency. Rather they use an existing PoW
|
||||
cryptocurrency to secure a new, separate blockchain.
|
||||
|
||||
This SIP is currently a draft and proposes to change the mining
|
||||
mechanism of the Stacks blockchain from proof-of-burn (SIP-001) to
|
||||
proof-of-transfer.
|
||||
|
||||
The PoX mining mechanism is a modification of proof-of-burn (PoB)
|
||||
mining (See
|
||||
the [Blockstack Technical Whitepaper](https://blockstack.org/papers)
|
||||
and [SIP-001](./sip-001-burn-election.md)). In
|
||||
proof-of-burn mining, miners burn a base cryptocurrency to participate
|
||||
in mining — effectively destroying the base cryptocurrency to mint
|
||||
units of a new cryptocurrency. **In proof-of-transfer, rather than
|
||||
destroying the base cryptocurrency, miners transfer the base
|
||||
cryptocurrency as a reward to owners of the new cryptocurrency**. In
|
||||
the case of the Stacks blockchain, miners would transfer Bitcoin to
|
||||
owners of Stacks tokens in order for miners to receive newly-minted
|
||||
Stacks tokens. The security properties of proof-of-transfer are
|
||||
comparable to proof-of-burn.
|
||||
|
||||
# Stacking with Bitcoin
|
||||
|
||||
In the Stacking consensus protocol, we require the base cryptocurrency
|
||||
to be a proof-of-work blockchain. In this proposed implementation of
|
||||
Stacking we assume that the PoW crypto-currency is Bitcoin, given it
|
||||
is by far the most secure PoW blockchain. Theoretically, other PoW
|
||||
blockchains can be used, but the security properties of Bitcoin are
|
||||
currently superior to other PoW blockchains.
|
||||
|
||||
As with PoB, in PoX, the protocol selects the winning miner (*i.e.*,
|
||||
the leader) of a round using a verifiable random function (VRF). The
|
||||
leader writes the new block of the Stacks blockchain and mints the
|
||||
rewards (newly minted Stacks). However, instead of bitcoins being sent
|
||||
to burn addresses, the bitcoins are sent to a set of specific
|
||||
addresses corresponding to Stacks (STX) tokens holders that are adding
|
||||
value to the network. Thus, rather than being destroyed, the bitcoins
|
||||
consumed in the mining process go to productive Stacks holders as a
|
||||
reward based on their holdings of Stacks and participation in the
|
||||
Stacking algorithm.
|
||||
|
||||
# Stacking Consensus Algorithm
|
||||
|
||||
In addition to the normal tasks of PoB mining
|
||||
(see [SIP-001](./sip-001-burn-election.md)), the Stacking consensus
|
||||
algorithm *must* determine the set of addresses that miners may
|
||||
validly transfer funds to. PoB mining does not need to perform these
|
||||
steps, because the address is always the same — the burn
|
||||
address. However, with Stacking, network participants must be able to
|
||||
validate the addresses that are sent to.
|
||||
|
||||
Progression in Stacking consensus happens over *reward cycles*. In
|
||||
each reward cycle, a set of Bitcoin addresses are iterated over, such
|
||||
that each Bitcoin address in the set of reward addresses has exactly
|
||||
one Bitcoin block in which miners will transfer funds to the reward
|
||||
address.
|
||||
|
||||
To qualify for a reward cycle, an STX holder must:
|
||||
|
||||
|
||||
* Control a Stacks wallet with >= 0.02% of the total share of unlocked
|
||||
Stacks tokens (currently, there are ~470m unlocked Stacks tokens,
|
||||
meaning this would require ~94k Stacks). This threshold level
|
||||
adjusts based on the participation levels in the Stacking protocol.
|
||||
* Broadcast a signed message before the reward cycle begins that:
|
||||
* Locks the associated Stacks tokens for a protocol-specified
|
||||
lockup period.
|
||||
* Specifies a Bitcoin address to receive the funds.
|
||||
* Votes on a Stacks chain tip.
|
||||
|
||||
Miners participating in the Stacks blockchain compete to lead blocks
|
||||
by transferring Bitcoin. Leaders for particular Stacks blocks are
|
||||
chosen by sortition, weighted by the amount of Bitcoin sent (see
|
||||
SIP-001). Before a reward cycle begins, the Stacks network must reach
|
||||
consensus on which addresses are valid recipients. Reaching consensus
|
||||
on this is non-trivial: the Stacks blockchain itself has many
|
||||
properties independent from the Bitcoin blockchain, and may experience
|
||||
forks, missing block data, etc., all of which make reaching consensus
|
||||
difficult. As an extreme example, consider a miner that forks the
|
||||
Stacks chain with a block that claims to hold a large fraction (e.g.,
|
||||
100%) of all Stacks holdings, and proceeds to issue block commitments
|
||||
that pay all of the fees to themselves. How can other nodes on the
|
||||
network detect that this miner’s commitment transfers are invalid?
|
||||
|
||||
The Stacking algorithm addresses this with a two-phase cycle. Before
|
||||
each reward cycle, Stacks nodes engage in a *prepare* phase, in which
|
||||
two items are decided:
|
||||
|
||||
|
||||
1. An **anchor block** — the anchor block is a Stacks chain block. For
|
||||
the duration of the reward cycle, mining any descendant forks of
|
||||
the anchor block requires transferring mining funds to the
|
||||
appropriate reward addresses.
|
||||
2. The **reward set** -- the reward set is the set of Bitcoin
|
||||
addresses which will receive funds in the reward cycle. This set is
|
||||
determined using Stacks chain state from the anchor block.
|
||||
|
||||
During the reward cycle, miners contend with one another to become the
|
||||
leader of the next Stacks block by broadcasting *block commitments* on
|
||||
the Bitcoin chain. These block commitments send Bitcoin funds to
|
||||
either a burn address or a PoX reward address.
|
||||
|
||||
Address validity is determined according to two different rules:
|
||||
|
||||
|
||||
1. If a miner is building off of any chain tip *which is not a
|
||||
descendant of the anchor block*, all of the miner's commitment
|
||||
funds must be burnt.
|
||||
2. If a miner is building off a descendant of the anchor block, the
|
||||
miner must send commitment funds to 2 addresses from the reward
|
||||
set, chosen as follows:
|
||||
* Use the verifiable random function (also used by sortition) to
|
||||
choose 2 addresses from the reward set. These 2 addresses are
|
||||
the reward addresses for this block.
|
||||
* Once addresses have been chosen for a block, these addresses are
|
||||
removed from the reward set, so that future blocks in the reward
|
||||
cycle do not repeat the addresses.
|
||||
|
||||
Note that the verifiable random function (VRF) used for address
|
||||
selection ensures that the same addresses are chosen by each miner
|
||||
selecting reward addresses. If a miner submits a burn commitment which
|
||||
*does not* send funds to a valid address, those commitments are
|
||||
ignored by the rest of the network (because other network participants
|
||||
can deduce that the transfer addresses are invalid).
|
||||
|
||||
To reduce the complexity of the consensus algorithm, Stacking reward
|
||||
cycles are fixed length --- if fewer addresses participate in the
|
||||
Stacking rewards than there are slots in the cycle, then the remaining
|
||||
slots are filled with *burn* addresses. Burn addresses are included
|
||||
in miner commitments at fixed intervals (e.g, if there are 1000 burn
|
||||
addresses for a reward cycle, then each miner commitment would have
|
||||
1 burn address as an output).
|
||||
|
||||
## Adjusting Reward Threshold Based on Participation
|
||||
|
||||
Each reward cycle may transfer miner funds to up to 4000 Bitcoin
|
||||
addresses (2 addresses in a 2000 burn block cycle). To ensure that
|
||||
this number of addresses is sufficient to cover the pool of
|
||||
participants (given 100% participation of liquid STX), the threshold
|
||||
for participation must be 0.025% (1/4000th) of the liquid supply of
|
||||
STX. However, if participation is _lower_ than 100%, the reward pool
|
||||
could admit lower STX holders. The Stacking protocol specifies **2
|
||||
operating levels**:
|
||||
|
||||
* **25%** If fewer than `0.25 * STX_LIQUID_SUPPLY` STX participate in
|
||||
a reward cycle, participant wallets controlling `x` STX may include
|
||||
`floor(x / (0.0000625*STX_LIQUID_SUPPLY))` addresses in the reward set.
|
||||
That is, the minimum participation threshold is 1/16,000th of the liquid
|
||||
supply.
|
||||
* **25%-100%** If between `0.25 * STX_LIQUID_SUPPLY` and `1.0 *
|
||||
STX_LIQUID_SUPPLY` STX participate in a reward cycle, the reward
|
||||
threshold is optimized in order to maximize the number of slots that
|
||||
are filled. That is, the minimum threshold `T` for participation will be
|
||||
roughly 1/4,000th of the participating STX (adjusted in increments
|
||||
of 10,000 STX). Participant wallets controlling `x` STX may
|
||||
include `floor(x / T)` addresses in the
|
||||
reward set.
|
||||
|
||||
In the event that a Stacker signals and locks up enough STX to submit
|
||||
multiple reward addresses, but only submits one reward address, that
|
||||
reward address will be included in the reward set multiple times.
|
||||
|
||||
## Submitting Reward Address and Chain Tip Signaling
|
||||
|
||||
Stacking participants must broadcast signed messages for three purposes:
|
||||
|
||||
1. Indicating to the network how many STX should be locked up, and for
|
||||
how many reward cycles.
|
||||
2. Indicate support for a particular chain tip.
|
||||
3. Specifying the Bitcoin address for receiving Stacking rewards.
|
||||
|
||||
These messages may be broadcast either on the Stacks chain or the
|
||||
Bitcoin chain. If broadcast on the Stacks chain, these messages must
|
||||
be confirmed on the Stacks chain _before_ the anchor block for the
|
||||
reward period. If broadcast on the Bitcoin chain, they may be
|
||||
broadcast during the prepare phase, but must be included before
|
||||
the prepare phase finishes.
|
||||
|
||||
These signed messages are valid for at most 12 reward cycles (25200 Bitcoin
|
||||
blocks or ~7 months). If the signed message specifies a lockup period `x` less
|
||||
than 25200 blocks, then the signed message is only valid for Stacking
|
||||
participation for `floor(x / 2100)` reward cycles (the minimum participation
|
||||
length is one cycle: 2100 blocks).
|
||||
|
||||
|
||||
# Anchor Blocks and Reward Consensus
|
||||
|
||||
In the **prepare** phase of the Stacking algorithm, miners and network
|
||||
participants determine the anchor block and the reward set. The
|
||||
prepare phase is a window `w` of Bitcoin blocks *before* the reward
|
||||
cycle begins (e.g., the window may be 100 Bitcoin blocks).
|
||||
|
||||
At a high-level, nodes determine whether any block was confirmed by
|
||||
`F*w` blocks during the phase, where `F` is a large fraction (e.g.,
|
||||
`0.8`). Once the window `w` closes at time `cur`, Stacks nodes find
|
||||
the potential anchor block as described in the following pseudocode:
|
||||
|
||||
|
||||
```python
|
||||
def find_anchor_block(cur):
|
||||
blocks_worked_on = get_all_stacks_blocks_between(cur - w, cur)
|
||||
|
||||
# get the highest/latest ancestor before the PREPARE phase for each block worked
|
||||
# on during the PREPARE phase.
|
||||
|
||||
candidate_anchors = {}
|
||||
for block in blocks_worked_on:
|
||||
pre_window_ancestor = last_ancestor_of_block_before(block, cur - w)
|
||||
if pre_window_ancestor is None:
|
||||
continue
|
||||
if pre_window_ancestor in candidate_anchors:
|
||||
candidate_anchors[pre_window_ancestor] += 1
|
||||
else:
|
||||
candidate_anchors[pre_window_ancestor] = 1
|
||||
|
||||
# if any block is confirmed by at least F*w, then it is the anchor block.
|
||||
for candidate, confirmed_by_count in candidate_anchors.items():
|
||||
if confirmed_by_count >= F*w
|
||||
return candidate
|
||||
|
||||
return None
|
||||
```
|
||||
|
||||
Note that there can be at most one anchor block (so long as `F >
|
||||
0.5`), because:
|
||||
|
||||
* Each of the `w` blocks in the prepare phase has at most one
|
||||
candidate ancestor.
|
||||
* The total possible number of confirmations for anchor blocks is `w`.
|
||||
* If any block is confirmed by `>= 0.5*w`, then any other block must
|
||||
have been confirmed by `< 0.5*w`.
|
||||
|
||||
The prepare phase, and the high threshold for `F`, are necessary to
|
||||
protect the Stacking consensus protocol from damage due to natural
|
||||
forks, missing block data, and potentially malicious participants. As
|
||||
proposed, PoX and the Stacking protocol require that Stacks nodes are
|
||||
able to use the anchor block to determine the *reward set*. If, by
|
||||
accident or malice, the data associated with the anchor block is
|
||||
unavailable to nodes, then the Stacking protocol cannot operate
|
||||
normally — nodes cannot know whether or not a miner is submitting
|
||||
valid block commitments. A high threshold for `F` ensures that a large
|
||||
fraction of the Stacks mining power has confirmed the receipt of the
|
||||
data associated with the anchor block.
|
||||
|
||||
## Recovery from Missing Data
|
||||
|
||||
In the extreme event that a malicious miner *is* able to get a hidden
|
||||
or invalid block accepted as an anchor block, Stacks nodes must be
|
||||
able to continue operation. To do so, Stacks nodes treat missing
|
||||
anchor block data as if no anchor block was chosen for the reward
|
||||
cycle — the only valid election commitments will therefore be *burns*
|
||||
(this is essentially a fallback to PoB). If anchor block data which
|
||||
was previously missing is revealed to the Stacks node, it must
|
||||
reprocess all of the leader elections for that anchor block's
|
||||
associated reward cycle, because there may now be many commitments
|
||||
which were previously invalid that are now valid.
|
||||
|
||||
Reprocessing leader elections is computationally expensive, and
|
||||
would likely result in a large reorganization of the Stacks
|
||||
chain. However, such an election reprocessing may only occur once per
|
||||
reward window (only one valid anchor block may exist for a reward
|
||||
cycle, whether it was hidden or not). Crucially, intentionally
|
||||
performing such an attack would require collusion amongst a large
|
||||
fraction `F` of the Stacks mining power — because such a hidden block
|
||||
must have been confirmed by `w*F` subsequent blocks. If collusion
|
||||
amongst such a large fraction of the Stacks mining power is possible,
|
||||
we contend that the security of the Stacks chain would be compromised
|
||||
through other means beyond attacking anchor blocks.
|
||||
|
||||
## Anchoring with Stacker Support.
|
||||
|
||||
The security of anchor block selection is further increased through
|
||||
Stacker support transactions. In this protocol, when Stacking
|
||||
participants broadcast their signed participation messages, they
|
||||
signal support of anchor blocks. This is specified by the chain tip's
|
||||
hash, and the support signal is valid as long as the message itself is
|
||||
valid.
|
||||
|
||||
This places an additional requirement on anchor block selection. In
|
||||
addition to an anchor block needing to reach a certain number of miner
|
||||
confirmations, it must also pass some threshold `t` of valid Stacker
|
||||
support message signals. This places an additional burden on an anchor
|
||||
block attack --- not only must the attacker collude amongst a large
|
||||
fraction of mining power, but they must also collude amongst a
|
||||
majority of the Stacking participants in their block.
|
||||
|
||||
# Stacker Delegation
|
||||
|
||||
The process of delegation allows a Stacks wallet address (the
|
||||
represented address) to designate another address (the delegate
|
||||
address) for participating in the Stacking protocol. This delegate
|
||||
address, for as long as the delegation is valid, is able to sign and
|
||||
broadcast Stacking messages (i.e., messages which lock up Stacks,
|
||||
designate the Bitcoin reward address, and signal support for chain
|
||||
tips) on behalf of the represented address. This allows the owner of
|
||||
the represented address to contribute to the security of the network
|
||||
by having the delegate address signal support for chain tips. This
|
||||
combats potential attacks on the blockchain stability by miners that
|
||||
may attempt to mine hidden forks, hide eventually invalid forks, and
|
||||
other forms of miner misbehavior.
|
||||
|
||||
Supporting delegation adds two new transaction types to the Stacks
|
||||
blockchain:
|
||||
|
||||
* **Delegate Funds.** This transaction initiates a
|
||||
represented-delegate relationship. It carries the following data:
|
||||
* Delegate address
|
||||
* End Block: the Bitcoin block height at which this relationship
|
||||
terminates, unless a subsequent delegate funds transaction updates
|
||||
the relationship.
|
||||
* Delegated Amount: the total amount of STX from this address
|
||||
that the delegate address will be able to issue Stacking messages
|
||||
on behalf of.
|
||||
* Reward Address (_optional_): a Bitcoin address that must be
|
||||
designated as the funds recipient in the delegate’s Stacking
|
||||
messages. If unspecified, the delegate can choose the address.
|
||||
* **Terminate Delegation.** This transaction terminates a
|
||||
represented-delegate relationship. It carries the following data:
|
||||
* Delegate Address
|
||||
|
||||
_Note_: There is only ever one active represented-delegate
|
||||
relationship between a given represented address and delegate address
|
||||
(i.e., the pair _(represented-address, delegate-address)_ uniquely
|
||||
identifies a relationship). If a represented-delegate relationship is
|
||||
still active and the represented address signs and broadcasts a new
|
||||
"delegate funds" transaction, the information from the new transaction
|
||||
replaces the prior relationship.
|
||||
|
||||
Both types of delegation transactions must be signed by the
|
||||
represented address. These are transactions on the Stacks blockchain,
|
||||
and will be implemented via a native smart contract, loaded into the
|
||||
blockchain during the Stacks 2.0 genesis block. These transactions,
|
||||
therefore, are `contract-call` invocations. The invoked methods are
|
||||
guarded by:
|
||||
|
||||
```
|
||||
(asserts! (is-eq contract-caller tx-sender) (err u0))
|
||||
```
|
||||
|
||||
This insures that the methods can only be invoked by direct
|
||||
transaction execution.
|
||||
|
||||
**Evaluating Stacking messages in the context of delegation.** In
|
||||
order to determine which addresses’ STX should be locked by a given
|
||||
Stacking message, the message must include the represented address in
|
||||
the Stacking message. Therefore, if a single Stacks address is the
|
||||
delegate for many represented Stacks addresses, the delegate address
|
||||
must broadcast a Stacking message for each of the represented
|
||||
addresses.
|
||||
|
||||
# Adressing Miner Consolidation in Stacking
|
||||
|
||||
PoX when used for Stacking rewards could lead to miner
|
||||
consolidation. Because miners that _also_ participate as Stackers
|
||||
could gain an advantage over miners who do not participate as
|
||||
Stackers, miners would be strongly incentivized to buy Stacks and use
|
||||
it to crowd out other miners. In the extreme case, this consolidation
|
||||
could lead to centralization of mining, which would undermine the
|
||||
decentralization goals of the Stacks blockchain. While we are actively
|
||||
investigating additional mechanisms to address this potential
|
||||
consolidation, we propose a time-bounded PoX mechanism and a Stacker-
|
||||
driven mechanism here.
|
||||
|
||||
**Time-Bounded PoX.** Stacking rewards incentivize miner consolidation
|
||||
if miners obtain _permanent_ advantages for obtaining the new
|
||||
cryptocurrency. However, by limiting the time period of PoX, this
|
||||
advantage declines over time. To do this, we define two time periods for Pox:
|
||||
|
||||
1. **Initial Phase.** In this phase, Stacking rewards proceed as
|
||||
described above -- commitment funds are sent to Stacking rewards
|
||||
addresses, except if a miner is not mining a descendant of the
|
||||
anchor block, or if the registered reward addresses for a given
|
||||
reward cycle have all been exhausted. This phase will last for
|
||||
approximately 2 years (100,000 Bitcoin blocks).
|
||||
|
||||
2. **Sunset Phase.** After the initial phase, a _sunset_ block is
|
||||
determined. This sunset block will be ~8 years (400,000 Bitcoin
|
||||
blocks) after the sunset phase begins. After the sunset block,
|
||||
_all_ miner commitments must be burned, rather than transfered to
|
||||
reward addresses. During the sunset phase, the reward / burn ratio
|
||||
linearly decreases by `0.25%` (1/400) on each reward cycle, such
|
||||
that in the 200th reward cycle, the ratio of funds that are
|
||||
transfered to reward addresses versus burnt must be equal to
|
||||
`0.5`. For example, if a miner commits 10 BTC, the miner must send
|
||||
5 BTC to reward addresses and 5 BTC to the burn address.
|
||||
|
||||
By time-bounding the PoX mechanism, we allow the Stacking protocol to
|
||||
use PoX to help bootstrap support for the new blockchain, providing
|
||||
miners and holders with incentives for participating in the network
|
||||
early on. Then, as natural use cases for the blockchain develop and
|
||||
gain steam, the PoX system could gradually scale down.
|
||||
|
||||
**Stacker-driven PoX.** To further discourage miners from consolidating,
|
||||
holders of liquid (i.e. non-Stacked) STX tokens may vote to disable PoX in the next upcoming
|
||||
reward cycle. This can be done with any amount of STX, and the act of voting
|
||||
to disable PoX does not lock the tokens.
|
||||
|
||||
This allows a community of vigilent
|
||||
users guard the chain from bad miner behavior arising from consolidation
|
||||
on a case-by-case basis. Specifically, if a fraction _R_ of liquid STX
|
||||
tokens vote to disable PoX, it is disabled
|
||||
only for the next reward cycle. To continuously deactivate PoX, the STX
|
||||
holders must continuously vote to disable it.
|
||||
|
||||
Due to the costs of remaining vigilent, this proposal recomments _R = 0.25_.
|
||||
At the time of this writing, this is higher than any single STX allocation, but
|
||||
not so high that large-scale cooperation is needed to stop a mining cartel.
|
||||
|
||||
# Bitcoin Wire Formats
|
||||
|
||||
Supporting PoX in the Stacks blockchain requires modifications to the
|
||||
wire format for leader block commitments, and the introduction of new
|
||||
wire formats for burnchain PoX participation (e.g., performing the STX
|
||||
lockup on the burnchain).
|
||||
|
||||
|
||||
## Leader Block Commits
|
||||
|
||||
For PoX, leader block commitments are similar to PoB block commits: the constraints on the
|
||||
BTC transaction's inputs are the same, and the `OP_RETURN` output is identical. However,
|
||||
the _burn output_ is no longer the same. For PoX, the following constraints are applied to
|
||||
the second through nth outputs:
|
||||
|
||||
1. If the block commitment is in a reward cycle, with a chosen anchor block, and this block
|
||||
commitment builds off a descendant of the PoX anchor block (or the anchor block itself),
|
||||
then the commitment must use the chosen PoX recipients for the current block.
|
||||
a. PoX recipients are chosen as described in "Stacking Consensus Algorithm": addresses
|
||||
are chosen without replacement, by using the previous burn block's sortition hash,
|
||||
mixed with the previous burn block's burn header hash as the seed for the ChaCha12
|
||||
pseudorandom function to select M addresses.
|
||||
b. The leader block commit transaction must use the selected M addresses as outputs [1, M]
|
||||
That is, the second through (M+1)th output correspond to the select PoX addresses.
|
||||
The order of these addresses does not matter. Each of these outputs must receive the
|
||||
same amount of BTC.
|
||||
c. If the number of remaining addresses in the reward set N is less than M, then the leader
|
||||
block commit transaction must burn BTC by including (M-N) burn outputs.
|
||||
2. Otherwise, the second through (M+1)th output must be burn addresses, and the amount burned by
|
||||
these outputs will be counted as the amount committed to by the block commit.
|
||||
|
||||
In addition, during the sunset phase (i.e., between the 100,000th and 500,000th burn block in the chain),
|
||||
the miner must include a _sunset burn_ output. This is an M+1 indexed output that includes the burn amount
|
||||
required to fulfill the sunset burn ratio, and must be sent to the burn address:
|
||||
|
||||
```
|
||||
sunset_burn_amount = (total_block_commit_amount) * (reward_cycle_start_height - 100,000) / (400,000)
|
||||
```
|
||||
|
||||
Where `total_block_commit_amount` is equal to the sum of outputs [1, M+1].
|
||||
|
||||
After the sunset phase _ends_ (i.e., blocks >= 500,000th burn block), block commits are _only_ burns, with
|
||||
a single burn output at index 1.
|
||||
|
||||
## STX Operations on Bitcoin
|
||||
|
||||
As described above, PoX allows stackers to submit `stack-stx`
|
||||
operations on Bitcoin as well as on the Stacks blockchain. The Stacks
|
||||
chain also allows addresses to submit STX transfers on the Bitcoin
|
||||
chain. Such operations are only evaluated by the miner of an anchor block
|
||||
elected in the burn block that immediately follows the burn block that included the
|
||||
operations. For example, if a `TransferStxOp` occurs in burnchain block 100, then the
|
||||
Stacks block elected by burnchain block 101 will process that transfer.
|
||||
|
||||
In order to submit on the Bitcoin chain, stackers must submit two Bitcoin transactions:
|
||||
|
||||
* `PreStxOp`: this operation prepares the Stacks blockchain node to validate the subsequent
|
||||
`StackStxOp` or `TransferStxOp`.
|
||||
* `StackStxOp`: this operation executes the `stack-stx` operation.
|
||||
* `TransferStxOp`: this operation transfers STX from a sender to a recipient
|
||||
|
||||
The wire formats for the above operations are as follows:
|
||||
|
||||
### PreStxOp
|
||||
|
||||
This operation includes an `OP_RETURN` output for the first Bitcoin output that looks as follows:
|
||||
|
||||
```
|
||||
0 2 3
|
||||
|------|--|
|
||||
magic op
|
||||
```
|
||||
|
||||
Where `op = p` (ascii encoded).
|
||||
|
||||
Then, the second Bitcoin output _must_ be Stacker address that will be used in a `StackStxOp`. This
|
||||
address must be a standard address type parseable by the stacks-blockchain node.
|
||||
|
||||
### StackStxOp
|
||||
|
||||
The first input to the Bitcoin operation _must_ consume a UTXO that is
|
||||
the second output of a `PreStxOp`. This validates that the `StackStxOp` was signed
|
||||
by the appropriate Stacker address.
|
||||
|
||||
This operation includes an `OP_RETURN` output for the first Bitcoin output:
|
||||
|
||||
```
|
||||
0 2 3 19 20
|
||||
|------|--|-----------------------------|---------|
|
||||
magic op uSTX to lock (u128) cycles (u8)
|
||||
```
|
||||
|
||||
Where `op = x` (ascii encoded).
|
||||
|
||||
Where the unsigned integer is big-endian encoded.
|
||||
|
||||
The second Bitcoin output will be used as the reward address for any stacking rewards.
|
||||
|
||||
### TransferStxOp
|
||||
|
||||
The first input to the Bitcoin operation _must_ consume a UTXO that is
|
||||
the second output of a `PreStxOp`. This validates that the `TransferStxOp` was signed
|
||||
by the appropriate STX address.
|
||||
|
||||
This operation includes an `OP_RETURN` output for the first Bitcoin output:
|
||||
|
||||
```
|
||||
0 2 3 19 80
|
||||
|------|--|-----------------------------|---------|
|
||||
magic op uSTX to transfer (u128) memo (up to 61 bytes)
|
||||
```
|
||||
|
||||
Where `op = $` (ascii encoded).
|
||||
|
||||
Where the unsigned integer is big-endian encoded.
|
||||
|
||||
The second Bitcoin output is either a `p2pkh` or `p2sh` output such
|
||||
that the recipient Stacks address can be derived from the
|
||||
corresponding 20-byte hash (hash160).
|
||||
This SIP is now located in the [stacksgov/sips repository](https://github.com/stacksgov/sips/blob/main/sips/sip-007/sip-007-stacking-consensus.md) as part of the [Stacks Community Governance organization](https://github.com/stacksgov).
|
||||
|
||||
@@ -1,333 +1,5 @@
|
||||
# SIP 008 Clarity Parsing and Analysis Cost Assessment
|
||||
|
||||
## Preamble
|
||||
|
||||
Title: Clarity Parsing and Analysis Cost Assessment
|
||||
|
||||
Author: Aaron Blankstein <aaron@blockstack.com>
|
||||
|
||||
Status: Draft
|
||||
|
||||
Type: Standard
|
||||
|
||||
Created: 03/05/2020
|
||||
|
||||
License: BSD 2-Clause
|
||||
|
||||
# Abstract
|
||||
|
||||
This document describes the measured costs and asymptotic costs
|
||||
assessed for parsing Clarity code into an abstract syntax tree (AST)
|
||||
and the static analysis of that Clarity code (type-checking and
|
||||
read-only enforcement). This will not specify the _constants_
|
||||
associated with those asymptotic cost functions. Those constants will
|
||||
necessarily be measured via benchmark harnesses and regression
|
||||
analyses.
|
||||
|
||||
# Measurements for Execution Cost
|
||||
|
||||
The cost of analyzing Clarity code is measured using the same 5 categories
|
||||
described in SIP-006 for the measurement of execution costs:
|
||||
|
||||
1. Runtime cost: captures the number of cycles that a single
|
||||
processor would require to process the Clarity block. This is a
|
||||
_unitless_ metric, so it will not correspond directly to cycles,
|
||||
but rather is meant to provide a basis for comparison between
|
||||
different Clarity code blocks.
|
||||
2. Data write count: captures the number of independent writes
|
||||
performed on the underlying data store (see SIP-004).
|
||||
3. Data read count: captures the number of independent reads
|
||||
performed on the underlying data store.
|
||||
4. Data write length: the number of bytes written to the underlying
|
||||
data store.
|
||||
5. Data read length: the number of bytes read from the underlying
|
||||
data store.
|
||||
|
||||
Importantly, these costs are used to set a _block limit_ for each
|
||||
block. When it comes to selecting transactions for inclusion in a
|
||||
block, miners are free to make their own choices based on transaction
|
||||
fees, however, blocks may not exceed the _block limit_. If they do so,
|
||||
the block is considered invalid by the network --- none of the block's
|
||||
transactions will be materialized and the leader forfeits all rewards
|
||||
from the block.
|
||||
|
||||
Costs for static analysis are assessed during the _type check_ pass.
|
||||
The read-only and trait-checking passes perform work which is strictly
|
||||
less than the work performed during type checking, and therefore, the
|
||||
cost assessment can safely fold any costs that would be incurred during
|
||||
those passes into the type checking pass.
|
||||
|
||||
# Common Analysis Metrics and Costs
|
||||
|
||||
## AST Parsing
|
||||
|
||||
The Clarity parser has a runtime that is linear with respect to the Clarity
|
||||
program length.
|
||||
|
||||
```
|
||||
a*X+b
|
||||
```
|
||||
|
||||
where a and b are constants, and
|
||||
|
||||
X := the program length in bytes
|
||||
|
||||
## Dependency cycle detection
|
||||
|
||||
Clarity performs cycle detection for intra-contract dependencies (e.g.,
|
||||
functions that depend on one another). This detection is linear in the
|
||||
number of dependency edges in the smart contract:
|
||||
|
||||
```
|
||||
a*X+b
|
||||
```
|
||||
|
||||
where a and b are constants, and
|
||||
X := the total number of dependency edges in the smart contract
|
||||
|
||||
Dependency edges are created anytime a top-level definition refers
|
||||
to another top-level definition.
|
||||
|
||||
## Type signature size
|
||||
|
||||
Types in Clarity may be described using type signatures. For example,
|
||||
`(tuple (a int) (b int))` describes a tuple with two keys `a` and `b`
|
||||
of type `int`. These type descriptions are used by the Clarity analysis
|
||||
passes to check the type correctness of Clarity code. Clarity type signatures
|
||||
have varying size, e.g., the signature `int` is smaller than the signature for a
|
||||
list of integers.
|
||||
|
||||
The signature size of a Clarity type is defined as follows:
|
||||
|
||||
```
|
||||
type_signature_size(x) :=
|
||||
if x =
|
||||
int => 1
|
||||
uint => 1
|
||||
bool => 1
|
||||
principal => 1
|
||||
buffer => 2
|
||||
optional => 1 + type_signature_size(entry_type)
|
||||
response => 1 + type_signature_size(ok_type) + type_signature_size(err_type)
|
||||
list => 2 + type_signature_size(entry_type)
|
||||
tuple => 1 + 2*(count(entries))
|
||||
+ sum(type_signature_size for each entry)
|
||||
+ sum(len(key_name) for each entry)
|
||||
```
|
||||
|
||||
## Type annotation
|
||||
|
||||
Each node in a Clarity contract's AST is annotated with the type value
|
||||
for that node during the type checking analysis pass.
|
||||
|
||||
The runtime cost of type annotation is:
|
||||
|
||||
```
|
||||
a + b*X
|
||||
```
|
||||
|
||||
where a and b are constants, and X is the type signature size of the
|
||||
type being annotated.
|
||||
|
||||
## Variable lookup
|
||||
|
||||
Looking up variables during static analysis incurs a non-constant cost -- the stack
|
||||
depth _and_ the length of the variable name affect this cost. However,
|
||||
variable names in Clarity have bounded length -- 128 characters. Therefore,
|
||||
the cost assessed for variable lookups may safely be constant with respect
|
||||
to name length.
|
||||
|
||||
The stack depth affects the lookup cost because the variable must be
|
||||
checked for in each context on the stack.
|
||||
|
||||
Cost Function:
|
||||
|
||||
```
|
||||
a*X+b*Y+c
|
||||
```
|
||||
|
||||
where a, b, and c are constants,
|
||||
X := stack depth
|
||||
Y := the type size of the looked up variable
|
||||
|
||||
## Function Lookup
|
||||
|
||||
Looking up a function incurs a constant cost with respect
|
||||
to name length (for the same reason as variable lookup). However,
|
||||
because functions may only be defined in the top-level contract
|
||||
context, stack depth does not affect function lookup.
|
||||
|
||||
Cost Function:
|
||||
|
||||
```
|
||||
a*X + b
|
||||
```
|
||||
|
||||
where a and b are constants,
|
||||
X := the sum of the type sizes for the function signature (each argument's type size, as well
|
||||
as the function's return type)
|
||||
|
||||
## Name Binding
|
||||
|
||||
The cost of binding a name in Clarity -- in either a local or the contract
|
||||
context is _constant_ with respect to the length of the name, but linear in
|
||||
the size of the type signature.
|
||||
|
||||
```
|
||||
binding_cost = a + b*X
|
||||
```
|
||||
|
||||
where a and b are constants, and
|
||||
X := the size of the bound type signature
|
||||
|
||||
## Type check cost
|
||||
|
||||
The cost of a static type check is _linear_ in the size of the type signature:
|
||||
|
||||
```
|
||||
type_check_cost(expected, actual) :=
|
||||
a + b*X
|
||||
```
|
||||
|
||||
where a and b are constants, and
|
||||
|
||||
X := `max(type_signature_size(expected), type_signature_size(actual))`
|
||||
|
||||
## Function Application
|
||||
|
||||
Static analysis of a function application in Clarity requires
|
||||
type checking the function's expected arguments against the
|
||||
supplied types.
|
||||
|
||||
The cost of applying a function is:
|
||||
|
||||
|
||||
```
|
||||
a + sum(type_check_cost(expected, actual) for each argument)
|
||||
```
|
||||
|
||||
where a is a constant.
|
||||
|
||||
This is also the _entire_ cost of type analysis for most function calls
|
||||
(e.g., intra-contract function calls, most simple native functions).
|
||||
|
||||
## Iterating the AST
|
||||
|
||||
Static analysis iterates over the entire program's AST in the type checker,
|
||||
the trait checker, and in the read-only checker. This cost is assessed
|
||||
as a constant cost for each node visited in the AST during the type
|
||||
checking pass.
|
||||
|
||||
# Special Function Costs
|
||||
|
||||
Some functions require additional work from the static analysis system.
|
||||
|
||||
## Functions on sequences (e.g., map, filter, fold)
|
||||
|
||||
Functions on sequences need to perform an additional check that the
|
||||
supplied type is a list or buffer before performing the normal
|
||||
argument type checking. This cost is assessed as:
|
||||
|
||||
```
|
||||
a
|
||||
```
|
||||
|
||||
where a is a constant.
|
||||
|
||||
## Functions on options/responses
|
||||
|
||||
Similarly to the functions on sequences, option/response functions
|
||||
must perform a simple check to see if the supplied input is an option or
|
||||
response before performing additional argument type checking. This cost is
|
||||
assessed as:
|
||||
|
||||
```
|
||||
a
|
||||
```
|
||||
|
||||
## Data functions (ft balance checks, nft lookups, map-get?, ...)
|
||||
|
||||
Static checks on intra-contract data functions do not require database lookups
|
||||
(unlike the runtime costs of these functions). Rather, these functions
|
||||
incur normal type lookup (i.e., fetching the type of an NFT, data map, or data var)
|
||||
and type checking costs.
|
||||
|
||||
## get
|
||||
|
||||
Checking a tuple _get_ requires accessing the tuple's signature
|
||||
for the specific field. This has runtime cost:
|
||||
|
||||
```
|
||||
a*log(N) + b
|
||||
```
|
||||
where a and b are constants, and
|
||||
|
||||
N := the number of fields in the tuple type
|
||||
|
||||
## tuple
|
||||
|
||||
Constructing a tuple requires building the tuple's BTree for
|
||||
accessing fields. This has runtime cost:
|
||||
|
||||
|
||||
```
|
||||
a*N*log(N) + b
|
||||
```
|
||||
where a and b are constants, and
|
||||
|
||||
N := the number of fields in the tuple type
|
||||
|
||||
## use-trait
|
||||
|
||||
Importing a trait imposes two kinds of costs on the analysis.
|
||||
First, the import requires a database read. Second, the imported
|
||||
trait is included in the static analysis output -- this increases
|
||||
the total storage usage and write length of the static analysis.
|
||||
|
||||
The costs are defined as:
|
||||
|
||||
```
|
||||
read_count = 1
|
||||
write_count = 0
|
||||
runtime = a*X+b
|
||||
write_length = c*X+d
|
||||
read_length = c*X+d
|
||||
```
|
||||
|
||||
where a, b, c, and d are constants, and
|
||||
|
||||
X := the total type size of the trait (i.e., the sum of the
|
||||
type sizes of each function signature).
|
||||
|
||||
## contract-call?
|
||||
|
||||
Checking a contract call requires a database lookup to inspect
|
||||
the function signature of a prior smart contract.
|
||||
|
||||
The costs are defined as:
|
||||
|
||||
```
|
||||
read_count = 1
|
||||
read_length = a*X+b
|
||||
runtime = c*X+d
|
||||
```
|
||||
|
||||
where a, b, c, and d are constants, and
|
||||
|
||||
X := the total type size of the function signature
|
||||
|
||||
## let
|
||||
|
||||
Let bindings require the static analysis system to iterate over
|
||||
each let binding and ensure that they are syntactically correct.
|
||||
|
||||
This imposes a runtime cost:
|
||||
|
||||
```
|
||||
a*X + b
|
||||
```
|
||||
where a and b are constants, and
|
||||
|
||||
X := the number of entries in the let binding.
|
||||
# SIP-008 Clarity Parsing and Analysis Cost Assessment
|
||||
|
||||
This document formerly contained SIP-008 before the Stacks 2.0 mainnet launched.
|
||||
|
||||
This SIP is now located in the [stacksgov/sips repository](https://github.com/stacksgov/sips/blob/main/sips/sip-008/sip-008-analysis-cost-assessment.md) as part of the [Stacks Community Governance organization](https://github.com/stacksgov).
|
||||
|
||||
@@ -85,6 +85,41 @@ is expired, or if there's never been such a stacker, then returns none."),
|
||||
("get-pox-info", "Returns information about PoX status.")
|
||||
];
|
||||
|
||||
let bns_descriptions = vec![
|
||||
("namespace-preorder", "Registers the salted hash of the namespace with BNS nodes, and burns the requisite amount of cryptocurrency. Additionally, this step proves to the BNS nodes that user has honored the BNS consensus rules by including a recent consensus hash in the transaction. Returns pre-order's expiration date (in blocks)."),
|
||||
("namespace-reveal", "Reveals the salt and the namespace ID (after a namespace preorder). It reveals how long names last in this namespace before they expire or must be renewed, and it sets a price function for the namespace that determines how cheap or expensive names its will be."),
|
||||
("name-import", "Imports name to a revealed namespace. Each imported name is given both an owner and some off-chain state."),
|
||||
("namespace-ready", "Launches the namespace and makes it available to the public. Once a namespace is launched, anyone can register a name in it if they pay the appropriate amount of cryptocurrency."),
|
||||
("name-preorder", "Preorders a name by telling all BNS nodes the salted hash of the BNS name. It pays the registration fee to the namespace owner's designated address."),
|
||||
("name-register", "Reveals the salt and the name to all BNS nodes, and assigns the name an initial public key hash and zone file hash."),
|
||||
("name-update", "Changes the name's zone file hash. You would send a name update transaction if you wanted to change the name's zone file contents. For example, you would do this if you want to deploy your own Gaia hub and want other people to read from it."),
|
||||
("name-transfer", "Changes the name's public key hash. You would send a name transfer transaction if you wanted to:
|
||||
* Change your private key
|
||||
* Send the name to someone else or
|
||||
* Update your zone file
|
||||
|
||||
When transferring a name, you have the option to also clear the name's zone file hash (i.e. set it to null). This is useful for when you send the name to someone else, so the recipient's name does not resolve to your zone file."),
|
||||
("name-revoke", "Makes a name unresolvable. The BNS consensus rules stipulate that once a name is revoked, no one can change its public key hash or its zone file hash. The name's zone file hash is set to null to prevent it from resolving. You should only do this if your private key is compromised, or if you want to render your name unusable for whatever reason."),
|
||||
("name-renewal", "Depending in the namespace rules, a name can expire. For example, names in the .id namespace expire after 2 years. You need to send a name renewal every so often to keep your name.
|
||||
|
||||
You will pay the registration cost of your name to the namespace's designated burn address when you renew it.
|
||||
When a name expires, it enters a \"grace period\". The period is set to 5000 blocks (a month) but can be configured for each namespace.
|
||||
|
||||
It will stop resolving in the grace period, and all of the above operations will cease to be honored by the BNS consensus rules.
|
||||
You may, however, send a NAME_RENEWAL during this grace period to preserve your name. After the grace period, everybody can register that name again.
|
||||
If your name is in a namespace where names do not expire, then you never need to use this transaction."),
|
||||
("get-namespace-price", "Gets the price for a namespace."),
|
||||
("get-name-price", "Gets the price for a name."),
|
||||
("can-namespace-be-registered", "Returns true if the provided namespace is available."),
|
||||
("is-name-lease-expired", "Return true if the provided name lease is expired."),
|
||||
("can-name-be-registered", "Returns true if the provided name can be registered."),
|
||||
("name-resolve", "Get name registration details."),
|
||||
("get-namespace-properties", "Get namespace properties."),
|
||||
("can-receive-name", "Returns true if the provided name can be received. That is, if it is not curretly owned, a previous lease is expired, and the name wasn't revoked."),
|
||||
("get-name", "Returns a response with the username that belongs to the user if any, otherwise an error ERROR_NOT_FOUND is returned."),
|
||||
("resolve-principal", "Returns the registered name that a principal owns if there is one. A principal can only own one name at a time.")
|
||||
];
|
||||
|
||||
let pox_skip_display = vec![
|
||||
"set-burnchain-parameters",
|
||||
"minimal-can-stack-stx",
|
||||
@@ -92,13 +127,29 @@ is expired, or if there's never been such a stacker, then returns none."),
|
||||
"get-reward-set-pox-address",
|
||||
];
|
||||
|
||||
HashMap::from_iter(vec![(
|
||||
"pox",
|
||||
ContractSupportDocs {
|
||||
descriptions: HashMap::from_iter(pox_descriptions.into_iter()),
|
||||
skip_func_display: HashSet::from_iter(pox_skip_display.into_iter()),
|
||||
},
|
||||
)])
|
||||
let bns_skip_display = vec![
|
||||
"namespace-update-function-price",
|
||||
"namespace-revoke-function-price-edition",
|
||||
"check-name-ops-preconditions",
|
||||
"is-name-in-grace-period",
|
||||
];
|
||||
|
||||
HashMap::from_iter(vec![
|
||||
(
|
||||
"pox",
|
||||
ContractSupportDocs {
|
||||
descriptions: HashMap::from_iter(pox_descriptions.into_iter()),
|
||||
skip_func_display: HashSet::from_iter(pox_skip_display.into_iter()),
|
||||
},
|
||||
),
|
||||
(
|
||||
"bns",
|
||||
ContractSupportDocs {
|
||||
descriptions: HashMap::from_iter(bns_descriptions.into_iter()),
|
||||
skip_func_display: HashSet::from_iter(bns_skip_display.into_iter()),
|
||||
},
|
||||
),
|
||||
])
|
||||
}
|
||||
|
||||
fn make_func_ref(func_name: &str, func_type: &FunctionType, description: &str) -> FunctionRef {
|
||||
|
||||
Reference in New Issue
Block a user