-
Notifications
You must be signed in to change notification settings - Fork 90
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
IMT optimization #154
Open
NouDaimon
wants to merge
35
commits into
master
Choose a base branch
from
IMT-optimization
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+135
−64
Open
IMT optimization #154
Changes from all commits
Commits
Show all changes
35 commits
Select commit
Hold shift + click to select a range
c2eae4f
remove length check to avoid storage read
NouDaimon 4a35971
add natspec
NouDaimon 5dcf49a
store IMT hashes via assembly
ItsNickBarry 4c5850b
rename rowSize to rowLength, modify lt comparison for clarity
ItsNickBarry af581c5
remove rootIndex variable
ItsNickBarry 045c652
optimize rowLength calculation
ItsNickBarry dfebf46
optimize IMT size function
ItsNickBarry cc90a1d
wrap contents of IMT pop function in unchecked block
ItsNickBarry ba217cd
remove unused unchecked block
ItsNickBarry 3fdae95
do not recalculate hashes if layer is removed on pop
ItsNickBarry bff831e
emphasize prallel structure of push and pop functions
ItsNickBarry 761ce87
optimize recursive row length calculation
ItsNickBarry 24b4834
replace lt with not eq comparison
ItsNickBarry 886ba4b
add additional at function revert test
ItsNickBarry e3116c3
use assembly for height and root functions
ItsNickBarry 511153c
circumvent all Solididity safety features for underlying IMT arrays
ItsNickBarry ad3f47d
wrap root functionality in assembly block
NouDaimon d455bb9
remove unrequired mstore operation
NouDaimon d9c9175
Merge branch 'master' into IMT-optimization
ItsNickBarry e8d8ebc
Merge branch 'IMT-optimization' into IMT-optimization-extreme
ItsNickBarry ea628a8
Merge branch 'master' into IMT-optimization
ItsNickBarry ba3c5ba
Merge branch 'IMT-optimization' into IMT-optimization-extreme
ItsNickBarry 5340f02
Merge branch 'master' into IMT-optimization
ItsNickBarry a594007
Merge branch 'IMT-optimization' into IMT-optimization-extreme
ItsNickBarry 4f204a3
Merge branch 'master' into IMT-optimization
ItsNickBarry 610d22b
Merge branch 'IMT-optimization' into IMT-optimization-extreme
ItsNickBarry 9ddd1fd
Merge branch 'master' into IMT-optimization
ItsNickBarry 0e2d727
Merge branch 'IMT-optimization' into IMT-optimization-extreme
ItsNickBarry b0adf4d
Merge branch 'master' into IMT-optimization
ItsNickBarry 6535209
Merge branch 'IMT-optimization' into IMT-optimization-extreme
ItsNickBarry ece54c2
Merge branch 'master' into IMT-optimization
ItsNickBarry 9131b4f
Merge branch 'IMT-optimization' into IMT-optimization-extreme
ItsNickBarry 1dedd3b
add comments explaining some IMT assembly blocks
ItsNickBarry fbc5115
panic via assembly rather than inline invalid array indexing
ItsNickBarry 4f1e391
Merge pull request #155 from solidstate-network/IMT-optimization-extreme
ItsNickBarry File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,7 +6,7 @@ library IncrementalMerkleTree { | |
using IncrementalMerkleTree for Tree; | ||
|
||
struct Tree { | ||
bytes32[][] nodes; | ||
bytes32[][] __nodes; | ||
} | ||
|
||
/** | ||
|
@@ -15,19 +15,30 @@ library IncrementalMerkleTree { | |
* @return treeSize size of tree | ||
*/ | ||
function size(Tree storage t) internal view returns (uint256 treeSize) { | ||
if (t.height() > 0) { | ||
treeSize = t.nodes[0].length; | ||
assembly { | ||
// assembly block equivalent to: | ||
// | ||
// if (t.height() > 0) treeSize = t.__nodes[0].length; | ||
|
||
mstore(0x00, t.slot) | ||
treeSize := sload(keccak256(0x00, 0x20)) | ||
} | ||
} | ||
|
||
/** | ||
* @notice query one-indexed height of tree | ||
* @dev conventional zero-indexed height would require the use of signed integers, so height is one-indexed instead | ||
* @param t Tree struct storage reference | ||
* @return one-indexed height of tree | ||
* @return treeHeight one-indexed height of tree | ||
*/ | ||
function height(Tree storage t) internal view returns (uint256) { | ||
return t.nodes.length; | ||
function height(Tree storage t) internal view returns (uint256 treeHeight) { | ||
assembly { | ||
// assembly block equivalent to: | ||
// | ||
// treeHeight = t.__nodes.length; | ||
|
||
treeHeight := sload(t.slot) | ||
} | ||
} | ||
|
||
/** | ||
|
@@ -36,11 +47,16 @@ library IncrementalMerkleTree { | |
* @return hash root hash | ||
*/ | ||
function root(Tree storage t) internal view returns (bytes32 hash) { | ||
uint256 treeHeight = t.height(); | ||
|
||
if (treeHeight > 0) { | ||
unchecked { | ||
hash = t.nodes[treeHeight - 1][0]; | ||
assembly { | ||
// assembly block equivalent to: | ||
// | ||
// if (t.height() > 0) hash = t.__nodes[t.height() - 1][0]; | ||
|
||
let treeHeight := sload(t.slot) | ||
if gt(treeHeight, 0) { | ||
mstore(0x00, t.slot) | ||
mstore(0x00, add(keccak256(0x00, 0x20), sub(treeHeight, 1))) | ||
hash := sload(keccak256(0x00, 0x20)) | ||
} | ||
} | ||
} | ||
|
@@ -49,7 +65,23 @@ library IncrementalMerkleTree { | |
Tree storage t, | ||
uint256 index | ||
) internal view returns (bytes32 hash) { | ||
hash = t.nodes[0][index]; | ||
if (index >= t.size()) { | ||
assembly { | ||
mstore(0x00, 0x4e487b71) | ||
mstore(0x20, 0x32) | ||
revert(0x1c, 0x24) | ||
} | ||
} | ||
|
||
assembly { | ||
// assembly block equivalent to: | ||
// | ||
// hash = t.__nodes[0][index]; | ||
|
||
mstore(0x00, t.slot) | ||
mstore(0x00, keccak256(0x00, 0x20)) | ||
hash := sload(add(keccak256(0x00, 0x20), index)) | ||
} | ||
} | ||
|
||
/** | ||
|
@@ -58,60 +90,67 @@ library IncrementalMerkleTree { | |
* @param hash to add | ||
*/ | ||
function push(Tree storage t, bytes32 hash) internal { | ||
unchecked { | ||
uint256 treeHeight = t.height(); | ||
uint256 treeSize = t.size(); | ||
// index to add to tree | ||
uint256 updateIndex = t.size(); | ||
|
||
// add new layer if tree is at capacity | ||
// update stored tree size | ||
|
||
if (treeSize == (1 << treeHeight) >> 1) { | ||
t.nodes.push(); | ||
treeHeight++; | ||
} | ||
assembly { | ||
mstore(0x00, t.slot) | ||
sstore(keccak256(0x00, 0x20), add(updateIndex, 1)) | ||
} | ||
|
||
// add new columns if rows are full | ||
// add new layer if tree is at capacity | ||
|
||
uint256 row; | ||
uint256 col = treeSize; | ||
uint256 treeHeight = t.height(); | ||
|
||
while (row < treeHeight && t.nodes[row].length <= col) { | ||
t.nodes[row].push(); | ||
row++; | ||
col >>= 1; | ||
if (updateIndex == (1 << treeHeight) >> 1) { | ||
// increment tree height in storage | ||
assembly { | ||
sstore(t.slot, add(treeHeight, 1)) | ||
} | ||
} | ||
|
||
// add hash to tree | ||
// add hash to tree | ||
|
||
t.set(treeSize, hash); | ||
} | ||
t.set(updateIndex, hash); | ||
} | ||
|
||
function pop(Tree storage t) internal { | ||
uint256 treeHeight = t.height(); | ||
uint256 treeSize = t.size() - 1; | ||
|
||
// remove layer if tree has excess capacity | ||
uint256 treeSize = t.size(); | ||
|
||
if (treeSize == (1 << treeHeight) >> 2) { | ||
treeHeight--; | ||
t.nodes.pop(); | ||
if (treeSize == 0) { | ||
assembly { | ||
mstore(0x00, 0x4e487b71) | ||
mstore(0x20, 0x32) | ||
revert(0x1c, 0x24) | ||
} | ||
} | ||
|
||
// remove columns if rows are too long | ||
unchecked { | ||
// index to remove from tree | ||
uint256 updateIndex = treeSize - 1; | ||
|
||
// update stored tree size | ||
|
||
uint256 row; | ||
uint256 col = treeSize; | ||
assembly { | ||
mstore(0x00, t.slot) | ||
sstore(keccak256(0x00, 0x20), updateIndex) | ||
} | ||
|
||
while (row < treeHeight && t.nodes[row].length > col) { | ||
t.nodes[row].pop(); | ||
row++; | ||
col = (col + 1) >> 1; | ||
} | ||
// if new tree is full, remove excess layer | ||
// if no layer is removed, recalculate hashes | ||
|
||
// recalculate hashes | ||
uint256 treeHeight = t.height(); | ||
|
||
if (treeSize > 0) { | ||
t.set(treeSize - 1, t.at(treeSize - 1)); | ||
if (updateIndex == (1 << treeHeight) >> 2) { | ||
// decrement tree height in storage | ||
assembly { | ||
sstore(t.slot, sub(treeHeight, 1)) | ||
} | ||
} else { | ||
t.set(updateIndex - 1, t.at(updateIndex - 1)); | ||
} | ||
} | ||
} | ||
|
||
|
@@ -122,48 +161,72 @@ library IncrementalMerkleTree { | |
* @param hash new hash to add | ||
*/ | ||
function set(Tree storage t, uint256 index, bytes32 hash) internal { | ||
unchecked { | ||
_set(t.nodes, 0, index, t.height() - 1, hash); | ||
uint256 treeSize = t.size(); | ||
|
||
if (index >= treeSize) { | ||
assembly { | ||
mstore(0x00, 0x4e487b71) | ||
mstore(0x20, 0x32) | ||
revert(0x1c, 0x24) | ||
} | ||
} | ||
|
||
_set(t, 0, index, treeSize, hash); | ||
} | ||
|
||
/** | ||
* @notice update element in tree and recursively recalculate hashes | ||
* @param nodes internal tree structure storage reference | ||
* @param t Tree struct storage reference | ||
* @param rowIndex index of current row to update | ||
* @param colIndex index of current column to update | ||
* @param rootIndex index of root row | ||
* @param rowLength length of row at rowIndex | ||
* @param hash hash to store at current position | ||
*/ | ||
function _set( | ||
bytes32[][] storage nodes, | ||
Tree storage t, | ||
uint256 rowIndex, | ||
uint256 colIndex, | ||
uint256 rootIndex, | ||
uint256 rowLength, | ||
bytes32 hash | ||
) private { | ||
bytes32[] storage row = nodes[rowIndex]; | ||
bytes32[] storage row; | ||
|
||
assembly { | ||
// assembly block equivalent to: | ||
// | ||
// row = nodes[rowIndex]; | ||
|
||
mstore(0x00, t.slot) | ||
row.slot := add(keccak256(0x00, 0x20), rowIndex) | ||
} | ||
|
||
row[colIndex] = hash; | ||
// store hash in array via assembly to avoid array length sload | ||
|
||
if (rowIndex == rootIndex) return; | ||
assembly { | ||
// assembly block equivalent to: | ||
// | ||
// row[colIndex] = hash; | ||
|
||
mstore(0x00, row.slot) | ||
sstore(add(keccak256(0x00, 0x20), colIndex), hash) | ||
} | ||
|
||
if (rowLength == 1) return; | ||
|
||
unchecked { | ||
if (colIndex & 1 == 1) { | ||
// sibling is on the left | ||
assembly { | ||
mstore(0x00, row.slot) | ||
let sibling := sload( | ||
add(keccak256(0x00, 0x20), sub(colIndex, 1)) | ||
) | ||
mstore(0x00, sibling) | ||
mstore(0x20, hash) | ||
hash := keccak256(0x00, 0x40) | ||
} | ||
} else if (colIndex + 1 < row.length) { | ||
} else if (colIndex < rowLength - 1) { | ||
// sibling is on the right (and sibling exists) | ||
assembly { | ||
mstore(0x00, row.slot) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. questionable |
||
let sibling := sload( | ||
add(keccak256(0x00, 0x20), add(colIndex, 1)) | ||
) | ||
|
@@ -172,8 +235,8 @@ library IncrementalMerkleTree { | |
hash := keccak256(0x00, 0x40) | ||
} | ||
} | ||
|
||
_set(nodes, rowIndex + 1, colIndex >> 1, rootIndex, hash); | ||
} | ||
|
||
_set(t, rowIndex + 1, colIndex >> 1, (rowLength + 1) >> 1, hash); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
questionable