Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: validate guarantee extrinsic #123

Merged
merged 2 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions internal/block/header.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import (
"github.com/eigerco/strawberry/internal/common"
"github.com/eigerco/strawberry/internal/crypto"
"github.com/eigerco/strawberry/internal/jamtime"
"github.com/eigerco/strawberry/pkg/serialization"
"github.com/eigerco/strawberry/pkg/serialization/codec"
"sync"
)

// Header as defined in the section 5 in the paper
Expand All @@ -27,3 +30,44 @@ type EpochMarker struct {
Entropy crypto.Hash
Keys [common.NumberOfValidators]crypto.BandersnatchPublicKey
}

// AncestorStoreSingleton the in memory store for headers that need to be kept for 24 hours
// TODO replace with pebble
danielvladco marked this conversation as resolved.
Show resolved Hide resolved
var AncestorStoreSingleton = &AncestorStore{
ancestorSet: make(map[crypto.Hash]*Header),
mu: sync.RWMutex{},
}

type AncestorStore struct {
ancestorSet map[crypto.Hash]*Header
mu sync.RWMutex
}

func (a *AncestorStore) StoreHeader(header *Header) error {
encodedHeader, err := serialization.NewSerializer(codec.NewJamCodec()).Encode(header)
if err != nil {
return err
}
hash := crypto.HashData(encodedHeader)
a.mu.Lock()
defer a.mu.Unlock()
a.ancestorSet[hash] = header
return nil
}

func (a *AncestorStore) GetAncestor(header *Header) *Header {
a.mu.RLock()
defer a.mu.RUnlock()
return a.ancestorSet[header.ParentHash]
}

