contractstaking

package
v2.5.0-rc2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 4, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

Documentation

Index

Constants

View Source
const LSDVoterIndexPrefix = byte(6)

LSDVoterIndexPrefix is the 1-byte tag of the owner -> contract-staking bucket index inside state.StakingNamespace.

Key shape is {LSDVoterIndexPrefix} || owner(20) = 21 bytes. That is the same length as the native _voterIndex / _candIndex / _candidateRewardSnapshot keys, which is fine because those carry a different leading tag; readers that scan the shared namespace discriminate on tag *and* length (see parseVoterWeightKey in the staking package), and no existing tag uses this value.

Variables

View Source
var ErrBucketNotExist = errors.New("bucket does not exist")

ErrBucketNotExist is the error when bucket does not exist

View Source
var ErrOwnerIndexNotExist = errors.New("contract-staking owner index does not exist")

ErrOwnerIndexNotExist is returned when an address owns no contract-staking bucket, i.e. the index key is absent. The empty list is never stored.

Functions

func BucketIndexUpperBounds

func BucketIndexUpperBounds(sr protocol.StateReader) ([]eracow.ContractBucketLimit, error)

BucketIndexUpperBounds returns every staking contract's exclusive bucket index upper bound, sorted ascending by contract address, for freezing into an IIP-59 era window.

The values come from the contract meta namespace, which holds exactly one small record per registered staking contract and nothing else, so this is a bounded scan rather than a state walk. The stored number is the max bucket id ever seen for the contract: it is only ever raised, never lowered, and contract bucket ids are minted from a strictly monotonic counter that burning does not touch. A bucket id above the frozen mark therefore cannot have existed at the freeze height. This function converts that inclusive mark to an exclusive upper bound, matching native bucket semantics.

Post-IIP-59 the mark is maintained for every contract by ContractStakingStateManager.RaiseNumOfBuckets, hooked into UpsertBucket. Do not assume the indexers maintain it: only the V1 indexer ever did, which is exactly why RaiseNumOfBuckets exists.

A contract that is missing from the result has never had a bucket written through UpsertBucket post-activation. eracow.Window.ContractBucketExisted rejects every bucket of such a contract, so a contract silently going missing here costs its owners their share -- staking.FrozenContractBucket logs that case rather than letting it pass unnoticed.

Sorted output matters: this ends up in a consensus record, and map iteration order would make two nodes disagree byte-for-byte on identical state.

func OwnerIndexEnabled

func OwnerIndexEnabled(ctx context.Context) bool

OwnerIndexEnabled reports whether the owner -> contract-staking bucket index may be written to the state trie.

Pre-activation it must stay out of it: nodes upgrade over days, and one that wrote these keys early would diverge from every node still on the old binary -- a split at deployment time rather than at activation. It is bound to protocol.FeatureCtx.NoVoterRewardDistribution, the IIP-59 fork gate.

A context with no feature context at all (indexer bootstraps, tests) is treated as pre-activation, so nothing is written by accident.

func ParseLSDVoterIndexKey

func ParseLSDVoterIndexKey(key []byte) (address.Address, bool)

ParseLSDVoterIndexKey reverses lsdVoterIndexKey. ok is false for any key in the staking namespace that is not an owner index entry -- buckets, native bucket indices, endorsements, reward snapshots and voter weights all share the namespace, so a scan must discriminate by key rather than by whether the value happens to deserialize.

Types

type Bucket

type Bucket struct {
	// Candidate is the address of the candidate that this bucket is staking for.
	Candidate address.Address
	// Owner is the address of the owner of this bucket.
	Owner address.Address
	// StakedAmount is the amount of tokens staked in this bucket.
	StakedAmount *big.Int

	// StakedDuration is the duration for which the tokens have been staked.
	StakedDuration uint64 // in seconds if timestamped, in block number if not
	// CreatedAt is the time when the bucket was created.
	CreatedAt uint64 // in unix timestamp if timestamped, in block height if not
	// UnlockedAt is the time when the bucket was unlocked.
	UnlockedAt uint64 // in unix timestamp if timestamped, in block height if not
	// UnstakedAt is the time when the bucket was unstaked.
	UnstakedAt uint64 // in unix timestamp if timestamped, in block height if not

	// IsTimestampBased indicates whether the bucket is timestamp-based
	IsTimestampBased bool
	// Muted indicates whether the bucket is vote weight muted
	Muted bool
}

