* fix: some spot fixes for obtaining epoch and block limit from clarity connection, adding evaluated epoch to block receipt * fix unit test errors, remove panics from net paths, use /v2/info to get current burn height in integration test * fix: use UnitTest versions of Burnstate and Headers dbs, which return sane-ish block height. Explicit special case for genesis block in get_epoch_of. Use Clarity epoch for read only connections * use drop_unconfirmed_state directly rather than begin_unconfirmed->rollback_unconfirmed * Fix: single line in sortdb for each db config version * feat: use serialized_size() aggregation for cost inputs of linear functions, #2871 * chore: remove unused CostInputHandle struct * chore: prepare changelog for release * chore: fix merge artifacts in unit tests * test: start of testing for #2913. adds a mined_block event to the dispatcher * fix: issue raised in #2913 * docs: add reference for the event dispatcher changes * fix: make burnchain test framework set the epoch marker in block-commits * chore: improve documentation for getting the parent block snapshot and block commit * feat: add has_microblock_parent() method to StacksBlockHeader and StacksBlock * fix: implement #2904 -- make it so that if an anchored block straddles an epoch boundary, it must not have a microblock parent. It will be invalid if it does. * feat: make it so the stacks anchored block miner will *not* confirm a microblock stream if the block it produces has a parent anchored block in a different epoch. Also, add unit tests to both the miner and consensus rules to make sure that the miner behaves this way, and a block that violates this new rule will be rejected * feat: add a variant for processing a tenure in the test framework that is allowed to fail * test: fix the assertion in runtime_overflow_unconfirmed_microblocks_integration_test to match new expected behavior * ci: add new test to github workflow * feat: update costs-2 with latest benchmark results, expand unit test coverage in vm::tests::costs * test: expand coverage for analysis costs in 2.05 * chore: refactoring with new API * fix: remember which blocks actually execute the epoch transition, so that when we process a child of that block, we can determine whether or not the child confirmed any microblocks of that epoch-transition block (it shouldn't) * fix: add a new table for stacks block IDs in which an epoch transition takes place, and add the schema migration code to apply it * fix: update the miner test to verify that both the validate_anchor_block_burnchain() method and the append_block() method correctly reject blocks whose parents encountered an epoch transaction and who confirmed microblocks * fix: test code refactoring to always restore sortdb and node in the test node when processing an epoch that could fail * chore: store network epoch in stacks epoch * chore: use network epoch in stacks epoch * chore: use network epoch in stacks epoch * chore: use network epoch in stacks epoch * chore: use network epoch in stacks epoch * feat: require the last byte of peer_version to indicate the epoch of the node (0x05 now) * feat: require a preamble's peer_version's epoch marker to be at least as high as the local node's epoch marker, or if it isn't, require that it is at least as high as the current epoch's epoch marker * refactor: pass along epochs * refactor: pass along epochs to peer network * refactor: add network_epoch to stacks epoch * refactor: add network_epoch to stacks epoch * refactor: add network_epoch to stacks epoch * refactor: add network_epoch to stacks epoch * refactor: pass epochs along to peer network * refactor: pass along epochs to peer network * refactor: add network_epoch to stacks epoch * refactor: add network epoch to stacks epoch * chore: test-debug output for rejecting peers based on their peer_version epoch * feat: e2e test to verify that nodes in different epochs can't talk to each other * chore: cargo fmt * fix: log the right hashes * fix: add epoch_205 test to verify that the new code that tracks which blocks contained epoch transitions works end-to-end * feat: update costs-2, fixing the matching between various paired methods * feat: match nft transfer/burn/mint/get-owner * Stacks 2.05: Allow epochs to be specified in Toml for Testnet (#2910) * added debug and serialize to the burnchainconfig * can supply epochs in toml, only use if not mainnet * add enough Debug to print ConfigFile * revert src/main * reduced some comments * info to debug * changed a few comments * removed the serialize's * fixed the doc * removed some Debug's * panicking earlier * chore: split testnet and mainnet costs * chore: select 2.05 xenon testnet start height, dead code elimination * fix: 2.0/1.0 epoch heights in testnet * chore: #2878 set 2.05 mainnet burn height * test: fix some test flakiness in epoch_205::exact * test: add assertion that the epoch switch is applied * chore: cargo fmt * fix: attempt #2 on testnet block height - 2104380 * test: add some comments for test changes * fix: include network_epoch * fix: add back in network_epoch * fix typo (#2929) * fix: insert epochs on migration in SortitionDB::connect, error in SortitionDB::open * smooth similar runtimes to same value, bump cost_principal_of to hash160(32) * Stacks 2.05: Add Epoch to Cost Estimate Key (#2923) * added bunch of things * moving the stuff * test compiles * added a test * fixed typo * added two tests * updated code/tests in stacks-node * replaced indexed conn with unindexed one * address PR feedback * add comment to validate_and_insert_epochs * fix: invoke connect early to avoid race-to-connect * fix: move connect_dbs to *after* initial burnchain sync * Ci/fix codecov (#2932) * ci: update codecov upload method * ci: give codecov git info * ci: codecov for stacks-node tests * ci: give codecov git access * ci: build binary and fix path * feat: add methods to directly query database version information from a path, so we can determine if the database is up to date with the current epoch * feat: add top-level method to check the sortition and chainstate database versions against the current epoch, to verify if they are consistent * chore: test for verifying that the database version check code works if the databases don't exist, if they do exist, and if they do exist in different epochs * chore: refactor the code for creating directories in the stacks chain state and for generating the various paths to its database files, and use the new refactored code to make it possible to query the chainstate database version directly from a path. * chore: document find_epoch() * feat: add get_stacks_epochs() trait implementation * feat: add get_stacks_epochs trait implementation * feat: require a burnchain controller to be able to determine the sequence of stacks epochs it supports * feat: check the chainstate database versions (if they exist), and abort the node if the versions are incompatible with the current epoch * fix: only return neighbors supported by the current epoch * fix: when querying neighbors, consider the peer version's epoch marker, and only return neighbors with epoch markers equal or higher * chore: api sync * chore: add helper method to get the current epoch from the peer network * chore: refactor /v2/neighbors to pass the current epoch marker * Ci/next costs coverage (#2934) * Ci/fix codecov (#2932) * ci: update codecov upload method * ci: give codecov git info * ci: codecov for stacks-node tests * ci: give codecov git access * ci: build binary and fix path * ci: code coverage for bitcoind_integration_test * ci: fix bin path * ci: cargo test first then build * ci: syntax * ci: name each code cov upload * ci: adjust source dir * switch cost tests to use mainnet as well as testnet * chore: add CHANGELOG.md entry for 2.05 * fix: use indexer's get_stacks_epochs() * chore: use testnet chain ID constant * mainnet tests are failing * chore: sync costs-2.clar with sip-012 * still not passing * all but two tests passing * fix run-on sentence * ok * changing the contract to be variable * test_cost_contract_short_circuits passing for mainnet * had to comment two things to get test to pass * Revert "had to comment two things to get test to pass" This reverts commit4974ea91f4. * mainnet doesn't clone * add libclariy * revert test_cost_voting_integration being rstest * Revert "mainnet doesn't clone" This reverts commit15a3b6e72a. * remove rstest dependency * rolling back rstest dependencies * remove clone again * remove extra arguments for OE:new * refactor the "analysis" cost tests * added test * ok * chore: refactor LimitedCostTracker into Free and Limited variants * use "Stacks boot address" * removed some ?? * small fixes * fixing double declares * removed more duplicate includes/defs * removed more dpulicates * cleaning up test code * fixed the epochs * fixed the epochs * clarityinstance::New * compiler errors * replace versions * added some limits * removed blocklimit from ClarityInstance * clarity version hardcode * eval_all fix * tenure_with_txs * error[E0592]: duplicate definitions with name `unit_test` * error[E0063]: missing field `epochs` in initializer of `BitcoinIndexerConfig` * iremove duplicate calls to StacksChainState::process_epoch_transition * add arg to type_check * back to execute in epoch * added execute_with_parameters * replaced many to execute_with_parameters * fix execute_with_parameters * default values in eval with params * ClarityInstance::new args * unused import * initialize contract * fix open_and_exec * added easy "not covered" match cases * final "not covered" case * Ok(ture) output * clarityersion * added clarity version args * refixed some clarityversion's * no field blocklimit on clarityinstance * no method StacksChainState::open_with_block_limit * test_burnstatedb_epoch arguments * remove bad import * fix SortitionDB::connect * SortitionDB::open * add pox constants * aded block limit and epoch * format * clarity version reference * iget_burn_start_height * iget_stacks_epoch_by_epoch_id * BlockLimitBurnStateDB * BlockLimitBurnStateDB * helper eecute * StacksEpoch::unit_test * PoxConstants::new * compiles * fixed double epochs creation * wrong assert * fixed some epoch stuff * set some epochs to 2.0 * adding to execute changes * don't add block limit * no sortdb * moved epochs * use clarityversion in tests * add epochs * add epochs and pox constants * stacks-node compiles * fix epochs * fixed epoch advacing * fix vm::tests::costs::test_all_mainnet * format * epoch_205_test_all_mainnet passing * analysis/tests/costs passing * fixing pox tests * gettig close * test worked but other regressions * tests passing * trimmed out debug info * removed debug files * format * ivm::tests::events::test_emit_stx_transfer_memo_ok * differentiate test_epoch_switch_2_05 test_epoch_switch_21 * remove dns * removed a warning * simplify key value pair * remove extra epochs defn * reinstate the serialize/deserialize * adjusted comments * s/NullBurnStateDB/BlockLimitBurnStateDB/ * changed stacks 2.0 test names in tests/costs to say that they are 'epoch20' * reinstate panics in pox methods for NullBurnStateDB * 0x0a is 10, not 0x10 * epoch21 gets its own key is pessimistic estimator * allow jump from epoch20 to epoch21 * test each epoch separately in tests/costs, including epoch21 * use >= for epoch condition * reinstate epochs.sort(), to see what will break * revert to ClaritySerializable for u32 * fixed inadvertent build error * chore: fix emit event tests Co-authored-by: Aaron Blankstein <aaron@blockstack.com> Co-authored-by: Pavitthra Pandurangan <paviusa23@gmail.com> Co-authored-by: Jude Nelson <judecn@gmail.com> Co-authored-by: Charlie <2747302+CharlieC3@users.noreply.github.com> Co-authored-by: Greg Coppola <greg@hiro.so>
16 KiB
Contributing to Blockstack Core
Blockstack Core is open-source software written in Rust. Contributions should adhere to the following best practices.
You can find information on joining online community forums (Discord, mailing list etc.) in the README.
Table Of Contents
Code of Conduct
This project and everyone participating in it is governed by this Code of Conduct.
How Can I Contribute?
Development Workflow
- For typical development, branch off of the
developbranch. - For consensus breaking changes, branch off of the
nextbranch. - For hotfixes, branch off of
master.
Documentation Updates
- Any major changes should be added to the CHANGELOG.
- Mention any required documentation changes in the description of your pull request.
- If adding an RPC endpoint, add an entry for the new endpoint to the OpenAPI spec
./docs/rpc/openapi.yaml. - If your code adds or modifies any major features (struct, trait, test, module, function, etc.), each should be documented according to our style rules.
- To generate HTML documentation for the library, run
cargo doc --no-deps --open. - It's possible to check the percentage of code coverage by (a) switching to the nightly version of rust (can run
rustup default nightly, and also might need to editrust-toolchainfile to say "nightly" instead of "stable"), and (b) runningRUSTDOCFLAGS='-Z unstable-options --show-coverage' cargo doc.
- To generate HTML documentation for the library, run
Each file should include relevant unit tests
Each Rust file should contain a mod test {} definition, in which unit tests
should be supplied for the file's methods. Unit tests should cover a maximal
amount of code paths.
Contributing Conventions
Simplicity of implementation
The most important consideration when accepting or rejecting a contribution is the simplicity (i.e. ease of understanding) of its implementation. Contributions that are "clever" or introduce functionality beyond the scope of the immediate problem they are meant to solve will be rejected.
Type simplicity
Simplicity of implementation includes simplicity of types. Type parameters and associated types should only be used if there are at least two possible implementations of those types.
Lifetime parameters should only be introduced if the compiler cannot deduce them on its own.
Builds with a stable Rust compiler
We use a recent, stable Rust compiler. Contributions should not require nightly Rust features to build and run.
Use built-in logging facilities
Blockstack Core implements logging macros in util::log. If your code needs to
output data, it should use these macros exclusively for doing so. The only
exception is code that is explicitly user-facing, such as help documentation.
Minimal dependencies
Adding new package dependencies is very much discouraged. Exceptions will be granted on a case-by-case basis, and only if deemed absolutely necessary.
Minimal global macros
Adding new global macros is discouraged. Exceptions will only be given if absolutely necessary.
Minimal compiler warnings
Contributions should not trigger compiler warnings if possible, and should not mask compiler warnings with macros. Common sources of compiler warnings that will not be accepted include, but are not limited to:
- unnecessary imports
- unused code
- variable naming conventions
- unhandled return types
Minimal unsafe code
Contributions should not contain unsafe blocks if at all possible.
Error definitions
Each module should include an Error enumeration in its mod.rs that encodes
errors specific to the module. All error code paths in the module should return
an Err type with one of the module's errors.
Style
Git Commit Messages
Aim to use descriptive git commit messages. We try to follow conventional commits. The general format is as follows:
<type>[optional scope]: <one-line description>
[optional body]
[optional footer(s)]
Common types include build, ci, docs, fix, feat, test, refactor, etc.
Rust styleguide
Code block consistency
Surrounding code blocks with { and } is encouraged, even when the enclosed
block is a single statement. Blocks in the same lexical scope must use
consistent conventions. For example, consider the following:
match foo {
1..2 => {
// this is a single statement, but it is surrounded
// with { and } because the other blocks in the match
// statement need them.
Ok(true)
},
3..4 => {
error!("Bad value for foo");
Err(Error::BadFoo)
},
_ => {
// similarly, this block uses { }
Ok(true)
}
}
// conversely, all of the arms of this match statement
// have one-statement blocks, so { and } can be elided.
match bar {
1..2 => Some("abc"),
3..4 => Some("def"),
_ => None
}
Whitespace
All contributions should use the same whitespacing as the rest of the project. Moreover, Pull requests where a large number of changes only deal with whitespace will be rejected.
Comments
Comments are very important for the readability and correctness of the codebase. The purpose of comments is:
- Allow readers to understand the roles of components and functions without having to check how they are used.
- Allow readers to check the correctness of the code against the comments.
- Allow readers to follow tests.
In the limit, if there are no comments, the problems that arise are:
- Understanding one part of the code requires understanding many parts of the code. This is because the reader is forced to learn the meanings of constructs inductively through their use. Learning how one construct is used requires understanding its neighbors, and then their neighbors, and so on, recursively. Instead, with a good comment, the reader can understand the role of a construct with
O(1)work by reading the comment. - The user cannot be certain if there is a bug in the code, because there is no distinction between the contract of a function, and its definition.
- The user cannot be sure if a test is correct, because the logic of the test is not specified, and the functions do not have contracts.
Comment Formatting
Comments are to be formatted in typical rust style, specifically:
-
Use markdown to format comments.
-
Use the triple forward slash "///" for modules, structs, enums, traits and functions. Use double forward slash "//" for comments on individual lines of code.
-
Start with a high-level description of the function, adding more sentences with details if necessary.
-
When documenting panics, errors, or other conceptual sections, introduce a Markdown section with a single
#, e.g.:-
# Errors * ContractTooLargeError: Thrown when `contract` is larger than `MAX_CONTRACT_SIZE`.
-
Content of Comments
The following kinds of things should have comments.
Components
Comments for a component (struct, trait, or enum) should explain what the overall
purpose of that component is. This is usually a concept, and not a formal contract. Include anything that is not obvious about this component.
Example:
/// The `ReadOnlyChecker` analyzes a contract to determine whether
/// there are any violations of read-only declarations. By a "violation"
/// we mean a function that is marked as "read only" but which tries
/// to modify chainstate.
pub struct ReadOnlyChecker<'a, 'b> {
This comment is considered positive because it explains the concept behind the class at a glance, so that the reader has some idea about what the methods will achieve, without reading each method declaration and comment. It also defines some terms that can be used in the comments on the method names.
Functions
The comments on a function should explain what the function does, without having to read it. Wherever practical, it should specify the contract of a function, such that a bug in the logic could be discovered by a discrepancy between contract and implementation, or such that a test could be written with only access to the function comment.
Without being unnecessarily verbose, explain how the output is calculated from the inputs. Explain the side effects. Explain any restrictions on the inputs. Explain failure conditions, including when the function will panic, return an error or return an empty value.
Example:
/// A contract that does not violate its read-only declarations is called
/// *read-only correct*.
impl<'a, 'b> ReadOnlyChecker<'a, 'b> {
/// Checks each top-level expression in `contract_analysis.expressions`
/// for read-only correctness.
///
/// Returns successfully iff this function is read-only correct.
///
/// # Errors
///
/// - Returns CheckErrors::WriteAttemptedInReadOnly if there is a read-only
/// violation, i.e. if some function marked read-only attempts to modify
/// the chainstate.
pub fn run(&mut self, contract_analysis: &ContractAnalysis) -> CheckResult<()>
This comment is considered positive because it explains the contract of the function in pseudo-code. Someone who understands the constructs mentioned could, e.g., write a test for this method from this description.
Comments on Implementations of Virtual Methods
Note that, if a function implements a virtual function on an interface, the comments should not repeat what was specified on the interface declaration. The comment should only add information specific to that implementation.
Data Members
Each data member in a struct should have a comment describing what that member is, and what it is used for. Such comments are usually brief but should clear up any ambiguity that might result from having only the variable name and type.
Example:
pub struct ReadOnlyChecker<'a, 'b> {
/// Mapping from function name to a boolean indicating whether
/// the function with that name is read-only.
/// This map contains all functions in the contract analyzed.
defined_functions: HashMap<ClarityName, bool>,
This comment is considered positive because it clarifies users might have about the content and role of this member. E.g., it explains that the bool indicates whether the function is read-only, whereas this cannot be gotten from the signature alone.
Tests
Each test should have enough comments to help an unfamiliar reader understand:
- what is conceptually being tested
- why a given answer is expected
Sometimes this can be obvious without much comments, perhaps from the context, or because the test is very simple. Often though, comments are necessary.
Example:
#[test]
#[ignore]
fn transaction_validation_integration_test() {
/// The purpose of this test is to check if the mempool admission checks
/// for the post tx endpoint are working as expected wrt the optional
/// `mempool_admission_check` query parameter.
///
/// In this test, we are manually creating a microblock as well as
/// reloading the unconfirmed state of the chainstate, instead of relying
/// on `next_block_and_wait` to generate microblocks. We do this because
/// the unconfirmed state is not automatically being initialized
/// on the node, so attempting to validate any transactions against the
/// expected unconfirmed state fails.
This comment is considered positive because it explains the purpose of the test (checking the case of an optional parameter), it also guides the reader to understand the low-level details about why a microblock is created manually.
How Much to Comment
Contributors should strike a balance between commenting "too much" and commenting "too little". Commenting "too much" primarily includes commenting things that are clear from the context. Commenting "too little" primarily includes writing no comments at all, or writing comments that leave important questions unresolved.
Human judgment and creativity must be used to create good comments, which convey important information with small amounts of text. There is no single rule which can determine what a good comment is. Longer comments are not always better, since needlessly long comments have a cost: they require the reader to read more, take up whitespace, and take longer to write and review.
Don't Restate the Function Names
The contracts of functions should be implemented precisely enough that tests could be written looking only at the declaration and the comments (and without looking at the definition!). However:
- the author should assume that the reader has already read and understood the function name, variable names, type names, etc.
- the author should only state information that is new
So, if a function and its variables have very descriptive names, then there may be nothing to add in the comments at all!
Bad Example
/// Appends a transaction to a block.
fn append_transaction_to_block(transaction:Transaction, &mut Block) -> Result<()>
This is considered bad because the function name already says "append transaction to block", so it doesn't add anything to restate it in the comments. However, do add anything that is not redundant, such as elaborating what it means to "append" (if there is more to say), or what conditions will lead to an error.
Good Example
/// # Errors
///
/// - BlockTooBigError: Is returned if adding `transaction` to `block` results
/// in a block size bigger than MAX_BLOCK_SIZE.
fn append_transaction_to_block(transaction:Transaction, block:&mut Block) -> Result<()>
This is considered good because the reader builds on the context created by the function and variable names. Rather than restating them, the function just adds elements of the contract that are not implicit in the declaration.
Do's and Dont's
Don't over-comment by documenting things that are clear from the context. E.g.:
- Don't document the types of inputs or outputs, since these are parts of the type signature in
rust. - Don't necessarily document standard "getters" and "setters", like
get_clarity_version(), unless there is unexpected information to add with the comment. - Don't explain that a specific test does type-checking, if it is in a file that is dedicated to type-checking.
Do document things that are not clear, e.g.:
- For a function called
process_block, explain what it means to "process" a block. - For a function called
process_block, make clear whether we mean anchored blocks, microblocks, or both. - For a function called
run, explain the steps involved in "running". - For a function that takes arguments
peer1andpeer2, explain the difference between the two. - For a function that takes an argument
height, either explain in the comment what this is the height of. Alternatively, expand the variable name to remove the ambiguity. - For a test, document what it is meant to test, and why the expected answers are, in fact, expected.
Changing Code Instead of Comments
Keep in mind that better variable names can reduce the need for comments, e.g.:
burnblock_heightinstead ofheightmay eliminate the need to comment thatheightrefers to a burnblock heightprocess_microblocksinstead ofprocess_blocksis more correct, and may eliminate the need to to explain that the inputs are microblocksadd_transaction_to_microblockexplains more thanhandle_transaction, and reduces the need to even read the comment
Licensing and contributor license agreement
Blockstack Core is released under the terms of the GPL version 3. Contributions that are not licensed under compatible terms will be rejected. Moreover, contributions will not be accepted unless all authors accept the project's contributor license agreement.