Skip to content

Commit

Permalink
Authorizations test vectors (#270)
Browse files Browse the repository at this point in the history
* Add authorizations test vectors
  • Loading branch information
carlos-romano authored Feb 21, 2025
1 parent 90ffc8d commit 9e99bf9
Show file tree
Hide file tree
Showing 12 changed files with 1,563 additions and 102 deletions.
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

0 comments on commit 9e99bf9

Please sign in to comment.