Bucket is the structure that holds information about a staking bucket.

func LoadBucketFromProto

func LoadBucketFromProto(pb *stakingpb.SystemStakingBucket) (*Bucket, error)

LoadBucketFromProto converts a protobuf representation of a staking bucket to a Bucket struct.

func (*Bucket) Clone

func (b *Bucket) Clone() *Bucket

Clone creates a deep copy of the Bucket.

func (*Bucket) Decode

func (b *Bucket) Decode(gv systemcontracts.GenericValue) error

Decode decodes the bucket from a GenericValue

func (*Bucket) Deserialize

func (b *Bucket) Deserialize(data []byte) error

Deserialize deserializes the bucket from a byte slice.

func (*Bucket) Encode

func (b *Bucket) Encode() (systemcontracts.GenericValue, error)

Encode encodes the bucket into a GenericValue

func (*Bucket) Serialize

func (b *Bucket) Serialize() ([]byte, error)

Serialize serializes the bucket to a byte slice.

type BucketType

type BucketType struct {
	Amount      *big.Int
	Duration    uint64
	ActivatedAt uint64
}

BucketType defines the type of contract staking bucket

func LoadBucketTypeFromProto

func LoadBucketTypeFromProto(pb *stakingpb.BucketType) (*BucketType, error)

LoadBucketTypeFromProto converts a protobuf representation of a staking bucket type to a BucketType struct.

func (*BucketType) Clone

func (bt *BucketType) Clone() *BucketType

Clone clones the bucket type

func (*BucketType) Decode

Decode decodes the bucket type from a GenericValue

func (*BucketType) Deserialize

func (bt *BucketType) Deserialize(b []byte) error

Deserialize deserializes the bucket type

func (*BucketType) Encode

func (bt *BucketType) Encode() (systemcontracts.GenericValue, error)

Encode encodes the bucket type into a GenericValue

func (*BucketType) Serialize

func (bt *BucketType) Serialize() ([]byte, error)

Serialize serializes the bucket type

type ContractBucketRef

type ContractBucketRef struct {
	Contract address.Address
	BucketID uint64
}

ContractBucketRef names one contract-staking bucket. Bucket ids are only unique per staking contract, so the contract address is part of the identity, not context.

type ContractBucketRefs

type ContractBucketRefs []ContractBucketRef

ContractBucketRefs is the value stored under an owner index key: every contract-staking bucket an address owns, across all staking contracts.

The slice is kept sorted ascending by (contract bytes, bucket id) at all times. This is consensus state: two nodes that applied the same set of bucket writes must produce byte-identical values, so the order can never come from map iteration.

func (ContractBucketRefs) Contains

func (refs ContractBucketRefs) Contains(ref ContractBucketRef) bool

Contains reports whether the ref is in the list.

func (*ContractBucketRefs) Decode

Decode decodes the refs from a GenericValue.

func (*ContractBucketRefs) Deserialize

func (refs *ContractBucketRefs) Deserialize(data []byte) error

Deserialize deserializes bytes into refs.

func (*ContractBucketRefs) Encode

Encode encodes the refs into a GenericValue for Erigon dual-storage.

func (*ContractBucketRefs) LoadProto

LoadProto converts protobuf to refs.

func (*ContractBucketRefs) Proto

Proto converts the refs to protobuf.

func (*ContractBucketRefs) Serialize

func (refs *ContractBucketRefs) Serialize() ([]byte, error)

Serialize serializes the refs into bytes. The list is sorted first so a caller that built one by hand cannot write a value that differs only in order from the one a replaying node would produce.

type ContractStakingStateManager

type ContractStakingStateManager struct {
	ContractStakingStateReader
	// contains filtered or unexported fields
}

ContractStakingStateManager wraps a state manager to provide staking contract-specific writes.

func NewContractStakingStateManager

