-
Notifications
You must be signed in to change notification settings - Fork 207
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Improve performance of answerGetBlockBodiesQuery (#2198)
Avoid decoding and re-encoding of the block body by using available RLP-encoded block body from the database, combine it with the RLP-encoded block hash to avoid unnecessary processing and extra memory allocations.
- Loading branch information
Showing
4 changed files
with
193 additions
and
5 deletions.
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
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
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 |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package rlp | ||
|
||
import ( | ||
"encoding/binary" | ||
"math" | ||
) | ||
|
||
// for payloads longer than 55 bytes for both strings and lists additional prefix signalizing | ||
// the total length of the payload is needed | ||
const payloadLengthThreshold = 0x37 // 55 in dec | ||
|
||
// indicates that the payload is a list | ||
const shortListEncodingByte = 0xC0 | ||
|
||
// listEncodingByte (0xC0) + payloadLengthThreshold (0x37) = 0xF7 | ||
const longListEncodingByte = 0xF7 | ||
|
||
// Combine takes two RLP-encoded values one and two and produces a combined one | ||
// as if it was an RLP encoding of the [one, two] list | ||
// based on https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/ | ||
func Combine(one []byte, two []byte) []byte { | ||
payloadLen := len(one) + len(two) | ||
result := make([]byte, 0, ListSize(uint64(payloadLen))) | ||
|
||
if payloadLen <= payloadLengthThreshold { | ||
// If the total payload of a list (i.e. the combined length of all its items being RLP | ||
// encoded) is 0-55 bytes long, the RLP encoding consists of a single byte with value | ||
// 0xc0 plus the length of the payload followed by the concatenation of the RLP encodings | ||
// of the items. | ||
result = append(result, byte(shortListEncodingByte+payloadLen)) | ||
} else { | ||
encodedPayloadLength := binaryEncode(uint64(payloadLen)) | ||
|
||
// If the total payload of a list is more than 55 bytes long, the RLP encoding consists | ||
// of a single byte with value 0xf7 plus the length in bytes of the length of the payload | ||
// in binary form, followed by the length of the payload, followed by the concatenation | ||
// of the RLP encodings of the items. | ||
result = append(result, byte(longListEncodingByte+len(encodedPayloadLength))) | ||
result = append(result, encodedPayloadLength...) | ||
} | ||
|
||
result = append(result, one...) | ||
result = append(result, two...) | ||
|
||
return result | ||
} | ||
|
||
// binaryEncode returns a binary-encoded number without leading zeroes | ||
func binaryEncode(number uint64) []byte { | ||
binaryEncoded := make([]byte, 8) | ||
|
||
binary.BigEndian.PutUint64(binaryEncoded, number) | ||
|
||
// we need to +1 as logarithm works for positive numbers | ||
lengthInBytes := uint(math.Ceil(math.Log2(float64(number+1)) / 8)) | ||
|
||
return binaryEncoded[8-lengthInBytes:] | ||
} |
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 |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package rlp | ||
|
||
import ( | ||
"reflect" | ||
"testing" | ||
) | ||
|
||
func TestCombine(t *testing.T) { | ||
one_str, _ := EncodeToBytes("Hello") | ||
two_str, _ := EncodeToBytes("World") | ||
list_of_strs, _ := EncodeToBytes([]string{"Hello", "World"}) | ||
|
||
one_list, _ := EncodeToBytes([]string{"1"}) | ||
two_list, _ := EncodeToBytes([]string{"2"}) | ||
list_of_lists, _ := EncodeToBytes([][]string{{"1"}, {"2"}}) | ||
|
||
str_and_list, _ := EncodeToBytes([]interface{}{"Hello", []string{"2"}}) | ||
list_and_str, _ := EncodeToBytes([]interface{}{[]string{"1"}, "World"}) | ||
|
||
empty_list, _ := EncodeToBytes([]byte{}) | ||
str_and_empty_list, _ := EncodeToBytes([]interface{}{"Hello", []byte{}}) | ||
empty_list_and_str, _ := EncodeToBytes([]interface{}{[]byte{}, "World"}) | ||
two_empty_lists, _ := EncodeToBytes([]interface{}{[]byte{}, []byte{}}) | ||
|
||
empty_str, _ := EncodeToBytes("") | ||
two_empty_str, _ := EncodeToBytes([]string{"", ""}) | ||
|
||
testCases := []struct { | ||
name string | ||
one []byte | ||
two []byte | ||
expectedResult []byte | ||
}{ | ||
{ | ||
name: "Two strings", | ||
one: one_str, | ||
two: two_str, | ||
expectedResult: list_of_strs, | ||
}, | ||
{ | ||
name: "Two empty strings", | ||
one: empty_str, | ||
two: empty_str, | ||
expectedResult: two_empty_str, | ||
}, | ||
{ | ||
name: "Two lists", | ||
one: one_list, | ||
two: two_list, | ||
expectedResult: list_of_lists, | ||
}, | ||
{ | ||
name: "Two empty lists", | ||
one: empty_list, | ||
two: empty_list, | ||
expectedResult: two_empty_lists, | ||
}, | ||
{ | ||
name: "String and list", | ||
one: one_str, | ||
two: two_list, | ||
expectedResult: str_and_list, | ||
}, | ||
{ | ||
name: "String and empty list", | ||
one: one_str, | ||
two: empty_list, | ||
expectedResult: str_and_empty_list, | ||
}, | ||
{ | ||
name: "List and string", | ||
one: one_list, | ||
two: two_str, | ||
expectedResult: list_and_str, | ||
}, | ||
{ | ||
name: "Empty list and string", | ||
one: empty_list, | ||
two: two_str, | ||
expectedResult: empty_list_and_str, | ||
}, | ||
} | ||
|
||
for _, c := range testCases { | ||
t.Run(c.name, func(t *testing.T) { | ||
result := Combine(c.one, c.two) | ||
if !reflect.DeepEqual(result, c.expectedResult) { | ||
t.Error("Expected", c.expectedResult, "got", result) | ||
} | ||
}) | ||
} | ||
} |