Documentation
¶
Overview ¶
height_backfill.go — RECOVERY for a proposervm finality index that sits BELOW the inner VM's accepted tip.
THE INVARIANT. Every post-fork accept commits the outer envelope, its height index entry and the last-accepted pointer in ONE versiondb batch BEFORE the inner block is accepted (vm.acceptPostForkBlock → postForkBlock.Accept), so the proposervm index can only ever be AHEAD of the inner VM, never behind.
HOW IT BREAKS ANYWAY. The proposervm has one accept path that advances the inner VM while leaving the outer index untouched: preForkBlock.Accept, whose acceptOuterBlk() is a no-op by construction. Post-fork that path was reachable because BOTH of the proposervm's block constructors fall back to it silently — ParseBlock() falls back to parsePreForkBlock when the outer envelope does not parse, and getBlock() falls back to getPreForkBlock when the id is not an outer id (which is exactly what the CANONICAL id recorded by the consensus ledger is: postForkCommonComponents.CanonicalID() == innerBlk.ID()). Either fallback hands the engine a preForkBlock wrapping a post-fork inner block; accepting it moves the inner VM and NOT the index. Nothing complained, because nothing asserted the invariant at the moment it was violated — it was only ever checked at the NEXT boot, by which point the node could not start. That is why the lag looked like a mysterious "persistence" problem: the writes were never issued at all.
The prevention half lives at the two fallbacks + preForkBlock.acceptOuterBlk (see pre_fork_block.go and vm.getPreForkBlock): post-fork, a pre-fork block is never constructed and never accepted.
THE RECOVERY half lives here, and it must work for nodes that are ALREADY damaged. It is two escalating steps, both OUTER-ONLY — they never re-execute, re-verify or re-accept an inner block, so a node whose EVM is already at the tip is repaired without touching the EVM:
rebuildOuterIndexFromStore: re-derive the height index and last-accepted pointer from the outer envelopes ALREADY in this node's block store, binding each candidate to the inner block this node itself accepted at that height. Fully local, no network, no operator. This is what turns "refuses to start" into "starts, repairs itself, and keeps its finality pointer".
If step 1 cannot reach the inner tip, the node still STARTS, in an explicit backfill-pending state: loud, fail-safe (it will not BUILD a block while its finality index is incomplete) and repairable through BackfillOuterBlock, which accepts the missing envelopes from any source and applies the SAME binding checks. The alternative — the previous behaviour — was a dead C-Chain and a destructive resync.
WHAT IS NEVER DONE, and why: the finality pointer is never dropped (DeleteLastAccepted). proposervm.LastAccepted would then fall back to an INNER-namespace id whose ParentID is contiguity-incompatible with the network's OUTER wrappers, permanently wedging bootstrap, catch-up and live Verify at the inner tip — a silent wedge strictly worse than the loud stop it would replace. Every path below only ever moves the pointer FORWARD, onto an envelope that was proven to wrap the inner block this node already accepted at that height.
Package proposervm is a generated GoMock package.
Index ¶
- Constants
- Variables
- func NewHTTPHandler(vm *VM) (http.Handler, error)
- type Block
- type Config
- type GetProposedHeightArgs
- type GetProposedHeightReply
- type MockPostForkBlock
- func (m *MockPostForkBlock) Accept(arg0 context.Context) error
- func (m *MockPostForkBlock) Bytes() []byte
- func (m *MockPostForkBlock) EXPECT() *MockPostForkBlockMockRecorder
- func (m *MockPostForkBlock) Height() uint64
- func (m *MockPostForkBlock) ID() ids.ID
- func (m *MockPostForkBlock) Parent() ids.ID
- func (m *MockPostForkBlock) Reject(arg0 context.Context) error
- func (m *MockPostForkBlock) Status() choices.Status
- func (m *MockPostForkBlock) Timestamp() time.Time
- func (m *MockPostForkBlock) Verify(arg0 context.Context) error
- type MockPostForkBlockMockRecorder
- func (mr *MockPostForkBlockMockRecorder) Accept(arg0 any) *gomock.Call
- func (mr *MockPostForkBlockMockRecorder) Bytes() *gomock.Call
- func (mr *MockPostForkBlockMockRecorder) Height() *gomock.Call
- func (mr *MockPostForkBlockMockRecorder) ID() *gomock.Call
- func (mr *MockPostForkBlockMockRecorder) Parent() *gomock.Call
- func (mr *MockPostForkBlockMockRecorder) Reject(arg0 any) *gomock.Call
- func (mr *MockPostForkBlockMockRecorder) Status() *gomock.Call
- func (mr *MockPostForkBlockMockRecorder) Timestamp() *gomock.Call
- func (mr *MockPostForkBlockMockRecorder) Verify(arg0 any) *gomock.Call
- type OracleBlock
- type PostForkBlock
- type Service
- type VM
- func (vm *VM) BackfillOuterBlock(ctx context.Context, outerBytes []byte) error
- func (vm *VM) BatchedParseBlock(ctx context.Context, blks [][]byte) ([]chain.Block, error)
- func (vm *VM) BuildBlock(ctx context.Context) (vmchain.Block, error)
- func (vm *VM) Commit() error
- func (vm *VM) CreateHandlers(ctx context.Context) (map[string]http.Handler, error)
- func (vm *VM) GetAncestors(ctx context.Context, blkID ids.ID, maxBlocksNum int, maxBlocksSize int, ...) ([][]byte, error)
- func (vm *VM) GetBlock(ctx context.Context, id ids.ID) (vmchain.Block, error)
- func (vm *VM) GetBlockIDAtHeight(ctx context.Context, height uint64) (ids.ID, error)
- func (vm *VM) GetFullPostForkBlock(ctx context.Context, blkID ids.ID) (chain.Block, error)
- func (vm *VM) GetLastStateSummary(ctx context.Context) (chain.StateSummary, error)
- func (vm *VM) GetOngoingSyncStateSummary(ctx context.Context) (chain.StateSummary, error)
- func (vm *VM) GetStateSummary(ctx context.Context, height uint64) (chain.StateSummary, error)
- func (vm *VM) Initialize(ctx context.Context, init vmcore.Init) error
- func (vm *VM) LastAccepted(ctx context.Context) (ids.ID, error)
- func (vm *VM) NeedsOuterBackfill() (from uint64, to uint64, pending bool)
- func (vm *VM) ParseBlock(ctx context.Context, b []byte) (vmchain.Block, error)
- func (vm *VM) ParseLocalBlock(ctx context.Context, b []byte) (vmchain.Block, error)
- func (vm *VM) ParseStateSummary(ctx context.Context, summaryBytes []byte) (chain.StateSummary, error)
- func (vm *VM) SetPreference(ctx context.Context, preferred ids.ID) error
- func (vm *VM) SetQuasarGate(g *pqfinality.Gate)
- func (vm *VM) SetState(ctx context.Context, newState uint32) error
- func (vm *VM) Shutdown(ctx context.Context) error
- func (vm *VM) StateSyncEnabled(ctx context.Context) (bool, error)
- func (vm *VM) WaitForEvent(ctx context.Context) (vmcore.Message, error)
Constants ¶
const ( // DefaultMinBlockDelay should be kept as whole seconds because block // timestamps are only specific to the second. DefaultMinBlockDelay = time.Second // DefaultNumHistoricalBlocks as 0 results in never deleting any historical // blocks. DefaultNumHistoricalBlocks uint64 = 0 )
Variables ¶
var ( // ErrOuterBackfillNotPending is returned by BackfillOuterBlock when the index // is already whole — the repair is idempotent, not an error to call twice. ErrOuterBackfillNotPending = errors.New("proposervm: no outer-index backfill is pending") // ErrOuterBackfillRejected is returned when a candidate envelope does not bind // to this node's own accepted chain. Fail-closed: an envelope is only indexed // when it provably wraps the inner block WE accepted at that height and chains // off the envelope WE already hold at height-1, so a peer (or a stale file) // cannot steer the finality pointer. ErrOuterBackfillRejected = errors.New("proposervm: outer backfill candidate rejected") )
Functions ¶
Types ¶
type Config ¶
type Config struct {
Upgrades upgrade.Config
// NetworkID is the validator-set ID the proposer windower resolves the
// schedule under — it MUST be the SAME ID the consensus cert side uses
// (manager.go resolves it once: PrimaryNetworkID for native chains,
// otherwise the L1's own chainID, falling back to primary only if that set is
// empty). CRITICAL-3: hardcoding PrimaryNetworkID here made the windower call
// GetValidatorSet(height, PrimaryNetworkID) on a sovereign L1 (Zoo/Hanzo/Pars,
// whose validators live under their OWN networkID == EVM chainID), get an
// EMPTY set, and degrade to ErrAnyoneCanPropose — so single-proposer silently
// did not hold and the L1 equivocated exactly like the unfixed C-Chain, AND
// the windower's set diverged from the cert's set (breaking determinism). The
// zero value (ids.Empty) IS constants.PrimaryNetworkID, so a native chain that
// resolves to primary is unchanged.
NetworkID ids.ID
// Configurable minimal delay among blocks issued consecutively
MinBlkDelay time.Duration
// ProposerWindowDuration overrides the proposer-slot spacing (proposer.
// WindowDuration). Zero keeps the 5s mainnet default; small local/dev nets set
// it low (e.g. 1s or less) so block cadence is not floored at 5s per proposer
// slot. Applied once at VM init via proposer.SetWindowDuration.
ProposerWindowDuration time.Duration
// Maximal number of block indexed.
// Zero signals all blocks are indexed.
NumHistoricalBlocks uint64
// Block signer
StakingLeafSigner crypto.Signer
// Block certificate
StakingCertLeaf *staking.Certificate
// Strict-PQ block signer. When StakingMLDSASigner is non-nil the proposer
// signs post-fork blocks with ML-DSA-65 and stamps the block's proposer
// identity as the raw ML-DSA-65 public key, so Proposer() derives via
// DeriveMLDSA and EQUALS the strict-PQ NodeID the windower elected (the
// validator set is ML-DSA-keyed under strict-PQ). Nil ⇒ classical TLS-leaf
// signing above. Exactly one of the two schemes is active per chain, chosen at
// wiring time from the resolved ChainSecurityProfile.
StakingMLDSASigner *mldsa.PrivateKey
StakingMLDSAPub []byte
// Registerer for metric metrics
Registerer metric.Registerer
// Automining configuration
AutominingEnabled bool
// AutominingInterval is the interval between automatic block production
AutominingInterval time.Duration
}
type GetProposedHeightArgs ¶ added in v1.16.56
type GetProposedHeightArgs struct{}
GetProposedHeightArgs are the arguments for GetProposedHeight
type GetProposedHeightReply ¶ added in v1.16.56
type GetProposedHeightReply struct {
// ProposedHeight is the P-Chain height that would be proposed
// for the next block built on the current preferred block
ProposedHeight uint64 `json:"proposedHeight"`
}
GetProposedHeightReply is the response from GetProposedHeight
type MockPostForkBlock ¶ added in v1.1.11
type MockPostForkBlock struct {
// contains filtered or unexported fields
}
MockPostForkBlock is a mock of PostForkBlock interface.
func NewMockPostForkBlock ¶ added in v1.1.11
func NewMockPostForkBlock(ctrl *gomock.Controller) *MockPostForkBlock
NewMockPostForkBlock creates a new mock instance.
func (*MockPostForkBlock) Accept ¶ added in v1.1.11
func (m *MockPostForkBlock) Accept(arg0 context.Context) error
Accept mocks base method.
func (*MockPostForkBlock) Bytes ¶ added in v1.1.11
func (m *MockPostForkBlock) Bytes() []byte
Bytes mocks base method.
func (*MockPostForkBlock) EXPECT ¶ added in v1.1.11
func (m *MockPostForkBlock) EXPECT() *MockPostForkBlockMockRecorder
EXPECT returns an object that allows the caller to indicate expected use.
func (*MockPostForkBlock) Height ¶ added in v1.1.11
func (m *MockPostForkBlock) Height() uint64
Height mocks base method.
func (*MockPostForkBlock) ID ¶ added in v1.1.11
func (m *MockPostForkBlock) ID() ids.ID
ID mocks base method.
func (*MockPostForkBlock) Parent ¶ added in v1.1.11
func (m *MockPostForkBlock) Parent() ids.ID
Parent mocks base method.
func (*MockPostForkBlock) Reject ¶ added in v1.1.11
func (m *MockPostForkBlock) Reject(arg0 context.Context) error
Reject mocks base method.
func (*MockPostForkBlock) Status ¶ added in v1.1.11
func (m *MockPostForkBlock) Status() choices.Status
Status mocks base method.
func (*MockPostForkBlock) Timestamp ¶ added in v1.1.11
func (m *MockPostForkBlock) Timestamp() time.Time
Timestamp mocks base method.
type MockPostForkBlockMockRecorder ¶ added in v1.1.11
type MockPostForkBlockMockRecorder struct {
// contains filtered or unexported fields
}
MockPostForkBlockMockRecorder is the mock recorder for MockPostForkBlock.
func (*MockPostForkBlockMockRecorder) Accept ¶ added in v1.1.11
func (mr *MockPostForkBlockMockRecorder) Accept(arg0 any) *gomock.Call
Accept indicates an expected call of Accept.
func (*MockPostForkBlockMockRecorder) Bytes ¶ added in v1.1.11
func (mr *MockPostForkBlockMockRecorder) Bytes() *gomock.Call
Bytes indicates an expected call of Bytes.
func (*MockPostForkBlockMockRecorder) Height ¶ added in v1.1.11
func (mr *MockPostForkBlockMockRecorder) Height() *gomock.Call
Height indicates an expected call of Height.
func (*MockPostForkBlockMockRecorder) ID ¶ added in v1.1.11
func (mr *MockPostForkBlockMockRecorder) ID() *gomock.Call
ID indicates an expected call of ID.
func (*MockPostForkBlockMockRecorder) Parent ¶ added in v1.1.11
func (mr *MockPostForkBlockMockRecorder) Parent() *gomock.Call
Parent indicates an expected call of Parent.
func (*MockPostForkBlockMockRecorder) Reject ¶ added in v1.1.11
func (mr *MockPostForkBlockMockRecorder) Reject(arg0 any) *gomock.Call
Reject indicates an expected call of Reject.
func (*MockPostForkBlockMockRecorder) Status ¶ added in v1.1.11
func (mr *MockPostForkBlockMockRecorder) Status() *gomock.Call
Status indicates an expected call of Status.
func (*MockPostForkBlockMockRecorder) Timestamp ¶ added in v1.1.11
func (mr *MockPostForkBlockMockRecorder) Timestamp() *gomock.Call
Timestamp indicates an expected call of Timestamp.
type OracleBlock ¶ added in v1.16.56
OracleBlock is a block that can return multiple child options
type PostForkBlock ¶
type PostForkBlock interface {
Block
// contains filtered or unexported methods
}
type Service ¶ added in v1.16.56
type Service struct {
// contains filtered or unexported fields
}
Service wraps proposervm for RPC/JSON-RPC access
func (*Service) GetProposedHeight ¶ added in v1.16.56
func (s *Service) GetProposedHeight(r *http.Request, _ *GetProposedHeightArgs, reply *GetProposedHeightReply) error
GetProposedHeight returns the P-Chain height that would be proposed for the next block built on the current preferred block.
Example JSON-RPC call:
curl -X POST --data '{
"jsonrpc":"2.0",
"id" :1,
"method" :"proposervm.getProposedHeight",
"params" :{}
}' -H 'content-type:application/json;' http://127.0.0.1:9650/v1/bc/C/rpc
type VM ¶
type VM struct {
vmchain.ChainVM
Config
state.State
proposer.Windower
tree.Tree
mockable.Clock
// contains filtered or unexported fields
}
func New ¶
New performs best when [minBlkDelay] is whole seconds. This is because block timestamps are only specific to the second.
func (*VM) BackfillOuterBlock ¶ added in v1.36.32
BackfillOuterBlock supplies ONE missing outer envelope, oldest-first, and is the operator/driver half of the recovery. It is fail-closed and idempotent:
- not pending -> ErrOuterBackfillNotPending
- envelope below the next height -> nil (already indexed; tolerate replay)
- signature/format invalid -> error (parsed WITH verification)
- not the next height, wrong parent, or wrapping a different inner block-> ErrOuterBackfillRejected
On the final height it clears the pending state and refreshes the in-memory last-accepted metadata, after which the chain behaves exactly like one that booted whole.
func (*VM) BatchedParseBlock ¶
func (*VM) CreateHandlers ¶ added in v1.16.56
CreateHandlers returns HTTP handlers for both the proposervm API and the inner ChainVM
func (*VM) GetAncestors ¶
func (*VM) GetBlockIDAtHeight ¶
vm.lock should be held
func (*VM) GetFullPostForkBlock ¶ added in v1.1.11
Note: this is a contention heavy call that should be avoided for frequent/repeated indexer ops
func (*VM) GetLastStateSummary ¶
func (*VM) GetOngoingSyncStateSummary ¶
func (*VM) GetStateSummary ¶
func (*VM) NeedsOuterBackfill ¶ added in v1.36.32
NeedsOuterBackfill reports the still-missing outer height range [from, to] and whether a backfill is pending. It is the seam an operator tool or a node-layer fetch driver reads; when pending is false the index is whole.
func (*VM) ParseBlock ¶
func (*VM) ParseLocalBlock ¶
func (*VM) ParseStateSummary ¶
func (vm *VM) ParseStateSummary(ctx context.Context, summaryBytes []byte) (chain.StateSummary, error)
Note: it's important that ParseStateSummary do not use any index or state to allow summaries being parsed also by freshly started node with no previous state.
func (*VM) SetQuasarGate ¶ added in v1.31.0
func (vm *VM) SetQuasarGate(g *pqfinality.Gate)
SetQuasarGate installs the post-quantum finality gate. Called once at chain wiring time when PQ-finality config is present; left unset (nil) otherwise so the accept path stays classical. Idempotent, set before consensus starts.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
LP181 implements the epoch logic specified here: https://github.com/luxfi/LPs/blob/main/LPs/181-p-chain-epoched-views/README.md
|
LP181 implements the epoch logic specified here: https://github.com/luxfi/LPs/blob/main/LPs/181-p-chain-epoched-views/README.md |
|
Package proposer is a generated GoMock package.
|
Package proposer is a generated GoMock package. |
|
proposermock
Package proposermock is a generated GoMock package.
|
Package proposermock is a generated GoMock package. |
|
Package scheduler is a generated GoMock package.
|
Package scheduler is a generated GoMock package. |
|
schedulermock
Package schedulermock is a generated GoMock package.
|
Package schedulermock is a generated GoMock package. |
|
Package state is a generated GoMock package.
|
Package state is a generated GoMock package. |
|
statemock
Package statemock is a generated GoMock package.
|
Package statemock is a generated GoMock package. |