341 lines
9.1 KiB
Go
Raw Normal View History

2023-04-04 17:02:12 -04:00
package dynamic
import (
"context"
"crypto/ecdsa"
"errors"
"fmt"
2023-04-05 15:44:46 -04:00
"math/big"
2023-04-04 17:02:12 -04:00
"sync"
2023-04-05 15:44:46 -04:00
"github.com/ethereum/go-ethereum/accounts/abi/bind"
2023-04-04 17:02:12 -04:00
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
2023-04-05 15:44:46 -04:00
"github.com/waku-org/go-waku/waku/v2/protocol/rln/contracts"
2023-04-04 17:02:12 -04:00
"github.com/waku-org/go-waku/waku/v2/protocol/rln/group_manager"
2023-04-05 15:44:46 -04:00
"github.com/waku-org/go-waku/waku/v2/protocol/rln/keystore"
2023-04-04 17:02:12 -04:00
"github.com/waku-org/go-zerokit-rln/rln"
2023-04-12 17:53:23 -04:00
om "github.com/wk8/go-ordered-map"
2023-04-04 17:02:12 -04:00
"go.uber.org/zap"
)
2023-04-05 15:44:46 -04:00
var RLNAppInfo = keystore.AppInfo{
Application: "go-waku-rln-relay",
AppIdentifier: "01234567890abcdef",
Version: "0.1",
}
2023-04-04 17:02:12 -04:00
type DynamicGroupManager struct {
rln *rln.RLN
log *zap.Logger
cancel context.CancelFunc
wg sync.WaitGroup
2023-04-05 15:44:46 -04:00
identityCredential *rln.IdentityCredential
membershipIndex *rln.MembershipIndex
2023-04-04 17:02:12 -04:00
membershipContractAddress common.Address
membershipGroupIndex uint
2023-04-04 17:02:12 -04:00
ethClientAddress string
ethClient *ethclient.Client
// ethAccountPrivateKey is required for signing transactions
// TODO may need to erase this ethAccountPrivateKey when is not used
// TODO may need to make ethAccountPrivateKey mandatory
ethAccountPrivateKey *ecdsa.PrivateKey
2023-04-26 11:55:03 -04:00
eventHandler RegistrationEventHandler
2023-04-04 17:02:12 -04:00
registrationHandler RegistrationHandler
2023-04-05 15:44:46 -04:00
chainId *big.Int
rlnContract *contracts.RLN
membershipFee *big.Int
2023-04-04 17:02:12 -04:00
2023-04-05 15:44:46 -04:00
saveKeystore bool
keystorePath string
keystorePassword string
keystoreIndex int
2023-04-05 15:44:46 -04:00
2023-04-04 17:02:12 -04:00
rootTracker *group_manager.MerkleRootTracker
}
2023-04-12 17:53:23 -04:00
func handler(gm *DynamicGroupManager, events []*contracts.RLNMemberRegistered) error {
toRemoveTable := om.New()
toInsertTable := om.New()
for _, event := range events {
if event.Raw.Removed {
var indexes []uint64
i_idx, ok := toRemoveTable.Get(event.Raw.BlockNumber)
if ok {
indexes = i_idx.([]uint64)
}
indexes = append(indexes, 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)
}
}
err := gm.RemoveMembers(toRemoveTable)
if err != nil {
return err
}
err = gm.InsertMembers(toInsertTable)
if err != nil {
return err
}
return nil
}
2023-04-04 17:02:12 -04:00
type RegistrationHandler = func(tx *types.Transaction)
func NewDynamicGroupManager(
ethClientAddr string,
ethAccountPrivateKey *ecdsa.PrivateKey,
memContractAddr common.Address,
membershipGroupIndex uint,
2023-04-05 15:44:46 -04:00
keystorePath string,
keystorePassword string,
keystoreIndex int,
2023-04-05 15:44:46 -04:00
saveKeystore bool,
2023-04-04 17:02:12 -04:00
registrationHandler RegistrationHandler,
log *zap.Logger,
) (*DynamicGroupManager, error) {
log = log.Named("rln-dynamic")
path := keystorePath
if path == "" {
log.Warn("keystore: no credentials path set, using default path", zap.String("path", keystore.RLN_CREDENTIALS_FILENAME))
path = keystore.RLN_CREDENTIALS_FILENAME
}
password := keystorePassword
if password == "" {
log.Warn("keystore: no credentials password set, using default password", zap.String("password", keystore.RLN_CREDENTIALS_PASSWORD))
password = keystore.RLN_CREDENTIALS_PASSWORD
}
2023-04-04 17:02:12 -04:00
return &DynamicGroupManager{
membershipGroupIndex: membershipGroupIndex,
2023-04-04 17:02:12 -04:00
membershipContractAddress: memContractAddr,
ethClientAddress: ethClientAddr,
ethAccountPrivateKey: ethAccountPrivateKey,
registrationHandler: registrationHandler,
2023-04-26 11:55:03 -04:00
eventHandler: handler,
2023-04-05 15:44:46 -04:00
saveKeystore: saveKeystore,
keystorePath: path,
keystorePassword: password,
keystoreIndex: keystoreIndex,
log: log,
2023-04-04 17:02:12 -04:00
}, nil
}
2023-04-05 15:44:46 -04:00
func (gm *DynamicGroupManager) getMembershipFee(ctx context.Context) (*big.Int, error) {
auth, err := bind.NewKeyedTransactorWithChainID(gm.ethAccountPrivateKey, gm.chainId)
if err != nil {
return nil, err
}
auth.Context = ctx
return gm.rlnContract.MEMBERSHIPDEPOSIT(&bind.CallOpts{Context: ctx})
}
2023-04-04 17:02:12 -04:00
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")
2023-04-05 15:44:46 -04:00
backend, err := ethclient.Dial(gm.ethClientAddress)
if err != nil {
return err
}
gm.ethClient = backend
2023-04-04 17:02:12 -04:00
gm.rln = rlnInstance
gm.rootTracker = rootTracker
2023-04-05 15:44:46 -04:00
gm.chainId, err = backend.ChainID(ctx)
2023-04-04 17:02:12 -04:00
if err != nil {
return err
}
2023-04-05 15:44:46 -04:00
gm.rlnContract, err = contracts.NewRLN(gm.membershipContractAddress, backend)
if err != nil {
return err
}
// check if the contract exists by calling a static function
gm.membershipFee, err = gm.getMembershipFee(ctx)
if err != nil {
return err
}
2023-04-26 11:55:03 -04:00
if gm.identityCredential == nil && gm.keystorePassword != "" && gm.keystorePath != "" {
2023-04-05 15:44:46 -04:00
credentials, err := keystore.GetMembershipCredentials(gm.log,
gm.keystorePath,
gm.keystorePassword,
RLNAppInfo,
nil,
[]keystore.MembershipContract{{
ChainId: gm.chainId.String(),
Address: gm.membershipContractAddress.Hex(),
}})
if err != nil {
return err
}
if len(credentials) >= gm.keystoreIndex+1 {
gm.identityCredential = &credentials[gm.keystoreIndex].IdentityCredential
gm.membershipIndex = &credentials[gm.keystoreIndex].MembershipGroups[gm.membershipGroupIndex].TreeIndex
}
}
if gm.identityCredential == nil && gm.ethAccountPrivateKey == nil {
return errors.New("either a credentials path or a private key must be specified")
2023-04-05 15:44:46 -04:00
}
2023-04-04 17:02:12 -04:00
// prepare rln membership key pair
if gm.identityCredential == nil && gm.ethAccountPrivateKey != nil {
gm.log.Info("no rln-relay key is provided, generating one")
2023-04-04 17:02:12 -04:00
identityCredential, err := rlnInstance.MembershipKeyGen()
if err != nil {
return err
}
gm.identityCredential = identityCredential
// register the rln-relay peer to the membership contract
2023-04-05 15:44:46 -04:00
gm.membershipIndex, err = gm.Register(ctx)
2023-04-04 17:02:12 -04:00
if err != nil {
return err
}
2023-04-05 15:44:46 -04:00
err = gm.persistCredentials()
if err != nil {
return err
}
2023-04-04 17:02:12 -04:00
gm.log.Info("registered peer into the membership contract")
}
2023-04-05 15:44:46 -04:00
if gm.identityCredential == nil || gm.membershipIndex == nil {
return errors.New("no credentials available")
}
2023-04-26 11:55:03 -04:00
if err = gm.HandleGroupUpdates(ctx, gm.eventHandler); err != nil {
2023-04-04 17:02:12 -04:00
return err
}
return nil
}
2023-04-05 15:44:46 -04:00
func (gm *DynamicGroupManager) persistCredentials() error {
if !gm.saveKeystore {
return nil
}
if gm.identityCredential == nil || gm.membershipIndex == nil {
return errors.New("no credentials to persist")
}
keystoreCred := keystore.MembershipCredentials{
IdentityCredential: *gm.identityCredential,
MembershipGroups: []keystore.MembershipGroup{{
TreeIndex: *gm.membershipIndex,
MembershipContract: keystore.MembershipContract{
ChainId: gm.chainId.String(),
Address: gm.membershipContractAddress.String(),
},
}},
}
err := keystore.AddMembershipCredentials(gm.keystorePath, []keystore.MembershipCredentials{keystoreCred}, gm.keystorePassword, RLNAppInfo, keystore.DefaultSeparator)
2023-04-05 15:44:46 -04:00
if err != nil {
return fmt.Errorf("failed to persist credentials: %w", err)
2023-04-05 15:44:46 -04:00
}
return nil
}
2023-04-12 17:53:23 -04:00
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
for _, evt := range events {
pubkey := rln.Bytes32(evt.Pubkey.Bytes())
// TODO: should we track indexes to identify missing?
err := gm.rln.InsertMember(pubkey)
if err != nil {
gm.log.Error("inserting member into merkletree", zap.Error(err))
return err
}
}
_, err := gm.rootTracker.UpdateLatestRoot(pair.Key.(uint64))
if err != nil {
return err
}
2023-04-04 17:02:12 -04:00
}
2023-04-12 17:53:23 -04:00
return nil
}
2023-04-04 17:02:12 -04:00
2023-04-12 17:53:23 -04:00
func (gm *DynamicGroupManager) RemoveMembers(toRemove *om.OrderedMap) error {
for pair := toRemove.Newest(); pair != nil; pair = pair.Prev() {
memberIndexes := pair.Value.([]uint64)
for _, index := range memberIndexes {
err := gm.rln.DeleteMember(uint(index))
if err != nil {
gm.log.Error("deleting member", zap.Error(err))
return err
}
}
gm.rootTracker.Backfill(pair.Key.(uint64))
2023-04-04 17:02:12 -04:00
}
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
}
2023-04-26 11:55:03 -04:00
func (gm *DynamicGroupManager) SetCredentials(identityCredential *rln.IdentityCredential, index *rln.MembershipIndex) {
gm.identityCredential = identityCredential
gm.membershipIndex = index
}
2023-04-04 17:02:12 -04:00
func (gm *DynamicGroupManager) MembershipIndex() (rln.MembershipIndex, error) {
if gm.membershipIndex == nil {
return 0, errors.New("membership index has not been setup")
}
return *gm.membershipIndex, nil
}
func (gm *DynamicGroupManager) Stop() {
if gm.cancel == nil {
return
}
gm.cancel()
gm.wg.Wait()
}