diff --git a/waku/v2/protocol/rln/group_manager/dynamic/dynamic.go b/waku/v2/protocol/rln/group_manager/dynamic/dynamic.go index cc7cb8de..637d791b 100644 --- a/waku/v2/protocol/rln/group_manager/dynamic/dynamic.go +++ b/waku/v2/protocol/rln/group_manager/dynamic/dynamic.go @@ -11,6 +11,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" + "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" @@ -97,12 +98,14 @@ func handler(gm *DynamicGroupManager, events []*contracts.RLNMemberRegistered) e 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)) + gm.log.Debug("rln metadata persisted", zap.Uint64("lastProcessedBlock", gm.lastBlockProcessed), zap.Uint64("chainID", gm.chainId.Uint64()), logging.HexBytes("contractAddress", gm.membershipContractAddress[:])) } return nil diff --git a/waku/v2/protocol/rln/group_manager/dynamic/handler_test.go b/waku/v2/protocol/rln/group_manager/dynamic/handler_test.go index 4e221dd1..70c985c9 100644 --- a/waku/v2/protocol/rln/group_manager/dynamic/handler_test.go +++ b/waku/v2/protocol/rln/group_manager/dynamic/handler_test.go @@ -43,6 +43,7 @@ func TestHandler(t *testing.T) { log: utils.Logger(), cancel: cancel, wg: sync.WaitGroup{}, + chainId: big.NewInt(1), rootTracker: rootTracker, } diff --git a/waku/v2/protocol/rln/group_manager/dynamic/metadata.go b/waku/v2/protocol/rln/group_manager/dynamic/metadata.go index e709b87d..657017c9 100644 --- a/waku/v2/protocol/rln/group_manager/dynamic/metadata.go +++ b/waku/v2/protocol/rln/group_manager/dynamic/metadata.go @@ -3,27 +3,47 @@ package dynamic import ( "encoding/binary" "errors" + "math/big" + + "github.com/ethereum/go-ethereum/common" ) // RLNMetadata persists attributes in the RLN database type RLNMetadata struct { LastProcessedBlock uint64 + ChainID *big.Int + ContractAddress common.Address } // Serialize converts a RLNMetadata into a binary format expected by zerokit's RLN func (r RLNMetadata) Serialize() []byte { - result := make([]byte, 8) - binary.LittleEndian.PutUint64(result, r.LastProcessedBlock) + chainID := r.ChainID + if chainID == nil { + chainID = big.NewInt(0) + } + + var result []byte + result = binary.LittleEndian.AppendUint64(result, r.LastProcessedBlock) + result = binary.LittleEndian.AppendUint64(result, chainID.Uint64()) + result = append(result, r.ContractAddress.Bytes()...) return result } +const lastProcessedBlockOffset = 0 +const chainIDOffset = lastProcessedBlockOffset + 8 +const contractAddressOffset = chainIDOffset + 8 +const metadataByteLen = 8 + 8 + 20 // 2 uint64 fields and a 20bytes address + // DeserializeMetadata converts a byte slice into a RLNMetadata instance func DeserializeMetadata(b []byte) (RLNMetadata, error) { - if len(b) != 8 { + if len(b) != metadataByteLen { return RLNMetadata{}, errors.New("wrong size") } + return RLNMetadata{ - LastProcessedBlock: binary.LittleEndian.Uint64(b), + LastProcessedBlock: binary.LittleEndian.Uint64(b[lastProcessedBlockOffset:chainIDOffset]), + ChainID: new(big.Int).SetUint64(binary.LittleEndian.Uint64(b[chainIDOffset:contractAddressOffset])), + ContractAddress: common.BytesToAddress(b[contractAddressOffset:]), }, nil } diff --git a/waku/v2/protocol/rln/group_manager/dynamic/metadata_test.go b/waku/v2/protocol/rln/group_manager/dynamic/metadata_test.go new file mode 100644 index 00000000..2cc13fd0 --- /dev/null +++ b/waku/v2/protocol/rln/group_manager/dynamic/metadata_test.go @@ -0,0 +1,33 @@ +package dynamic + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func TestMetadata(t *testing.T) { + + metadata := &RLNMetadata{ + LastProcessedBlock: 128, + ChainID: big.NewInt(1155511), + ContractAddress: common.HexToAddress("0x9c09146844c1326c2dbc41c451766c7138f88155"), + } + + serializedMetadata := metadata.Serialize() + + unserializedMetadata, err := DeserializeMetadata(serializedMetadata) + require.NoError(t, err) + require.Equal(t, metadata.ChainID.Uint64(), unserializedMetadata.ChainID.Uint64()) + require.Equal(t, metadata.LastProcessedBlock, unserializedMetadata.LastProcessedBlock) + require.Equal(t, metadata.ContractAddress.Hex(), unserializedMetadata.ContractAddress.Hex()) + + // Handle cases where the chainId is not specified (for some reason?) + metadata.ChainID = nil + serializedMetadata = metadata.Serialize() + unserializedMetadata, err = DeserializeMetadata(serializedMetadata) + require.NoError(t, err) + require.Equal(t, uint64(0), unserializedMetadata.ChainID.Uint64()) +} diff --git a/waku/v2/protocol/rln/group_manager/dynamic/web3.go b/waku/v2/protocol/rln/group_manager/dynamic/web3.go index e8e15f47..2c44b435 100644 --- a/waku/v2/protocol/rln/group_manager/dynamic/web3.go +++ b/waku/v2/protocol/rln/group_manager/dynamic/web3.go @@ -1,6 +1,7 @@ package dynamic import ( + "bytes" "context" "errors" "time" @@ -20,7 +21,24 @@ type RegistrationEventHandler = func(*DynamicGroupManager, []*contracts.RLNMembe // It connects to the eth client, subscribes to the `MemberRegistered` event emitted from the `MembershipContract` // and collects all the events, for every received event, it calls the `handler` func (gm *DynamicGroupManager) HandleGroupUpdates(ctx context.Context, handler RegistrationEventHandler) error { - err := gm.loadOldEvents(ctx, gm.rlnContract, handler) + fromBlock := uint64(0) + metadata, err := gm.GetMetadata() + if err != nil { + gm.log.Warn("could not load last processed block from metadata. Starting onchain sync from scratch", zap.Error(err)) + } else { + if gm.chainId.Uint64() != metadata.ChainID.Uint64() { + return errors.New("persisted data: chain id mismatch") + } + + if !bytes.Equal(gm.membershipContractAddress[:], metadata.ContractAddress[:]) { + return errors.New("persisted data: contract address mismatch") + } + + fromBlock = metadata.LastProcessedBlock + gm.log.Info("resuming onchain sync", zap.Uint64("fromBlock", fromBlock)) + } + + err = gm.loadOldEvents(ctx, gm.rlnContract, fromBlock, handler) if err != nil { return err } @@ -32,16 +50,7 @@ func (gm *DynamicGroupManager) HandleGroupUpdates(ctx context.Context, handler R return <-errCh } -func (gm *DynamicGroupManager) loadOldEvents(ctx context.Context, rlnContract *contracts.RLN, handler RegistrationEventHandler) error { - fromBlock := uint64(0) - metadata, err := gm.GetMetadata() - if err == nil { - fromBlock = metadata.LastProcessedBlock - gm.log.Info("resuming onchain sync", zap.Uint64("fromBlock", fromBlock)) - } else { - gm.log.Warn("could not load last processed block from metadata. Starting onchain sync from scratch", zap.Error(err)) - } - +func (gm *DynamicGroupManager) loadOldEvents(ctx context.Context, rlnContract *contracts.RLN, fromBlock uint64, handler RegistrationEventHandler) error { events, err := gm.getEvents(ctx, fromBlock, nil) if err != nil { return err