diff --git a/internal/block/guarantee.go b/internal/block/guarantee.go index 9bbf521..9c1b2c4 100644 --- a/internal/block/guarantee.go +++ b/internal/block/guarantee.go @@ -26,8 +26,7 @@ type CredentialSignature struct { Signature [crypto.Ed25519SignatureSize]byte // The Ed25519 signature } -// WorkReport represents a work report in the JAM state (equation 11.2 v0.5.0) -// TODO: The total serialized size of a work-report may be no greater than MaxWorkPackageSizeBytes. +// WorkReport represents a work report in the JAM state (equation 11.2 v0.5.4) type WorkReport struct { WorkPackageSpecification WorkPackageSpecification // Work-package specification (s) RefinementContext RefinementContext // Refinement context (x) @@ -46,11 +45,11 @@ type WorkPackageSpecification struct { SegmentCount uint16 // Segment count (n) } -// RefinementContext describes the context of the chain at the point that the report’s corresponding work-package was evaluated. +// RefinementContext describes the context of the chain at the point that the report’s corresponding work-package was evaluated. 11.4 GP 0.5.4 type RefinementContext struct { Anchor RefinementContextAnchor // Historical block anchor LookupAnchor RefinementContextLookupAnchor // Historical block anchor - PrerequisiteWorkPackage []crypto.Hash // Prerequisite work package (p) (optional) + PrerequisiteWorkPackage []crypto.Hash // Prerequisite work package (p) } type RefinementContextAnchor struct { diff --git a/internal/service/service.go b/internal/service/service.go index fa1e93d..8ba9ef8 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -100,7 +100,7 @@ func (sa ServiceAccount) AddPreimage(p []byte, currentTimeslot jamtime.Timeslot) return nil } -// LookupPreimage implements the historical lookup function (Λ) as defined in Equation (9.7 v0.5.0). +// LookupPreimage implements the historical lookup function (Λ) as defined in Equation (9.7 v0.5.4). func (sa ServiceAccount) LookupPreimage(t jamtime.Timeslot, h crypto.Hash) []byte { p, exists := sa.PreimageLookup[h] if !exists { diff --git a/internal/statetransition/state_transition.go b/internal/statetransition/state_transition.go index 3b7b829..674715d 100644 --- a/internal/statetransition/state_transition.go +++ b/internal/statetransition/state_transition.go @@ -1367,7 +1367,7 @@ func ValidateExtrinsicGuarantees( prerequisitePackageHashes[key] = struct{}{} } - // Check total dependencies + // Check total dependencies. 11.3 GP 0.5.4 totalDeps := len(guarantee.WorkReport.RefinementContext.PrerequisiteWorkPackage) + len(guarantee.WorkReport.SegmentRootLookup) if totalDeps > common.WorkReportMaxSumOfDependencies { diff --git a/internal/work/constants.go b/internal/work/constants.go index cc3a037..40d5d0c 100644 --- a/internal/work/constants.go +++ b/internal/work/constants.go @@ -6,6 +6,7 @@ const ( NumberOfErasureCodecPiecesInSegment = 6 // WP = 6: The number of erasure-coded pieces in a segment. SizeOfErasureCodecPiecesInOctets = 684 // WE = 684: The basic size of erasure-coded pieces in octets. SizeOfSegment = NumberOfErasureCodecPiecesInSegment * SizeOfErasureCodecPiecesInOctets // WG = WP*WE = 4104: The size of a segment in octets. - MaxSizeOfEncodedWorkPackage = 12 * 1 << 20 // WB = 12*2^20 = 12MB: The maximum size of an encoded work-package together with its extrinsic data and import implications, in octets. - MaxAllocatedGasRefine = 500_000_000 // GR = 500, 000, 000: The gas allocated to invoke a work-package’s Refine logic. + SegmentsPerPage = 64 + MaxSizeOfEncodedWorkPackage = 12 * 1 << 20 // WB = 12*2^20 = 12MB: The maximum size of an encoded work-package together with its extrinsic data and import implications, in octets. + MaxAllocatedGasRefine = 500_000_000 // GR = 500, 000, 000: The gas allocated to invoke a work-package’s Refine logic. ) diff --git a/internal/work/item.go b/internal/work/item.go index ed39d65..59aa22b 100644 --- a/internal/work/item.go +++ b/internal/work/item.go @@ -15,7 +15,7 @@ type Extrinsic struct { Length uint32 } -// Item represents I (14.2 v0.5.2) +// Item represents I (14.2 v0.5.4) type Item struct { ServiceId uint32 // s ∈ N_S CodeHash crypto.Hash // c ∈ H @@ -37,7 +37,7 @@ func (w *Item) Size() uint64 { return total } -// ToWorkResult item-to-result function C (14.8 v0.5.2) +// ToWorkResult item-to-result function C (14.8 v0.5.4) func (w *Item) ToWorkResult(o block.WorkResultOutputOrError) block.WorkResult { payloadHash := crypto.HashData(w.Payload) diff --git a/internal/work/package.go b/internal/work/package.go index 8791af0..efeaaf5 100644 --- a/internal/work/package.go +++ b/internal/work/package.go @@ -2,14 +2,16 @@ package work import ( "fmt" - "github.com/eigerco/strawberry/internal/block" "github.com/eigerco/strawberry/internal/common" "github.com/eigerco/strawberry/internal/crypto" + "github.com/eigerco/strawberry/internal/merkle/binary_tree" + "github.com/eigerco/strawberry/internal/polkavm" "github.com/eigerco/strawberry/internal/service" + "github.com/eigerco/strawberry/pkg/serialization/codec/jam" ) -// Package represents P (14.2 v0.5.2) +// Package represents P (14.2 v0.5.4) type Package struct { AuthorizationToken []byte // j ∈ Y AuthorizerService uint32 // h ∈ N_S @@ -19,7 +21,7 @@ type Package struct { WorkItems []Item // w ∈ ⟦I⟧ } -// ValidateNumberOfEntries (14.4 v0.5.2) +// ValidateNumberOfEntries (14.4 v0.5.4) func (wp *Package) ValidateNumberOfEntries() error { var totalExported, totalImported uint16 for _, w := range wp.WorkItems { @@ -37,7 +39,7 @@ func (wp *Package) ValidateNumberOfEntries() error { return nil } -// ValidateSize (14.5 v0.5.2) +// ValidateSize (14.5 v0.5.4) func (wp *Package) ValidateSize() error { totalSize := uint64(len(wp.AuthorizationToken)) + uint64(len(wp.Parameterization)) @@ -52,7 +54,7 @@ func (wp *Package) ValidateSize() error { return nil } -// ValidateGas (14.7 v0.5.2) +// ValidateGas (14.7 v0.5.4) func (wp *Package) ValidateGas() error { var totalAccumulate, totalRefine uint64 for _, w := range wp.WorkItems { @@ -70,7 +72,7 @@ func (wp *Package) ValidateGas() error { return nil } -// ComputeAuthorizerHashes (14.9 v0.5.2) +// ComputeAuthorizerHashes (14.9 v0.5.4) func (wp *Package) ComputeAuthorizerHashes( serviceState service.ServiceState, ) (authorizationCode []byte, impliedAuthorizerHash crypto.Hash, err error) { @@ -85,7 +87,7 @@ func (wp *Package) ComputeAuthorizerHashes( return authorizationCode, impliedAuthorizerHash, nil } -// GetAuthorizationCode pc = Λ(δ[p.h], (p.x)^t, p.u) (14.9 v0.5.2) +// GetAuthorizationCode pc = Λ(δ[p.h], (p.x)^t, p.u) (14.9 v0.5.4) func (wp *Package) GetAuthorizationCode(serviceState service.ServiceState) ([]byte, error) { // Retrieve the service account by authorizer service index p.h sa, exists := serviceState[block.ServiceId(wp.AuthorizerService)] @@ -101,3 +103,35 @@ func (wp *Package) GetAuthorizationCode(serviceState service.ServiceState) ([]by return authorizationCode, nil } + +// ComputePagedProofs P(s) → [E(J₆(s,i), L₆(s,i))₍l₎ | i ∈ ℕ₍⌈|s|/64⌉₎] (14.10 v0.5.4) +func ComputePagedProofs(segments []polkavm.Segment) ([]polkavm.Segment, error) { + if len(segments) == 0 { + return nil, fmt.Errorf("no segments provided") + } + blobs := make([][]byte, len(segments)) + for i, seg := range segments { + blobs[i] = seg[:] + } + numPages := (len(segments) + SegmentsPerPage - 1) / SegmentsPerPage + pagedProofs := make([]polkavm.Segment, numPages) + for pageIndex := 0; pageIndex < numPages; pageIndex++ { + // Get leaf hashes and proof for page + leafHashes := binary_tree.GetLeafPage(blobs, pageIndex, NumberOfErasureCodecPiecesInSegment, crypto.HashData) + proof := binary_tree.GeneratePageProof(blobs, pageIndex, NumberOfErasureCodecPiecesInSegment, crypto.HashData) + + // Encode leaves and proof + marshalledLeaves, err := jam.Marshal(leafHashes) + if err != nil { + return nil, fmt.Errorf("failed to marshal leaf hashes: %w", err) + } + marshalledProof, err := jam.Marshal(proof) + if err != nil { + return nil, fmt.Errorf("failed to marshal proof: %w", err) + } + combined := append(marshalledLeaves, marshalledProof...) + padded := ZeroPadding(combined, SizeOfSegment) + copy(pagedProofs[pageIndex][:], padded) + } + return pagedProofs, nil +} diff --git a/internal/work/package_test.go b/internal/work/package_test.go index 92aaec0..4f59859 100644 --- a/internal/work/package_test.go +++ b/internal/work/package_test.go @@ -1,6 +1,8 @@ package work_test import ( + "bytes" + "github.com/eigerco/strawberry/internal/polkavm" "testing" "github.com/stretchr/testify/assert" @@ -14,6 +16,22 @@ import ( "github.com/eigerco/strawberry/internal/work" ) +// Helper functions +func createTestSegment(pattern byte) (seg polkavm.Segment) { + for i := range seg { + seg[i] = pattern + } + return seg +} + +func createSegments(count int) []polkavm.Segment { + segments := make([]polkavm.Segment, count) + for i := range segments { + segments[i] = createTestSegment(0x42) + } + return segments +} + func Test_ValidateNumberOfEntries(t *testing.T) { p := work.Package{ WorkItems: []work.Item{ @@ -125,3 +143,62 @@ func Test_ComputeAuthorizerHashes(t *testing.T) { _, _, err = p.ComputeAuthorizerHashes(serviceState) assert.Error(t, err) } + +func TestComputePagedProofs(t *testing.T) { + tests := []struct { + name string + inputSegments []polkavm.Segment + expectError bool + errorMessage string + }{ + { + name: "empty segments", + inputSegments: []polkavm.Segment{}, + expectError: true, + errorMessage: "no segments provided", + }, + { + name: "single page of segments", + inputSegments: createSegments(work.SegmentsPerPage), + expectError: false, + }, + { + name: "multiple pages of segments", + inputSegments: createSegments(work.SegmentsPerPage * 2), + expectError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + proofs, err := work.ComputePagedProofs(tt.inputSegments) + + if tt.expectError { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.errorMessage) + return + } + + require.NoError(t, err) + expectedNumPages := len(tt.inputSegments) / work.SegmentsPerPage + assert.Equal(t, expectedNumPages, len(proofs)) + }) + } +} + +func TestComputePagedProofsConsistency(t *testing.T) { + // Create two identical sets of segments + segments1 := createSegments(work.SegmentsPerPage) + segments2 := createSegments(work.SegmentsPerPage) + + proofs1, err := work.ComputePagedProofs(segments1) + require.NoError(t, err) + + proofs2, err := work.ComputePagedProofs(segments2) + require.NoError(t, err) + + assert.Equal(t, len(proofs1), len(proofs2)) + for i := range proofs1 { + assert.True(t, bytes.Equal(proofs1[i][:], proofs2[i][:])) + } +}