Skip to content

Commit

Permalink
core: add rlp cheat to avoid full decoding for reading header parent …
Browse files Browse the repository at this point in the history
…hash
  • Loading branch information
holiman committed Aug 13, 2021
1 parent 40ec873 commit 5d34b01
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 5 deletions.
4 changes: 2 additions & 2 deletions core/headerchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -535,15 +535,15 @@ func (hc *HeaderChain) GetHeadersFrom(number, count uint64) []rlp.RawValue {
// If the request is for future headers, we still return the portion of
// headers that we are able to serve
if current := hc.CurrentHeader().Number.Uint64(); current < number {
if count >= number-current {
if count > number-current {
count -= number - current
number = current
} else {
return nil
}
}
var headers []rlp.RawValue
//If we have some of the headers in cache already, use that before going to db.
// If we have some of the headers in cache already, use that before going to db.
hash := rawdb.ReadCanonicalHash(hc.chainDb, number)
if hash == (common.Hash{}) {
return nil
Expand Down
4 changes: 1 addition & 3 deletions core/rawdb/accessors_chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,13 +287,11 @@ func ReadHeadersRLP(db ethdb.Reader, number uint64, count uint64) []rlp.RawValue
if i >= limit {
// If we need to read live blocks, we need to figure out the hash first
hash := ReadCanonicalHash(db, number)
var hdr types.Header
for ; i >= limit && count > 0; i-- {
if data, _ := db.Get(headerKey(i, hash)); len(data) > 0 {
rlpHeaders = append(rlpHeaders, data)
// Get the parent hash for next query
rlp.DecodeBytes(data, &hdr)
hash = hdr.ParentHash
hash = types.HeaderParentHashFromRLP(data)
} else {
break // Maybe got moved to ancients
}
Expand Down
48 changes: 48 additions & 0 deletions core/types/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -383,3 +383,51 @@ func (b *Block) Hash() common.Hash {
}

type Blocks []*Block

// HeaderParentHashFromRLP returns the parentHash of an rlp-encoded
// header-blob.
// Fix-size gields in an RLP-encoded types.Header are:
//
// | name | size | type |
// | ------------|--------| ---------------|
// | ParentHash | 32 | common.Hash |
// | UncleHash | 32 | common.Hash |
// | Coinbase | 20 | common.Address |
// | Root | 32 | common.Hash |
// | TxHash | 32 | common.Hash |
// | ReceiptHash | 32 | common.Hash |
// | Bloom | 256 | Bloom |
// | GasLimit | 8 | uint64 |
// | GasUsed | 8 | uint64 |
// | Time | 8 | uint64 |
// | MixDigest | 32 | common.Hash |
// | Nonce | 8 | BlockNonce |
//
// Size of constant fields: `500`.
//
// | name | size | type | max
// | ------------|--------| ---------------|-----------------------|
// | Difficulty | dynamic| *big.Int | 0x5ad3c2c71bbff854908 (current mainnet TD: 76 bits) => 10 byte |
// | Number | dynamic| *big.Int | 32 bits = 4 bytes |
// | Extra | dynamic| []byte | 32 byte + 65 byte +(20 bytes per signer for certain Clique blocks) |
// | BaseFee | dynamic| *big.Int | 64 bits = 8 byte |
//
// Maximum size of dynamic fields: 119 bytes + dynamic overhead
//
// Total max size + 619 + dynamic overhead
//
// Any RLP-encoded data structure between `256` bytes, and `65535` bytes will have three leading bytes for `size`.
// Since we know that the header is at least `500` bytes, and practically will never go above `800`, we can be certain that
// the first field, `parentHash` is located at a constant byte-offset.
//
func HeaderParentHashFromRLP(value rlp.RawValue) common.Hash {
if len(value) < 65535 {
return common.BytesToHash(value[4:36])
}
// This path is expected to _never_ be used, but the method should
// behave correctly even in the presence of very very odd blocks, with
// thousands of clique signers.
var h Header
rlp.DecodeBytes(value, &h)
return h.ParentHash
}
48 changes: 48 additions & 0 deletions core/types/block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,3 +281,51 @@ func makeBenchBlock() *Block {
}
return NewBlock(header, txs, uncles, receipts, newHasher())
}

func TestRlpDecodeParentHash(t *testing.T) {
// A minimum one
want := common.HexToHash("0x112233445566778899001122334455667788990011223344556677889900aabb")
if rlpData, err := rlp.EncodeToBytes(Header{ParentHash: want}); err != nil {
t.Fatal(err)
} else {
if have := HeaderParentHashFromRLP(rlpData); have != want {
t.Fatalf("have %x, want %x", have, want)
}
}
// And a maximum one
// | Difficulty | dynamic| *big.Int | 0x5ad3c2c71bbff854908 (current mainnet TD: 76 bits) |
// | Number | dynamic| *big.Int | 64 bits |
// | Extra | dynamic| []byte | 65+32 byte (clique) |
// | BaseFee | dynamic| *big.Int | 64 bits |
mainnetTd := new(big.Int)
mainnetTd.SetString("5ad3c2c71bbff854908", 16)
if rlpData, err := rlp.EncodeToBytes(Header{
ParentHash: want,
Difficulty: mainnetTd,
Number: new(big.Int).SetUint64(math.MaxUint64),
Extra: make([]byte, 65+32),
BaseFee: new(big.Int).SetUint64(math.MaxUint64),
}); err != nil {
t.Fatal(err)
} else {
if have := HeaderParentHashFromRLP(rlpData); have != want {
t.Fatalf("have %x, want %x", have, want)
}
}
// Also test a very very large header.
{
// The rlp-encoding of the heder belowCauses _total_ length of 65540,
// which is the first to blow the fast-path.
h := Header{
ParentHash: want,
Extra: make([]byte, 65041),
}
if rlpData, err := rlp.EncodeToBytes(h); err != nil {
t.Fatal(err)
} else {
if have := HeaderParentHashFromRLP(rlpData); have != want {
t.Fatalf("have %x, want %x", have, want)
}
}
}
}

0 comments on commit 5d34b01

Please sign in to comment.