func NewContractStakingStateManager(sm protocol.StateManager, opts ...protocol.StateOption) *ContractStakingStateManager

NewContractStakingStateManager creates a new ContractStakingStateManager

func (*ContractStakingStateManager) AddOwnerRefs

func (cs *ContractStakingStateManager) AddOwnerRefs(ctx context.Context, owner address.Address, refs []ContractBucketRef) error

AddOwnerRefs records that owner owns every ref in refs.

Semantically identical to calling addOwnerRef once per ref, including its idempotence and its "a no-op write produces no era copy either" rule. The difference is cost: the per-ref form is a read-modify-write of the same trie key for each ref, which is what made the IIP-59 activation backfill expensive for owners holding many buckets. This reads once, copies aside at most once, and writes once.

Exported because the backfill lives in the staking package (see staking.backfillOwnerIndex); nothing else needs it — the live paths add one ref at a time.

func (*ContractStakingStateManager) DeleteBucket

func (cs *ContractStakingStateManager) DeleteBucket(ctx context.Context, contractAddr address.Address, bucketID uint64) error

DeleteBucket removes a bucket for a given contract and bucket ID.

ctx carries the fork gate for the owner index only; the bucket write itself is unconditional and byte-for-byte what it was before the index existed.

func (*ContractStakingStateManager) RaiseNumOfBuckets

func (cs *ContractStakingStateManager) RaiseNumOfBuckets(contractAddr address.Address, bucketID uint64) error

RaiseNumOfBuckets raises the contract's bucket high-water mark to cover bucketID, and does nothing if it already does.

Why this exists

StakingContract.NumOfBuckets is the max bucket id ever seen for a contract (see BackfillContract for why that is not a count). It is the only bound the IIP-59 era window has on contract bucket ids -- eracow.Window. ContractBucketExisted answers "did this id exist at H" purely from it -- and a contract with no record at all is rejected outright.

Before IIP-59 exactly one code path maintained it: blockindex/contractstaking/cache.go Commit, i.e. the V1 indexer only. The V2 and V3 indexers in systemcontractindex/stakingindex never wrote it, and neither did staking.nftEventHandler, the shared trie-write path all three indexers funnel their bucket writes through. So V2/V3 buckets had no frozen mark and were silently dropped from every frozen weight, no matter what the owner index said about them.

Hooking the raise here rather than in each indexer covers all three at once, because UpsertBucket is the single writer of contract bucket state.

Raise-only, and gated

Raise-only because the mark's whole meaning is "no id above this existed before now": lowering it would let a post-freeze bucket into a frozen era. It is also what makes this safe to run alongside the V1 indexer's own unconditional write of the same key -- V1's value is monotone too, so the two agree and the extra write is a no-op.

Gated behind the IIP-59 fork by its only caller, because writing a meta record for a contract that has none today changes the state root.

func (*ContractStakingStateManager) UpdateNumOfBuckets

func (cs *ContractStakingStateManager) UpdateNumOfBuckets(contractAddr address.Address, numOfBuckets uint64) error

UpdateNumOfBuckets updates the number of buckets.

This is an unconditional write, used by the V1 indexer which tracks the mark itself. Prefer RaiseNumOfBuckets anywhere the value is not already known to be monotone.

func (*ContractStakingStateManager) UpsertBucket

func (cs *ContractStakingStateManager) UpsertBucket(ctx context.Context, contractAddr address.Address, bid uint64, bucket *Bucket) error

UpsertBucket inserts or updates a bucket for a given contract and bid.

This and DeleteBucket are the only writers of contract-staking bucket state, which is what makes them the single choke point for the owner index.

func (*ContractStakingStateManager) UpsertBucketType

func (cs *ContractStakingStateManager) UpsertBucketType(contractAddr address.Address, bucketID uint64, bucketType *BucketType) error

UpsertBucketType inserts or updates a bucket type for a given contract and bucket ID.

type ContractStakingStateReader

type ContractStakingStateReader struct {
	// contains filtered or unexported fields
}

ContractStakingStateReader wraps a state reader to provide staking contract-specific reads.

func NewStateReader

NewStateReader creates a new ContractStakingStateReader.

