|
| 1 | +// SPDX-License-Identifier: Apache-2.0 |
| 2 | +pragma solidity ^0.8.19; |
| 3 | + |
| 4 | +import "openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol"; |
| 5 | + |
| 6 | +import "../Constants.sol"; |
| 7 | +import "../DataRootTuple.sol"; |
| 8 | +import "../Blobstream.sol"; |
| 9 | +import "../lib/tree/binary/BinaryMerkleProof.sol"; |
| 10 | + |
| 11 | +import "ds-test/test.sol"; |
| 12 | + |
| 13 | +interface CheatCodes { |
| 14 | + function addr(uint256 privateKey) external returns (address); |
| 15 | + function sign(uint256 privateKey, bytes32 digest) external returns (uint8 v, bytes32 r, bytes32 s); |
| 16 | + function deriveKey(string calldata, string calldata, uint32) external returns (uint256); |
| 17 | +} |
| 18 | + |
| 19 | +/// @notice Example command to run the benchmark: |
| 20 | +/// `forge test --match-test testBenchmarkSubmitDataRootTupleRoot -vvvvvv --gas-report`. |
| 21 | +/// To change the validator set size, change the `numberOfValidators` constant. |
| 22 | +/// To make custom calculations of the gas, you can use the `gasleft()` solidity |
| 23 | +/// built-in function. |
| 24 | +/// The following answer has some insights on using that: |
| 25 | +/// https://ethereum.stackexchange.com/a/132325/65649 |
| 26 | +/// The gas estimations might not be accurate to the real cost in a real network, |
| 27 | +/// and that's because foundry doesn't track calldata cost. source: |
| 28 | +/// https://github.com/foundry-rs/foundry/issues/3475#issuecomment-1469940917 |
| 29 | +/// To have accurate results, make sure to add the following costs: |
| 30 | +/// A byte of calldata costs either 4 gas (if it is zero) or 16 gas (if it is any other value). |
| 31 | +contract Benchmark is DSTest { |
| 32 | + uint256 private constant numberOfValidators = 100; |
| 33 | + uint256 private constant numberOfSigners = 30; |
| 34 | + |
| 35 | + // Private keys used for test signatures. |
| 36 | + uint256[] private privateKeys; |
| 37 | + |
| 38 | + Blobstream private bridge; |
| 39 | + |
| 40 | + Validator[] private validators; |
| 41 | + uint256 private totalValidatorPower = 1000000; |
| 42 | + uint256 private dataTupleRootNonce = 0; |
| 43 | + |
| 44 | + // Set up Foundry cheatcodes. |
| 45 | + CheatCodes cheats = CheatCodes(HEVM_ADDRESS); |
| 46 | + |
| 47 | + function setUp() public { |
| 48 | + uint256 initialVelsetNonce = 0; |
| 49 | + privateKeys = derivePrivateKeys(numberOfValidators); |
| 50 | + validators = initializeValidators(privateKeys); |
| 51 | + |
| 52 | + bytes32 hash = computeValidatorSetHash(validators); |
| 53 | + bridge = new Blobstream(); |
| 54 | + bridge.initialize(initialVelsetNonce, (2 * totalValidatorPower) / 3, hash); |
| 55 | + } |
| 56 | + |
| 57 | + function testBenchmarkSubmitDataRootTupleRoot() public { |
| 58 | + uint256 initialVelsetNonce = 0; |
| 59 | + uint256 nonce = 1; |
| 60 | + |
| 61 | + // 32 bytes, chosen at random. |
| 62 | + bytes32 newTupleRoot = 0x0de92bac0b356560d821f8e7b6f5c9fe4f3f88f6c822283efd7ab51ad56a640e; |
| 63 | + bytes32 newDataRootTupleRoot = domainSeparateDataRootTupleRoot(nonce, newTupleRoot); |
| 64 | + |
| 65 | + // Signature for the update. |
| 66 | + Signature[] memory sigs = new Signature[](numberOfValidators); |
| 67 | + bytes32 digest_eip191 = ECDSA.toEthSignedMessageHash(newDataRootTupleRoot); |
| 68 | + uint256 threshold = 2 * totalValidatorPower / 3; |
| 69 | + uint256 cumulatedPower = 0; |
| 70 | + for (uint256 i = 0; i < numberOfValidators; i++) { |
| 71 | + if (cumulatedPower > threshold) { |
| 72 | + break; |
| 73 | + } |
| 74 | + (uint8 v, bytes32 r, bytes32 s) = cheats.sign(privateKeys[i], digest_eip191); |
| 75 | + sigs[i] = Signature(v, r, s); |
| 76 | + cumulatedPower += validators[i].power; |
| 77 | + } |
| 78 | + |
| 79 | + // these are called here so that they're part of the gas report. |
| 80 | + // uint256 currentPowerThreshold = (2 * votingPower * numberOfValidators) / 3; |
| 81 | + // bytes32 currentValidatorSetHash = bridge.computeValidatorSetHash(validators); |
| 82 | + // bridge.domainSeparateValidatorSetHash(nonce, currentPowerThreshold, currentValidatorSetHash); |
| 83 | + // bridge.checkValidatorSignatures(validators, sigs, newDataRootTupleRoot, currentPowerThreshold); |
| 84 | + |
| 85 | + bridge.submitDataRootTupleRoot(nonce, initialVelsetNonce, newTupleRoot, validators, sigs); |
| 86 | + } |
| 87 | + |
| 88 | + function computeValidatorSetHash(Validator[] memory _validators) private pure returns (bytes32) { |
| 89 | + return keccak256(abi.encode(_validators)); |
| 90 | + } |
| 91 | + |
| 92 | + function domainSeparateDataRootTupleRoot(uint256 _nonce, bytes32 _dataRootTupleRoot) |
| 93 | + private |
| 94 | + pure |
| 95 | + returns (bytes32) |
| 96 | + { |
| 97 | + bytes32 c = keccak256(abi.encode(DATA_ROOT_TUPLE_ROOT_DOMAIN_SEPARATOR, _nonce, _dataRootTupleRoot)); |
| 98 | + |
| 99 | + return c; |
| 100 | + } |
| 101 | + |
| 102 | + function derivePrivateKeys(uint256 count) private returns (uint256[] memory) { |
| 103 | + string memory mnemonic = "test test test test test test test test test test test junk"; |
| 104 | + uint256[] memory keys = new uint256[](count); |
| 105 | + for (uint32 i = 0; i < count; i++) { |
| 106 | + keys[i] = cheats.deriveKey(mnemonic, "m/44'/60'/0'/0", i); |
| 107 | + } |
| 108 | + return keys; |
| 109 | + } |
| 110 | + |
| 111 | + function initializeValidators(uint256[] memory keys) private returns (Validator[] memory) { |
| 112 | + Validator[] memory vs = new Validator[](keys.length); |
| 113 | + uint256 threshold = 2 * totalValidatorPower / 3; |
| 114 | + uint256 primaryPower = threshold / (numberOfSigners - 1); |
| 115 | + uint256 secondaryPower = (totalValidatorPower - threshold) / (numberOfValidators - numberOfSigners + 1); |
| 116 | + for (uint256 i = 0; i < keys.length; i++) { |
| 117 | + if (i < numberOfSigners) { |
| 118 | + vs[i] = Validator(cheats.addr(keys[i]), primaryPower); |
| 119 | + } else { |
| 120 | + vs[i] = Validator(cheats.addr(keys[i]), secondaryPower); |
| 121 | + } |
| 122 | + } |
| 123 | + return vs; |
| 124 | + } |
| 125 | +} |
0 commit comments