2023-01-16 12:56:18 +00:00
when ( NimMajor , NimMinor ) < ( 1 , 4 ) :
{. push raises : [ Defect ] . }
else :
{. push raises : [ ] . }
import
2023-02-13 10:43:49 +00:00
std / [ options , osproc , streams , strutils , sequtils ] ,
2023-01-16 12:56:18 +00:00
stew / results ,
2023-02-13 10:43:49 +00:00
stew / shims / net as stewNet ,
testutils / unittests ,
chronos ,
chronicles ,
stint ,
web3 ,
json ,
libp2p / crypto / crypto ,
eth / keys
import
2023-01-16 12:56:18 +00:00
.. / .. / waku / v2 / protocol / waku_rln_relay / protocol_types ,
.. / .. / waku / v2 / protocol / waku_rln_relay / constants ,
.. / .. / waku / v2 / protocol / waku_rln_relay / contract ,
.. / .. / waku / v2 / protocol / waku_rln_relay / rln ,
.. / .. / waku / v2 / protocol / waku_rln_relay / conversion_utils ,
2023-02-13 10:43:49 +00:00
.. / .. / waku / v2 / protocol / waku_rln_relay / group_manager / on_chain / group_manager ,
. / testlib / common ,
2023-01-16 12:56:18 +00:00
. / test_utils
from posix import kill , SIGINT
proc generateCredentials ( rlnInstance : ptr RLN ) : IdentityCredential =
let credRes = membershipKeyGen ( rlnInstance )
return credRes . get ( )
proc generateCredentials ( rlnInstance : ptr RLN , n : int ) : seq [ IdentityCredential ] =
var credentials : seq [ IdentityCredential ]
for i in 0 .. < n :
credentials . add ( generateCredentials ( rlnInstance ) )
return credentials
# a util function used for testing purposes
# it deploys membership contract on Ganache (or any Eth client available on EthClient address)
# must be edited if used for a different contract than membership contract
proc uploadRLNContract * ( ethClientAddress : string ) : Future [ Address ] {. async . } =
let web3 = await newWeb3 ( ethClientAddress )
debug " web3 connected to " , ethClientAddress
# fetch the list of registered accounts
let accounts = await web3 . provider . eth_accounts ( )
web3 . defaultAccount = accounts [ 1 ]
let add = web3 . defaultAccount
debug " contract deployer account address " , add
let balance = await web3 . provider . eth_getBalance ( web3 . defaultAccount , " latest " )
debug " Initial account balance: " , balance
# deploy the poseidon hash contract and gets its address
let
hasherReceipt = await web3 . deployContract ( PoseidonHasherCode )
hasherAddress = hasherReceipt . contractAddress . get
debug " hasher address: " , hasherAddress
# encode membership contract inputs to 32 bytes zero-padded
let
membershipFeeEncoded = encode ( MembershipFee ) . data
depthEncoded = encode ( MerkleTreeDepth . u256 ) . data
hasherAddressEncoded = encode ( hasherAddress ) . data
# this is the contract constructor input
contractInput = membershipFeeEncoded & depthEncoded & hasherAddressEncoded
debug " encoded membership fee: " , membershipFeeEncoded
debug " encoded depth: " , depthEncoded
debug " encoded hasher address: " , hasherAddressEncoded
debug " encoded contract input: " , contractInput
# deploy membership contract with its constructor inputs
let receipt = await web3 . deployContract ( MembershipContractCode ,
contractInput = contractInput )
let contractAddress = receipt . contractAddress . get
debug " Address of the deployed membership contract: " , contractAddress
let newBalance = await web3 . provider . eth_getBalance ( web3 . defaultAccount , " latest " )
debug " Account balance after the contract deployment: " , newBalance
await web3 . close ( )
debug " disconnected from " , ethClientAddress
return contractAddress
proc createEthAccount ( ) : Future [ ( keys . PrivateKey , Address ) ] {. async . } =
let web3 = await newWeb3 ( EthClient )
let accounts = await web3 . provider . eth_accounts ( )
let gasPrice = int ( await web3 . provider . eth_gasPrice ( ) )
web3 . defaultAccount = accounts [ 0 ]
2023-02-13 10:43:49 +00:00
let pk = keys . PrivateKey . random ( rng [ ] )
2023-01-16 12:56:18 +00:00
let acc = Address ( toCanonicalAddress ( pk . toPublicKey ( ) ) )
var tx : EthSend
tx . source = accounts [ 0 ]
tx . value = some ( ethToWei ( 10 . u256 ) )
tx . to = some ( acc )
tx . gasPrice = some ( gasPrice )
# Send 10 eth to acc
discard await web3 . send ( tx )
let balance = await web3 . provider . eth_getBalance ( acc , " latest " )
assert ( balance = = ethToWei ( 10 . u256 ) )
return ( pk , acc )
# Runs Ganache daemon
proc runGanache ( ) : Process =
# We run directly "node node_modules/ganache/dist/node/cli.js" rather than using "npx ganache", so that the daemon does not spawn in a new child process.
# In this way, we can directly send a SIGINT signal to the corresponding PID to gracefully terminate Ganache without dealing with multiple processes.
# Passed options are
# --port Port to listen on.
# --miner.blockGasLimit Sets the block gas limit in WEI.
# --wallet.defaultBalance The default account balance, specified in ether.
# See ganache documentation https://www.npmjs.com/package/ganache for more details
try :
let runGanache = startProcess ( " npx " , args = [ " --yes " , " ganache " , " --port " , " 8540 " , " --miner.blockGasLimit " , " 300000000000000 " , " --wallet.defaultBalance " , " 10000 " ] , options = { poUsePath } )
let ganachePID = runGanache . processID
# We read stdout from Ganache to see when daemon is ready
var ganacheStartLog : string
var cmdline : string
while true :
try :
if runGanache . outputstream . readLine ( cmdline ) :
ganacheStartLog . add ( cmdline )
if cmdline . contains ( " Listening on 127.0.0.1:8540 " ) :
break
except :
break
debug " Ganache daemon is running and ready " , pid = ganachePID , startLog = ganacheStartLog
return runGanache
except :
error " Ganache daemon run failed "
# Stops Ganache daemon
proc stopGanache ( runGanache : Process ) {. used . } =
let ganachePID = runGanache . processID
# We gracefully terminate Ganache daemon by sending a SIGINT signal to the runGanache PID to trigger RPC server termination and clean-up
let returnCodeSIGINT = kill ( ganachePID . int32 , SIGINT )
debug " Sent SIGINT to Ganache " , ganachePID = ganachePID , returnCode = returnCodeSIGINT
# We wait the daemon to exit
try :
let returnCodeExit = runGanache . waitForExit ( )
debug " Ganache daemon terminated " , returnCode = returnCodeExit
debug " Ganache daemon run log " , log = runGanache . outputstream . readAll ( )
except :
error " Ganache daemon termination failed "
proc setup ( ) : Future [ OnchainGroupManager ] {. async . } =
let rlnInstanceRes = createRlnInstance ( )
require :
rlnInstanceRes . isOk ( )
let rlnInstance = rlnInstanceRes . get ( )
let contractAddress = await uploadRLNContract ( EthClient )
# connect to the eth client
let web3 = await newWeb3 ( EthClient )
let accounts = await web3 . provider . eth_accounts ( )
web3 . defaultAccount = accounts [ 1 ]
let ( pk , _ ) = await createEthAccount ( )
let onchainConfig = OnchainGroupManagerConfig ( ethClientUrl : EthClient ,
ethContractAddress : $ contractAddress ,
ethPrivateKey : some ( $ pk ) )
let manager {. used . } = OnchainGroupManager ( config : onchainConfig ,
rlnInstance : rlnInstance )
return manager
suite " Onchain group manager " :
# We run Ganache
let runGanache {. used . } = runGanache ( )
asyncTest " should initialize successfully " :
let manager = await setup ( )
await manager . init ( )
check :
manager . config . ethRpc . isSome ( )
manager . config . rlnContract . isSome ( )
manager . config . membershipFee . isSome ( )
manager . initialized
asyncTest " startGroupSync: should start group sync " :
let manager = await setup ( )
await manager . init ( )
await manager . startGroupSync ( )
asyncTest " startGroupSync: should guard against uninitialized state " :
let manager = await setup ( )
expect ( ValueError ) :
await manager . startGroupSync ( )
asyncTest " startGroupSync: should sync to the state of the group " :
let manager = await setup ( )
let credentials = generateCredentials ( manager . rlnInstance )
manager . idCredentials = some ( credentials )
await manager . init ( )
let merkleRootBeforeRes = manager . rlnInstance . getMerkleRoot ( )
require :
merkleRootBeforeRes . isOk ( )
let merkleRootBefore = merkleRootBeforeRes . get ( )
let future = newFuture [ void ] ( " startGroupSync " )
proc generateCallback ( fut : Future [ void ] , idCommitment : IDCommitment ) : OnRegisterCallback =
proc callback ( registrations : seq [ Membership ] ) : Future [ void ] {. async . } =
require :
registrations . len = = 1
registrations [ 0 ] . idCommitment = = idCommitment
registrations [ 0 ] . index = = 0
fut . complete ( )
return callback
manager . onRegister ( generateCallback ( future , credentials . idCommitment ) )
await manager . startGroupSync ( )
await future
let merkleRootAfterRes = manager . rlnInstance . getMerkleRoot ( )
require :
merkleRootAfterRes . isOk ( )
let merkleRootAfter = merkleRootAfterRes . get ( )
check :
merkleRootBefore ! = merkleRootAfter
asyncTest " startGroupSync: should fetch history correctly " :
let manager = await setup ( )
let credentials = generateCredentials ( manager . rlnInstance , 5 )
await manager . init ( )
let merkleRootBeforeRes = manager . rlnInstance . getMerkleRoot ( )
require :
merkleRootBeforeRes . isOk ( )
let merkleRootBefore = merkleRootBeforeRes . get ( )
var futures = [ newFuture [ void ] ( ) , newFuture [ void ] ( ) , newFuture [ void ] ( ) , newFuture [ void ] ( ) , newFuture [ void ] ( ) ]
proc generateCallback ( futs : array [ 0 .. 4 , Future [ system . void ] ] , credentials : seq [ IdentityCredential ] ) : OnRegisterCallback =
var futureIndex = 0
proc callback ( registrations : seq [ Membership ] ) : Future [ void ] {. async . } =
require :
registrations . len = = 1
registrations [ 0 ] . idCommitment = = credentials [ futureIndex ] . idCommitment
registrations [ 0 ] . index = = MembershipIndex ( futureIndex )
futs [ futureIndex ] . complete ( )
futureIndex + = 1
return callback
manager . onRegister ( generateCallback ( futures , credentials ) )
await manager . startGroupSync ( )
for i in 0 .. < credentials . len ( ) :
await manager . register ( credentials [ i ] )
await allFutures ( futures )
let merkleRootAfterRes = manager . rlnInstance . getMerkleRoot ( )
require :
merkleRootAfterRes . isOk ( )
let merkleRootAfter = merkleRootAfterRes . get ( )
check :
merkleRootBefore ! = merkleRootAfter
asyncTest " register: should guard against uninitialized state " :
let manager = await setup ( )
let dummyCommitment = default ( IDCommitment )
expect ( ValueError ) :
await manager . register ( dummyCommitment )
asyncTest " register: should register successfully " :
let manager = await setup ( )
await manager . init ( )
await manager . startGroupSync ( )
let idCommitment = generateCredentials ( manager . rlnInstance ) . idCommitment
let merkleRootBeforeRes = manager . rlnInstance . getMerkleRoot ( )
require :
merkleRootBeforeRes . isOk ( )
let merkleRootBefore = merkleRootBeforeRes . get ( )
await manager . register ( idCommitment )
let merkleRootAfterRes = manager . rlnInstance . getMerkleRoot ( )
require :
merkleRootAfterRes . isOk ( )
let merkleRootAfter = merkleRootAfterRes . get ( )
check :
merkleRootAfter . inHex ( ) ! = merkleRootBefore . inHex ( )
manager . latestIndex = = 1
asyncTest " register: callback is called " :
let manager = await setup ( )
var callbackCalled = false
let idCommitment = generateCredentials ( manager . rlnInstance ) . idCommitment
let fut = newFuture [ void ] ( )
proc callback ( registrations : seq [ Membership ] ) : Future [ void ] {. async . } =
require :
registrations . len = = 1
registrations [ 0 ] . idCommitment = = idCommitment
registrations [ 0 ] . index = = 0
callbackCalled = true
fut . complete ( )
manager . onRegister ( callback )
await manager . init ( )
await manager . startGroupSync ( )
await manager . register ( idCommitment )
await fut
check :
callbackCalled
asyncTest " withdraw: should guard against uninitialized state " :
let manager = await setup ( )
let idSecretHash = generateCredentials ( manager . rlnInstance ) . idSecretHash
expect ( ValueError ) :
await manager . withdraw ( idSecretHash )
################################
## Terminating/removing Ganache
################################
# We stop Ganache daemon
stopGanache ( runGanache )