func (*ContractStakingStateReader) Bucket

func (r *ContractStakingStateReader) Bucket(contractAddr address.Address, bucketID uint64) (*Bucket, error)

Bucket returns the Bucket for a given contract and bucket id.

func (*ContractStakingStateReader) BucketRefsByOwner

func (r *ContractStakingStateReader) BucketRefsByOwner(owner address.Address) (ContractBucketRefs, uint64, error)

BucketRefsByOwner returns every contract-staking bucket the address owns, sorted by (contract, bucket id), plus the height the state was read at.

This lives in the contractstaking package rather than next to NativeBucketIndicesByVoter because the value type names a staking contract: `staking` already imports `contractstaking`, so the ref type cannot live on the other side without inverting the dependency.

An owner with no contract-staking buckets has no key at all; the error is state.ErrStateNotExist, wrapped as ErrOwnerIndexNotExist.

func (*ContractStakingStateReader) BucketStateOpts

func (r *ContractStakingStateReader) BucketStateOpts(contractAddr address.Address, bucketID uint64) []protocol.StateOption

BucketStateOpts addresses one contract-staking bucket.

This is the single expression for that address in the repository. Every live read and write of a bucket goes through it, and so does the IIP-59 era copy-on-write resolver's live-value fallback (staking.FrozenContractBucket), which would otherwise re-derive the same address by hand and drift from this one — silently, since a frozen read that misses is skipped, not failed.

func (*ContractStakingStateReader) BucketType

func (r *ContractStakingStateReader) BucketType(contractAddr address.Address, tID uint64) (*BucketType, error)

BucketType returns the BucketType for a given contract and bucket id.

func (*ContractStakingStateReader) BucketTypes

func (r *ContractStakingStateReader) BucketTypes(contractAddr address.Address) ([]uint64, []*BucketType, error)

BucketTypes returns all BucketType for a given contract and bucket id.

func (*ContractStakingStateReader) Buckets

func (r *ContractStakingStateReader) Buckets(contractAddr address.Address) ([]uint64, []*Bucket, error)

Buckets returns all BucketInfo for a given contract.

func (*ContractStakingStateReader) FrozenBucket

func (r *ContractStakingStateReader) FrozenBucket(
	window eracow.Window,
	contractAddr address.Address,
	bucketID uint64,
) (*Bucket, error)

FrozenBucket reads a contract-staking bucket as of the era freeze height.

func (*ContractStakingStateReader) FrozenBucketRefs

func (r *ContractStakingStateReader) FrozenBucketRefs(window eracow.Window, owner address.Address) (ContractBucketRefs, error)

FrozenBucketRefs reads an owner's contract-staking bucket list as of the era freeze height. An absent list is returned as nil without an error.

func (*ContractStakingStateReader) NumOfBuckets

func (r *ContractStakingStateReader) NumOfBuckets(contractAddr address.Address) (uint64, error)

NumOfBuckets returns the total number of buckets for a contract.

func (*ContractStakingStateReader) OwnerIndexStateOpts

func (r *ContractStakingStateReader) OwnerIndexStateOpts(owner address.Address) []protocol.StateOption

OwnerIndexStateOpts addresses one owner's contract-staking bucket ref list.

This is the single expression for that address in the repository; see BucketStateOpts for why frozen and live reads must share one.

type StakingContract

type StakingContract struct {
	// NumOfBuckets is the number of buckets in the staking contract
	NumOfBuckets uint64
}

StakingContract represents the staking contract in the system

func LoadStakingContractFromProto

func LoadStakingContractFromProto(pb *stakingpb.SystemStakingContract) (*StakingContract, error)

LoadStakingContractFromProto converts a protobuf representation of a staking contract to a StakingContract struct.

func (*StakingContract) Decode

Decode decodes the staking contract from a GenericValue

func (*StakingContract) Deserialize

func (sc *StakingContract) Deserialize(b []byte) error

Deserialize deserializes the staking contract

func (*StakingContract) Encode

Encode encodes the staking contract into a GenericValue

func (*StakingContract) Serialize

func (sc *StakingContract) Serialize() ([]byte, error)

Serialize serializes the staking contract

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL