mirror of https://github.com/status-im/go-waku.git
121 lines
2.9 KiB
Go
121 lines
2.9 KiB
Go
package rln
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"sync"
|
|
"time"
|
|
|
|
"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) {
|
|
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:]
|
|
}()
|
|
}
|
|
}
|
|
|
|
}
|