Skip to content

Commit

Permalink
chaindataloader: Added new package
Browse files Browse the repository at this point in the history
Signed-off-by: Ononiwu Maureen <[email protected]>
  • Loading branch information
Ononiwu Maureen committed Mar 12, 2024
1 parent 43f5a58 commit 317ead5
Show file tree
Hide file tree
Showing 3 changed files with 745 additions and 0 deletions.
257 changes: 257 additions & 0 deletions chaindataloader/binary.go
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
}
Loading

0 comments on commit 317ead5

Please sign in to comment.