func (a *AncestorStore) FindAncestor(fn func(header *Header) bool) *Header {
a.mu.RLock()
defer a.mu.RUnlock()
for _, h := range a.ancestorSet {
if fn(h) {
return h
}
}
return nil
}
156 changes: 155 additions & 1 deletion internal/statetransition/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@ import (
"fmt"
"log"
"maps"
"reflect"
"slices"
"sort"
"sync"

"github.com/eigerco/strawberry/pkg/serialization"
"github.com/eigerco/strawberry/pkg/serialization/codec"
"github.com/eigerco/strawberry/pkg/serialization/codec/jam"

"github.com/eigerco/strawberry/internal/block"
Expand Down Expand Up @@ -45,7 +48,11 @@ func UpdateState(s *state.State, newBlock block.Block) error {
return errors.New("invalid block, the assurance is not anchored on parent")
}
if !assuranceIsOrderedByValidatorIndex(newBlock.Extrinsic.EA) {
return errors.New("Invalid block, the assurance is not ordered by validator index")
return errors.New("invalid block, the assurance is not ordered by validator index")
}

if err := validateExtrinsicGuarantees(newBlock.Header, s, newBlock.Extrinsic.EG, block.AncestorStoreSingleton); err != nil {
return fmt.Errorf("extrinsic guarantees validation failed, err: %w", err)
}

newTimeState := calculateNewTimeState(newBlock.Header)
Expand Down Expand Up @@ -620,6 +627,153 @@ func calculateNewCoreAssignments(
return newAssignments
}

func validateExtrinsicGuarantees(header block.Header, currentState *state.State, guarantees block.GuaranteesExtrinsic, ancestorStore *block.AncestorStore) error {

// let x ≡ {w_x S w ∈ w} , p ≡ {(w_s)h S w ∈ w} (145 v0.4.5)
contexts := make(map[block.RefinementContext]struct{})
extrinsicWorkPackages := make(map[crypto.Hash]crypto.Hash)

prerequisitePackageHashes := make(map[crypto.Hash]struct{})

// [⋃ x∈β] K(xp)
recentBlockPrerequisites := make(map[crypto.Hash]crypto.Hash)

// [⋃ x∈β] K(x_p) ∪ [⋃ x∈ξ] x ∪ q ∪ a
pastWorkPackages := make(map[crypto.Hash]struct{})
for _, recentBlock := range currentState.RecentBlocks {
for key, val := range recentBlock.WorkReportHashes {
recentBlockPrerequisites[key] = val
pastWorkPackages[key] = struct{}{}
}
}

for _, guarantee := range guarantees.Guarantees {
contexts[guarantee.WorkReport.RefinementContext] = struct{}{}
extrinsicWorkPackages[guarantee.WorkReport.WorkPackageSpecification.WorkPackageHash] = guarantee.WorkReport.WorkPackageSpecification.SegmentRoot
// ∀w ∈ w ∶ [∑ r∈wr] (rg) ≤ GA ∧ ∀r ∈ wr ∶ rg ≥ δ[rs]g (eq. 144 v0.4.5)
totalGas := uint64(0)
for _, r := range guarantee.WorkReport.WorkResults {
if r.GasPrioritizationRatio < currentState.Services[r.ServiceId].GasLimitForAccumulator {
return fmt.Errorf("work-report allotted accumulation gas for service is too small")
}
totalGas += r.GasPrioritizationRatio
}
if totalGas > service.CoreGasAccumulation {
return fmt.Errorf("work-reports total allotted accumulation gas is greater than the gas limit GA")
}

for key := range guarantee.WorkReport.SegmentRootLookup {
prerequisitePackageHashes[key] = struct{}{}
}
if guarantee.WorkReport.RefinementContext.PrerequisiteWorkPackage != nil {
prerequisitePackageHashes[*guarantee.WorkReport.RefinementContext.PrerequisiteWorkPackage] = struct{}{}

// let q = {(wx)p S q ∈ ϑ, w ∈ K(q)} (150 v0.4.5)
for _, workReportsAndDeps := range currentState.AccumulationQueue {
for _, wd := range workReportsAndDeps {
if reflect.DeepEqual(wd.WorkReport, guarantee.WorkReport) { // TODO maybe use hash compare instead of reflect
pastWorkPackages[*guarantee.WorkReport.RefinementContext.PrerequisiteWorkPackage] = struct{}{}
}
}
}
}

// let a = {((iw )x)p S i ∈ ρ, i ≠ ∅} (151 v0.4.5)
for _, ca := range currentState.CoreAssignments {
if ca.WorkReport.RefinementContext.PrerequisiteWorkPackage != nil {
pastWorkPackages[*ca.WorkReport.RefinementContext.PrerequisiteWorkPackage] = struct{}{}
}
}
}

// |p| = |w| (146 v0.4.5)
if len(extrinsicWorkPackages) != len(guarantees.Guarantees) {
return fmt.Errorf("cardinality of work-package hashes is not equal to the length of work-reports")
}

for context := range contexts {
// ∀x ∈ x ∶ ∃y ∈ β ∶ x_a = y_h ∧ x_s = ys ∧ xb = HK (EM (yb)) (147 v0.4.5)
if !anchorBlockInRecentBlocks(context, currentState) {
return fmt.Errorf("anchor block not present within recent blocks")
}

// ∀x ∈ x ∶ xt ≥ Ht − L (148 v0.4.5)
if context.LookupAnchor.Timeslot < header.TimeSlotIndex-state.MaxTimeslotsForPreimage {
return fmt.Errorf("lookup anchor block not withing the last %d timeslots", state.MaxTimeslotsForPreimage)
}

// ∀x ∈ x ∶ ∃h ∈ A ∶ ht = xt ∧ H(h) = xl (149 v0.4.5)
ancestor := ancestorStore.FindAncestor(func(ancestor *block.Header) bool {
encodedHeader, err := serialization.NewSerializer(codec.NewJamCodec()).Encode(ancestor)
if err != nil {
return false
}
if ancestor.TimeSlotIndex == context.LookupAnchor.Timeslot && crypto.HashData(encodedHeader) == context.LookupAnchor.HeaderHash {
return true
}
return false
})
if ancestor == nil {
return fmt.Errorf("no record of header found")
}
}

accHistoryPrerequisites := make(map[crypto.Hash]struct{})
for _, hashSet := range currentState.AccumulationHistory {
maps.Copy(accHistoryPrerequisites, hashSet)
}

// ∀p ∈ p, p ∉ [⋃ x∈β] K(x_p) ∪ [⋃ x∈ξ] x ∪ q ∪ a (152 v0.4.5)
for p := range extrinsicWorkPackages {
if _, ok := pastWorkPackages[p]; ok {
return fmt.Errorf("report work-package is the work-package of some other report made in the past")
}
}

// p ∪ {x | x ∈ b_p, b ∈ β} (153,154 v0.4.5)
extrinsicAndRecentWorkPackages := maps.Clone(extrinsicWorkPackages)
maps.Copy(extrinsicAndRecentWorkPackages, recentBlockPrerequisites)

// ∀w ∈ w, ∀p ∈ (wx)p ∪ K(wl) ∶ p ∈ p ∪ {x S x ∈ K(bp), b ∈ β} (153 v0.4.5)
for p := range prerequisitePackageHashes {
if _, ok := extrinsicWorkPackages[p]; !ok {
return fmt.Errorf("prerequisite report work-package is neither in the extrinsic nor in recent history")
}
}

for _, guarantee := range guarantees.Guarantees {
// ∀w ∈ w ∶ wl ⊆ p ∪ [⋃ b∈β] b_p (155 v0.4.5)
for lookupKey, lookupValue := range guarantee.WorkReport.SegmentRootLookup {
if extrinsicAndRecentWorkPackages[lookupKey] != lookupValue {
return fmt.Errorf("segment root not present in the present nor recent blocks")
}
}

// ∀w ∈ w, ∀r ∈ wr ∶ rc = δ[rs]c (156 v0.4.5)
for _, workResult := range guarantee.WorkReport.WorkResults {
if workResult.ServiceHashCode != currentState.Services[workResult.ServiceId].CodeHash {
return fmt.Errorf("work result code does not correspond with the service code")
}
}
}
return nil
}

// anchorBlockInRecentBlocks ∀x ∈ x ∶ ∃y ∈ β ∶ x_a = y_h ∧ x_s = ys ∧ xb = HK (EM (yb)) (147 v0.4.5)
func anchorBlockInRecentBlocks(context block.RefinementContext, currentState *state.State) bool {
for _, y := range currentState.RecentBlocks {
encodedMMR, err := serialization.NewSerializer(codec.NewJamCodec()).Encode(y.AccumulationResultMMR)
if err != nil {
return false
}

if context.Anchor.HeaderHash == y.HeaderHash && context.Anchor.PosteriorStateRoot == y.StateRoot && context.Anchor.PosteriorBeefyRoot != crypto.KeccakData(encodedMMR) {
return true
}
}
return false
}

// determineValidatorSet implements validator set selection from equations 135 and 139:
// From equation 139:
//
Expand Down
Loading