2023-09-06 16:02:30 -04:00
package rln
import (
"bytes"
"context"
"errors"
"sync"
"time"
2024-09-25 17:15:20 +08:00
"github.com/waku-org/go-waku/waku/v2/utils"
2023-09-06 16:02:30 -04:00
"github.com/waku-org/go-zerokit-rln/rln"
"go.uber.org/zap"
)
// NullifierLog is the log of nullifiers and Shamir shares of the past messages grouped per epoch
type NullifierLog struct {
sync . RWMutex
log * zap . Logger
nullifierLog map [ rln . Nullifier ] [ ] rln . ProofMetadata // Might make sense to replace this map by a shrinkable map due to https://github.com/golang/go/issues/20135.
nullifierQueue [ ] rln . Nullifier
}
// NewNullifierLog creates an instance of NullifierLog
func NewNullifierLog ( ctx context . Context , log * zap . Logger ) * NullifierLog {
result := & NullifierLog {
nullifierLog : make ( map [ rln . Nullifier ] [ ] rln . ProofMetadata ) ,
log : log ,
}
go result . cleanup ( ctx )
return result
}
var errAlreadyExists = errors . New ( "proof already exists" )
// Insert stores a proof in the nullifier log only if it doesnt exist already
func ( n * NullifierLog ) Insert ( proofMD rln . ProofMetadata ) error {
n . Lock ( )
defer n . Unlock ( )
proofs , ok := n . nullifierLog [ proofMD . ExternalNullifier ]
if ok {
// check if an identical record exists
for _ , p := range proofs {
if p . Equals ( proofMD ) {
// TODO: slashing logic
return errAlreadyExists
}
}
}
n . nullifierLog [ proofMD . ExternalNullifier ] = append ( proofs , proofMD )
n . nullifierQueue = append ( n . nullifierQueue , proofMD . ExternalNullifier )
return nil
}
// HasDuplicate returns true if there is another message in the `nullifierLog` with the same
// epoch and nullifier as `msg`'s epoch and nullifier but different Shamir secret shares
// otherwise, returns false
func ( n * NullifierLog ) HasDuplicate ( proofMD rln . ProofMetadata ) ( bool , error ) {
n . RLock ( )
defer n . RUnlock ( )
proofs , ok := n . nullifierLog [ proofMD . ExternalNullifier ]
if ! ok {
// epoch does not exist
return false , nil
}
for _ , p := range proofs {
if p . Equals ( proofMD ) {
// there is an identical record, ignore the msg
return true , nil
}
}
// check for a message with the same nullifier but different secret shares
matched := false
for _ , it := range proofs {
if bytes . Equal ( it . Nullifier [ : ] , proofMD . Nullifier [ : ] ) && ( ! bytes . Equal ( it . ShareX [ : ] , proofMD . ShareX [ : ] ) || ! bytes . Equal ( it . ShareY [ : ] , proofMD . ShareY [ : ] ) ) {
matched = true
break
}
}
return matched , nil
}
// cleanup cleans up the log every time there are more than MaxEpochGap epochs stored in it
func ( n * NullifierLog ) cleanup ( ctx context . Context ) {
2024-09-25 17:15:20 +08:00
defer utils . LogOnPanic ( )
2023-09-06 16:02:30 -04:00
t := time . NewTicker ( 1 * time . Minute ) // TODO: tune this
defer t . Stop ( )
for {
select {
case <- ctx . Done ( ) :
return
case <- t . C :
func ( ) {
n . Lock ( )
defer n . Unlock ( )
if int64 ( len ( n . nullifierQueue ) ) < maxEpochGap {
return
}
n . log . Debug ( "clearing epochs from the nullifier log" , zap . Int64 ( "count" , maxEpochGap ) )
toDelete := n . nullifierQueue [ 0 : maxEpochGap ]
for _ , l := range toDelete {
delete ( n . nullifierLog , l )
}
n . nullifierQueue = n . nullifierQueue [ maxEpochGap : ]
} ( )
}
}
}