-
Notifications
You must be signed in to change notification settings - Fork 186
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Ononiwu Maureen <[email protected]>
- Loading branch information
Ononiwu Maureen
committed
Mar 12, 2024
1 parent
43f5a58
commit 317ead5
Showing
3 changed files
with
745 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,257 @@ | ||
package chaindataloader | ||
|
||
import ( | ||
"bytes" | ||
"errors" | ||
"fmt" | ||
"github.com/btcsuite/btcd/chaincfg/chainhash" | ||
"io" | ||
|
||
"github.com/btcsuite/btcd/wire" | ||
"github.com/lightningnetwork/lnd/tlv" | ||
) | ||
|
||
const ( | ||
// blockHeaderSize is the size one block header occupies in bytes. | ||
blockHeaderSize dataSize = 80 | ||
|
||
// filterHeaderSize is the size one filter header occupies in bytes. | ||
filterHeaderSize dataSize = 32 | ||
) | ||
|
||
// binReader is an internal struct that holds all data the binReader needs | ||
// to fetch headers. | ||
// Each file has a header of varint size consisting of, | ||
// | ||
// dataType || extra metadata (e.g. filterType) || startHeight || endHeight | ||
// || chain | ||
// in that order. | ||
type binReader struct { | ||
// reader represents the source to be read. | ||
reader io.ReadSeeker | ||
|
||
// startHeight represents the height of the first header in the file. | ||
startHeight int32 | ||
|
||
// endHeight represents the height of the last header in the file. | ||
endHeight int32 | ||
|
||
// offset represents the distance required to read the first header from | ||
// the file. | ||
initialOffset uint32 | ||
|
||
// chain represents the bitcoin network the headers in the file belong to. | ||
chain wire.BitcoinNet | ||
|
||
dataSize dataSize | ||
} | ||
|
||
type binConfig struct { | ||
reader io.ReadSeeker | ||
headerType dataType | ||
} | ||
|
||
type blkHdrBinReader struct { | ||
*binReader | ||
headerDecoder[*wire.BlockHeader] | ||
} | ||
|
||
type cfHdrBinReader struct { | ||
*binReader | ||
headerDecoder[*chainhash.Hash] | ||
} | ||
|
||
func newBinaryBlkHdrLoader(c *binConfig) (*blkHdrBinReader, error) { | ||
|
||
b, err := newBinaryLoader(c) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return &blkHdrBinReader{ | ||
binReader: b, | ||
headerDecoder: blkHdrDecoder, | ||
}, nil | ||
} | ||
|
||
func newBinaryFilterHdrLoader(c *binConfig) (*cfHdrBinReader, error) { | ||
|
||
b, err := newBinaryLoader(c) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return &cfHdrBinReader{ | ||
binReader: b, | ||
headerDecoder: filterHdrDecoder, | ||
}, nil | ||
} | ||
|
||
// newBinaryChainDataLoader initializes a Binary Loader. | ||
func newBinaryLoader(c *binConfig) (*binReader, error) { | ||
|
||
// Create scratch buffer. | ||
scratch := [8]byte{} | ||
|
||
typeOfData, err := tlv.ReadVarInt(c.reader, &scratch) | ||
if err != nil { | ||
return nil, fmt.Errorf("error obtaining data type "+ | ||
"of file %w", err) | ||
} | ||
|
||
if dataType(typeOfData) != c.headerType { | ||
return nil, fmt.Errorf("data type mismatch: got %v but expected %v", | ||
dataType(typeOfData), blockHeaders) | ||
} | ||
|
||
var headerSize dataSize | ||
switch c.headerType { | ||
case blockHeaders: | ||
headerSize = blockHeaderSize | ||
case filterHeaders: | ||
headerSize = filterHeaderSize | ||
default: | ||
return nil, errors.New("unsupported header type") | ||
|
||
} | ||
|
||
// Read start height of block header file. | ||
start, err := tlv.ReadVarInt(c.reader, &scratch) | ||
if err != nil { | ||
return nil, fmt.Errorf("error obtaining start height "+ | ||
"of file %w", err) | ||
} | ||
|
||
// Read end height of block header file. | ||
end, err := tlv.ReadVarInt(c.reader, &scratch) | ||
if err != nil { | ||
return nil, fmt.Errorf("error obtaining end height of file "+ | ||
"%w", err) | ||
} | ||
|
||
// Read the bitcoin network, the headers in the header file belong to. | ||
chainChar, err := tlv.ReadVarInt(c.reader, &scratch) | ||
if err != nil { | ||
return nil, fmt.Errorf("error obtaining chain of file %w", err) | ||
} | ||
|
||
// obtain space occupied by metadata as initial offset | ||
initialOffset, err := c.reader.Seek(0, io.SeekCurrent) | ||
if err != nil { | ||
return nil, fmt.Errorf("unable to determine initial offset: "+ | ||
"%v", err) | ||
} | ||
|
||
return &binReader{ | ||
reader: c.reader, | ||
startHeight: int32(start), | ||
endHeight: int32(end), | ||
initialOffset: uint32(initialOffset), | ||
chain: wire.BitcoinNet(chainChar), | ||
dataSize: headerSize, | ||
}, nil | ||
} | ||
|
||
// NeutrinoHeader is a type parameter for block header and filter hash. | ||
type NeutrinoHeader interface { | ||
*wire.BlockHeader | *chainhash.Hash | ||
} | ||
|
||
// headerDecoder type serializes the passed byte to the required header type. | ||
type headerDecoder[T NeutrinoHeader] func([]byte) (T, error) | ||
|
||
// blkHdrDecoder serializes the passed data in bytes to *wire.BlockHeader type. | ||
func blkHdrDecoder(data []byte) (*wire.BlockHeader, error) { | ||
var blockHeader wire.BlockHeader | ||
|
||
headerReader := bytes.NewReader(data) | ||
|
||
// Finally, decode the raw bytes into a proper bitcoin header. | ||
if err := blockHeader.Deserialize(headerReader); err != nil { | ||
return nil, fmt.Errorf("error deserializing block header: %w", | ||
err) | ||
} | ||
|
||
return &blockHeader, nil | ||
} | ||
|
||
// filterHdrDecoder serializes the passed data in bytes to filter header hash. | ||
func filterHdrDecoder(data []byte) (*chainhash.Hash, error) { | ||
|
||
return chainhash.NewHash(data) | ||
} | ||
|
||
// readHeaders fetches headers from the binary file. | ||
func readHeaders[T NeutrinoHeader](numHeaders int32, reader io.ReadSeeker, dataTypeSize dataSize, | ||
decoder headerDecoder[T]) ([]T, error) { | ||
|
||
hdrs := make([]T, numHeaders) | ||
for i := int32(0); i < numHeaders; i++ { | ||
rawData := make([]byte, dataTypeSize) | ||
|
||
if _, err := reader.Read(rawData); err != nil { | ||
if err == io.EOF { | ||
break | ||
} | ||
return nil, err | ||
} | ||
|
||
T, err := decoder(rawData) | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
hdrs[i] = T | ||
} | ||
return hdrs, nil | ||
} | ||
|
||
func (b *blkHdrBinReader) FetchHeaders(numHeaders int32) ([]*wire.BlockHeader, error) { | ||
return readHeaders[*wire.BlockHeader](numHeaders, b.reader, b.dataSize, b.headerDecoder) | ||
} | ||
|
||
func (b *cfHdrBinReader) FetchHeaders(numHeaders int32) ([]*chainhash.Hash, error) { | ||
return readHeaders[*chainhash.Hash](numHeaders, b.reader, b.dataSize, b.headerDecoder) | ||
} | ||
|
||
// EndHeight function returns the height of the last header in the file. | ||
func (b *binReader) EndHeight() int32 { | ||
return b.endHeight | ||
} | ||
|
||
// StartHeight function returns the height of the first header in the file. | ||
func (b *binReader) StartHeight() int32 { | ||
return b.startHeight | ||
} | ||
|
||
// HeadersChain function returns the network the headers in the file belong to. | ||
func (b *binReader) HeadersChain() wire.BitcoinNet { | ||
return b.chain | ||
} | ||
|
||
// SetHeight function receives a height which should be the height of the last | ||
// header the caller has. It uses this to set the appropriate offest for | ||
// fetching Headers. | ||
func (b *binReader) SetHeight(height int32) error { | ||
if b.startHeight > height || height > b.endHeight { | ||
return errors.New("unable to set height as file does not " + | ||
"contain requested height") | ||
} | ||
|
||
// Compute offset to fetch header at `height` relative to the binReader's | ||
// header's start height. | ||
offset := (height - b.startHeight) * int32(b.dataSize) | ||
|
||
// Compute total offset to fetch next header *after* header at `height` | ||
// relative to the reader's start point which contains the reader's | ||
// metadata as well. | ||
totalOffset := (offset + int32(b.initialOffset)) + int32(b.dataSize) | ||
|
||
_, err := b.reader.Seek(int64(totalOffset), io.SeekStart) | ||
|
||
if err != nil { | ||
return fmt.Errorf("unable to set seek for Loader: %v", err) | ||
} | ||
|
||
return nil | ||
} |
Oops, something went wrong.