Skip to content
Closed
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
4 changes: 0 additions & 4 deletions internal/blockchain/chain_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -892,10 +892,6 @@ func (cs *ChainState) restoreWithState(
// Keep only ancestry up to the restored headerHash (fallback point)
cs.KeepAncestryUpTo(blockHeaderHash)

// Clear verifier cache when restoring to a different state point
// as the epoch may have changed
ClearVerifierCache()

return nil
}

Expand Down
35 changes: 29 additions & 6 deletions internal/blockchain/ring_verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,19 @@ import (
"sync"

"github.com/New-JAMneration/JAM-Protocol/internal/types"
"github.com/New-JAMneration/JAM-Protocol/internal/utilities/hash"

vrf "github.com/New-JAMneration/JAM-Protocol/pkg/Rust-VRF/vrf-func-ffi/src"
)

type ringVerifierCacheKey struct {
epoch types.TimeSlot
gammaKHash types.OpaqueHash
}

type ringVerifierCache struct {
sync.RWMutex
epoch types.TimeSlot
key ringVerifierCacheKey
*vrf.Verifier
}

Expand All @@ -33,18 +39,23 @@ func ClearVerifierCache() {
// the pointer anymore.
func (c *ringVerifierCache) release() {
c.Verifier = nil
c.epoch = 0
c.key = ringVerifierCacheKey{}
}

func GetVerifier(epoch types.TimeSlot, gammaK types.ValidatorsData) (*vrf.Verifier, error) {
if len(gammaK) != types.ValidatorsCount {
return nil, fmt.Errorf("gammaK size %d is not equal to validators count %d", len(gammaK), types.ValidatorsCount)
}

key, err := newRingVerifierCacheKey(epoch, gammaK)
if err != nil {
return nil, fmt.Errorf("failed to hash gammaK: %w", err)
}

// First path: read lock
// Try to get both cached verifiers
cache.RLock()
if cache.epoch == epoch && cache.Verifier != nil {
if cache.key == key && cache.Verifier != nil {
cache.RUnlock()
return cache.Verifier, nil
}
Expand All @@ -55,11 +66,11 @@ func GetVerifier(epoch types.TimeSlot, gammaK types.ValidatorsData) (*vrf.Verifi
defer cache.Unlock()

// Double check
if cache.epoch == epoch && cache.Verifier != nil {
if cache.key == key && cache.Verifier != nil {
return cache.Verifier, nil
}

// epoch transition or not initialized: drop the old verifier reference.
// Cache miss or key transition: drop the old verifier reference.
// Do not Free() it here — a concurrent reader may still hold the pointer
// from the read-lock fast path above. The finalizer frees it later.
cache.release()
Expand All @@ -77,7 +88,19 @@ func GetVerifier(epoch types.TimeSlot, gammaK types.ValidatorsData) (*vrf.Verifi
}

// update cache and return
cache.epoch = epoch
cache.key = key
cache.Verifier = ringVerifier
return ringVerifier, nil
}

func newRingVerifierCacheKey(epoch types.TimeSlot, gammaK types.ValidatorsData) (ringVerifierCacheKey, error) {
gammaKHash, err := hash.HashEncode(&gammaK)
if err != nil {
return ringVerifierCacheKey{}, err
}

return ringVerifierCacheKey{
epoch: epoch,
gammaKHash: gammaKHash,
}, nil
}
93 changes: 93 additions & 0 deletions internal/blockchain/ring_verifier_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package blockchain

import (
"testing"

"github.com/New-JAMneration/JAM-Protocol/internal/types"
jamhash "github.com/New-JAMneration/JAM-Protocol/internal/utilities/hash"
vrf "github.com/New-JAMneration/JAM-Protocol/pkg/Rust-VRF/vrf-func-ffi/src"
)

func testGammaK(seed byte) types.ValidatorsData {
gammaK := make(types.ValidatorsData, types.ValidatorsCount)
for i := range gammaK {
b := byte(i) + seed
gammaK[i].Bandersnatch[0] = b
gammaK[i].Ed25519[0] = b + 1
gammaK[i].Bls[0] = b + 2
gammaK[i].Metadata[0] = b + 3
}
return gammaK
}

func TestRingVerifierCacheKeyIncludesEpochAndGammaKContents(t *testing.T) {
gammaK := testGammaK(1)

key, err := newRingVerifierCacheKey(42, gammaK)
if err != nil {
t.Fatalf("newRingVerifierCacheKey failed: %v", err)
}

sameKey, err := newRingVerifierCacheKey(42, gammaK)
if err != nil {
t.Fatalf("newRingVerifierCacheKey failed: %v", err)
}
if key != sameKey {
t.Fatalf("same epoch and gammaK produced different keys")
}

differentEpochKey, err := newRingVerifierCacheKey(43, gammaK)
if err != nil {
t.Fatalf("newRingVerifierCacheKey failed: %v", err)
}
if key == differentEpochKey {
t.Fatalf("different epoch produced the same key")
}

differentGammaK := append(types.ValidatorsData(nil), gammaK...)
differentGammaK[0].Metadata[0] ^= 0xff
differentGammaKKey, err := newRingVerifierCacheKey(42, differentGammaK)
if err != nil {
t.Fatalf("newRingVerifierCacheKey failed: %v", err)
}
if key == differentGammaKKey {
t.Fatalf("different gammaK contents produced the same key")
}
}

func TestRestoreWithStateKeepsVerifierCache(t *testing.T) {
ClearVerifierCache()
t.Cleanup(ClearVerifierCache)

gammaK := testGammaK(2)
key, err := newRingVerifierCacheKey(7, gammaK)
if err != nil {
t.Fatalf("newRingVerifierCacheKey failed: %v", err)
}
verifier := &vrf.Verifier{}

cache.Lock()
cache.key = key
cache.Verifier = verifier
cache.Unlock()

cs := newChainState()
block := types.Block{Header: types.Header{Slot: 1}}
headerHash, err := jamhash.ComputeBlockHeaderHash(block.Header)
if err != nil {
t.Fatalf("ComputeBlockHeaderHash failed: %v", err)
}

if err := cs.restoreWithState(headerHash, block, types.State{}, nil); err != nil {
t.Fatalf("restoreWithState failed: %v", err)
}

cache.RLock()
defer cache.RUnlock()
if cache.key != key {
t.Fatalf("restoreWithState changed verifier cache key")
}
if cache.Verifier != verifier {
t.Fatalf("restoreWithState cleared verifier cache")
}
}
Loading