From ba7650828d72fb326cda31d22fea667fde1aee5f Mon Sep 17 00:00:00 2001 From: rachid Date: Wed, 4 Oct 2023 23:08:34 +0200 Subject: [PATCH 1/6] feat: add the ability to run benchmarks on the QGB contract --- foundry.toml | 1 + src/test/QuantumGravityBridgeBenchmark.t.sol | 105 +++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 src/test/QuantumGravityBridgeBenchmark.t.sol diff --git a/foundry.toml b/foundry.toml index ec54b755..16088832 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,3 +1,4 @@ [profile.default] solc_version = "0.8.20" via_ir = true +gas_reports = ["*"] diff --git a/src/test/QuantumGravityBridgeBenchmark.t.sol b/src/test/QuantumGravityBridgeBenchmark.t.sol new file mode 100644 index 00000000..973d3ef7 --- /dev/null +++ b/src/test/QuantumGravityBridgeBenchmark.t.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.19; + +import "openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol"; + +import "../Constants.sol"; +import "../DataRootTuple.sol"; +import "../QuantumGravityBridge.sol"; +import "../lib/tree/binary/BinaryMerkleProof.sol"; + +import "ds-test/test.sol"; + +interface CheatCodes { + function addr(uint256 privateKey) external returns (address); + function sign(uint256 privateKey, bytes32 digest) external returns (uint8 v, bytes32 r, bytes32 s); + function deriveKey(string calldata, string calldata, uint32) external returns (uint256); +} + +/// @notice In order for the benchmark to work, the methods inside the bridge +/// contract should be public. Also, the benchmark test should be prefixed +/// with "test". Finally, the following `gas_reports = ["*"]` should be added +/// to "foundry.toml". +/// Example command to run the benchmark: +/// `forge test --match-test testBenchmarkSubmitDataRootTupleRoot -vvvvvv --gas-report`. +/// To change the validator set size, change the `numberOfValidators` constant. +/// To make custom calculations of the gas, you can use the `gasleft()` solidity +/// built-in function. +/// The following answer has some insights on using that: +/// https://ethereum.stackexchange.com/a/132325/65649 +/// The gas estimations might not be accurate to the real cost in a real network, +/// and that's because foundry doesn't track calldata cost. source: +/// https://github.com/foundry-rs/foundry/issues/3475#issuecomment-1469940917 +/// To have accurate results, make sure to add the following costs: +/// A byte of calldata costs either 4 gas (if it is zero) or 16 gas (if it is any other value). +contract Benchmark is DSTest { + uint256 private constant numberOfValidators = 70; + + // Private keys used for test signatures. + uint256[] private privateKeys; + + QuantumGravityBridge private bridge; + + Validator[] private validators; + uint256 private votingPower = 5000; + uint256 private dataTupleRootNonce = 0; + + // Set up Foundry cheatcodes. + CheatCodes cheats = CheatCodes(HEVM_ADDRESS); + + function setUp() public { + uint256 initialVelsetNonce = 0; + privateKeys = derivePrivateKeys(numberOfValidators); + validators = initializeValidators(privateKeys); + + bytes32 hash = computeValidatorSetHash(validators); + bridge = new QuantumGravityBridge(); + bridge.initialize(initialVelsetNonce, (2 * votingPower * numberOfValidators) / 3, hash); + } + + function testBenchmarkSubmitDataRootTupleRoot() public { + uint256 initialVelsetNonce = 0; + uint256 nonce = 1; + + // 32 bytes, chosen at random. + bytes32 newTupleRoot = 0x0de92bac0b356560d821f8e7b6f5c9fe4f3f88f6c822283efd7ab51ad56a640e; + bytes32 newDataRootTupleRoot = bridge.domainSeparateDataRootTupleRoot(nonce, newTupleRoot); + + // Signature for the update. + Signature[] memory sigs = new Signature[](numberOfValidators); + bytes32 digest_eip191 = ECDSA.toEthSignedMessageHash(newDataRootTupleRoot); + for (uint256 i = 0; i < numberOfValidators; i++) { + (uint8 v, bytes32 r, bytes32 s) = cheats.sign(privateKeys[i], digest_eip191); + sigs[i] = Signature(v, r, s); + } + + // these are called here so that they're part of the gas report. + uint256 currentPowerThreshold = (2 * votingPower * numberOfValidators) / 3; + bytes32 currentValidatorSetHash = bridge.computeValidatorSetHash(validators); + bridge.domainSeparateValidatorSetHash(nonce, currentPowerThreshold, currentValidatorSetHash); + bridge.checkValidatorSignatures(validators, sigs, newDataRootTupleRoot, currentPowerThreshold); + + bridge.submitDataRootTupleRoot(nonce, initialVelsetNonce, newTupleRoot, validators, sigs); + } + + function computeValidatorSetHash(Validator[] memory _validators) private pure returns (bytes32) { + return keccak256(abi.encode(_validators)); + } + + function derivePrivateKeys(uint256 count) private returns (uint256[] memory) { + string memory mnemonic = "test test test test test test test test test test test junk"; + uint256[] memory keys = new uint256[](count); + for (uint32 i = 0; i < count; i++) { + keys[i] = cheats.deriveKey(mnemonic, "m/44'/60'/0'/0", i); + } + return keys; + } + + function initializeValidators(uint256[] memory keys) private returns (Validator[] memory) { + Validator[] memory vs = new Validator[](keys.length); + for (uint256 i = 0; i < keys.length; i++) { + vs[i] = Validator(cheats.addr(keys[i]), votingPower); + } + return vs; + } +} From 3b20de7a802af278a3e4af77c8df26cecf7137d1 Mon Sep 17 00:00:00 2001 From: rachid Date: Wed, 4 Oct 2023 23:13:04 +0200 Subject: [PATCH 2/6] chore: rename benchmark test not to be run --- src/test/QuantumGravityBridgeBenchmark.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/QuantumGravityBridgeBenchmark.t.sol b/src/test/QuantumGravityBridgeBenchmark.t.sol index 973d3ef7..ed5ac9c8 100644 --- a/src/test/QuantumGravityBridgeBenchmark.t.sol +++ b/src/test/QuantumGravityBridgeBenchmark.t.sol @@ -57,7 +57,7 @@ contract Benchmark is DSTest { bridge.initialize(initialVelsetNonce, (2 * votingPower * numberOfValidators) / 3, hash); } - function testBenchmarkSubmitDataRootTupleRoot() public { + function benchmarkSubmitDataRootTupleRoot() public { uint256 initialVelsetNonce = 0; uint256 nonce = 1; From b4db380ceb830bc11f691ba8288dcb9b5f2a3f91 Mon Sep 17 00:00:00 2001 From: rachid Date: Wed, 4 Oct 2023 23:16:08 +0200 Subject: [PATCH 3/6] chore: make the benhcmark always run --- src/test/QuantumGravityBridgeBenchmark.t.sol | 30 ++++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/test/QuantumGravityBridgeBenchmark.t.sol b/src/test/QuantumGravityBridgeBenchmark.t.sol index ed5ac9c8..0b1933f5 100644 --- a/src/test/QuantumGravityBridgeBenchmark.t.sol +++ b/src/test/QuantumGravityBridgeBenchmark.t.sol @@ -16,11 +16,7 @@ interface CheatCodes { function deriveKey(string calldata, string calldata, uint32) external returns (uint256); } -/// @notice In order for the benchmark to work, the methods inside the bridge -/// contract should be public. Also, the benchmark test should be prefixed -/// with "test". Finally, the following `gas_reports = ["*"]` should be added -/// to "foundry.toml". -/// Example command to run the benchmark: +/// @notice Example command to run the benchmark: /// `forge test --match-test testBenchmarkSubmitDataRootTupleRoot -vvvvvv --gas-report`. /// To change the validator set size, change the `numberOfValidators` constant. /// To make custom calculations of the gas, you can use the `gasleft()` solidity @@ -33,7 +29,7 @@ interface CheatCodes { /// To have accurate results, make sure to add the following costs: /// A byte of calldata costs either 4 gas (if it is zero) or 16 gas (if it is any other value). contract Benchmark is DSTest { - uint256 private constant numberOfValidators = 70; + uint256 private constant numberOfValidators = 1; // Private keys used for test signatures. uint256[] private privateKeys; @@ -57,13 +53,13 @@ contract Benchmark is DSTest { bridge.initialize(initialVelsetNonce, (2 * votingPower * numberOfValidators) / 3, hash); } - function benchmarkSubmitDataRootTupleRoot() public { + function testBenchmarkSubmitDataRootTupleRoot() public { uint256 initialVelsetNonce = 0; uint256 nonce = 1; // 32 bytes, chosen at random. bytes32 newTupleRoot = 0x0de92bac0b356560d821f8e7b6f5c9fe4f3f88f6c822283efd7ab51ad56a640e; - bytes32 newDataRootTupleRoot = bridge.domainSeparateDataRootTupleRoot(nonce, newTupleRoot); + bytes32 newDataRootTupleRoot = domainSeparateDataRootTupleRoot(nonce, newTupleRoot); // Signature for the update. Signature[] memory sigs = new Signature[](numberOfValidators); @@ -74,10 +70,10 @@ contract Benchmark is DSTest { } // these are called here so that they're part of the gas report. - uint256 currentPowerThreshold = (2 * votingPower * numberOfValidators) / 3; - bytes32 currentValidatorSetHash = bridge.computeValidatorSetHash(validators); - bridge.domainSeparateValidatorSetHash(nonce, currentPowerThreshold, currentValidatorSetHash); - bridge.checkValidatorSignatures(validators, sigs, newDataRootTupleRoot, currentPowerThreshold); +// uint256 currentPowerThreshold = (2 * votingPower * numberOfValidators) / 3; +// bytes32 currentValidatorSetHash = bridge.computeValidatorSetHash(validators); +// bridge.domainSeparateValidatorSetHash(nonce, currentPowerThreshold, currentValidatorSetHash); +// bridge.checkValidatorSignatures(validators, sigs, newDataRootTupleRoot, currentPowerThreshold); bridge.submitDataRootTupleRoot(nonce, initialVelsetNonce, newTupleRoot, validators, sigs); } @@ -86,6 +82,16 @@ contract Benchmark is DSTest { return keccak256(abi.encode(_validators)); } + function domainSeparateDataRootTupleRoot(uint256 _nonce, bytes32 _dataRootTupleRoot) + private + pure + returns (bytes32) + { + bytes32 c = keccak256(abi.encode(DATA_ROOT_TUPLE_ROOT_DOMAIN_SEPARATOR, _nonce, _dataRootTupleRoot)); + + return c; + } + function derivePrivateKeys(uint256 count) private returns (uint256[] memory) { string memory mnemonic = "test test test test test test test test test test test junk"; uint256[] memory keys = new uint256[](count); From 7aa86412bbee6defd880f74317e964cf6bf5d0b2 Mon Sep 17 00:00:00 2001 From: rachid Date: Wed, 4 Oct 2023 23:17:13 +0200 Subject: [PATCH 4/6] chore: fmt --- src/test/QuantumGravityBridgeBenchmark.t.sol | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/test/QuantumGravityBridgeBenchmark.t.sol b/src/test/QuantumGravityBridgeBenchmark.t.sol index 0b1933f5..9ae994e1 100644 --- a/src/test/QuantumGravityBridgeBenchmark.t.sol +++ b/src/test/QuantumGravityBridgeBenchmark.t.sol @@ -70,10 +70,10 @@ contract Benchmark is DSTest { } // these are called here so that they're part of the gas report. -// uint256 currentPowerThreshold = (2 * votingPower * numberOfValidators) / 3; -// bytes32 currentValidatorSetHash = bridge.computeValidatorSetHash(validators); -// bridge.domainSeparateValidatorSetHash(nonce, currentPowerThreshold, currentValidatorSetHash); -// bridge.checkValidatorSignatures(validators, sigs, newDataRootTupleRoot, currentPowerThreshold); + // uint256 currentPowerThreshold = (2 * votingPower * numberOfValidators) / 3; + // bytes32 currentValidatorSetHash = bridge.computeValidatorSetHash(validators); + // bridge.domainSeparateValidatorSetHash(nonce, currentPowerThreshold, currentValidatorSetHash); + // bridge.checkValidatorSignatures(validators, sigs, newDataRootTupleRoot, currentPowerThreshold); bridge.submitDataRootTupleRoot(nonce, initialVelsetNonce, newTupleRoot, validators, sigs); } @@ -83,9 +83,9 @@ contract Benchmark is DSTest { } function domainSeparateDataRootTupleRoot(uint256 _nonce, bytes32 _dataRootTupleRoot) - private - pure - returns (bytes32) + private + pure + returns (bytes32) { bytes32 c = keccak256(abi.encode(DATA_ROOT_TUPLE_ROOT_DOMAIN_SEPARATOR, _nonce, _dataRootTupleRoot)); From 9797baedaa539b5805250e97a00fa03988810ba4 Mon Sep 17 00:00:00 2001 From: rachid Date: Wed, 11 Oct 2023 01:43:42 +0200 Subject: [PATCH 5/6] chore: only sign the necessary number --- src/test/QuantumGravityBridgeBenchmark.t.sol | 22 ++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/test/QuantumGravityBridgeBenchmark.t.sol b/src/test/QuantumGravityBridgeBenchmark.t.sol index 9ae994e1..515bdbc1 100644 --- a/src/test/QuantumGravityBridgeBenchmark.t.sol +++ b/src/test/QuantumGravityBridgeBenchmark.t.sol @@ -29,7 +29,8 @@ interface CheatCodes { /// To have accurate results, make sure to add the following costs: /// A byte of calldata costs either 4 gas (if it is zero) or 16 gas (if it is any other value). contract Benchmark is DSTest { - uint256 private constant numberOfValidators = 1; + uint256 private constant numberOfValidators = 100; + uint256 private constant numberOfSigners = 30; // Private keys used for test signatures. uint256[] private privateKeys; @@ -37,7 +38,7 @@ contract Benchmark is DSTest { QuantumGravityBridge private bridge; Validator[] private validators; - uint256 private votingPower = 5000; + uint256 private totalValidatorPower = 1000000; uint256 private dataTupleRootNonce = 0; // Set up Foundry cheatcodes. @@ -50,7 +51,7 @@ contract Benchmark is DSTest { bytes32 hash = computeValidatorSetHash(validators); bridge = new QuantumGravityBridge(); - bridge.initialize(initialVelsetNonce, (2 * votingPower * numberOfValidators) / 3, hash); + bridge.initialize(initialVelsetNonce, (2 * totalValidatorPower) / 3, hash); } function testBenchmarkSubmitDataRootTupleRoot() public { @@ -64,9 +65,15 @@ contract Benchmark is DSTest { // Signature for the update. Signature[] memory sigs = new Signature[](numberOfValidators); bytes32 digest_eip191 = ECDSA.toEthSignedMessageHash(newDataRootTupleRoot); + uint256 threshold = 2 * totalValidatorPower / 3; + uint256 cumulatedPower = 0; for (uint256 i = 0; i < numberOfValidators; i++) { + if (cumulatedPower > threshold) { + break; + } (uint8 v, bytes32 r, bytes32 s) = cheats.sign(privateKeys[i], digest_eip191); sigs[i] = Signature(v, r, s); + cumulatedPower += validators[i].power; } // these are called here so that they're part of the gas report. @@ -103,8 +110,15 @@ contract Benchmark is DSTest { function initializeValidators(uint256[] memory keys) private returns (Validator[] memory) { Validator[] memory vs = new Validator[](keys.length); + uint256 threshold = 2 * totalValidatorPower / 3; + uint256 primaryPower = threshold / (numberOfSigners - 1); + uint256 secondaryPower = (totalValidatorPower - threshold) / (numberOfValidators - numberOfSigners + 1); for (uint256 i = 0; i < keys.length; i++) { - vs[i] = Validator(cheats.addr(keys[i]), votingPower); + if (i < numberOfSigners) { + vs[i] = Validator(cheats.addr(keys[i]), primaryPower); + } else { + vs[i] = Validator(cheats.addr(keys[i]), secondaryPower); + } } return vs; } From 37cd4366ed1861b2da3414d90e7a560039977bcc Mon Sep 17 00:00:00 2001 From: rachid Date: Thu, 12 Oct 2023 12:16:19 +0200 Subject: [PATCH 6/6] chore: rename test file to Blobstream prefix --- ...avityBridgeBenchmark.t.sol => BlobstreamBenchmark.t.sol} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename src/test/{QuantumGravityBridgeBenchmark.t.sol => BlobstreamBenchmark.t.sol} (97%) diff --git a/src/test/QuantumGravityBridgeBenchmark.t.sol b/src/test/BlobstreamBenchmark.t.sol similarity index 97% rename from src/test/QuantumGravityBridgeBenchmark.t.sol rename to src/test/BlobstreamBenchmark.t.sol index 515bdbc1..5fd31c51 100644 --- a/src/test/QuantumGravityBridgeBenchmark.t.sol +++ b/src/test/BlobstreamBenchmark.t.sol @@ -5,7 +5,7 @@ import "openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol"; import "../Constants.sol"; import "../DataRootTuple.sol"; -import "../QuantumGravityBridge.sol"; +import "../Blobstream.sol"; import "../lib/tree/binary/BinaryMerkleProof.sol"; import "ds-test/test.sol"; @@ -35,7 +35,7 @@ contract Benchmark is DSTest { // Private keys used for test signatures. uint256[] private privateKeys; - QuantumGravityBridge private bridge; + Blobstream private bridge; Validator[] private validators; uint256 private totalValidatorPower = 1000000; @@ -50,7 +50,7 @@ contract Benchmark is DSTest { validators = initializeValidators(privateKeys); bytes32 hash = computeValidatorSetHash(validators); - bridge = new QuantumGravityBridge(); + bridge = new Blobstream(); bridge.initialize(initialVelsetNonce, (2 * totalValidatorPower) / 3, hash); }