Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bridge gas optimisations #301

Merged
merged 27 commits into from
May 26, 2022
Merged

Bridge gas optimisations #301

merged 27 commits into from
May 26, 2022

Conversation

eth-r
Copy link
Contributor

@eth-r eth-r commented May 17, 2022

The bridge is mostly fairly close to the limit of achievable performance, but a few optimisations could be identified:

  • some hashing operations can be made more efficient
  • storage slots in BridgeState can be aligned and packed so that multiple accesses are minimised
  • some allocations can be avoided by adding at-functions in bitcoin-spv

All in all, single percent improvements can be made in some functions (particularly proof submissions) and rarely called operations involving storage (initialisation, updating parameters) become significantly cheaper

Refs #260

@pdyraga
Copy link
Member

pdyraga commented May 18, 2022

Let's also inspect the usage of bytes memory across the project. I was able to get some small savings for Bank.approveBalanceAndCall in 237b3c1 (PR #302) but I haven't gone through all bytes memory usages in the project to not run into conflicts with this PR.

1 similar comment
@pdyraga
Copy link
Member

pdyraga commented May 18, 2022

Let's also inspect the usage of bytes memory across the project. I was able to get some small savings for Bank.approveBalanceAndCall in 237b3c1 (PR #302) but I haven't gone through all bytes memory usages in the project to not run into conflicts with this PR.

@eth-r
Copy link
Contributor Author

eth-r commented May 18, 2022

I specifically checked bitcoin-spv for possible calldata optimisations and determined the following:

  • unless there is a way to call precompiled contracts with calldata instead of memory, ValidateSPV.validateHeaderChain cannot benefit from calldata
  • ValidateSPV.prove could theoretically use calldata for the intermediate nodes
  • the input and output vectors of proofs could theoretically use calldata
  • removing all memory copying in validateHeaderChain gave savings of around 2000 gas in all proofs (deposit, sweep, moving funds) so there is minor potential for savings from avoiding the copying of calldata into memory as well
  • without making bitcoin-spv calldata compatible there doesn't seem to be a benefit from using calldata more widely in the bridge

@pdyraga pdyraga added the ⛓️ solidity Solidity contracts label May 20, 2022
@pdyraga pdyraga added this to the solidity/v1.0.0 milestone May 20, 2022
@eth-r eth-r marked this pull request as ready for review May 25, 2022 14:27
pdyraga and others added 6 commits May 25, 2022 17:04
Using keccak256(keccak256(script), walletPubKeyHash) gives higher
performance in some circumstances. Update the key calculation method in
tests to match what is implemented in the contracts.
@pdyraga
Copy link
Member

pdyraga commented May 26, 2022

👀

tomaszslabon
tomaszslabon previously approved these changes May 26, 2022
pdyraga
pdyraga previously approved these changes May 26, 2022
);
// Make sure the actual output script matches either the P2PKH
// or P2WPKH format.
uint256 scriptLen = output.length - 8;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could note why it's - 8, something like:

// The output consists of an 8-byte value and a variable length script.
// To extract just the script, we ignore the first 8 bytes.
uint256 scriptLen = output.length - 8;

This could be helpful, especially that later we also slice32(8).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in #345 in 9aeb58c.

solidity/contracts/bridge/BridgeState.sol Show resolved Hide resolved
// Move movingFundsTxMaxTotalFee to the next storage slot for a more
// efficient variable layout
// slither-disable-next-line unused-state
bytes8 __depositAlignmentGap;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it actually moving movingFundsTxMaxTotalFee to the next slot? Shouldn't we use bytes16 instead?

