Details

Prior reading:

Goal

Propose an implementation that supports fork-aware protocol state. Specifically, we want to track different protocol variables that can be updated through consensus mechanism. At start we will focus on identity table but will want to keep the design general so that additional data can be added and maintained by the Protocol State in future.

Changes to core types: Payload

We will need to extend the block payload to include the protocol state’s root hash:

// Payload is the actual content of each block.
type Payload struct {
	// Guarantees are ordered in execution order. May be empty, in which case
	// only the system chunk is executed for this block.
	Guarantees []*CollectionGuarantee
	// Seals holds block seals for ancestor blocks.
	// The oldest seal must connect to the latest seal in the fork extended by this block.
	// Seals must be internally connected, containing no seals with duplicate block IDs or heights.
	// Seals may be empty. It presents a set, i.e. there is no protocol-defined ordering.
	Seals    []*Seal
	Receipts ExecutionReceiptMetaList
	Results  ExecutionResultList

  // Root hash of protocol state. Per convention, this is the resulting state after applying all
  // identity-chaning operations potentially contained in the block. The block payload itself is
  // validated wrt. to the protocol state committed to by its parent. Thereby, we are only
  // accepting protocol states that have been certified by a valid QC. 
	ProtocolStateID Identifier 
}

The new field ProtocolStateID is the root hash of protocol state AFTER applying block to block storage. Replicas must ensure their local protocol state has the same root hash as ProtocolStateID. (see Identity-Changing Operations for more details)

Precedence of identity-changing Operations and interaction with HotStuff-Page-1 (2).png

Changes to core types: Identity

Splitting identity into immutable and mutable parts as suggested here: https://github.com/dapperlabs/flow-go/issues/6232 introduces more structure into code and allows us to build safer APIs.

type IdentitySkeleton struct {
	// NodeID uniquely identifies a particular node. A node's ID is fixed for
	// the duration of that node's participation in the network.
	NodeID Identifier
	// Address is the network address where the node can be reached.
	Address string
	// Role is the node's role in the network and defines its abilities and
	// responsibilities.
	Role          Role
  // InitialWeight is a 'trust score' initially assigned by EpochSetup event after
  // the staking phase. The initial weights define the supermajority thresholds for
  // the cluster and security node consensus throughout the Epoch.
	InitialWeight uint64
	StakingPubKey crypto.PublicKey
	NetworkPubKey crypto.PublicKey
}

type DynamicIdentity struct {
	// Weight represents the node's authority to perform certain tasks relative
	// to other nodes. 
	//
	// A node's weight is distinct from its stake. Stake represents the quantity
	// of FLOW tokens held by the network in escrow during the course of the node's
	// participation in the network. The stake is strictly managed by the service
	// account smart contracts.
	//
	// Nodes which are registered to join at the next epoch will appear in the
	// identity table but are considered to have zero weight up until their first
	// epoch begins. Likewise nodes which were registered in the previous epoch
	// but have left at the most recent epoch boundary will appear in the identity
	// table with zero weight.
	Weight uint64
	// Ejected represents whether a node has been permanently removed from the
	// network. A node may be ejected for either:
  // * request self-ejection to protect its stake in case the node operator suspects
  //   the node's keys to be compromized
	// * committing a serious protocol violation or multiple smaller misdemeanours
	Ejected bool
}

type Identity struct {
	*IdentitySkeleton
	*DynamicIdentity
}