Data such as chunks and transactions are ultimately stored in the LEVELDB database, and this article describes the storage formats for chunks and transactions in Leveldb. All code related to chunk storage and reading is encapsulated in Core/database_util.go, which allows you to figure out how chunks, transactions, and other data structures are stored in the database.
Chunk storage
LEVELDB is a key-value database, and all data is stored as key-value pairs. Key is generally related to hash, and value is generally the RLP encoding of the data structure to be stored. Chunk storage is stored separately from chunk size and block body.
The storage format for the chunk header is:
Headerprefix + num (UInt64 big endian) + hash Rlpencode (header)
Where key is composed of chunk number (UInt64 big-endian format), chunk hash, and a prefix, value is the RLP code of the chunk header.
The storage format for block bodies is:
Bodyprefix + num (UInt64 big endian) + Hash-rlpencode (block body)
Where key is composed of chunk number (UInt64 big-endian format), chunk hash, and a prefix, value is the RLP encoding of the block body.
The prefixes in key can be used to differentiate between types of data, and various prefixes are defined in Core/database_util.go:
Headerprefix = []byte ("H") //Headerprefix + num (UInt64 big endian) + hash header
Tdsuffix = []byte ("T") Headerprefix + num (UInt64 big endian) + hash + tdsuffix td
Numsuffix = []byte ("n") //Headerprefix + Num (UInt64 big endian) + Numsuffix, hash
blockhashprefix = []byte ("H") //Blockhashprefix + hash Num (UInt64 big endian)
bodyprefix = []byte ("B") //Bodyprefix + num (UInt64 big endian) + hash bloc K Body
Where Headerprefix defines the prefix for the chunk header to H,bodyprefix defines the chunk body prefixed with B.
Here is the function that stores the chunk header:
Writeheader serializes a block header into the database.
Func writeheader (db ethdb. Database, Header *types. Header) Error {
data, err: = RLP. Encodetobytes (header)
if err! = Nil {
return err
}
Hash: = header. Hash (). Bytes ()
num: = header. Number.uint64 ()
encnum: = Encodeblocknumber (num)
key: = Append (Blockhashprefix, hash ...)
If err: = db. Put (key, Encnum); Err! = Nil {
glog. Fatalf ("Failed to store hash to number mapping into database:%v", err)
}
key = Append (Append (Headerprefix, ENCN Um ...), hash ...)
If err: = db. Put (key, data); Err! = Nil {
glog. Fatalf ("failed to store header into database:%v", err)
}
glog. V (logger. Debug). Infof ("Stored header #%v [%x ...]", header. Number, Hash[:4])
return nil
}
It first RLP the chunk header, encodeblocknumber the chunk number into the big-endian format, and then assembles the key. This first stores a chunk hash-> block number mapping to the database, and then writes the RLP encoding of the chunk header to the database.
Here is the function that stores the chunk body:
Writebody serializes the body of a block into the database.
Func writebody (db ethdb. Database, Hash common. Hash, number UInt64, body *types. Body) Error {
data, err: = RLP. Encodetobytes (body)
if err! = Nil {
return err
}
return WRITEBODYRLP (db, hash, number, data)
}
WRITEBODYRLP writes a serialized body of a block into the database.
Func WRITEBODYRLP (db ethdb. Database, Hash common. Hash, number UInt64, RLP RLP. RawValue) Error {
key: = Append (Append (Bodyprefix, encodeblocknumber (number) ...), hash. Bytes () ...)
If err: = db. Put (key, RLP); Err! = Nil {
glog. Fatalf ("Failed to store block body into database:%v", err)
}
glog. V (logger. Debug). Infof ("stored block body [%x ...]", hash. Bytes () [: 4])
return nil
}
Writebody RLP code The chunk body first, and then calls WRITEBODYRLP to write the RLP encoding of the chunk body to the database. WRITEBODYRLP assembles the key according to the rules above, and then writes a record to the database.
The
also has a writeblock function that calls Writebody and writeheader to write chunks to the database, respectively. The GetHeader getbody getblock function is also used to read chunks from the database.