Slot 1: Bank bank
Slot 2: IRelay relay | uint96 txProofDifficultyFactor
Slot 3: EcdsaWalletRegistry ecdsaWalletRegistry
Slot 4: address treasury | uint64 depositDustThreshold
Slot 5: uint64 depositTreasuryFeeDivisor | uint64 depositTxMaxFee | bytes8 __depositAlignmentGap | uint64 movingFundsTxMaxTotalFee

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this was bytes16, then the next alignment gap would match:
Slot 6: uint64 movingFundsTxMaxTotalFee | uint64 movingFundsDustThreshold | uint32 movingFundsTimeoutResetDelay | uint32 movingFundsTimeout
Slot 7: uint96 movingFundsTimeoutSlashingAmount
Slot 8: uint256 movingFundsTimeoutNotifierRewardMultiplier
Slot 9: uint64 movedFundsSweepTxMaxTotalFee | uint32 movedFundsSweepTimeout | uint96 movedFundsSweepTimeoutSlashingAmount | uint64 movedFundsSweepTimeoutNotifierRewardMultiplier
Slot 10: uint64 redemptionDustThreshold | uint64 redemptionTreasuryFeeDivisor | uint64 redemptionTxMaxFee | bytes8 __redemptionAlignmentGap

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in #345 in 9d15d6e.

// Time after which the redemption request can be reported as
// timed out. It is counted from the moment when the redemption
// request was created via `requestRedemption` call. Reported
// timed out requests are cancelled and locked TBTC is returned
// to the redeemer in full amount.
uint256 redemptionTimeout;
uint64 redemptionTimeout;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All other timeouts (movingFundsTimeout, movedFundsSweepTimeout, fraudChallengeDefeatTimeout) are uint32. We could also make redemptionTimeout uint32 for consistency. I think it does not break the layout we want to achieve because the next uint96 will not fit into the 10th storage slot anyway.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in #345 in 9d15d6e.

@@ -129,14 +119,7 @@ library BridgeState {
// The percentage of the notifier reward from the staking contract
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could change movingFundsTimeoutNotifierRewardMultiplier into uint32 and add bytes16 gap. That would merge:
Slot 7: uint96 movingFundsTimeoutSlashingAmount
Slot 8: uint256 movingFundsTimeoutNotifierRewardMultiplier

together into one slot:

Slot 7: uint96 movingFundsTimeoutSlashingAmount | uint32 movingFundsTimeoutNotifierRewardMultiplier | bytes16 movingFundsTimeoutSlashingGap

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Furthermore, we could change movedFundsSweepTimeoutNotifierRewardMultiplier to uint32. It's not going to destroy the layout of 9th slot. We could also change redemptionTimeoutNotifierRewardMultiplier to uint32 because it's not going to destroy the layout of 11th slot. And this way, all *RewardMultiplier* fields would be uint32 consistently.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in #345 in 9d15d6e.

/// @notice Calculate redemption key without allocations.
/// @param walletPubKeyHash the pubkey hash of the wallet.
/// @param script the output script of the redemption.
/// @return The key = keccak256(keccak256(script), walletPubKeyHash).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to adjust all occurrences of keccak256(walletPubKeyHash | redeemerOutputScript) in the documentation when we talk about pendingRedemptions or timedOutRedemptions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in #345 in 73dee0b.

/* solhint-disable-next-line no-inline-assembly */
assembly {
outputScriptHash := keccak256(
add(redemptionTxOutputVector, add(outputScriptStart, 32)),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we explain 32 in a comment? My understanding of this line is:

The first argument to assembly keccak256 is the pointer. We point to redemptionTxOutputVector but at the position indicated by outputScriptStart. To load that position, we need to call add(outputScriptStart, 32) because outputScriptStart has 32 bytes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in #345 in 01b490c.

@@ -783,24 +782,33 @@ library Redemption {
// https://github.com/keep-network/tbtc-v2/issues/257
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This TODO can now be removed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in #345 in 7082d42.

processInfo.outputStartingIndex
);

uint256 scriptLength = outputLength - 8;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could move this line below the comment explaining 8-byte value and variable length script.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in #345 in 5531f8f.

Comment on lines -871 to -873
// This function should be called only if the given output is
// supposed to represent a redemption. Build the redemption key
// to perform that check.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could add

/// @dev This function should be called only if the given output is supposed to represent redemption.

to the documentation of this function.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in #345 in f6a1740.

The detector was complaining about calls to bitcoin-spv which is a
trusted library.
@pdyraga pdyraga dismissed stale reviews from tomaszslabon and themself via ab98d8b May 26, 2022 13:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
⛓️ solidity Solidity contracts
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants