This repository has been archived by the owner on Aug 2, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 3.8k
protocol upgrade activation mechanism (also implements PREACTIVATE_FEATURE and ONLY_LINK_TO_EXISTING_PERMISSION protocol features) #6831
Merged
arhag
merged 83 commits into
protocol-feature-foundations
from
6429-protocol-upgrade-activation-mechanism
Mar 26, 2019
Merged
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
…e-activation-mechanism
…g for recognized protocol features #6429
fix pending_schedule_hash
…e-activation-mechanism
…e-activation-mechanism
…ome inconsistent with the contents
…e-activation-mechanism
…e-activation-mechanism
…scheduled_protocol_feature_activations and schedule_protocol_feature_activations to producer_api_plugin
…inconsistent header_exts
…tin_protocol_feature and reimplement to respect dependencies and earliest allow activation time
…hey are updated to initially activate PREACTIVATE_FEATURE
prevent producer plugin from scheduling a feature that require preactivation
…eature_set of tester
Add subjective restriction procotol feature test
fix bios_boot script for feature_digest param
taokayan
approved these changes
Mar 26, 2019
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've briefly go through it and can't see any vulnerability. But since the changes are more than 4000 lines of code, it is unlikely to be checked very deeply. I suggest to merge the code first and then we can start testing.
This was referenced Mar 26, 2019
arhag
changed the title
protocol upgrade activation mechanism
protocol upgrade activation mechanism (also implements PREACTIVATE_FEATURE and ONLY_LINK_TO_EXISTING_PERMISSION protocol features)
Apr 16, 2019
3 tasks
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Change Description
Resolves #6429, #6431, #6437, and #6333.
This PR builds the foundations needed to enable consensus protocol upgrades (also referred to as protocol features throughout the code and documentation). These foundations consist of the following three pillars:
PREACTIVATE_FEATURE
which cannot be pre-activated) and another new RPC endpoint in chain_api_plugin to allow users to see which protocol features have been activated as of the current state of a node.eosio::setcode
(Dynamic whitelist of intrinsics #6437).preactivate_feature
andis_feature_activated
, which become linkable (via the mechanism of pillar 2 above) on activation of thePREACTIVATE_FEATURE
intrinsic, enable, respectively, pre-activating a protocol feature from within a privileged contract and checking if a particular protocol feature has been activated from within any contract (Consensus protocol upgrade to enable protocol feature pre-activation #6431).PRs #6588 and #6624 made changes to critical blockchain data structures which made it not possible to replay with a state directory generated by v1.7.x or earlier of nodeos nor recover from a snapshot generated by those earlier versions. This PR continues to take advantage of the freedom of not being constrained by replay-ability concerns by making necessary changes to the block header state and state data structures to support the three pillars of the protocol feature foundations.
Changes to block header state
The block header state structures were modified to include a new shared pointer field providing access to the set of digests (each protocol feature is represented by a 256-bit digest) for each of the protocol features that are active as of that block. If a new block building upon another does not activate any new protocol features, the
activated_protocol_features
of the correspondingblock_header_state
of the new block will point to the same data structure as the pointed to byactivated_protocol_features
of theblock_header_state
of the prior block. However, if the new block activates new protocol features, theactivated_protocol_features
of its correspondingblock_header_state
will point to a new set which is the union of the one for the prior block and any new protocol features activated in the new block.New protocol features are signaled for activation through a block header extension.
pending_block_header_state::finish_block
uses theprotocol_feature_activation
extracted from the block header extension to first determine whether they are considered valid (via a passed invalidator
function) and then add them to the protocol feature activation set. Details of this process will be discussed under the pillar 1 section below.block_header_state
also has a newheader_exts
field which is merely a cache of the vector ofblock_header_extensions
extracted from a block header using the newblock_header::validate_and_extract_header_extensions
method.There are three other changes added to
block_header_state.cpp
. One is an optimization toblock_header_state::calc_dpos_last_irreversible
that usesstd::nth_element
to get the desired element rather than unnecessarily sorting the full sequence. The other two are fixes to bugs introduced in the refactoring effort of PR #6588 that would break existing consensus if left unfixed: the hash of the pending schedule was incorrectly computed when promoted from proposed; a quirk in updatingproducer_to_last_produced
in the existing consensus algorithm was not properly replicated in the refactor (required adding thenew_producer_to_last_produced[prokey.producer_name] = result.block_num;
line). The unit testproducer_schedule_tests/producer_watermark_test
was added in response to that second bug fix.Changes to fork database
Additional minor changes were made to
fork_database
beyond PR #6624. An explicitopen
method was added to decouple constructing afork_database
instance from actually opening and loading the database it points to; it also better complements the existingclose
method.fork_database::open
takes avalidator
function whichcontroller_impl
provides as one that utilizes thecontroller_impl::check_protocol_features
method. For safety and clarity, I wanted to ensure the fields ofcontroller_impl
(specifically the newprotocol_features
field) are fully constructed prior to utilizing avalidator
function that calls thecontroller_impl::check_protocol_features
method, since that method expects theprotocol_features
to be constructed. Adding a newblock_state_ptr
tofork_database
using theadd
method will now allow (optionally) validating any new protocol features signaled for activation in the block header. The validation is enforced when opening afork_database
to ensure that any blocks retrieved from the serialized fork database file are still consistent with the protocol features locally recognized by the node.Pillar 1: The core protocol feature activation mechanism
The original EOSIO consensus mechanism disallows having any block header extensions (i.e. having a non-empty
header_extensions
field). However, this field was added to the block header data structure explicitly to support adding data in there in future upgrades. The core protocol feature activation mechanism introduced in this PR takes advantage of this extension field to signal activation of protocol features.It is acceptable that non-empty fields containing protocol feature activations will rejected by nodeos v1.7.x and earlier since the activation of a protocol feature not supported by an older version of nodeos is intended to be rejected. Old nodes will be unable to follow the blockchain branch utilizing the new features. With a two-thirds supermajority of BPs upgraded to support the activation of the first protocol feature, any old nodes will remain stuck on a part of the blockchain that cannot advance the last irreversible block until they upgrade their nodes to support the protocol feature(s).
However, there are strict requirements for what the data in
header_extensions
must look like to be accepted as a signal to activate protocol features. This was described in issue #6429 with the phrase "well-formed protocol feature activation". These rules have been implemented in the code through the newprotocol_feature_activation
type and the validation rules inblock_header::validate_and_extract_header_extensions
which actually deserializes the data inheader_extensions
into a more useable form:vector<block_header_extensions>
whereblock_header_extensions
is astatic_variant
of the supported forms of block header extensions (currently onlyprotocol_feature_activation
).The discussion of block header state changes above discussed how the set of activated protocol features is tracked per
block_header_state
(which are managed as a tree of possible blockchain branches to switch to infork_database
). Some validation also occurs at the stage of creating a newblock_header_state
. For example, the validation guarantees that no protocol features are repeated. Furthermore, by passing in an appropriatevalidator
function into the constructor ofblock_state
, it is also able to validate (via thecontroller_impl::check_protocol_features
method which thevalidator
function calls) that all of the new protocol features to activate satisfy the following: are recognized by the software; are subjectively enabled; are ready for activation according to the subjectively setearliest_allowed_activation_time
for that protocol feature; and, all of their dependencies have either already been activated or are earlier in the activation sequence for the current block.These validations are also done through the other entry point of getting
controller
to process a new block:controller::start_block
used by theproducer_plugin
(ortester
). These checks are also necessary for blocks that were already validated before (e.g. when replaying blocks or opening an existing fork database from disk) because nodeos have started up with a new configuration of supported protocol features that is more restrictive than its configuration when these blocks were first validated and stored to disk.However, any state manipulations by the activated protocol features, and any further validation that depends on state must wait until
controller_impl::start_block
. Incontroller_impl::start_block
, the new protocol features to activate are traversed in the order specified in the block header and for each: if the protocol feature is subjectively configured to require pre-activation, assert that the feature has already been pre-activated (see pillar 3 for details); run the trigger function (if one exists) for the protocol feature under the context that only new protocol features traversed prior to this point are considered activated; and then, consider that new protocol feature activated by adding it to theactivated_protocol_features
vector which is a field of the newprotocol_state_object
singleton added to the chain state (also activate the feature inprotocol_feature_manager
which is discussed in detail below).At the end of traversing through the new protocol features, if any pre-activated features exist, the code ensures that all were activated in that block, otherwise it reject the block. It also then clears the existing pre-activated features since they were all activated. Again, for more information on pre-activation, see the section on pillar 3.
So far there has been no mention of what these protocol features actually are. At a high-level,
controller
just considers a protocol feature to be some unique thing (uniquely identified by a digest) that can be activated (at most once) assuming its validation requirements (discussed above) are met. Some of those validation requirements, like dependencies, are objective and are actually committed to by the digest that refers to the protocol feature. Other validation requirements are subjective and locally determined by the configuration of the node (the only three subjective validation requirements thus far are whether the protocol feature is enabled, whether it require pre-activation; and the earliest allowed time, as measured by the block timestamp, it can be activated); these subjective requirements are not included in the calculation of the digest. These protocol features may optionally trigger some code to run at the start of the block (prior to processing theonblock
transaction). However, in many cases the change in behavior of the code occurs elsewhere simply by checking whether a particular protocol feature has been activated or not, and conditionally adjusting behavior as appropriate.Issue #6429 discusses different classes of protocol features that could be supported by the design. Currently, the implementation only supports the
builtin
class of protocol features, which are ones associated with a hard-codedcodename
which the nodeos code is written to understand how to handle through custom trigger functions and/or referencing whether the particular builtin protocol feature is activated from elsewhere in the code. (Note that a builtin codename cannot be repeated across multiple protocol features.) However, this framework does support adding future classes of protocol features without too much difficulty. The specification describing what the protocol feature does would be committed to in the digest representing the protocol feature. A generic trigger function applicable to all protocol features of a specific class would read the specification of the protocol feature and adjust state appropriately.Nodeos is designed to populate the
config/protocol_features
directory with JSON files specifying the default configuration of any builtin protocol features that version of nodeos understands but is not represented by an existing JSON file in that directory. With sensible defaults (plus the pre-activation featured discussed in the pillar 3 section), this enables users to not need to change the configuration files of any protocol features in typical scenarios. However, a community can choose, if they so desire, to customize one or more of the supported protocol features for their EOSIO blockchain to a certain degree without changing code. For example, thefeature_digest
ordependencies
could be changed (which would affect the digest representing the protocol feature). More typically, thesubjective_restrictions
might be adjusted to control if and when a protocol feature can be activated. However, with pre-activate feature functionality enabled, there is unlikely to be any need to modify the JSON files generated in theconfig/protocol_features
directory.The only real exception with the current state of the code is that the block producers will likely choose to modify the
earliest_allowed_activation_time
subjective restriction of thePREACTIVATE_FEATURE
protocol feature to have better control over when the first protocol feature is activated. But due to the nature ofearliest_allowed_activation_time
, other users can keep the default permissive value without breaking away from consensus whenPREACTIVATE_FEATURE
is activated. More exceptions will likely come into play in the future if other classes of protocol features (beyondbuiltin
) are added. In a case of a future community-initiated protocol feature that does not require any code changes to nodeos, users would have to manually include the appropriately configured JSON file intoconfig/protocol_features
(since nodeos would not know how to create such a thing) in order to configure their local nodeos to support the protocol feature and not break away from consensus when it is activated.A new accessor
is_protocol_feature_activated
is added tocontroller
to allow any part of the code (either internal or external tocontroller
) to determine if a particular protocol feature (referenced by digest) is activated as of the current state thecontroller
is in. However, a more efficient accessor has been added to check if a builtin protocol feature is activated. Since critical parts of the code will be checking if certain builtin protocol features are activated to adjust behavior appropriately, it is important to ensure that this accessor is fast. Socontroller::is_builtin_activated
is added in this PR to enable fast constant-time lookups (bycodename
rather than digest) of whether a particular builtin protocol feature is activated or not. That function is actually a relatively simple wrapper over a methodis_builtin_activated
in the newly addedprotocol_feature_manager
class.The
protocol_feature_manager
instance is included as a field incontroller_impl
. It is constructed with aprotocol_feature_set
which wrapper over a set of recognizedprotocol_feature
s (with an accompanyingvector
index to speed up lookups of builtin protocol features by theircodename
) that only allows mutation through itsadd_feature
method. Aprotocol_feature
type holds the objective specifications of a protocol feature as well as the subjective restrictions in a convenient format for internal use.The
protocol_feature_set
is passed into one of the constructors ofcontroller
(the other constructor just default constructsprotocol_feature_set
) and so it is created bychain_plugin
during initialization (or tester in the case of unit tests) by instantiating a defaultprotocol_feature_set
and then adding recognized protocol features through theadd_feature
method. During initialization ofchain_plugin
, it goes through a series of steps in theinitialize_protocol_features
function to read the configuration/specification of protocol features specified in theconfig/protocol_features
and add them to theprotocol_feature_set
(in the right order to respect any dependency ordering as theadd_feature
method requires that) before then iterating through builtin protocol features recognized by nodeos that were not reflected in theconfig/protocol_features
directory and adding them with their default configuration (again in right order) to theprotocol_feature_set
while also (by default) writing out the configuration/specification JSON for those missing builtin protocol features to theconfig/protocol_features
directory.Once a
protocol_feature_manager
is instantiated with aprotocol_feature_set
it will not allow thatprotocol_feature_set
to be mutated. (Note this preserves the requirementscontroller_impl::check_protocol_features
, which usesprotocol_feature_manager
, needs to remain thread-safe.) It allows read-only access to the wrappedprotocol_feature_set
instance. The main goal ofprotocol_feature_manager
is to track the mutating state of which of the recognized protocol features (included with their specifications in the wrappedprotocol_feature_set
instance) are actually activated at the moment. To enable this it has two mutators:activate_feature
which is called fromcontroller_impl::start_block
when a protocol feature is activated; and,popped_blocks_to
which is called when a pending block is aborted/rejected or prior applied blocks are popped during a fork switch so thatcontroller
ensures the state ofprotocol_feature_manager
remains consistent with the protocol features considered activated according to the chain state (again see theactivated_protocol_features
vector of theprotocol_state_object
singleton). The state information of activation is duplicated and kept in sync in this manner purely to make it very quick to lookup whether a built protocol feature has been activated. There is another non-const
method inprotocol_feature_manager
:init
. Again to guarantee synchronization of the activation state, theprotocol_feature_manager::init
method is called fromcontroller_impl::init
on nodeos startup to ensure theprotocol_feature_manager
is initialized to reflect the state of protocol feature activations as determined by theactivated_protocol_features
stored in the chain state Chainbase database.Pillar 2: Dynamic whitelist of intrinsics
The new
protocol_state_object
singleton in the chain state also includes a field calledwhitelisted_intrinsics
that represents the set of names for the intrinsic against which a contract deployed witheosio::setcode
is allowed to link.The type of
whitelisted_intrinsics
(referenced by type aliaswhitelisted_intrinsics_type
) is actually a multimap (specifically aboost::interprocess::flat_multimap<uint64_t, shared_string>
) for performance reasons. While aboost::interprocess::set<shared_string>
would work, it would require contract validation to dolog(N)
string comparisons (whereN
is the number of intrinsics in the whitelist) for each of theM
unique intrinsics used in a contract that is being set each timeeosio::setcode
is called. So it is important to improve the performance of this validation step. One major gain is significantly reducing the number of string comparisons necessary. By hashing the string to auint64_t
value, thelog(N)
comparison become fast integer comparisons, and since there should be a low likelihood of collision, it is unlikely that more than one string comparison would be necessary in practice. Furthermore, since the multimap is in a flat structure (where the actual intrinsic name strings are not stored in the contiguous memory region of the multimap unless very short) it improves the cache properties of the lookup process.While
whitelisted_intrinsics
withinprotocol_state_object
is of typeboost::interprocess::flat_multimap<uint64_t, shared_string>
, it is not serialized that way in the portable snapshots. It is converted into astd::set<std::string>
(which is what it logically represents) when writing it out to the portable snapshot (and vice versa when reading from portable snapshot) so that the portable snapshot always contains the list of whitelisted intrinsics in the deterministic lexicographical order (and so that the redundant and implementation-specificuint64_t
hash is not stored in the portable snapshot).Three helper functions (see
whitelisted_intrinsics.hpp
andwhitelisted_intrinsics.cpp
) that accept a reference to awhitelisted_intrinsics_type
enable: checking if a particular intrinsic name is in the whitelist (is_intrinsic_whitelisted
); and, adding (add_intrinsic_to_whitelist
) or (remove_intrinsic_from_whitelist
) a intrinsic name to/from the whitelist. Two additional helper functions (again see same files mentioned before) allow converting to and from thestd::set<std::string>
type:reset_intrinsic_whitelist
allows converting from thestd::set<std::string>
type into thewhitelisted_intrinsics_type
andconvert_intrinsic_whitelist_to_set
allows converting from thewhitelisted_intrinsics_type
to thestd::set<std::string>
.Checking whether an intrinsic name exists in the current whitelist is used by the
eosio::chain::webassembly::common::root_resolver::resolve
(seewasm_interface.hpp
) to validate that a new contract to be set does not try to use intrinsics that it should not have access to at the current time (specifically, intrinsics recognized by the nodeos software but not yet enabled via a corresponding protocol feature activation). Note that this validation is only done on aeosio::setcode
(which is wheneosio::chain::wasm_interface::validate
is called). That validation is not done when intrinsics simply need to be resolved when instantiating the WASM cache of an already set contract; in that case the default constructor ofresolver
is called (seewavm_runtime::instantiate_module
inwavm.cpp
) setting up the instance to skip validation in theresolve
method.Adding intrinsics to the whitelist is what the trigger function of a protocol feature must do in order for the protocol feature to enable those intrinsics. Removing intrinsics from the whitelist is also allowed in the trigger function of a protocol feature, but it only affects future
eosio::setcode
actions after the protocol feature is activated. Existing contracts that use the "removed" intrinsics are effectively grandfathered in, as long as their code is never updated.When initializing the chain state to the genesis state (see
controller_impl::initialize_database
), theprotocol_state_object
is constructed with thewhitelisted_intrinsics
field set up with the initial set of intrinsics supported in the original EOSIO consensus protocol (called the "genesis intrinsics"). The genesis intrinsics are stored in thegenesis_intrinsics
global variable of typeconst std::vector<const char*>
(seegenesis_intrinsics.cpp
).Pillar 3: Pre-activation of protocol features
The third pillar is actually a protocol feature (codename:
PREACTIVATE_FEATURE
) made possible using the prior pillars. However, it is a requirement for the way all other protocol features are intended to be activated, so it is considered a foundational feature to the protocol feature activation mechanism.Pre-activation of a protocol feature in block
N
forces the activation of the protocol feature in blockN+1
. Furthermore, the subjective restrictions of a protocol feature can be configured to require pre-activation. So, a protocol feature can be setup to only allow activation by a privileged contract exactly when desired. This allows switching from a subjective policy by BPs of when to activate a protocol feature and making it an objective on-chain policy controlled by, for example, theeosio
account. Typically theeosio
account would still be controlled by a two-thirds supermajority of the active BPs, but now because of pre-activation, it might require an on-chain multisig to trigger that pre-activation and thus subsequent activation of the protocol feature.The implementation ensures that a protocol feature that is configured locally to require pre-activation cannot be activated in
controller_impl::start_block
if that feature is not referenced in the newpreactivated_protocol_features
vector of theprotocol_state_object
singleton. This prevents an eager BP from activating a protocol feature (that requires pre-activation) sooner than it was intended to be by the objective on-chain policy. Also, by asserting that all the protocol features referenced in thepreactivated_protocol_features
vector were activated and then clearingpreactivated_protocol_features
at the start of the block, the implementation ensures that BPs cannot arbitrarily delay the activation of a pre-activated protocol feature.It is allowed to pre-activate a protocol feature that does not require pre-activation. However, in this case, the protocol rules do still require that the pre-activated protocol feature is activated in the next block.
The code implementing the pre-activation of a new protocol feature is primarily in
controller::preactivate_feature
.Nodes should not accept a transaction that attempts to pre-activates a protocol feature that it would not accept the activation of in the next block. Obviously this means the node should reject a transaction that attempts to pre-activate a protocol feature that it does not recognize or that it recognizes but is configured to be disabled. But it also means that if it is too early to activate a recognized and enabled protocol feature (because of the
earliest_allowed_activation_time
parameter) in the next block, then the node should not accept the transaction. Since it is impossible to know what the timestamp of the next block will be, the rules simply require that the current block's timestamp is sufficient to meet the time requirements of the protocol feature. So even if the next block's timestamp is exactly theearliest_allowed_activation_time
of the protocol feature, the attempt to pre-activate that protocol feature will fail in the current block since the current block's timestamp is strictly less than theearliest_allowed_activation_time
.Notice that the decisions to reject the pre-activation attempt described in the paragraph above were made based on subjective information: the locally configured
subjective_restrictions
values, such asenabled
orearliest_allowed_activation_time
; or whether the node even recognized the protocol feature. Care must be taken when making processing decisions for a block or transaction based on subjective information to avoid breaking consensus. If the local node is processing a block containing a transaction that attempts a pre-activation that the local node cannot accept according to its local configuration rules, the node is forced to reject the block. So, if the node cannot accept the pre-activation due to the reasons above, it throws aprotocol_feature_bad_block_exception
which forces it to propagate all the way up until it rejects the block; it does not allow it to get caught and re-direct processing to an error handler of a deferred transaction, for example. However, when producing a block, we do not want the transaction author to be able to abort the speculative block being produced (even if it requires a privileged contract which presumably would require special trusted authorizations prior to getting to the point of thepreactivate_feature
intrinsic call). So, when producing a block, asubjective_block_production_exception
is thrown instead to just reject the transaction and prevent it from being included in the block in the first place. Using a subjective exception type is critical here because of deferred transactions: throwing a regular exception would have lead to the deferred transaction retiring as eithersoft_fail
orhard_fail
which is an outcome that would be based on subjective local information and therefore not necessarily repeatable on other nodes (with other subjective configurations) replaying the blockchain.However, once the subjective assertions mentioned above pass in the
controller::preactivate_feature
implementation, the rest of the function can throw regular exceptions on failure because at that point it is safely in an objective context. All nodes that reach that point and are also not referring to thesubjective_restrictions
of protocol features should be working with the same deterministic data and therefore have the same deterministic results (whether in the form of failure or success). So, for example, if the code detects that the feature to pre-activate is already in thepreactivated_protocol_features
vector of theprotocol_state_object
singleton, it will throw a regular exception complaining about the attempt to pre-activate an already pre-activated protocol feature. If all the checks pass, the code will then add the digest of the protocol feature to pre-activate to the end of thepreactivated_protocol_features
vector.A helpful accessor
get_preactivated_protocol_features
is also added tocontroller
which is used by theproducer_plugin
(andtester
in the case of unit tests) to get the sequence of protocol feature digests that it must include in the header extensions of the next block produced to ensure the block is accepted. However, theproducer_plugin
is free to inject the digests of other ready-to-activate protocol features into the sequence. The current implementation in this PR takes any digests in an internally tracked_protocol_features_to_activate
vector and prepends them to the sequence obtained fromget_preactivated_protocol_features
before then including that combined sequence into the header extensions of the next block it creates. The_protocol_features_to_activate
vector can be modified with the newschedule_protocol_feature_activations
RPC endpoint added to theproducer_api_plugin
(read more about it under the API changes section).Implemented
ONLY_LINK_TO_EXISTING_PERMISSION
protocol featureIn order to facilitate tests of the pre-activate functionality, another protocol feature needed to be implemented. The
ONLY_LINK_TO_EXISTING_PERMISSION
protocol feature was a relatively simple one that was already planned for eventual implementation.So, this PR implements the
ONLY_LINK_TO_EXISTING_PERMISSION
protocol feature, which when activated ensures that aeosio::linkauth
action cannot be used to link an action to a non-existent permission (see #6333 for details). The behavior ofeosio::linkauth
of course remains exactly the same prior toONLY_LINK_TO_EXISTING_PERMISSION
activation in order to not break consensus.Changes to test framework and new tests
To facilitate testing the new protocol feature activation functionality, changes have been made to both the unit testing framework (
tester
) and the integration testing framework (the Python scripts in thetests
directory).Furthermore, the eosio.bios contract was upgraded to support a new active
preactivate
which enables an outside client, such as a testing script, to actually pre-activate a particular protocol feature. The eosio.bios contract with the modified code was compiled (see code changes that were used here and its compiled WASM and ABI replaced the ones in theunittests/contracts/eosio.bios
directory. The compiled WASM and ABI of the version of the bios contract without thepreactivate
action addition (specifically the one from v1.6.0-rc3 of eosio.contracts) were also added to to theunittests/contracts/old_versions/1.6.0-rc3/eosio.bios
directory. This old version (as in prior to the preactive addition) of the eosio.bios contract is necessary to keep around because some tests will need a bios contract around in an environment where thePREACTIVATE_FEATURE
protocol feature has not yet been activated. But the new eosio.bios contract with thepreactivate
action requires linking to intrinsics (specificallypreactivate_feature
andis_feature_activated
) which are not allowed unlessPREACTIVATE_FEATURE
has already been activated.tester
has been modified to support different policies for how to initialize the fresh blockchain it creates oninit
. One of the overloads ofbase_tester::init
which used to take apush_genesis
boolean (and one of the constructors oftester
which used to optionally take thepush_genesis
boolean) now take a newenum class
calledsetup_policy
instead ofpush_genesis
. Thesetup_policy
enum can take one of the following values:none
: nothing is done after starting up the chain (the same as what happened before whenpush_genesis
was false);old_bios_only
: set the old eosio.bios contract (prior to thepreactivate
action addition) on theeosio
account after starting up the chain (similar to the behavior before whenpush_genesis
was true);preactivate_feature_only
: after starting up the chain, create a single block that activatesPREACTIVATE_FEATURE
and do nothing else;preactivate_feature_and_new_bios
: after starting up the chain, create a single block that activatesPREACTIVATE_FEATURE
and then set the new eosio.bios contract (with the newpreactivate
action) on theeosio
account.full
: do all of the steps of thepreactivate_feature_and_new_bios
setup policy but then also pre-activate all the support builtin protocol features before then producing another block so that they can all become activated.The default value for the
setup_policy
field for both theinit
method overload and thetester
constructor issetup_policy::full
. Furthermore, the the constructor ofvalidating_tester
which takes one optional argument callsinit
. This means that a default constructedtester
and default constructedvalidating_tester
(which covers most of the existing unit tests) will automatically choose thefull
policy and thus now operate in an environment where all builtin protocol features have been activated. If any tests need to start running in an environment where some protocol features are not activated (for example to test those protocol features), then they will need to starttester
with an alternatesetup_policy
.Additional methods have been added to
base_tester
to facilitate testing the protocol features. These include:schedule_protocol_features_wo_preactivation
: takes a vector of protocol digests to include in the activations in the next block produced;preactivate_protocol_features
: pushes theeosio::preactivate
action for each protocol feature digest in the vector passed into the method;preactivate_all_builtin_protocol_features
: a convenience method to automatically pre-activate all the support builtin protocol features.Seven new unit tests were added to the new
protocol_feature_tests
test suite (seeunittests/protocol_feature_tests.cpp
):activate_preactivate_feature
: Tests that thePREACTIVATE_FEATURE
protocol feature can be properly activated, and that the new bios contract can only be deployed afterPREACTIVATE_FEATURE
is activated.activate_and_restart
: Ensures that the fact that protocol feature was activated persists after a restart.double_preactivation
: Verifies that double pre-activation (in the same block) is not allowed.double_activation
: Verifies that signaling activation of the same protocol feature twice within a block is not allowed. It also verifies that pre-activating another protocol feature (ONLY_LINK_TO_EXISTING_PERMISSION
in this case) done the proper way will work fine.require_preactivation_test
: Verifies that it is not possible to activate a protocol feature that requires pre-activation (ONLY_LINK_TO_EXISTING_PERMISSION
in this case) without first going through pre-activation.only_link_to_existing_permission_test
: Tests the behavior related to theONLY_LINK_TO_EXISTING_PERMISSION
protocol feature for correctness both before and after activation ofONLY_LINK_TO_EXISTING_PERMISSION
.subjective_restrictions_test
: Tests that thesubjective_restrictions
of a protocol feature (specifically theenabled
andearliest_allowed_activation_time
parameters) are properly enforced for both a protocol feature that does not require pre-activation (PREACTIVATE_FEATURE
) and a protocol feature that does require pre-activation (ONLY_LINK_TO_EXISTING_PERMISSION
).The Python testing scripts have also been modified to support different setup policies. In this case, it is a little more simplified:
NONE
: do nothing like the old behavior (on top of this the old eosio.bios contract can be deployed through the regular bootstrapping processes);PREACTIVATE_FEATURE_ONLY
: produce a block which activates thePREACTIVATE_FEATURE
protocol feature after starting up the blockchain (again a eosio.bios contract can then be deployed on top of this);FULL
: do the same thing asPREACTIVATE_FEATURE_ONLY
but then during the bootstrapping process, after the new eosio.bios contract has been deployed, pre-activate all the recognized builtin protocol features and ensure a block is produced so that they can all be activated.Note that when the setup policy is
FULL
, the pre-activation of builtin protocol features will not occur if the test does not go through the bootstrapping process (for example ifdontBootstrap
is true). Furthermore, when the setup policy isPREACTIVATE_FEATURE_ONLY
orFULL
, the eosio.bios contract which is deployed during the bootstrapping process is the new one with thepreactivate
action. However, if the setup policy isNONE
, the bootstrapping process (if it occurs) will use the old bios contract that does not have thepreactivate
action.tests/Node.py
also adds some convenience methods to schedule protocol features to be activated, pre-activate specific protocol features (or all the builtin ones), and get the protocol features supported by the local node through the newget_supported_protocol_features
RPC endpoint of theproducer_api_plugin
.Two new smoke tests were added:
tests/prod_preactivation_test.py
: Simulates activating thePREACTIVATE_FEATURE
protocol feature in a two producer blockchain test.tests/nodeos_protocol_feature_test.py
: Verifies that changes to the JSON files inconfig/protocol_features
are properly read by nodeos on startup.Consensus Changes
Yes... definitely. Read above for details.
In addition to the protocol feature foundations (include the
PREACTIVATE_FEATURE
protocol feature), this PR also includes an additional protocol feature:ONLY_LINK_TO_EXISTING_PERMISSION
(#6333).API Changes
The
producer_api_plugin
has added three new RPC endpoints:get_supported_protocol_features
: Returns the protocol features recognized by the local nodeos.exclude_disabled
(defaults tofalse
) determines whether to filter out any protocol features that are not enabled.exclude_unactivatable
(defaults tofalse
) determines whether to filter out any protocol features that could not be activated at the present time (either because they are disabled or because theearliest_allowed_activation_time
for the protocol feature has not yet been reached).feature_digest
: Hexadecimal string representation of the digest of the protocol feature.subjective_restrictions
: A JSON object containing the following fields:enabled
: A boolean that specifies whether the protocol feature is enabled.preactivation_required
: A boolean that specifies whether the protocol feature require pre-activation before it can be activated.earliest_allowed_activation_time
: The earliest block timestamp at which the protocol feature can be pre-activated or activated.description_digest
: Hexadecimal string representation of the digest of the textual description for the protocol feature.dependencies
: An array of hexadecimal strings representing the digests of any protocol features this protocol feature depends on (can be an empty array and usually would be).protocol_feature_type
: A string representing the class of protocol features. Currently only thebuiltin
class is supported which would be represented here with the string"builtin"
.specification
: An array of JSON objects each containing a string-valued field calledname
and any-valued field calledvalue
which provide the specification for the protocol feature. In the case ofbuiltin
protocol features, there should be only one JSON object in the array that has aname
field with value"builtin_feature_codename"
and avalue
field with value equal to a string representing the codename of the builtin protocol feature.schedule_protocol_feature_activations
: Sets the vector of protocol feature digests to activate for the next time the local node can produce a block.protocol_features_to_activate
which is the vector of hexadecimal strings representing the digests of protocol features to activate (in the specified order).get_scheduled_protocol_feature_activations
: Returns the vector of protocol feature digests that are currently scheduled to be activated the next time the local node can produce a block. This is the vector that was last set byschedule_protocol_feature_activations
, unless the activation attempt already happened in which case the returned vector will be empty.The
chain_api_plugin
has added one new RPC endpoint:get_activated_protocol_features
: Returns the protocol features that have been activated as of the current state of the blockchain (ordered according to order of activation, unless reversed by request). It also includes metadata like the ordinal of the activation and the block in which it was activated.lower_bound
acts as the lower bound to filter the returned results (providing nolower_bound
avoids putting a lower bound filter on the results which acts equivalent to passing alower_bound
value of 0).upper_bound
acts as the upper bound to filter the returned results (providing noupper_bound
avoids putting a upper bound filter on the results which acts equivalent to passing aupper_bound
value equivalent to the largest value representable by a C++uint32_t
).limit
(defaults to 10) puts a maximum limit on the number of protocol features returned.search_by_block_num
(defaults tofalse
) determines how thelower_bound
andupper_bound
values will be treated in the filtering process. Ifsearch_by_block_num
isfalse
, those values act as bounds on the activation ordinal (which can act as a unique lookup key for protocol features and can support pagination). Ifsearch_by_block_num
istrue
, those values act as bounds on the activate block number (the number of the block in which the protocol features were activated).reverse
(defaults tofalse
) determines whether to reverse the order of the returned results.activated_protocol_features
and optionally including an integer field namedmore
which if present means that more results of the query exist than what was returned (due to time constraints) and in that case the value ofmore
is the activation ordinal of the next protocol feature that should have been returned if time allowed. Theactivate_protocol_features
array contains JSON objects (representing the protocol features) that contain the following fields:feature_digest
: Hexadecimal string representation of the digest of the protocol feature.activation_ordinal
: An integer value of the activation ordinal of the protocol feature, where the activation ordinal is the sequence number of ordered protocol feature activations that have occurred on the chain since genesis.activation_block_num
: An integer value of the number of the block in which the protocol feature was activated.description_digest
: Hexadecimal string representation of the digest of the textual description for the protocol feature.dependencies
: An array of hexadecimal strings representing the digests of any protocol features this protocol feature depends on (can be an empty array and usually would be).protocol_feature_type
: A string representing the class of protocol features. Currently only thebuiltin
class is supported which would be represented here with the string"builtin"
.specification
: An array of JSON objects each containing a string-valued field calledname
and any-valued field calledvalue
which provide the specification for the protocol feature. In the case ofbuiltin
protocol features, there should be only one JSON object in the array that has aname
field with value"builtin_feature_codename"
and avalue
field with value equal to a string representing the codename of the builtin protocol feature.Documentation Additions
In addition to the API changes discussed above, the documentation will likely need updates to reflect the addition of the
config/protocol_features
directory and the format of the JSON files contained within. Also, the whole concept of protocol features and how they work will likely need to be documented elsewhere in a manner more accessible to users, contract developers, and block producers than what is in this PR.