mirror of
https://github.com/waku-org/nwaku.git
synced 2025-01-14 17:04:53 +00:00
deploy: 47a9fcfd77a7c452dfb1d19a708aee5ca64c596f
This commit is contained in:
parent
27fa2aebbb
commit
804a182ce7
@ -27,6 +27,8 @@ import
|
||||
|
||||
when defined(rln):
|
||||
import ./v2/test_waku_rln_relay
|
||||
when defined(onchain_rln):
|
||||
import ./v2/test_waku_rln_relay_onchain
|
||||
|
||||
|
||||
# TODO Only enable this once swap module is integrated more nicely as a dependency, i.e. as submodule with CI etc
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -6,7 +6,8 @@ import
|
||||
testutils/unittests, chronos, chronicles, stint, web3,
|
||||
stew/byteutils, stew/shims/net as stewNet,
|
||||
libp2p/crypto/crypto,
|
||||
../../waku/v2/protocol/waku_rln_relay/[rln, waku_rln_relay_utils, waku_rln_relay_types],
|
||||
../../waku/v2/protocol/waku_rln_relay/[rln, waku_rln_relay_utils,
|
||||
waku_rln_relay_types],
|
||||
../../waku/v2/node/wakunode2,
|
||||
../test_helpers,
|
||||
./test_utils
|
||||
@ -14,258 +15,7 @@ import
|
||||
const RLNRELAY_PUBSUB_TOPIC = "waku/2/rlnrelay/proto"
|
||||
const RLNRELAY_CONTENT_TOPIC = "waku/2/rlnrelay/proto"
|
||||
|
||||
# POSEIDON_HASHER_CODE holds the bytecode of Poseidon hasher solidity smart contract:
|
||||
# https://github.com/kilic/rlnapp/blob/master/packages/contracts/contracts/crypto/PoseidonHasher.sol
|
||||
# the solidity contract is compiled separately and the resultant bytecode is copied here
|
||||
const POSEIDON_HASHER_CODE = readFile("tests/v2/poseidonHasher.txt")
|
||||
# MEMBERSHIP_CONTRACT_CODE contains the bytecode of the membership solidity smart contract:
|
||||
# https://github.com/kilic/rlnapp/blob/master/packages/contracts/contracts/RLN.sol
|
||||
# the solidity contract is compiled separately and the resultant bytecode is copied here
|
||||
const MEMBERSHIP_CONTRACT_CODE = readFile("tests/v2/membershipContract.txt")
|
||||
|
||||
# the membership contract code in solidity
|
||||
# uint256 public immutable MEMBERSHIP_DEPOSIT;
|
||||
# uint256 public immutable DEPTH;
|
||||
# uint256 public immutable SET_SIZE;
|
||||
# uint256 public pubkeyIndex = 0;
|
||||
# mapping(uint256 => uint256) public members;
|
||||
# IPoseidonHasher public poseidonHasher;
|
||||
|
||||
# event MemberRegistered(uint256 indexed pubkey, uint256 indexed index);
|
||||
# event MemberWithdrawn(uint256 indexed pubkey, uint256 indexed index);
|
||||
|
||||
# constructor(
|
||||
# uint256 membershipDeposit,
|
||||
# uint256 depth,
|
||||
# address _poseidonHasher
|
||||
# ) public {
|
||||
# MEMBERSHIP_DEPOSIT = membershipDeposit;
|
||||
# DEPTH = depth;
|
||||
# SET_SIZE = 1 << depth;
|
||||
# poseidonHasher = IPoseidonHasher(_poseidonHasher);
|
||||
# }
|
||||
|
||||
# function register(uint256 pubkey) external payable {
|
||||
# require(pubkeyIndex < SET_SIZE, "RLN, register: set is full");
|
||||
# require(msg.value == MEMBERSHIP_DEPOSIT, "RLN, register: membership deposit is not satisfied");
|
||||
# _register(pubkey);
|
||||
# }
|
||||
|
||||
# function registerBatch(uint256[] calldata pubkeys) external payable {
|
||||
# require(pubkeyIndex + pubkeys.length <= SET_SIZE, "RLN, registerBatch: set is full");
|
||||
# require(msg.value == MEMBERSHIP_DEPOSIT * pubkeys.length, "RLN, registerBatch: membership deposit is not satisfied");
|
||||
# for (uint256 i = 0; i < pubkeys.length; i++) {
|
||||
# _register(pubkeys[i]);
|
||||
# }
|
||||
# }
|
||||
|
||||
# function withdrawBatch(
|
||||
# uint256[] calldata secrets,
|
||||
# uint256[] calldata pubkeyIndexes,
|
||||
# address payable[] calldata receivers
|
||||
# ) external {
|
||||
# uint256 batchSize = secrets.length;
|
||||
# require(batchSize != 0, "RLN, withdrawBatch: batch size zero");
|
||||
# require(batchSize == pubkeyIndexes.length, "RLN, withdrawBatch: batch size mismatch pubkey indexes");
|
||||
# require(batchSize == receivers.length, "RLN, withdrawBatch: batch size mismatch receivers");
|
||||
# for (uint256 i = 0; i < batchSize; i++) {
|
||||
# _withdraw(secrets[i], pubkeyIndexes[i], receivers[i]);
|
||||
# }
|
||||
# }
|
||||
|
||||
# function withdraw(
|
||||
# uint256 secret,
|
||||
# uint256 _pubkeyIndex,
|
||||
# address payable receiver
|
||||
# ) external {
|
||||
# _withdraw(secret, _pubkeyIndex, receiver);
|
||||
# }
|
||||
|
||||
|
||||
contract(MembershipContract):
|
||||
proc register(pubkey: Uint256) # external payable
|
||||
# proc registerBatch(pubkeys: seq[Uint256]) # external payable
|
||||
# TODO will add withdraw function after integrating the keyGeneration function (required to compute public keys from secret keys)
|
||||
# proc withdraw(secret: Uint256, pubkeyIndex: Uint256, receiver: Address)
|
||||
# proc withdrawBatch( secrets: seq[Uint256], pubkeyIndex: seq[Uint256], receiver: seq[Address])
|
||||
|
||||
proc uploadContract(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
|
||||
|
||||
var balance = await web3.provider.eth_getBalance(web3.defaultAccount , "latest")
|
||||
debug "Initial account balance: ", balance
|
||||
|
||||
# deploy the poseidon hash first
|
||||
let
|
||||
hasherReceipt = await web3.deployContract(POSEIDON_HASHER_CODE)
|
||||
hasherAddress = hasherReceipt.contractAddress.get
|
||||
debug "hasher address: ", hasherAddress
|
||||
|
||||
|
||||
# encode membership contract inputs to 32 bytes zero-padded
|
||||
let
|
||||
membershipFeeEncoded = encode(MembershipFee).data
|
||||
depthEncoded = encode(MERKLE_TREE_DEPTH.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(MEMBERSHIP_CONTRACT_CODE, contractInput = contractInput)
|
||||
var contractAddress = receipt.contractAddress.get
|
||||
debug "Address of the deployed membership contract: ", contractAddress
|
||||
|
||||
# balance = await web3.provider.eth_getBalance(web3.defaultAccount , "latest")
|
||||
# debug "Account balance after the contract deployment: ", balance
|
||||
|
||||
await web3.close()
|
||||
debug "disconnected from ", ethClientAddress
|
||||
|
||||
return contractAddress
|
||||
|
||||
procSuite "Waku rln relay":
|
||||
when defined(onchain_rln):
|
||||
asyncTest "contract membership":
|
||||
debug "ethereum client address", ETH_CLIENT
|
||||
let contractAddress = await uploadContract(ETH_CLIENT)
|
||||
# connect to the eth client
|
||||
let web3 = await newWeb3(ETH_CLIENT)
|
||||
debug "web3 connected to", ETH_CLIENT
|
||||
|
||||
# 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
|
||||
|
||||
# prepare a contract sender to interact with it
|
||||
var sender = web3.contractSender(MembershipContract, contractAddress) # creates a Sender object with a web3 field and contract address of type Address
|
||||
|
||||
# send takes three parameters, c: ContractCallBase, value = 0.u256, gas = 3000000'u64 gasPrice = 0
|
||||
# should use send proc for the contract functions that update the state of the contract
|
||||
let tx = await sender.register(20.u256).send(value = MembershipFee)
|
||||
debug "The hash of registration tx: ", tx # value is the membership fee
|
||||
|
||||
# var members: array[2, uint256] = [20.u256, 21.u256]
|
||||
# debug "This is the batch registration result ", await sender.registerBatch(members).send(value = (members.len * membershipFee)) # value is the membership fee
|
||||
|
||||
# balance = await web3.provider.eth_getBalance(web3.defaultAccount , "latest")
|
||||
# debug "Balance after registration: ", balance
|
||||
|
||||
await web3.close()
|
||||
debug "disconnected from", ETH_CLIENT
|
||||
|
||||
asyncTest "registration procedure":
|
||||
# deploy the contract
|
||||
let contractAddress = await uploadContract(ETH_CLIENT)
|
||||
|
||||
# prepare rln-relay peer inputs
|
||||
let
|
||||
web3 = await newWeb3(ETH_CLIENT)
|
||||
accounts = await web3.provider.eth_accounts()
|
||||
# choose one of the existing accounts for the rln-relay peer
|
||||
ethAccountAddress = accounts[9]
|
||||
await web3.close()
|
||||
|
||||
# create an RLN instance
|
||||
var rlnInstance = createRLNInstance()
|
||||
check: rlnInstance.isOk == true
|
||||
|
||||
# generate the membership keys
|
||||
let membershipKeyPair = membershipKeyGen(rlnInstance.value)
|
||||
|
||||
check: membershipKeyPair.isSome
|
||||
|
||||
# initialize the WakuRLNRelay
|
||||
var rlnPeer = WakuRLNRelay(membershipKeyPair: membershipKeyPair.get(),
|
||||
membershipIndex: MembershipIndex(0),
|
||||
ethClientAddress: ETH_CLIENT,
|
||||
ethAccountAddress: ethAccountAddress,
|
||||
membershipContractAddress: contractAddress)
|
||||
|
||||
# register the rln-relay peer to the membership contract
|
||||
let is_successful = await rlnPeer.register()
|
||||
check:
|
||||
is_successful
|
||||
asyncTest "mounting waku rln-relay":
|
||||
let
|
||||
nodeKey = crypto.PrivateKey.random(Secp256k1, rng[])[]
|
||||
node = WakuNode.new(nodeKey, ValidIpAddress.init("0.0.0.0"),
|
||||
Port(60000))
|
||||
await node.start()
|
||||
|
||||
# deploy the contract
|
||||
let membershipContractAddress = await uploadContract(ETH_CLIENT)
|
||||
|
||||
# prepare rln-relay inputs
|
||||
let
|
||||
web3 = await newWeb3(ETH_CLIENT)
|
||||
accounts = await web3.provider.eth_accounts()
|
||||
# choose one of the existing account for the rln-relay peer
|
||||
ethAccountAddress = accounts[9]
|
||||
await web3.close()
|
||||
|
||||
# create current peer's pk
|
||||
var rlnInstance = createRLNInstance()
|
||||
check rlnInstance.isOk == true
|
||||
var rln = rlnInstance.value
|
||||
# generate a key pair
|
||||
var keypair = rln.membershipKeyGen()
|
||||
doAssert(keypair.isSome())
|
||||
|
||||
# current peer index in the Merkle tree
|
||||
let index = uint(5)
|
||||
|
||||
# Create a group of 10 members
|
||||
var group = newSeq[IDCommitment]()
|
||||
for i in 0..10:
|
||||
var member_is_added: bool = false
|
||||
if (uint(i) == index):
|
||||
# insert the current peer's pk
|
||||
group.add(keypair.get().idCommitment)
|
||||
member_is_added = rln.insertMember(keypair.get().idCommitment)
|
||||
doAssert(member_is_added)
|
||||
debug "member key", key=keypair.get().idCommitment.toHex
|
||||
else:
|
||||
var memberKeypair = rln.membershipKeyGen()
|
||||
doAssert(memberKeypair.isSome())
|
||||
group.add(memberKeypair.get().idCommitment)
|
||||
member_is_added = rln.insertMember(memberKeypair.get().idCommitment)
|
||||
doAssert(member_is_added)
|
||||
debug "member key", key=memberKeypair.get().idCommitment.toHex
|
||||
let expectedRoot = rln.getMerkleRoot().value().toHex
|
||||
debug "expected root ", expectedRoot
|
||||
|
||||
# start rln-relay
|
||||
node.mountRelay(@[RLNRELAY_PUBSUB_TOPIC])
|
||||
await node.mountRlnRelay(ethClientAddrOpt = some(EthClient),
|
||||
ethAccAddrOpt = some(ethAccountAddress),
|
||||
memContractAddOpt = some(membershipContractAddress),
|
||||
groupOpt = some(group),
|
||||
memKeyPairOpt = some(keypair.get()),
|
||||
memIndexOpt = some(index),
|
||||
pubsubTopic = RLNRELAY_PUBSUB_TOPIC,
|
||||
contentTopic = RLNRELAY_CONTENT_TOPIC)
|
||||
let calculatedRoot = node.wakuRlnRelay.rlnInstance.getMerkleRoot().value().toHex
|
||||
debug "calculated root ", calculatedRoot
|
||||
|
||||
check expectedRoot == calculatedRoot
|
||||
|
||||
await node.stop()
|
||||
|
||||
asyncTest "mount waku-rln-relay in the off-chain mode":
|
||||
let
|
||||
nodeKey = crypto.PrivateKey.random(Secp256k1, rng[])[]
|
||||
@ -278,7 +28,8 @@ procSuite "Waku rln relay":
|
||||
# create a group of 100 membership keys
|
||||
let
|
||||
(groupKeys, root) = createMembershipList(100)
|
||||
check groupKeys.len == 100
|
||||
check:
|
||||
groupKeys.len == 100
|
||||
let
|
||||
# convert the keys to MembershipKeyPair structs
|
||||
groupKeyPairs = groupKeys.toMembershipKeyPairs()
|
||||
@ -308,7 +59,8 @@ procSuite "Waku rln relay":
|
||||
# this part checks whether the Merkle tree is constructed correctly inside the mountRlnRelay proc
|
||||
# this check is done by comparing the tree root resulted from mountRlnRelay i.e., calculatedRoot
|
||||
# against the root which is the expected root
|
||||
check calculatedRoot == root
|
||||
check:
|
||||
calculatedRoot == root
|
||||
|
||||
await node.stop()
|
||||
|
||||
@ -323,7 +75,7 @@ suite "Waku rln relay":
|
||||
# the file is generated separately and copied here
|
||||
parameters = readFile("waku/v2/protocol/waku_rln_relay/parameters.key")
|
||||
pbytes = parameters.toBytes()
|
||||
len : csize_t = uint(pbytes.len)
|
||||
len: csize_t = uint(pbytes.len)
|
||||
parametersBuffer = Buffer(`ptr`: addr(pbytes[0]), len: len)
|
||||
check:
|
||||
# check the parameters.key is not empty
|
||||
@ -331,14 +83,15 @@ suite "Waku rln relay":
|
||||
|
||||
var
|
||||
rlnInstance: RLN[Bn256]
|
||||
let res = new_circuit_from_params(merkleDepth, addr parametersBuffer, addr rlnInstance)
|
||||
let res = new_circuit_from_params(merkleDepth, addr parametersBuffer,
|
||||
addr rlnInstance)
|
||||
check:
|
||||
# check whether the circuit parameters are generated successfully
|
||||
res == true
|
||||
|
||||
# keysBufferPtr will hold the generated key pairs i.e., secret and public keys
|
||||
var
|
||||
keysBuffer : Buffer
|
||||
keysBuffer: Buffer
|
||||
keysBufferPtr = addr(keysBuffer)
|
||||
done = key_gen(rlnInstance, keysBufferPtr)
|
||||
check:
|
||||
@ -359,7 +112,7 @@ suite "Waku rln relay":
|
||||
rlnInstance.isOk == true
|
||||
|
||||
var key = membershipKeyGen(rlnInstance.value)
|
||||
var empty : array[32,byte]
|
||||
var empty: array[32, byte]
|
||||
check:
|
||||
key.isSome
|
||||
key.get().idKey.len == 32
|
||||
@ -377,7 +130,7 @@ suite "Waku rln relay":
|
||||
|
||||
# read the Merkle Tree root
|
||||
var
|
||||
root1 {.noinit.} : Buffer = Buffer()
|
||||
root1 {.noinit.}: Buffer = Buffer()
|
||||
rootPtr1 = addr(root1)
|
||||
get_root_successful1 = get_root(rlnInstance.value, rootPtr1)
|
||||
check:
|
||||
@ -386,21 +139,22 @@ suite "Waku rln relay":
|
||||
|
||||
# read the Merkle Tree root
|
||||
var
|
||||
root2 {.noinit.} : Buffer = Buffer()
|
||||
root2 {.noinit.}: Buffer = Buffer()
|
||||
rootPtr2 = addr(root2)
|
||||
get_root_successful2 = get_root(rlnInstance.value, rootPtr2)
|
||||
check:
|
||||
get_root_successful2
|
||||
root2.len == 32
|
||||
|
||||
var rootValue1 = cast[ptr array[32,byte]] (root1.`ptr`)
|
||||
var rootValue1 = cast[ptr array[32, byte]] (root1.`ptr`)
|
||||
let rootHex1 = rootValue1[].toHex
|
||||
|
||||
var rootValue2 = cast[ptr array[32,byte]] (root2.`ptr`)
|
||||
var rootValue2 = cast[ptr array[32, byte]] (root2.`ptr`)
|
||||
let rootHex2 = rootValue2[].toHex
|
||||
|
||||
# the two roots must be identical
|
||||
check rootHex1 == rootHex2
|
||||
check:
|
||||
rootHex1 == rootHex2
|
||||
test "getMerkleRoot utils":
|
||||
# create an RLN instance which also includes an empty Merkle tree
|
||||
var rlnInstance = createRLNInstance()
|
||||
@ -409,16 +163,19 @@ suite "Waku rln relay":
|
||||
|
||||
# read the Merkle Tree root
|
||||
var root1 = getMerkleRoot(rlnInstance.value())
|
||||
check root1.isOk
|
||||
check:
|
||||
root1.isOk
|
||||
let rootHex1 = root1.value().toHex
|
||||
|
||||
# read the Merkle Tree root
|
||||
var root2 = getMerkleRoot(rlnInstance.value())
|
||||
check root2.isOk
|
||||
check:
|
||||
root2.isOk
|
||||
let rootHex2 = root2.value().toHex
|
||||
|
||||
# the two roots must be identical
|
||||
check rootHex1 == rootHex2
|
||||
check:
|
||||
rootHex1 == rootHex2
|
||||
|
||||
test "update_next_member Nim Wrapper":
|
||||
# create an RLN instance which also includes an empty Merkle tree
|
||||
@ -428,7 +185,8 @@ suite "Waku rln relay":
|
||||
|
||||
# generate a key pair
|
||||
var keypair = membershipKeyGen(rlnInstance.value)
|
||||
check keypair.isSome()
|
||||
check:
|
||||
keypair.isSome()
|
||||
var pkBuffer = toBuffer(keypair.get().idCommitment)
|
||||
let pkBufferPtr = addr pkBuffer
|
||||
|
||||
@ -446,7 +204,8 @@ suite "Waku rln relay":
|
||||
# delete the first member
|
||||
var deleted_member_index = MembershipIndex(0)
|
||||
let deletion_success = delete_member(rlnInstance.value, deleted_member_index)
|
||||
check deletion_success
|
||||
check:
|
||||
deletion_success
|
||||
|
||||
test "insertMember rln utils":
|
||||
# create an RLN instance which also includes an empty Merkle tree
|
||||
@ -456,7 +215,8 @@ suite "Waku rln relay":
|
||||
var rln = rlnInstance.value
|
||||
# generate a key pair
|
||||
var keypair = rln.membershipKeyGen()
|
||||
check keypair.isSome()
|
||||
check:
|
||||
keypair.isSome()
|
||||
check:
|
||||
rln.insertMember(keypair.get().idCommitment)
|
||||
|
||||
@ -477,7 +237,7 @@ suite "Waku rln relay":
|
||||
|
||||
# read the Merkle Tree root
|
||||
var
|
||||
root1 {.noinit.} : Buffer = Buffer()
|
||||
root1 {.noinit.}: Buffer = Buffer()
|
||||
rootPtr1 = addr(root1)
|
||||
get_root_successful1 = get_root(rlnInstance.value, rootPtr1)
|
||||
check:
|
||||
@ -492,11 +252,12 @@ suite "Waku rln relay":
|
||||
|
||||
# add the member to the tree
|
||||
var member_is_added = update_next_member(rlnInstance.value, pkBufferPtr)
|
||||
check member_is_added
|
||||
check:
|
||||
member_is_added
|
||||
|
||||
# read the Merkle Tree root after insertion
|
||||
var
|
||||
root2 {.noinit.} : Buffer = Buffer()
|
||||
root2 {.noinit.}: Buffer = Buffer()
|
||||
rootPtr2 = addr(root2)
|
||||
get_root_successful2 = get_root(rlnInstance.value, rootPtr2)
|
||||
check:
|
||||
@ -506,26 +267,27 @@ suite "Waku rln relay":
|
||||
# delete the first member
|
||||
var deleted_member_index = MembershipIndex(0)
|
||||
let deletion_success = delete_member(rlnInstance.value, deleted_member_index)
|
||||
check deletion_success
|
||||
check:
|
||||
deletion_success
|
||||
|
||||
# read the Merkle Tree root after the deletion
|
||||
var
|
||||
root3 {.noinit.} : Buffer = Buffer()
|
||||
root3 {.noinit.}: Buffer = Buffer()
|
||||
rootPtr3 = addr(root3)
|
||||
get_root_successful3 = get_root(rlnInstance.value, rootPtr3)
|
||||
check:
|
||||
get_root_successful3
|
||||
root3.len == 32
|
||||
|
||||
var rootValue1 = cast[ptr array[32,byte]] (root1.`ptr`)
|
||||
var rootValue1 = cast[ptr array[32, byte]] (root1.`ptr`)
|
||||
let rootHex1 = rootValue1[].toHex
|
||||
debug "The initial root", rootHex1
|
||||
|
||||
var rootValue2 = cast[ptr array[32,byte]] (root2.`ptr`)
|
||||
var rootValue2 = cast[ptr array[32, byte]] (root2.`ptr`)
|
||||
let rootHex2 = rootValue2[].toHex
|
||||
debug "The root after insertion", rootHex2
|
||||
|
||||
var rootValue3 = cast[ptr array[32,byte]] (root3.`ptr`)
|
||||
var rootValue3 = cast[ptr array[32, byte]] (root3.`ptr`)
|
||||
let rootHex3 = rootValue3[].toHex
|
||||
debug "The root after deletion", rootHex3
|
||||
|
||||
@ -534,7 +296,8 @@ suite "Waku rln relay":
|
||||
|
||||
## The initial root of the tree (empty tree) must be identical to
|
||||
## the root of the tree after one insertion followed by a deletion
|
||||
check rootHex1 == rootHex3
|
||||
check:
|
||||
rootHex1 == rootHex3
|
||||
test "Merkle tree consistency check between deletion and insertion using rln utils":
|
||||
# create an RLN instance
|
||||
var rlnInstance = createRLNInstance()
|
||||
@ -544,29 +307,35 @@ suite "Waku rln relay":
|
||||
|
||||
# read the Merkle Tree root
|
||||
var root1 = rln.getMerkleRoot()
|
||||
check root1.isOk
|
||||
check:
|
||||
root1.isOk
|
||||
let rootHex1 = root1.value().toHex()
|
||||
|
||||
# generate a key pair
|
||||
var keypair = rln.membershipKeyGen()
|
||||
check keypair.isSome()
|
||||
check:
|
||||
keypair.isSome()
|
||||
let member_inserted = rln.insertMember(keypair.get().idCommitment)
|
||||
check member_inserted
|
||||
check:
|
||||
member_inserted
|
||||
|
||||
# read the Merkle Tree root after insertion
|
||||
var root2 = rln.getMerkleRoot()
|
||||
check root2.isOk
|
||||
check:
|
||||
root2.isOk
|
||||
let rootHex2 = root2.value().toHex()
|
||||
|
||||
|
||||
# delete the first member
|
||||
var deleted_member_index = MembershipIndex(0)
|
||||
let deletion_success = rln.removeMember(deleted_member_index)
|
||||
check deletion_success
|
||||
check:
|
||||
deletion_success
|
||||
|
||||
# read the Merkle Tree root after the deletion
|
||||
var root3 = rln.getMerkleRoot()
|
||||
check root3.isOk
|
||||
check:
|
||||
root3.isOk
|
||||
let rootHex3 = root3.value().toHex()
|
||||
|
||||
|
||||
@ -575,11 +344,13 @@ suite "Waku rln relay":
|
||||
debug "The root after deletion", rootHex3
|
||||
|
||||
# the root must change after the insertion
|
||||
check not(rootHex1 == rootHex2)
|
||||
check:
|
||||
not(rootHex1 == rootHex2)
|
||||
|
||||
## The initial root of the tree (empty tree) must be identical to
|
||||
## the root of the tree after one insertion followed by a deletion
|
||||
check rootHex1 == rootHex3
|
||||
check:
|
||||
rootHex1 == rootHex3
|
||||
|
||||
test "hash Nim Wrappers":
|
||||
# create an RLN instance
|
||||
@ -596,14 +367,17 @@ suite "Waku rln relay":
|
||||
# prepare other inputs to the hash function
|
||||
var outputBuffer: Buffer
|
||||
|
||||
let hashSuccess = hash(rlnInstance.value, addr hashInputBuffer, addr outputBuffer)
|
||||
check hashSuccess
|
||||
let outputArr = cast[ptr array[32,byte]](outputBuffer.`ptr`)[]
|
||||
let hashSuccess = hash(rlnInstance.value, addr hashInputBuffer,
|
||||
addr outputBuffer)
|
||||
check:
|
||||
"efb8ac39dc22eaf377fe85b405b99ba78dbc2f3f32494add4501741df946bd1d" == outputArr.toHex()
|
||||
hashSuccess
|
||||
let outputArr = cast[ptr array[32, byte]](outputBuffer.`ptr`)[]
|
||||
check:
|
||||
"efb8ac39dc22eaf377fe85b405b99ba78dbc2f3f32494add4501741df946bd1d" ==
|
||||
outputArr.toHex()
|
||||
|
||||
var
|
||||
hashOutput = cast[ptr array[32,byte]] (outputBuffer.`ptr`)[]
|
||||
hashOutput = cast[ptr array[32, byte]] (outputBuffer.`ptr`)[]
|
||||
hashOutputHex = hashOutput.toHex()
|
||||
|
||||
debug "hash output", hashOutputHex
|
||||
@ -620,7 +394,8 @@ suite "Waku rln relay":
|
||||
|
||||
let hash = rln.hash(msg)
|
||||
check:
|
||||
"efb8ac39dc22eaf377fe85b405b99ba78dbc2f3f32494add4501741df946bd1d" == hash.toHex()
|
||||
"efb8ac39dc22eaf377fe85b405b99ba78dbc2f3f32494add4501741df946bd1d" ==
|
||||
hash.toHex()
|
||||
|
||||
test "create a list of membership keys and construct a Merkle tree based on the list":
|
||||
let
|
||||
@ -663,12 +438,12 @@ suite "Waku rln relay":
|
||||
shareY: MerkleNode
|
||||
nullifier: Nullifier
|
||||
# populate fields with dummy values
|
||||
for x in proof.mitems : x = 1
|
||||
for x in merkleRoot.mitems : x = 2
|
||||
for x in epoch.mitems : x = 3
|
||||
for x in shareX.mitems : x = 4
|
||||
for x in shareY.mitems : x = 5
|
||||
for x in nullifier.mitems : x = 6
|
||||
for x in proof.mitems: x = 1
|
||||
for x in merkleRoot.mitems: x = 2
|
||||
for x in epoch.mitems: x = 3
|
||||
for x in shareX.mitems: x = 4
|
||||
for x in shareY.mitems: x = 5
|
||||
for x in nullifier.mitems: x = 6
|
||||
|
||||
let
|
||||
rateLimitProof = RateLimitProof(proof: proof,
|
||||
@ -686,7 +461,8 @@ suite "Waku rln relay":
|
||||
|
||||
test "test proofVerify and proofGen for a valid proof":
|
||||
var rlnInstance = createRLNInstance()
|
||||
check rlnInstance.isOk
|
||||
check:
|
||||
rlnInstance.isOk
|
||||
var rln = rlnInstance.value
|
||||
|
||||
let
|
||||
@ -706,27 +482,30 @@ suite "Waku rln relay":
|
||||
let memberKeys = rln.membershipKeyGen()
|
||||
member_is_added = rln.insertMember(memberKeys.get().idCommitment)
|
||||
# check the member is added
|
||||
check member_is_added
|
||||
check:
|
||||
member_is_added
|
||||
|
||||
# prepare the message
|
||||
let messageBytes = "Hello".toBytes()
|
||||
|
||||
# prepare the epoch
|
||||
var epoch : Epoch
|
||||
debug "epoch", epochHex=epoch.toHex()
|
||||
var epoch: Epoch
|
||||
debug "epoch", epochHex = epoch.toHex()
|
||||
|
||||
# generate proof
|
||||
let proofRes = rln.proofGen(data = messageBytes,
|
||||
memKeys = memKeys,
|
||||
memIndex = MembershipIndex(index),
|
||||
epoch = epoch)
|
||||
check proofRes.isOk()
|
||||
check:
|
||||
proofRes.isOk()
|
||||
let proof = proofRes.value
|
||||
|
||||
# verify the proof
|
||||
let verified = rln.proofVerify(data = messageBytes,
|
||||
proof = proof)
|
||||
check verified == true
|
||||
check:
|
||||
verified == true
|
||||
|
||||
test "test proofVerify and proofGen for an invalid proof":
|
||||
var rlnInstance = createRLNInstance()
|
||||
@ -751,14 +530,15 @@ suite "Waku rln relay":
|
||||
let memberKeys = rln.membershipKeyGen()
|
||||
member_is_added = rln.insertMember(memberKeys.get().idCommitment)
|
||||
# check the member is added
|
||||
check member_is_added
|
||||
check:
|
||||
member_is_added
|
||||
|
||||
# prepare the message
|
||||
let messageBytes = "Hello".toBytes()
|
||||
|
||||
# prepare the epoch
|
||||
var epoch : Epoch
|
||||
debug "epoch in bytes", epochHex=epoch.toHex()
|
||||
var epoch: Epoch
|
||||
debug "epoch in bytes", epochHex = epoch.toHex()
|
||||
|
||||
|
||||
let badIndex = 4
|
||||
@ -767,21 +547,25 @@ suite "Waku rln relay":
|
||||
memKeys = memKeys,
|
||||
memIndex = MembershipIndex(badIndex),
|
||||
epoch = epoch)
|
||||
check proofRes.isOk()
|
||||
check:
|
||||
proofRes.isOk()
|
||||
let proof = proofRes.value
|
||||
|
||||
# verify the proof (should not be verified)
|
||||
let verified = rln.proofVerify(data = messageBytes,
|
||||
proof = proof)
|
||||
check verified == false
|
||||
check:
|
||||
verified == false
|
||||
test "toEpoch and fromEpoch consistency check":
|
||||
# check edge cases
|
||||
let
|
||||
time = uint64.high
|
||||
epoch = time.toEpoch()
|
||||
decodedTime = epoch.fromEpoch()
|
||||
check time == decodedTime
|
||||
debug "encoded and decode time", time=time, epoch=epoch, decodedTime=decodedTime
|
||||
epoch = uint64.high # rln epoch
|
||||
epochBytes = epoch.toEpoch()
|
||||
decodedEpoch = epochBytes.fromEpoch()
|
||||
check:
|
||||
epoch == decodedEpoch
|
||||
debug "encoded and decode time", epoch = epoch, epochBytes = epochBytes,
|
||||
decodedEpoch = decodedEpoch
|
||||
|
||||
test "Epoch comparison":
|
||||
# check edge cases
|
||||
@ -790,8 +574,9 @@ suite "Waku rln relay":
|
||||
time2 = uint64.high - 1
|
||||
epoch1 = time1.toEpoch()
|
||||
epoch2 = time2.toEpoch()
|
||||
check compare(epoch1, epoch2) == int64(1)
|
||||
check compare(epoch2, epoch1) == int64(-1)
|
||||
check:
|
||||
diff(epoch1, epoch2) == int64(1)
|
||||
diff(epoch2, epoch1) == int64(-1)
|
||||
|
||||
test "updateLog and hasDuplicate tests":
|
||||
let
|
||||
@ -817,9 +602,12 @@ suite "Waku rln relay":
|
||||
let shareY3 = shareX3
|
||||
|
||||
let
|
||||
wm1 = WakuMessage(proof: RateLimitProof(epoch: epoch, nullifier: nullifier1, shareX: shareX1, shareY: shareY1))
|
||||
wm2 = WakuMessage(proof: RateLimitProof(epoch: epoch, nullifier: nullifier2, shareX: shareX2, shareY: shareY2))
|
||||
wm3 = WakuMessage(proof: RateLimitProof(epoch: epoch, nullifier: nullifier3, shareX: shareX3, shareY: shareY3))
|
||||
wm1 = WakuMessage(proof: RateLimitProof(epoch: epoch,
|
||||
nullifier: nullifier1, shareX: shareX1, shareY: shareY1))
|
||||
wm2 = WakuMessage(proof: RateLimitProof(epoch: epoch,
|
||||
nullifier: nullifier2, shareX: shareX2, shareY: shareY2))
|
||||
wm3 = WakuMessage(proof: RateLimitProof(epoch: epoch,
|
||||
nullifier: nullifier3, shareX: shareX3, shareY: shareY3))
|
||||
|
||||
# check whether hasDuplicate correctly finds records with the same nullifiers but different secret shares
|
||||
# no duplicate for wm1 should be found, since the log is empty
|
||||
@ -874,7 +662,8 @@ suite "Waku rln relay":
|
||||
discard rln.addAll(groupIDCommitments)
|
||||
|
||||
let
|
||||
wakuRlnRelay = WakuRLNRelay(membershipIndex: index, membershipKeyPair: groupKeyPairs[index], rlnInstance: rln)
|
||||
wakuRlnRelay = WakuRLNRelay(membershipIndex: index,
|
||||
membershipKeyPair: groupKeyPairs[index], rlnInstance: rln)
|
||||
|
||||
# get the current epoch time
|
||||
let time = epochTime()
|
||||
|
271
tests/v2/test_waku_rln_relay_onchain.nim
Normal file
271
tests/v2/test_waku_rln_relay_onchain.nim
Normal file
@ -0,0 +1,271 @@
|
||||
|
||||
# contains rln-relay tests that require interaction with Ganache i.e., onchain tests
|
||||
{.used.}
|
||||
|
||||
import
|
||||
std/options, sequtils, times,
|
||||
testutils/unittests, chronos, chronicles, stint, web3, json,
|
||||
stew/byteutils, stew/shims/net as stewNet,
|
||||
libp2p/crypto/crypto,
|
||||
../../waku/v2/protocol/waku_rln_relay/[rln, waku_rln_relay_utils,
|
||||
waku_rln_relay_types, rln_relay_contract],
|
||||
../../waku/v2/node/wakunode2,
|
||||
../test_helpers,
|
||||
./test_utils
|
||||
|
||||
const RLNRELAY_PUBSUB_TOPIC = "waku/2/rlnrelay/proto"
|
||||
const RLNRELAY_CONTENT_TOPIC = "waku/2/rlnrelay/proto"
|
||||
|
||||
# contract ABI
|
||||
contract(MembershipContract):
|
||||
proc register(pubkey: Uint256) # external payable
|
||||
proc MemberRegistered(pubkey: Uint256, index: Uint256) {.event.}
|
||||
# proc registerBatch(pubkeys: seq[Uint256]) # external payable
|
||||
# proc withdraw(secret: Uint256, pubkeyIndex: Uint256, receiver: Address)
|
||||
# proc withdrawBatch( secrets: seq[Uint256], pubkeyIndex: seq[Uint256], receiver: seq[Address])
|
||||
|
||||
# a util function used for testing purposes
|
||||
# it deploys membership contract on Ganache (or any Eth client available on ETH_CLIENT 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
|
||||
|
||||
var 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(POSEIDON_HASHER_CODE)
|
||||
hasherAddress = hasherReceipt.contractAddress.get
|
||||
debug "hasher address: ", hasherAddress
|
||||
|
||||
|
||||
# encode membership contract inputs to 32 bytes zero-padded
|
||||
let
|
||||
membershipFeeEncoded = encode(MEMBERSHIP_FEE).data
|
||||
depthEncoded = encode(MERKLE_TREE_DEPTH.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(MEMBERSHIP_CONTRACT_CODE,
|
||||
contractInput = contractInput)
|
||||
var contractAddress = receipt.contractAddress.get
|
||||
debug "Address of the deployed membership contract: ", contractAddress
|
||||
|
||||
balance = await web3.provider.eth_getBalance(web3.defaultAccount, "latest")
|
||||
debug "Account balance after the contract deployment: ", balance
|
||||
|
||||
await web3.close()
|
||||
debug "disconnected from ", ethClientAddress
|
||||
|
||||
return contractAddress
|
||||
|
||||
procSuite "Waku-rln-relay":
|
||||
asyncTest "event subscription":
|
||||
# preparation ------------------------------
|
||||
debug "ethereum client address", ETH_CLIENT
|
||||
let contractAddress = await uploadRLNContract(ETH_CLIENT)
|
||||
# connect to the eth client
|
||||
let web3 = await newWeb3(ETH_CLIENT)
|
||||
debug "web3 connected to", ETH_CLIENT
|
||||
|
||||
# fetch the list of registered accounts
|
||||
let accounts = await web3.provider.eth_accounts()
|
||||
web3.defaultAccount = accounts[1]
|
||||
debug "contract deployer account address ",
|
||||
defaultAccount = web3.defaultAccount
|
||||
|
||||
# prepare a contract sender to interact with it
|
||||
var contractObj = web3.contractSender(MembershipContract,
|
||||
contractAddress) # creates a Sender object with a web3 field and contract address of type Address
|
||||
|
||||
# create an RLN instance
|
||||
var rlnInstance = createRLNInstance()
|
||||
check:
|
||||
rlnInstance.isOk == true
|
||||
# generate the membership keys
|
||||
let membershipKeyPair = membershipKeyGen(rlnInstance.value)
|
||||
check:
|
||||
membershipKeyPair.isSome
|
||||
let pk = membershipKeyPair.get().idCommitment.toUInt256()
|
||||
debug "membership commitment key", pk = pk
|
||||
|
||||
# test ------------------------------
|
||||
var fut = newFuture[void]()
|
||||
let s = await contractObj.subscribe(MemberRegistered, %*{"fromBlock": "0x0",
|
||||
"address": contractAddress}) do(
|
||||
pubkey: Uint256, index: Uint256){.raises: [Defect], gcsafe.}:
|
||||
try:
|
||||
debug "onRegister", pubkey = pubkey, index = index
|
||||
check:
|
||||
pubkey == pk
|
||||
fut.complete()
|
||||
except Exception as err:
|
||||
# chronos still raises exceptions which inherit directly from Exception
|
||||
doAssert false, err.msg
|
||||
do (err: CatchableError):
|
||||
echo "Error from subscription: ", err.msg
|
||||
|
||||
# register a member
|
||||
let tx = await contractObj.register(pk).send(value = MEMBERSHIP_FEE)
|
||||
debug "a member is registered", tx = tx
|
||||
|
||||
# wait for the event to be received
|
||||
await fut
|
||||
|
||||
# release resources -----------------------
|
||||
await web3.close()
|
||||
|
||||
asyncTest "insert a key to the membership contract":
|
||||
# preparation ------------------------------
|
||||
debug "ethereum client address", ETH_CLIENT
|
||||
let contractAddress = await uploadRLNContract(ETH_CLIENT)
|
||||
# connect to the eth client
|
||||
let web3 = await newWeb3(ETH_CLIENT)
|
||||
debug "web3 connected to", ETH_CLIENT
|
||||
|
||||
# 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
|
||||
|
||||
# prepare a contract sender to interact with it
|
||||
var sender = web3.contractSender(MembershipContract,
|
||||
contractAddress) # creates a Sender object with a web3 field and contract address of type Address
|
||||
|
||||
# send takes the following parameters, c: ContractCallBase, value = 0.u256, gas = 3000000'u64 gasPrice = 0
|
||||
# should use send proc for the contract functions that update the state of the contract
|
||||
let tx = await sender.register(20.u256).send(value = MEMBERSHIP_FEE) # value is the membership fee
|
||||
debug "The hash of registration tx: ", tx
|
||||
|
||||
# var members: array[2, uint256] = [20.u256, 21.u256]
|
||||
# debug "This is the batch registration result ", await sender.registerBatch(members).send(value = (members.len * MEMBERSHIP_FEE)) # value is the membership fee
|
||||
|
||||
let balance = await web3.provider.eth_getBalance(web3.defaultAccount, "latest")
|
||||
debug "Balance after registration: ", balance
|
||||
|
||||
await web3.close()
|
||||
debug "disconnected from", ETH_CLIENT
|
||||
|
||||
asyncTest "registration procedure":
|
||||
# preparation ------------------------------
|
||||
# deploy the contract
|
||||
let contractAddress = await uploadRLNContract(ETH_CLIENT)
|
||||
|
||||
# prepare rln-relay peer inputs
|
||||
let
|
||||
web3 = await newWeb3(ETH_CLIENT)
|
||||
accounts = await web3.provider.eth_accounts()
|
||||
# choose one of the existing accounts for the rln-relay peer
|
||||
ethAccountAddress = accounts[0]
|
||||
await web3.close()
|
||||
|
||||
# create an RLN instance
|
||||
var rlnInstance = createRLNInstance()
|
||||
check:
|
||||
rlnInstance.isOk == true
|
||||
|
||||
# generate the membership keys
|
||||
let membershipKeyPair = membershipKeyGen(rlnInstance.value)
|
||||
check:
|
||||
membershipKeyPair.isSome
|
||||
|
||||
# test ------------------------------
|
||||
# initialize the WakuRLNRelay
|
||||
var rlnPeer = WakuRLNRelay(membershipKeyPair: membershipKeyPair.get(),
|
||||
membershipIndex: MembershipIndex(0),
|
||||
ethClientAddress: ETH_CLIENT,
|
||||
ethAccountAddress: ethAccountAddress,
|
||||
membershipContractAddress: contractAddress)
|
||||
|
||||
# register the rln-relay peer to the membership contract
|
||||
let is_successful = await rlnPeer.register()
|
||||
check:
|
||||
is_successful
|
||||
|
||||
asyncTest "mounting waku rln-relay":
|
||||
# preparation ------------------------------
|
||||
let
|
||||
nodeKey = crypto.PrivateKey.random(Secp256k1, rng[])[]
|
||||
node = WakuNode.new(nodeKey, ValidIpAddress.init("0.0.0.0"),
|
||||
Port(60000))
|
||||
await node.start()
|
||||
|
||||
# deploy the contract
|
||||
let membershipContractAddress = await uploadRLNContract(ETH_CLIENT)
|
||||
|
||||
# prepare rln-relay inputs
|
||||
let
|
||||
web3 = await newWeb3(ETH_CLIENT)
|
||||
accounts = await web3.provider.eth_accounts()
|
||||
# choose one of the existing account for the rln-relay peer
|
||||
ethAccountAddress = accounts[9]
|
||||
await web3.close()
|
||||
|
||||
# create current peer's pk
|
||||
var rlnInstance = createRLNInstance()
|
||||
check:
|
||||
rlnInstance.isOk == true
|
||||
var rln = rlnInstance.value
|
||||
# generate a key pair
|
||||
var keypair = rln.membershipKeyGen()
|
||||
doAssert(keypair.isSome())
|
||||
|
||||
# current peer index in the Merkle tree
|
||||
let index = uint(5)
|
||||
|
||||
# Create a group of 10 members
|
||||
var group = newSeq[IDCommitment]()
|
||||
for i in 0..10:
|
||||
var member_is_added: bool = false
|
||||
if (uint(i) == index):
|
||||
# insert the current peer's pk
|
||||
group.add(keypair.get().idCommitment)
|
||||
member_is_added = rln.insertMember(keypair.get().idCommitment)
|
||||
doAssert(member_is_added)
|
||||
debug "member key", key = keypair.get().idCommitment.toHex
|
||||
else:
|
||||
var memberKeypair = rln.membershipKeyGen()
|
||||
doAssert(memberKeypair.isSome())
|
||||
group.add(memberKeypair.get().idCommitment)
|
||||
member_is_added = rln.insertMember(memberKeypair.get().idCommitment)
|
||||
doAssert(member_is_added)
|
||||
debug "member key", key = memberKeypair.get().idCommitment.toHex
|
||||
let expectedRoot = rln.getMerkleRoot().value().toHex
|
||||
debug "expected root ", expectedRoot
|
||||
|
||||
# test ------------------------------
|
||||
# start rln-relay
|
||||
node.mountRelay(@[RLNRELAY_PUBSUB_TOPIC])
|
||||
await node.mountRlnRelay(ethClientAddrOpt = some(EthClient),
|
||||
ethAccAddrOpt = some(ethAccountAddress),
|
||||
memContractAddOpt = some(membershipContractAddress),
|
||||
groupOpt = some(group),
|
||||
memKeyPairOpt = some(keypair.get()),
|
||||
memIndexOpt = some(index),
|
||||
pubsubTopic = RLNRELAY_PUBSUB_TOPIC,
|
||||
contentTopic = RLNRELAY_CONTENT_TOPIC)
|
||||
let calculatedRoot = node.wakuRlnRelay.rlnInstance.getMerkleRoot().value().toHex
|
||||
debug "calculated root ", calculatedRoot
|
||||
|
||||
check:
|
||||
expectedRoot == calculatedRoot
|
||||
|
||||
await node.stop()
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
# libtool - Provide generalized library-building support services.
|
||||
# Generated automatically by config.status (libbacktrace) version-unused
|
||||
# Libtool was configured on host fv-az275-407:
|
||||
# Libtool was configured on host fv-az193-59:
|
||||
# NOTE: Changes made to this file will be lost: look at ltmain.sh.
|
||||
#
|
||||
# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2003, 2004, 2005,
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user