mirror of
https://github.com/status-im/go-waku.git
synced 2025-01-29 23:15:40 +00:00
290 lines
7.4 KiB
Go
290 lines
7.4 KiB
Go
package dynamic
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"math/big"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/core/types"
|
|
"github.com/ethereum/go-ethereum/ethclient"
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/waku-org/go-waku/logging"
|
|
"github.com/waku-org/go-waku/waku/v2/protocol/rln/contracts"
|
|
"github.com/waku-org/go-waku/waku/v2/protocol/rln/group_manager"
|
|
"github.com/waku-org/go-waku/waku/v2/protocol/rln/keystore"
|
|
"github.com/waku-org/go-zerokit-rln/rln"
|
|
om "github.com/wk8/go-ordered-map"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
var RLNAppInfo = keystore.AppInfo{
|
|
Application: "waku-rln-relay",
|
|
AppIdentifier: "01234567890abcdef",
|
|
Version: "0.2",
|
|
}
|
|
|
|
type DynamicGroupManager struct {
|
|
rln *rln.RLN
|
|
log *zap.Logger
|
|
metrics Metrics
|
|
|
|
cancel context.CancelFunc
|
|
wg sync.WaitGroup
|
|
|
|
identityCredential *rln.IdentityCredential
|
|
membershipIndex rln.MembershipIndex
|
|
|
|
membershipContractAddress common.Address
|
|
ethClientAddress string
|
|
ethClient *ethclient.Client
|
|
|
|
lastBlockProcessed uint64
|
|
|
|
eventHandler RegistrationEventHandler
|
|
|
|
chainId *big.Int
|
|
rlnContract *contracts.RLN
|
|
|
|
appKeystore *keystore.AppKeystore
|
|
keystorePassword string
|
|
|
|
rootTracker *group_manager.MerkleRootTracker
|
|
}
|
|
|
|
func handler(gm *DynamicGroupManager, events []*contracts.RLNMemberRegistered) error {
|
|
toRemoveTable := om.New()
|
|
toInsertTable := om.New()
|
|
|
|
lastBlockProcessed := gm.lastBlockProcessed
|
|
for _, event := range events {
|
|
if event.Raw.Removed {
|
|
var indexes []uint
|
|
i_idx, ok := toRemoveTable.Get(event.Raw.BlockNumber)
|
|
if ok {
|
|
indexes = i_idx.([]uint)
|
|
}
|
|
indexes = append(indexes, uint(event.Index.Uint64()))
|
|
toRemoveTable.Set(event.Raw.BlockNumber, indexes)
|
|
} else {
|
|
var eventsPerBlock []*contracts.RLNMemberRegistered
|
|
i_evt, ok := toInsertTable.Get(event.Raw.BlockNumber)
|
|
if ok {
|
|
eventsPerBlock = i_evt.([]*contracts.RLNMemberRegistered)
|
|
}
|
|
eventsPerBlock = append(eventsPerBlock, event)
|
|
toInsertTable.Set(event.Raw.BlockNumber, eventsPerBlock)
|
|
|
|
if event.Raw.BlockNumber > lastBlockProcessed {
|
|
lastBlockProcessed = event.Raw.BlockNumber
|
|
}
|
|
}
|
|
}
|
|
|
|
err := gm.RemoveMembers(toRemoveTable)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = gm.InsertMembers(toInsertTable)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
gm.metrics.RecordRegisteredMembership(toInsertTable.Len() - toRemoveTable.Len())
|
|
|
|
gm.lastBlockProcessed = lastBlockProcessed
|
|
err = gm.SetMetadata(RLNMetadata{
|
|
LastProcessedBlock: gm.lastBlockProcessed,
|
|
ChainID: gm.chainId,
|
|
ContractAddress: gm.membershipContractAddress,
|
|
})
|
|
if err != nil {
|
|
// this is not a fatal error, hence we don't raise an exception
|
|
gm.log.Warn("failed to persist rln metadata", zap.Error(err))
|
|
} else {
|
|
gm.log.Debug("rln metadata persisted", zap.Uint64("lastProcessedBlock", gm.lastBlockProcessed), zap.Uint64("chainID", gm.chainId.Uint64()), logging.HexBytes("contractAddress", gm.membershipContractAddress[:]))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type RegistrationHandler = func(tx *types.Transaction)
|
|
|
|
func NewDynamicGroupManager(
|
|
ethClientAddr string,
|
|
memContractAddr common.Address,
|
|
membershipIndex uint,
|
|
appKeystore *keystore.AppKeystore,
|
|
keystorePassword string,
|
|
reg prometheus.Registerer,
|
|
log *zap.Logger,
|
|
) (*DynamicGroupManager, error) {
|
|
log = log.Named("rln-dynamic")
|
|
|
|
return &DynamicGroupManager{
|
|
membershipIndex: membershipIndex,
|
|
membershipContractAddress: memContractAddr,
|
|
ethClientAddress: ethClientAddr,
|
|
eventHandler: handler,
|
|
appKeystore: appKeystore,
|
|
keystorePassword: keystorePassword,
|
|
log: log,
|
|
metrics: newMetrics(reg),
|
|
}, nil
|
|
}
|
|
|
|
func (gm *DynamicGroupManager) getMembershipFee(ctx context.Context) (*big.Int, error) {
|
|
return gm.rlnContract.MEMBERSHIPDEPOSIT(&bind.CallOpts{Context: ctx})
|
|
}
|
|
|
|
func (gm *DynamicGroupManager) Start(ctx context.Context, rlnInstance *rln.RLN, rootTracker *group_manager.MerkleRootTracker) error {
|
|
if gm.cancel != nil {
|
|
return errors.New("already started")
|
|
}
|
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
gm.cancel = cancel
|
|
|
|
gm.log.Info("mounting rln-relay in on-chain/dynamic mode")
|
|
|
|
backend, err := ethclient.Dial(gm.ethClientAddress)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
gm.ethClient = backend
|
|
|
|
gm.rln = rlnInstance
|
|
gm.rootTracker = rootTracker
|
|
|
|
gm.chainId, err = backend.ChainID(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
gm.rlnContract, err = contracts.NewRLN(gm.membershipContractAddress, backend)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// check if the contract exists by calling a static function
|
|
_, err = gm.getMembershipFee(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = gm.loadCredential()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = gm.HandleGroupUpdates(ctx, gm.eventHandler); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (gm *DynamicGroupManager) loadCredential() error {
|
|
start := time.Now()
|
|
|
|
credentials, err := gm.appKeystore.GetMembershipCredentials(
|
|
gm.keystorePassword,
|
|
gm.membershipIndex,
|
|
keystore.NewMembershipContractInfo(gm.chainId, gm.membershipContractAddress))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
gm.metrics.RecordMembershipCredentialsImportDuration(time.Since(start))
|
|
|
|
if credentials == nil {
|
|
return errors.New("no credentials available")
|
|
}
|
|
|
|
gm.identityCredential = credentials.IdentityCredential
|
|
|
|
return nil
|
|
}
|
|
|
|
func (gm *DynamicGroupManager) InsertMembers(toInsert *om.OrderedMap) error {
|
|
for pair := toInsert.Oldest(); pair != nil; pair = pair.Next() {
|
|
events := pair.Value.([]*contracts.RLNMemberRegistered) // TODO: should these be sortered by index? we assume all members arrive in order
|
|
var idCommitments []rln.IDCommitment
|
|
var oldestIndexInBlock *big.Int
|
|
for _, evt := range events {
|
|
if oldestIndexInBlock == nil {
|
|
oldestIndexInBlock = evt.Index
|
|
}
|
|
idCommitments = append(idCommitments, rln.BigIntToBytes32(evt.IdCommitment))
|
|
}
|
|
|
|
if len(idCommitments) == 0 {
|
|
continue
|
|
}
|
|
|
|
// TODO: should we track indexes to identify missing?
|
|
startIndex := rln.MembershipIndex(uint(oldestIndexInBlock.Int64()))
|
|
start := time.Now()
|
|
err := gm.rln.InsertMembers(startIndex, idCommitments)
|
|
if err != nil {
|
|
gm.log.Error("inserting members into merkletree", zap.Error(err))
|
|
return err
|
|
}
|
|
gm.metrics.RecordMembershipInsertionDuration(time.Since(start))
|
|
|
|
_, err = gm.rootTracker.UpdateLatestRoot(pair.Key.(uint64))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (gm *DynamicGroupManager) RemoveMembers(toRemove *om.OrderedMap) error {
|
|
for pair := toRemove.Newest(); pair != nil; pair = pair.Prev() {
|
|
memberIndexes := pair.Value.([]uint)
|
|
err := gm.rln.DeleteMembers(memberIndexes)
|
|
if err != nil {
|
|
gm.log.Error("deleting members", zap.Error(err))
|
|
return err
|
|
}
|
|
gm.rootTracker.Backfill(pair.Key.(uint64))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (gm *DynamicGroupManager) IdentityCredentials() (rln.IdentityCredential, error) {
|
|
if gm.identityCredential == nil {
|
|
return rln.IdentityCredential{}, errors.New("identity credential has not been setup")
|
|
}
|
|
|
|
return *gm.identityCredential, nil
|
|
}
|
|
|
|
func (gm *DynamicGroupManager) MembershipIndex() rln.MembershipIndex {
|
|
return gm.membershipIndex
|
|
}
|
|
|
|
// Stop stops all go-routines, eth client and closes the rln database
|
|
func (gm *DynamicGroupManager) Stop() error {
|
|
if gm.cancel == nil {
|
|
return nil
|
|
}
|
|
|
|
gm.cancel()
|
|
|
|
err := gm.rln.Flush()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
gm.ethClient.Close()
|
|
|
|
gm.wg.Wait()
|
|
|
|
return nil
|
|
}
|