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

Authorizations test vectors #270

Merged
merged 7 commits into from
Feb 21, 2025
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
17 changes: 17 additions & 0 deletions internal/crypto/hash.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package crypto

import (
"encoding/hex"
"golang.org/x/crypto/blake2b"
"golang.org/x/crypto/sha3"
"log"
"strings"
)

type Hash [HashSize]byte
Expand All @@ -22,3 +25,17 @@ func KeccakData(data []byte) Hash {
copy(result[:], hashed)
return result
}

// StringToHex converts a hex string to a byte slice
func StringToHex(s string) ([]byte, error) {
// Remove 0x prefix if present
s = strings.TrimPrefix(s, "0x")

// Decode hex string
bytes, err := hex.DecodeString(s)
if err != nil {
log.Printf("Error decoding hex string '%s': %v", s, err)
return nil, err
}
return bytes, nil
}
11 changes: 6 additions & 5 deletions internal/state/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ type Judgements struct {

type CoreAssignments [common.TotalNumberOfCores]*Assignment

type PendingAuthorizersQueues [common.TotalNumberOfCores][PendingAuthorizersQueueSize]crypto.Hash
type PendingAuthorizersQueue [PendingAuthorizersQueueSize]crypto.Hash
type PendingAuthorizersQueues [common.TotalNumberOfCores]PendingAuthorizersQueue

type EntropyPool [EntropyPoolSize]crypto.Hash
type CoreAuthorizersPool [common.TotalNumberOfCores][]crypto.Hash // TODO: Maximum length per core: MaxAuthorizersPerCore
Expand All @@ -41,10 +42,10 @@ type AccumulationHistory [jamtime.TimeslotsPerEpoch]map[crypto.Hash]struct{} //

// AccumulationState characterization of state components (equation 174 v0.4.5)
type AccumulationState struct {
ServiceState service.ServiceState // Service accounts δ (d ∈ D⟨NS → A⟩)
ValidatorKeys safrole.ValidatorsData // Validator keys ι (i ∈ ⟦K⟧V)
PendingAuthorizersQueues [common.TotalNumberOfCores][PendingAuthorizersQueueSize]crypto.Hash // Queue of authorizers (q ∈ C⟦H⟧QHC)
PrivilegedServices service.PrivilegedServices // Privileges state (x ∈ (NS, NS, NS, D⟨NS → NG⟩))
ServiceState service.ServiceState // Service accounts δ (d ∈ D⟨NS → A⟩)
ValidatorKeys safrole.ValidatorsData // Validator keys ι (i ∈ ⟦K⟧V)
PendingAuthorizersQueues [common.TotalNumberOfCores]PendingAuthorizersQueue // Queue of authorizers (q ∈ C⟦H⟧QHC)
PrivilegedServices service.PrivilegedServices // Privileges state (x ∈ (NS, NS, NS, D⟨NS → NG⟩))
}

// AccumulationOperand represents a single operand for accumulation (equation 179 v0.4.5)
Expand Down
11 changes: 10 additions & 1 deletion internal/statetransition/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -698,14 +698,23 @@ func CalculateNewCoreAuthorizations(header block.Header, guarantees block.Guaran
newAuths := make([]crypto.Hash, len(currentAuthorizations[c]))
copy(newAuths, currentAuthorizations[c])

// F(c) - Remove authorizer if it was used in a guarantee for this core. 8.3 Graypaper 0.5.4
// Track whether a guarantee's authorizer removal has occurred
guaranteeAuthorizerRemoved := false

// F(c) - Remove authorizer if it was used in a guarantee for this core. 8.3 Graypaper 0.6.2
for _, guarantee := range guarantees.Guarantees {
if guarantee.WorkReport.CoreIndex == c {
// Remove the used authorizer from the list
newAuths = removeAuthorizer(newAuths, guarantee.WorkReport.AuthorizerHash)
guaranteeAuthorizerRemoved = true
}
}

// If no guarantee was found for this core, then left-shift the authorizers (remove the first element)
if !guaranteeAuthorizerRemoved && len(newAuths) > 0 {
newAuths = newAuths[1:]
}

// Get new authorizer from the queue based on current timeslot
// φ'[c][Ht]↺O - Get authorizer from queue, wrapping around queue size
queueIndex := header.TimeSlotIndex % state.PendingAuthorizersQueueSize
Expand Down
113 changes: 97 additions & 16 deletions internal/statetransition/state_transition_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -716,7 +716,6 @@ func TestCalculateNewCoreAuthorizations(t *testing.T) {
})

t.Run("remove used authorizer and add new one", func(t *testing.T) {
t.Skip("[Issue 223] Due to reports test vector not expecting this logic")
header := block.Header{
TimeSlotIndex: 1,
}
Expand Down Expand Up @@ -749,6 +748,31 @@ func TestCalculateNewCoreAuthorizations(t *testing.T) {
assert.NotContains(t, newAuths[0], usedAuth)
})

t.Run("left-shift authorizers when no guarantee used", func(t *testing.T) {
header := block.Header{
TimeSlotIndex: 1,
}

// Set up current authorizations with multiple authorizers
currentAuths := state.CoreAuthorizersPool{}
auth1 := testutils.RandomHash(t)
auth2 := testutils.RandomHash(t)
currentAuths[0] = []crypto.Hash{auth1, auth2}

// Set up pending authorizations with new authorizer
pendingAuths := state.PendingAuthorizersQueues{}
newAuth := testutils.RandomHash(t)
pendingAuths[0][1] = newAuth

newAuths := CalculateNewCoreAuthorizations(header, block.GuaranteesExtrinsic{}, pendingAuths, currentAuths)

// Check that auth1 was removed (left-shift) and newAuth was added
require.Len(t, newAuths[0], 2)
assert.Equal(t, auth2, newAuths[0][0], "First authorizer should be auth2 after left-shift")
assert.Equal(t, newAuth, newAuths[0][1], "Second authorizer should be the new one")
assert.NotContains(t, newAuths[0], auth1, "auth1 should be removed by left-shift")
})

t.Run("maintain max size limit", func(t *testing.T) {
header := block.Header{
TimeSlotIndex: 1,
Expand All @@ -767,10 +791,11 @@ func TestCalculateNewCoreAuthorizations(t *testing.T) {

newAuths := CalculateNewCoreAuthorizations(header, block.GuaranteesExtrinsic{}, pendingAuths, currentAuths)

// Check that size limit is maintained and oldest auth was removed
// Check that size limit is maintained and both oldest auth and left-shifted auth were removed
require.Len(t, newAuths[0], state.MaxAuthorizersPerCore)
assert.Equal(t, newAuth, newAuths[0][state.MaxAuthorizersPerCore-1])
assert.NotEqual(t, currentAuths[0][0], newAuths[0][0])
assert.NotEqual(t, currentAuths[0][0], newAuths[0][0], "First auth should be removed")
assert.Equal(t, currentAuths[0][1], newAuths[0][0], "Second auth should be first now due to left-shift")
})

t.Run("handle empty pending authorization", func(t *testing.T) {
Expand All @@ -779,17 +804,55 @@ func TestCalculateNewCoreAuthorizations(t *testing.T) {
}

currentAuths := state.CoreAuthorizersPool{}
existingAuth := testutils.RandomHash(t)
currentAuths[0] = []crypto.Hash{existingAuth}
auth1 := testutils.RandomHash(t)
auth2 := testutils.RandomHash(t)
currentAuths[0] = []crypto.Hash{auth1, auth2}

// Empty pending authorizations
pendingAuths := state.PendingAuthorizersQueues{}

newAuths := CalculateNewCoreAuthorizations(header, block.GuaranteesExtrinsic{}, pendingAuths, currentAuths)

// Should keep existing authorizations unchanged
// Should left-shift existing authorizations when no new auth is available
require.Len(t, newAuths[0], 1)
assert.Equal(t, existingAuth, newAuths[0][0])
assert.Equal(t, auth2, newAuths[0][0], "Only second auth should remain after left-shift")
assert.NotContains(t, newAuths[0], auth1, "First auth should be removed by left-shift")
})

t.Run("no left-shift when guarantee removes authorizer", func(t *testing.T) {
header := block.Header{
TimeSlotIndex: 1,
}

// Set up current authorizations
currentAuths := state.CoreAuthorizersPool{}
auth1 := testutils.RandomHash(t)
auth2 := testutils.RandomHash(t)
currentAuths[0] = []crypto.Hash{auth1, auth2}

// Create a guarantee that uses the first authorizer
workReport := block.WorkReport{
CoreIndex: 0,
AuthorizerHash: auth1,
}
guarantees := block.GuaranteesExtrinsic{
Guarantees: []block.Guarantee{
{WorkReport: workReport},
},
}

// Set up new pending authorizer
pendingAuths := state.PendingAuthorizersQueues{}
newAuth := testutils.RandomHash(t)
pendingAuths[0][1] = newAuth

newAuths := CalculateNewCoreAuthorizations(header, guarantees, pendingAuths, currentAuths)

// Check that only the used authorizer was removed (no left-shift) and new auth was added
require.Len(t, newAuths[0], 2)
assert.Equal(t, auth2, newAuths[0][0], "Second auth should remain in first position")
assert.Equal(t, newAuth, newAuths[0][1], "New auth should be added at the end")
assert.NotContains(t, newAuths[0], auth1, "Used auth should be removed")
})

t.Run("handle multiple cores", func(t *testing.T) {
Expand All @@ -799,10 +862,23 @@ func TestCalculateNewCoreAuthorizations(t *testing.T) {

// Set up authorizations for two cores
currentAuths := state.CoreAuthorizersPool{}
existingAuth0 := testutils.RandomHash(t)
existingAuth1 := testutils.RandomHash(t)
currentAuths[0] = []crypto.Hash{existingAuth0}
currentAuths[1] = []crypto.Hash{existingAuth1}
auth0_1 := testutils.RandomHash(t)
auth0_2 := testutils.RandomHash(t)
auth1_1 := testutils.RandomHash(t)
auth1_2 := testutils.RandomHash(t)
currentAuths[0] = []crypto.Hash{auth0_1, auth0_2}
currentAuths[1] = []crypto.Hash{auth1_1, auth1_2}

// Create a guarantee that uses an authorizer for core 1 only
workReport := block.WorkReport{
CoreIndex: 1,
AuthorizerHash: auth1_1,
}
guarantees := block.GuaranteesExtrinsic{
Guarantees: []block.Guarantee{
{WorkReport: workReport},
},
}

// Set up new pending authorizations
pendingAuths := state.PendingAuthorizersQueues{}
Expand All @@ -811,14 +887,19 @@ func TestCalculateNewCoreAuthorizations(t *testing.T) {
pendingAuths[0][1] = newAuth0
pendingAuths[1][1] = newAuth1

newAuths := CalculateNewCoreAuthorizations(header, block.GuaranteesExtrinsic{}, pendingAuths, currentAuths)
newAuths := CalculateNewCoreAuthorizations(header, guarantees, pendingAuths, currentAuths)

// Core 0: Should left-shift (no guarantee)
require.Len(t, newAuths[0], 2)
assert.Equal(t, auth0_2, newAuths[0][0], "Core 0: First auth should be removed by left-shift")
assert.Equal(t, newAuth0, newAuths[0][1], "Core 0: New auth should be added")
assert.NotContains(t, newAuths[0], auth0_1, "Core 0: Original first auth should be removed")

// Core 1: Should remove used auth (no left-shift)
require.Len(t, newAuths[1], 2)
assert.Contains(t, newAuths[0], existingAuth0)
assert.Contains(t, newAuths[0], newAuth0)
assert.Contains(t, newAuths[1], existingAuth1)
assert.Contains(t, newAuths[1], newAuth1)
assert.Equal(t, auth1_2, newAuths[1][0], "Core 1: Second auth should remain")
assert.Equal(t, newAuth1, newAuths[1][1], "Core 1: New auth should be added")
assert.NotContains(t, newAuths[1], auth1_1, "Core 1: Used auth should be removed")
})
}

Expand Down
16 changes: 12 additions & 4 deletions tests/integration/assurances_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ type AssurancesInput struct {
Parent string `json:"parent"`
}

func mustStringToHex(s string) []byte {
bytes, err := crypto.StringToHex(s)
if err != nil {
panic(err)
}
return bytes
}

func mapBlock(i AssurancesInput) block.Block {
return block.Block{
Header: block.Header{
Expand All @@ -48,9 +56,9 @@ func mapBlock(i AssurancesInput) block.Block {
EA: mapSlice(i.Assurances, func(a Assurance) block.Assurance {
return block.Assurance{
Anchor: mapHash(a.Anchor),
Bitfield: [block.AvailBitfieldBytes]byte(stringToHex(a.Bitfield)),
Bitfield: [block.AvailBitfieldBytes]byte(mustStringToHex(a.Bitfield)),
ValidatorIndex: a.ValidatorIndex,
Signature: crypto.Ed25519Signature(stringToHex(a.Signature)),
Signature: crypto.Ed25519Signature(mustStringToHex(a.Signature)),
}
}),
},
Expand Down Expand Up @@ -142,7 +150,7 @@ func mapReport(r *Report) *block.WorkReport {
},
CoreIndex: r.CoreIndex,
AuthorizerHash: mapHash(r.AuthorizerHash),
Output: stringToHex(r.AuthOutput),
Output: mustStringToHex(r.AuthOutput),
SegmentRootLookup: segmentRootLookup,
WorkResults: mapSlice(r.Results, func(rr ReportResult) block.WorkResult {
return block.WorkResult{
Expand All @@ -159,7 +167,7 @@ func mapReport(r *Report) *block.WorkReport {
}

func mapHash(s string) crypto.Hash {
return crypto.Hash(stringToHex(s))
return crypto.Hash(mustStringToHex(s))
}

func TestAssurancesTiny(t *testing.T) {
Expand Down
Loading
Loading