Skip to content

Commit

Permalink
feat(solidity): Add ValidatorsAccumulator.sol
Browse files Browse the repository at this point in the history
  • Loading branch information
Dimo99 committed Jun 29, 2023
1 parent a2b2458 commit 2882898
Show file tree
Hide file tree
Showing 8 changed files with 1,334 additions and 63 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ jobs:
- name: Run Verifier in Cosmos - Relayer test
run: nix develop -c yarn test './tests/cosmosLightClient/test-verifier-in-cosmos-relay.ts'

- name: Run Solidity Validators accumulator tests
run: nix develop -c make test-validator-accumulator

# - name: Run Verifier in EOS test
# run: nix develop -c yarn test './tests/eosLightClient/test-verifier-in-EOS.ts'

Expand Down
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ build-relay-image:

docker build -t relayimage -f Dockerfile.relay .

test-validator-accumulator: yarn-check
cd beacon-light-client/solidity && \
yarn hardhat test test/ValidatorAccumulator.test.ts

evm-simulation: yarn-check
cd beacon-light-client/solidity && \
yarn hardhat test test/BeaconLightClientReadyProofs.test.ts
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ function getValidatorTree(validator, ssz): Tree {
return new Tree(validatorView.node);
}

function calculateValidatorsAccumulator(
// TODO: fix so it matches the test in beacon-light-client/solidity
export function calculateValidatorsAccumulator(
validatorsPubkeys: Uint8Array[],
eth1DepositIndexes: BigInt[],
) {
Expand All @@ -41,7 +42,7 @@ function calculateValidatorsAccumulator(
);
}

const validatorsAccumulator = hexToBytes(hashTreeRoot(leaves));
const validatorsAccumulator = hexToBytes(hashTreeRoot(leaves, leaves.length));

return bytesToBinaryArray(validatorsAccumulator);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,69 +6,129 @@ A Merkle tree accumulator is a binary tree of hashes, which is used for efficien
This is a sample solidity implementation of the `validatorsAccumulator`

```
// The depth of the validator accumulator tree
uint constant VALIDATOR_ACCUMULATOR_TREE_DEPTH = 32;
// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
// This interface is designed to be compatible with the Vyper version.
/// @notice This is the Ethereum 2.0 deposit contract interface.
/// For more information see the Phase 0 specification under https://github.com/ethereum/eth2.0-specs
interface IDepositContract {
/// @notice A processed deposit event.
event DepositEvent(
bytes pubkey,
bytes withdrawal_credentials,
bytes amount,
bytes signature,
bytes index
);
/// @notice Submit a Phase 0 DepositData object.
/// @param pubkey A BLS12-381 public key.
/// @param withdrawal_credentials Commitment to a public key for withdrawals.
/// @param signature A BLS12-381 signature.
/// @param deposit_data_root The SHA-256 hash of the SSZ-encoded DepositData object.
/// Used as a protection against malformed input.
function deposit(
bytes calldata pubkey,
bytes calldata withdrawal_credentials,
bytes calldata signature,
bytes32 deposit_data_root
) external payable;
/// @notice Query the current deposit root hash.
/// @return The deposit root hash.
function get_deposit_root() external view returns (bytes32);
/// @notice Query the current deposit count.
/// @return The deposit count encoded as a little endian 64-bit number.
function get_deposit_count() external view returns (bytes memory);
}
// An array to hold the branch hashes for the Merkle tree
bytes32[VALIDATOR_ACCUMULATOR_TREE_DEPTH] branch;
contract ValidatorsAccumulator {
address depositAddress;
// A counter for the total number of validators
uint validators_count;
// The depth of the validator accumulator tree
uint constant VALIDATOR_ACCUMULATOR_TREE_DEPTH = 32;
constructor() {
// Compute hashes in empty Merkle tree
for (uint height = 0; height < DEPOSIT_CONTRACT_TREE_DEPTH - 1; height++)
zero_hashes[height + 1] = sha256(abi.encodePacked(zero_hashes[height], zero_hashes[height]));
}
// An array to hold the branch hashes for the Merkle tree
bytes32[VALIDATOR_ACCUMULATOR_TREE_DEPTH] branch;
bytes32[VALIDATOR_ACCUMULATOR_TREE_DEPTH] zero_hashes;
// A counter for the total number of validators
uint validators_count;
constructor(address _depositAddress) {
depositAddress = _depositAddress;
// Function to calculate and return the Merkle accumulator root of the validators
function get_validators_accumulator() override external view returns (bytes32) {
// Compute hashes in empty Merkle tree
for (
uint height = 0;
height < VALIDATOR_ACCUMULATOR_TREE_DEPTH - 1;
height++
)
zero_hashes[height + 1] = sha256(
abi.encodePacked(zero_hashes[height], zero_hashes[height])
);
}
// Function to calculate and return the Merkle accumulator root of the validators
function get_validators_accumulator() external view returns (bytes32) {
bytes32 node;
uint size = validators_count;
// Calculate the Merkle accumulator root
for (uint height = 0; height < VALIDATOR_ACCUMULATOR_TREE_DEPTH; height++) {
// This if-else structure supports tree balancing
// If size is odd, the new node will be hashed with the previous node on this level
// If size is even, the new node will be hashed with a predefined zero hash
if ((size & 1) == 1)
node = sha256(abi.encodePacked(branch[height], node));
else
node = sha256(abi.encodePacked(node, zero_hashes[height]));
size /= 2;
// This if-else structure supports tree balancing
// If size is odd, the new node will be hashed with the previous node on this level
// If size is even, the new node will be hashed with a predefined zero hash
if ((size & 1) == 1)
node = sha256(abi.encodePacked(branch[height], node));
else node = sha256(abi.encodePacked(node, zero_hashes[height]));
size /= 2;
}
return node;
}
// Function to handle deposits from validators
function deposit(
bytes calldata pubkey,
bytes calldata withdrawal_credentials,
bytes calldata signature,
bytes32 deposit_data_root) {
// Perform the deposit using the DepositContract
IDepositContract(depositAddress).deposit(pubkey, withdrawal_credentials, signature, deposit_data_root);
// Get the deposit count and increase the validator count
bytes deposit_index = IDepositContract(depositAddress).get_deposit_count();
validators_count += 1;
// Create a node for the validator
bytes32 node = sha256(abi.encodePacked(pubkey, deposit_index));
// Insert the node into the Merkle accumulator tree
uint size = validators_count;
for(uint height = 0; height < VALIDATOR_ACCUMULATOR_TREE_DEPTH; height++) {
if ((size & 1) == 1) {
branch[height] = node;
return;
}
// Function to handle deposits from validators
function deposit(
bytes calldata pubkey,
bytes calldata withdrawal_credentials,
bytes calldata signature,
bytes32 deposit_data_root
) public payable {
// Perform the deposit using the DepositContract
IDepositContract(depositAddress).deposit{value: msg.value}(
pubkey,
withdrawal_credentials,
signature,
deposit_data_root
);
// Get the deposit count and increase the validator count
bytes memory deposit_index = IDepositContract(depositAddress)
.get_deposit_count();
validators_count += 1;
// Create a node for the validator
bytes32 node = sha256(abi.encodePacked(pubkey, deposit_index));
// Insert the node into the Merkle accumulator tree
uint size = validators_count;
for (uint height = 0; height < VALIDATOR_ACCUMULATOR_TREE_DEPTH; height++) {
if ((size & 1) == 1) {
branch[height] = node;
return;
}
node = sha256(abi.encodePacked(branch[height], node));
size /= 2;
}
node = sha256(abi.encodePacked(branch[height], node));
size /= 2;
}
}
}
```

In the deposit function, each validator's public key and their Eth1 deposit index are packed together and hashed to form a node. This node represents the validator in the Merkle tree.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;

// This interface is designed to be compatible with the Vyper version.
/// @notice This is the Ethereum 2.0 deposit contract interface.
/// For more information see the Phase 0 specification under https://github.com/ethereum/eth2.0-specs
interface IDepositContract {
/// @notice A processed deposit event.
event DepositEvent(
bytes pubkey,
bytes withdrawal_credentials,
bytes amount,
bytes signature,
bytes index
);

/// @notice Submit a Phase 0 DepositData object.
/// @param pubkey A BLS12-381 public key.
/// @param withdrawal_credentials Commitment to a public key for withdrawals.
/// @param signature A BLS12-381 signature.
/// @param deposit_data_root The SHA-256 hash of the SSZ-encoded DepositData object.
/// Used as a protection against malformed input.
function deposit(
bytes calldata pubkey,
bytes calldata withdrawal_credentials,
bytes calldata signature,
bytes32 deposit_data_root
) external payable;

/// @notice Query the current deposit root hash.
/// @return The deposit root hash.
function get_deposit_root() external view returns (bytes32);

/// @notice Query the current deposit count.
/// @return The deposit count encoded as a little endian 64-bit number.
function get_deposit_count() external view returns (bytes memory);
}

contract ValidatorsAccumulator {
address depositAddress;

// The depth of the validator accumulator tree
uint constant VALIDATOR_ACCUMULATOR_TREE_DEPTH = 32;

// An array to hold the branch hashes for the Merkle tree
bytes32[VALIDATOR_ACCUMULATOR_TREE_DEPTH] branch;

bytes32[VALIDATOR_ACCUMULATOR_TREE_DEPTH] zero_hashes;

// A counter for the total number of validators
uint validators_count;

constructor(address _depositAddress) {
depositAddress = _depositAddress;

// Compute hashes in empty Merkle tree
for (
uint height = 0;
height < VALIDATOR_ACCUMULATOR_TREE_DEPTH - 1;
height++
)
zero_hashes[height + 1] = sha256(
abi.encodePacked(zero_hashes[height], zero_hashes[height])
);
}

// Function to calculate and return the Merkle accumulator root of the validators
function get_validators_accumulator() external view returns (bytes32) {
bytes32 node;
uint size = validators_count;

// Calculate the Merkle accumulator root
for (uint height = 0; height < VALIDATOR_ACCUMULATOR_TREE_DEPTH; height++) {
// This if-else structure supports tree balancing
// If size is odd, the new node will be hashed with the previous node on this level
// If size is even, the new node will be hashed with a predefined zero hash
if ((size & 1) == 1)
node = sha256(abi.encodePacked(branch[height], node));
else node = sha256(abi.encodePacked(node, zero_hashes[height]));

size /= 2;
}

return node;
}

// Function to handle deposits from validators
function deposit(
bytes calldata pubkey,
bytes calldata withdrawal_credentials,
bytes calldata signature,
bytes32 deposit_data_root
) public payable {
// Perform the deposit using the DepositContract

IDepositContract(depositAddress).deposit{value: msg.value}(
pubkey,
withdrawal_credentials,
signature,
deposit_data_root
);

// Get the deposit count and increase the validator count
bytes memory deposit_index = IDepositContract(depositAddress)
.get_deposit_count();
validators_count += 1;

// Create a node for the validator
bytes32 node = sha256(abi.encodePacked(pubkey, deposit_index));

// Insert the node into the Merkle accumulator tree
uint size = validators_count;
for (uint height = 0; height < VALIDATOR_ACCUMULATOR_TREE_DEPTH; height++) {
if ((size & 1) == 1) {
branch[height] = node;
return;
}
node = sha256(abi.encodePacked(branch[height], node));
size /= 2;
}
}
}
33 changes: 24 additions & 9 deletions beacon-light-client/solidity/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,26 @@ if (!/^0x[0-9a-fA-F]{64}$/.test(conf.USER_PRIVATE_KEY ?? '')) {

export default {
solidity: {
version: '0.8.9',
settings: {
optimizer: {
enabled: true,
runs: 200,
compilers: [
{
version: '0.8.9',
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
},
{
version: '0.8.18',
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
],
},
defaultNetwork: 'hardhat',
networks: {
Expand All @@ -54,8 +67,10 @@ export default {
accounts: [conf.LOCAL_HARDHAT_PRIVATE_KEY],
},
hardhat: {
blockGasLimit: 30000000,
allowUnlimitedContractSize: true,
forking: {
url: 'https://eth.llamarpc.com',
blockNumber: 17578101,
},
},
ropsten: {
url: `https://ropsten.infura.io/v3/${conf.INFURA_API_KEY}`,
Expand Down Expand Up @@ -118,7 +133,7 @@ export default {
accounts: [conf.USER_PRIVATE_KEY],
},
gnosis: {
url: `https://gnosis.api.onfinality.io/public`,
url: `https://rpc.gnosis.gateway.fm`,
accounts: [conf.USER_PRIVATE_KEY],
},
},
Expand Down
Loading

0 comments on commit 2882898

Please sign in to comment.