Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: store and retrieve valid merkle roots in RLN db #702

Merged
merged 1 commit into from
Sep 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions waku/v2/protocol/rln/group_manager/dynamic/dynamic.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ func handler(gm *DynamicGroupManager, events []*contracts.RLNMemberRegistered) e
LastProcessedBlock: gm.lastBlockProcessed,
ChainID: gm.web3Config.ChainID,
ContractAddress: gm.web3Config.RegistryContract.Address,
ValidRootsPerBlock: gm.rootTracker.ValidRootsPerBlock(),
})
if err != nil {
// this is not a fatal error, hence we don't raise an exception
Expand Down
33 changes: 30 additions & 3 deletions waku/v2/protocol/rln/group_manager/dynamic/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import (
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/waku-org/go-waku/waku/v2/protocol/rln/group_manager"
)

// RLNMetadata persists attributes in the RLN database
type RLNMetadata struct {
LastProcessedBlock uint64
ChainID *big.Int
ContractAddress common.Address
ValidRootsPerBlock []group_manager.RootsPerBlock
}

// Serialize converts a RLNMetadata into a binary format expected by zerokit's RLN
Expand All @@ -26,24 +28,49 @@ func (r RLNMetadata) Serialize() []byte {
result = binary.LittleEndian.AppendUint64(result, r.LastProcessedBlock)
result = binary.LittleEndian.AppendUint64(result, chainID.Uint64())
result = append(result, r.ContractAddress.Bytes()...)
result = binary.LittleEndian.AppendUint64(result, uint64(len(r.ValidRootsPerBlock)))
for _, v := range r.ValidRootsPerBlock {
result = append(result, v.Root[:]...)
result = binary.LittleEndian.AppendUint64(result, v.BlockNumber)
}

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
const validRootsLenOffset = contractAddressOffset + 20
const validRootsValOffset = validRootsLenOffset + 8
const metadataByteLen = 8 + 8 + 20 + 8 // uint64 + uint64 + ethAddress + uint64

// DeserializeMetadata converts a byte slice into a RLNMetadata instance
func DeserializeMetadata(b []byte) (RLNMetadata, error) {
if len(b) != metadataByteLen {
if len(b) < metadataByteLen {
return RLNMetadata{}, errors.New("wrong size")
}

validRootLen := binary.LittleEndian.Uint64(b[validRootsLenOffset:validRootsValOffset])
if len(b) < int(metadataByteLen+validRootLen*(32+8)) { // len of a merkle node and len of a uint64 for the block number
return RLNMetadata{}, errors.New("wrong size")
}

validRoots := make([]group_manager.RootsPerBlock, 0, validRootLen)
for i := 0; i < int(validRootLen); i++ {
rootOffset := validRootsValOffset + (i * (32 + 8))
blockOffset := rootOffset + 32
root := group_manager.RootsPerBlock{
BlockNumber: binary.LittleEndian.Uint64(b[blockOffset : blockOffset+8]),
}
copy(root.Root[:], b[rootOffset:blockOffset])
validRoots = append(validRoots, root)
}

return RLNMetadata{
LastProcessedBlock: binary.LittleEndian.Uint64(b[lastProcessedBlockOffset:chainIDOffset]),
ChainID: new(big.Int).SetUint64(binary.LittleEndian.Uint64(b[chainIDOffset:contractAddressOffset])),
ContractAddress: common.BytesToAddress(b[contractAddressOffset:]),
ContractAddress: common.BytesToAddress(b[contractAddressOffset:validRootsLenOffset]),
ValidRootsPerBlock: validRoots,
}, nil
}

Expand Down
10 changes: 9 additions & 1 deletion waku/v2/protocol/rln/group_manager/dynamic/metadata_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
"github.com/waku-org/go-waku/waku/v2/protocol/rln/group_manager"
)

func TestMetadata(t *testing.T) {
Expand All @@ -14,6 +15,7 @@ func TestMetadata(t *testing.T) {
LastProcessedBlock: 128,
ChainID: big.NewInt(1155511),
ContractAddress: common.HexToAddress("0x9c09146844c1326c2dbc41c451766c7138f88155"),
ValidRootsPerBlock: []group_manager.RootsPerBlock{{Root: [32]byte{1}, BlockNumber: 100}, {Root: [32]byte{2}, BlockNumber: 200}},
}

serializedMetadata := metadata.Serialize()
Expand All @@ -23,9 +25,15 @@ func TestMetadata(t *testing.T) {
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())
require.Len(t, unserializedMetadata.ValidRootsPerBlock, len(metadata.ValidRootsPerBlock))
require.Equal(t, metadata.ValidRootsPerBlock[0].BlockNumber, unserializedMetadata.ValidRootsPerBlock[0].BlockNumber)
require.Equal(t, metadata.ValidRootsPerBlock[0].Root, unserializedMetadata.ValidRootsPerBlock[0].Root)
require.Equal(t, metadata.ValidRootsPerBlock[1].BlockNumber, unserializedMetadata.ValidRootsPerBlock[1].BlockNumber)
require.Equal(t, metadata.ValidRootsPerBlock[1].Root, unserializedMetadata.ValidRootsPerBlock[1].Root)

// Handle cases where the chainId is not specified (for some reason?)
// Handle cases where the chainId is not specified (for some reason?) or no valid roots were specified
metadata.ChainID = nil
metadata.ValidRootsPerBlock = nil
serializedMetadata = metadata.Serialize()
unserializedMetadata, err = DeserializeMetadata(serializedMetadata)
require.NoError(t, err)
Expand Down
2 changes: 2 additions & 0 deletions waku/v2/protocol/rln/group_manager/dynamic/web3.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ func (gm *DynamicGroupManager) HandleGroupUpdates(ctx context.Context, handler R
gm.log.Info("resuming onchain sync", zap.Uint64("fromBlock", fromBlock))
}

gm.rootTracker.SetValidRootsPerBlock(metadata.ValidRootsPerBlock)

err = gm.loadOldEvents(ctx, fromBlock, handler)
if err != nil {
return err
Expand Down
46 changes: 37 additions & 9 deletions waku/v2/protocol/rln/group_manager/root_tracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ import (
"github.com/waku-org/go-zerokit-rln/rln"
)

// RootsPerBlock stores the merkle root generated at N block number
type RootsPerBlock struct {
root rln.MerkleNode
blockNumber uint64
Root rln.MerkleNode
BlockNumber uint64
}

// MerkleRootTracker keeps track of the latest N merkle roots considered
// valid for RLN proofs.
type MerkleRootTracker struct {
sync.RWMutex

Expand All @@ -23,6 +26,7 @@ type MerkleRootTracker struct {

const maxBufferSize = 20

// NewMerkleRootTracker creates an instance of MerkleRootTracker
func NewMerkleRootTracker(acceptableRootWindowSize int, rlnInstance *rln.RLN) (*MerkleRootTracker, error) {
result := &MerkleRootTracker{
acceptableRootWindowSize: acceptableRootWindowSize,
Expand All @@ -37,13 +41,14 @@ func NewMerkleRootTracker(acceptableRootWindowSize int, rlnInstance *rln.RLN) (*
return result, nil
}

// Backfill is used to pop merkle roots when there is a chain fork
func (m *MerkleRootTracker) Backfill(fromBlockNumber uint64) {
m.Lock()
defer m.Unlock()

numBlocks := 0
for i := len(m.validMerkleRoots) - 1; i >= 0; i-- {
if m.validMerkleRoots[i].blockNumber >= fromBlockNumber {
if m.validMerkleRoots[i].BlockNumber >= fromBlockNumber {
numBlocks++
}
}
Expand Down Expand Up @@ -87,14 +92,16 @@ func (m *MerkleRootTracker) IndexOf(root [32]byte) int {
defer m.RUnlock()

for i := range m.validMerkleRoots {
if bytes.Equal(m.validMerkleRoots[i].root[:], root[:]) {
if bytes.Equal(m.validMerkleRoots[i].Root[:], root[:]) {
return i
}
}

return -1
}

// UpdateLatestRoot should be called when a block containing a new
// IDCommitment is received so we can keep track of the merkle root change
func (m *MerkleRootTracker) UpdateLatestRoot(blockNumber uint64) (rln.MerkleNode, error) {
m.Lock()
defer m.Unlock()
Expand All @@ -111,8 +118,8 @@ func (m *MerkleRootTracker) UpdateLatestRoot(blockNumber uint64) (rln.MerkleNode

func (m *MerkleRootTracker) pushRoot(blockNumber uint64, root [32]byte) {
m.validMerkleRoots = append(m.validMerkleRoots, RootsPerBlock{
root: root,
blockNumber: blockNumber,
Root: root,
BlockNumber: blockNumber,
})

// Maintain valid merkle root window
Expand All @@ -125,29 +132,50 @@ func (m *MerkleRootTracker) pushRoot(blockNumber uint64, root [32]byte) {
if len(m.merkleRootBuffer) > maxBufferSize {
m.merkleRootBuffer = m.merkleRootBuffer[1:]
}

}

// Roots return the list of valid merkle roots
func (m *MerkleRootTracker) Roots() []rln.MerkleNode {
m.RLock()
defer m.RUnlock()

result := make([]rln.MerkleNode, len(m.validMerkleRoots))
for i := range m.validMerkleRoots {
result[i] = m.validMerkleRoots[i].root
result[i] = m.validMerkleRoots[i].Root
}

return result
}

// Buffer is used as a repository of older merkle roots that although
// they were valid once, they have left the acceptable window of
// merkle roots. We keep track of them in case a chain fork occurs
// and we need to restore the valid merkle roots to a previous point
// of time
richard-ramos marked this conversation as resolved.
Show resolved Hide resolved
func (m *MerkleRootTracker) Buffer() []rln.MerkleNode {
m.RLock()
defer m.RUnlock()

result := make([]rln.MerkleNode, len(m.merkleRootBuffer))
for i := range m.merkleRootBuffer {
result[i] = m.merkleRootBuffer[i].root
result[i] = m.merkleRootBuffer[i].Root
}

return result
}

// ValidRootsPerBlock returns the current valid merkle roots and block numbers
func (m *MerkleRootTracker) ValidRootsPerBlock() []RootsPerBlock {
m.RLock()
defer m.RUnlock()

return m.validMerkleRoots
}

// SetValidRootsPerBlock is used to overwrite the valid merkle roots
func (m *MerkleRootTracker) SetValidRootsPerBlock(roots []RootsPerBlock) {
m.Lock()
defer m.Unlock()

m.validMerkleRoots = roots
}
2 changes: 1 addition & 1 deletion waku/v2/protocol/rln/web3/web3.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func NewConfig(ethClientAddress string, registryAddress common.Address) *Config

// BuildConfig returns an instance of Web3Config with all the required elements for interaction with the RLN smart contracts
func BuildConfig(ctx context.Context, ethClientAddress string, registryAddress common.Address) (*Config, error) {
ethClient, err := ethclient.Dial(ethClientAddress)
ethClient, err := ethclient.DialContext(ctx, ethClientAddress)
if err != nil {
return nil, err
}
Expand Down