Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tbcd, bfgd: add various hemi specific indexes #380

Open
wants to merge 39 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
5b6f14a
Add boilerplate for hemi indexers
marcopeereboom Jan 16, 2025
0592f17
Add keystones table
marcopeereboom Jan 16, 2025
9ef62aa
Flip map to l2 keystone hash to PkScript
marcopeereboom Jan 16, 2025
421a5ad
Implement BlockHemiUpdate
marcopeereboom Jan 16, 2025
6eb287c
Simplify indexer locations
marcopeereboom Jan 17, 2025
5169187
The grand rename
marcopeereboom Jan 17, 2025
5466873
Add bockhash to the keystone indexer call
marcopeereboom Jan 17, 2025
c77ad82
Unwind path
marcopeereboom Jan 17, 2025
0c92cd1
Enforce pkscript length
marcopeereboom Jan 17, 2025
84f83d0
Added L2Keystone by abrev hash functionality and test cases
AL-CT Jan 17, 2025
9d99da7
Added valid test case
AL-CT Jan 17, 2025
b1f47df
Fixed types, tests, and parsing
AL-CT Jan 17, 2025
9d753b8
removed debugging bytes
ClaytonNorthey92 Jan 20, 2025
dc41fc5
remove debug poopie
marcopeereboom Jan 20, 2025
774be8b
Banish some dragons from col > 80
marcopeereboom Jan 20, 2025
abbf438
Fix some comments while here
marcopeereboom Jan 20, 2025
e7cf1e0
Stuff for antonio
marcopeereboom Jan 21, 2025
cc10326
Fixed createBtCTx
AL-CT Jan 21, 2025
06b9606
fixed index tests
AL-CT Jan 22, 2025
1d0e760
Almost working test
marcopeereboom Jan 23, 2025
ad77835
Move btc to pop account by adding recipient
marcopeereboom Jan 27, 2025
cbf75ea
Blergh
marcopeereboom Jan 27, 2025
a64a9e9
added keystone checks in test and fixed helper functions
AL-CT Jan 28, 2025
faec733
Mine keystone in block 3 as well, note that we swapped b3 from spendi…
marcopeereboom Jan 28, 2025
d5eed69
Style and remove log.Infof
marcopeereboom Jan 28, 2025
926c7dd
TestFork keystone checks and db checks for keystones
AL-CT Jan 28, 2025
eaf73fe
Update service/tbc/crawler.go
marcopeereboom Jan 29, 2025
552210e
Update service/tbc/crawler.go
marcopeereboom Jan 29, 2025
455264e
added test case for BlockKeystoneUpdate
AL-CT Jan 29, 2025
35e1516
improved TestKeystoneUpdate
AL-CT Jan 29, 2025
66abf69
even further improved TestKeystoneUpdate and added processKeystones t…
AL-CT Jan 29, 2025
b9d3c19
fixed error type check
AL-CT Jan 29, 2025
6d9fc60
delete leftover code
AL-CT Jan 29, 2025
59beabb
beware of >80col dragons
marcopeereboom Jan 30, 2025
c027c32
Add encode/decode keystone
marcopeereboom Jan 30, 2025
be3175a
Add encode/decode keystone test
marcopeereboom Jan 30, 2025
b7d950b
encode testing and new TestKeystoneUpdate test case
AL-CT Jan 30, 2025
81c7c95
test simple wind/unwind keystones
marcopeereboom Jan 30, 2025
c13e61d
grumble grumble
marcopeereboom Jan 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 53 additions & 37 deletions api/tbcapi/tbcapi.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Hemi Labs, Inc.
// Copyright (c) 2024-2025 Hemi Labs, Inc.
// Use of this source code is governed by the MIT License,
// which can be found in the LICENSE file.

Expand All @@ -15,6 +15,7 @@ import (

"github.com/hemilabs/heminetwork/api"
"github.com/hemilabs/heminetwork/api/protocol"
"github.com/hemilabs/heminetwork/hemi"
)

// XXX we should kill the wrapping types that are basically identical to wire.
Expand Down Expand Up @@ -76,6 +77,9 @@ const (

CmdBlockDownloadAsyncRawRequest = "tbcapi-block-download-async-raw-request"
CmdBlockDownloadAsyncRawResponse = "tbcapi-block-download-async-raw-response"

CmdBlockKeystoneByL2KeystoneAbrevHashRequest = "tbcapi-l2-keystone-abrev-by-abrev-hash-request"
CmdBlockKeystoneByL2KeystoneAbrevHashResponse = "tbcapi-l2-keystone-abrev-by-abrev-hash-response"
)

var (
Expand Down Expand Up @@ -284,6 +288,16 @@ type BlockInsertRawRequest struct {
Block api.ByteSlice `json:"block"`
}

type BlockKeystoneByL2KeystoneAbrevHashRequest struct {
L2KeystoneAbrevHash *chainhash.Hash `json:"l2_keystones_abrev_hash"`
}

type BlockKeystoneByL2KeystoneAbrevHashResponse struct {
L2KeystoneAbrev *hemi.L2KeystoneAbrev `json:"l2_keystone_abrev"`
BtcBlockHash *chainhash.Hash `json:"btc_block_hash"`
Error *protocol.Error `json:"error,omitempty"`
}

type BlockInsertRawResponse struct {
BlockHash *chainhash.Hash `json:"block_hash"`
Error *protocol.Error `json:"error,omitempty"`
Expand Down Expand Up @@ -315,42 +329,44 @@ type BlockDownloadAsyncRawResponse struct {
}

var commands = map[protocol.Command]reflect.Type{
CmdPingRequest: reflect.TypeOf(PingRequest{}),
CmdPingResponse: reflect.TypeOf(PingResponse{}),
CmdBlockByHashRequest: reflect.TypeOf(BlockByHashRequest{}),
CmdBlockByHashResponse: reflect.TypeOf(BlockByHashResponse{}),
CmdBlockByHashRawRequest: reflect.TypeOf(BlockByHashRawRequest{}),
CmdBlockByHashRawResponse: reflect.TypeOf(BlockByHashRawResponse{}),
CmdBlockHeadersByHeightRawRequest: reflect.TypeOf(BlockHeadersByHeightRawRequest{}),
CmdBlockHeadersByHeightRawResponse: reflect.TypeOf(BlockHeadersByHeightRawResponse{}),
CmdBlockHeadersByHeightRequest: reflect.TypeOf(BlockHeadersByHeightRequest{}),
CmdBlockHeadersByHeightResponse: reflect.TypeOf(BlockHeadersByHeightResponse{}),
CmdBlockHeaderBestRawRequest: reflect.TypeOf(BlockHeaderBestRawRequest{}),
CmdBlockHeaderBestRawResponse: reflect.TypeOf(BlockHeaderBestRawResponse{}),
CmdBlockHeaderBestRequest: reflect.TypeOf(BlockHeaderBestRequest{}),
CmdBlockHeaderBestResponse: reflect.TypeOf(BlockHeaderBestResponse{}),
CmdBalanceByAddressRequest: reflect.TypeOf(BalanceByAddressRequest{}),
CmdBalanceByAddressResponse: reflect.TypeOf(BalanceByAddressResponse{}),
CmdUTXOsByAddressRawRequest: reflect.TypeOf(UTXOsByAddressRawRequest{}),
CmdUTXOsByAddressRawResponse: reflect.TypeOf(UTXOsByAddressRawResponse{}),
CmdUTXOsByAddressRequest: reflect.TypeOf(UTXOsByAddressRequest{}),
CmdUTXOsByAddressResponse: reflect.TypeOf(UTXOsByAddressResponse{}),
CmdTxByIdRawRequest: reflect.TypeOf(TxByIdRawRequest{}),
CmdTxByIdRawResponse: reflect.TypeOf(TxByIdRawResponse{}),
CmdTxByIdRequest: reflect.TypeOf(TxByIdRequest{}),
CmdTxByIdResponse: reflect.TypeOf(TxByIdResponse{}),
CmdTxBroadcastRequest: reflect.TypeOf(TxBroadcastRequest{}),
CmdTxBroadcastResponse: reflect.TypeOf(TxBroadcastResponse{}),
CmdTxBroadcastRawRequest: reflect.TypeOf(TxBroadcastRawRequest{}),
CmdTxBroadcastRawResponse: reflect.TypeOf(TxBroadcastRawResponse{}),
CmdBlockInsertRequest: reflect.TypeOf(BlockInsertRequest{}),
CmdBlockInsertResponse: reflect.TypeOf(BlockInsertResponse{}),
CmdBlockInsertRawRequest: reflect.TypeOf(BlockInsertRawRequest{}),
CmdBlockInsertRawResponse: reflect.TypeOf(BlockInsertRawResponse{}),
CmdBlockDownloadAsyncRequest: reflect.TypeOf(BlockDownloadAsyncRequest{}),
CmdBlockDownloadAsyncResponse: reflect.TypeOf(BlockDownloadAsyncResponse{}),
CmdBlockDownloadAsyncRawRequest: reflect.TypeOf(BlockDownloadAsyncRawRequest{}),
CmdBlockDownloadAsyncRawResponse: reflect.TypeOf(BlockDownloadAsyncRawResponse{}),
CmdPingRequest: reflect.TypeOf(PingRequest{}),
CmdPingResponse: reflect.TypeOf(PingResponse{}),
CmdBlockByHashRequest: reflect.TypeOf(BlockByHashRequest{}),
CmdBlockByHashResponse: reflect.TypeOf(BlockByHashResponse{}),
CmdBlockByHashRawRequest: reflect.TypeOf(BlockByHashRawRequest{}),
CmdBlockByHashRawResponse: reflect.TypeOf(BlockByHashRawResponse{}),
CmdBlockHeadersByHeightRawRequest: reflect.TypeOf(BlockHeadersByHeightRawRequest{}),
CmdBlockHeadersByHeightRawResponse: reflect.TypeOf(BlockHeadersByHeightRawResponse{}),
CmdBlockHeadersByHeightRequest: reflect.TypeOf(BlockHeadersByHeightRequest{}),
CmdBlockHeadersByHeightResponse: reflect.TypeOf(BlockHeadersByHeightResponse{}),
CmdBlockHeaderBestRawRequest: reflect.TypeOf(BlockHeaderBestRawRequest{}),
CmdBlockHeaderBestRawResponse: reflect.TypeOf(BlockHeaderBestRawResponse{}),
CmdBlockHeaderBestRequest: reflect.TypeOf(BlockHeaderBestRequest{}),
CmdBlockHeaderBestResponse: reflect.TypeOf(BlockHeaderBestResponse{}),
CmdBalanceByAddressRequest: reflect.TypeOf(BalanceByAddressRequest{}),
CmdBalanceByAddressResponse: reflect.TypeOf(BalanceByAddressResponse{}),
CmdUTXOsByAddressRawRequest: reflect.TypeOf(UTXOsByAddressRawRequest{}),
CmdUTXOsByAddressRawResponse: reflect.TypeOf(UTXOsByAddressRawResponse{}),
CmdUTXOsByAddressRequest: reflect.TypeOf(UTXOsByAddressRequest{}),
CmdUTXOsByAddressResponse: reflect.TypeOf(UTXOsByAddressResponse{}),
CmdTxByIdRawRequest: reflect.TypeOf(TxByIdRawRequest{}),
CmdTxByIdRawResponse: reflect.TypeOf(TxByIdRawResponse{}),
CmdTxByIdRequest: reflect.TypeOf(TxByIdRequest{}),
CmdTxByIdResponse: reflect.TypeOf(TxByIdResponse{}),
CmdTxBroadcastRequest: reflect.TypeOf(TxBroadcastRequest{}),
CmdTxBroadcastResponse: reflect.TypeOf(TxBroadcastResponse{}),
CmdTxBroadcastRawRequest: reflect.TypeOf(TxBroadcastRawRequest{}),
CmdTxBroadcastRawResponse: reflect.TypeOf(TxBroadcastRawResponse{}),
CmdBlockInsertRequest: reflect.TypeOf(BlockInsertRequest{}),
CmdBlockInsertResponse: reflect.TypeOf(BlockInsertResponse{}),
CmdBlockInsertRawRequest: reflect.TypeOf(BlockInsertRawRequest{}),
CmdBlockInsertRawResponse: reflect.TypeOf(BlockInsertRawResponse{}),
CmdBlockDownloadAsyncRequest: reflect.TypeOf(BlockDownloadAsyncRequest{}),
CmdBlockDownloadAsyncResponse: reflect.TypeOf(BlockDownloadAsyncResponse{}),
CmdBlockDownloadAsyncRawRequest: reflect.TypeOf(BlockDownloadAsyncRawRequest{}),
CmdBlockDownloadAsyncRawResponse: reflect.TypeOf(BlockDownloadAsyncRawResponse{}),
CmdBlockKeystoneByL2KeystoneAbrevHashRequest: reflect.TypeOf(BlockKeystoneByL2KeystoneAbrevHashRequest{}),
CmdBlockKeystoneByL2KeystoneAbrevHashResponse: reflect.TypeOf(BlockKeystoneByL2KeystoneAbrevHashResponse{}),
}

type tbcAPI struct{}
Expand Down
14 changes: 13 additions & 1 deletion cmd/tbcd/tbcd.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,16 @@ var (
},
"TBC_BLOCK_SANITY": config.Config{
Value: &cfg.BlockSanity,
DefaultValue: false,
DefaultValue: true,
Help: "enable/disable block sanity checks before inserting",
Print: config.PrintAll,
},
"TBC_HEMI_INDEX": config.Config{
Value: &cfg.HemiIndex,
DefaultValue: false,
Help: "enable/disable various hemi related indexes",
Print: config.PrintAll,
},
"TBC_LEVELDB_HOME": config.Config{
Value: &cfg.LevelDBHome,
DefaultValue: defaultHome,
Expand All @@ -77,6 +83,12 @@ var (
Help: "loglevel for various packages; INFO, DEBUG and TRACE",
Print: config.PrintAll,
},
"TBC_MAX_CACHED_KEYSTONES": config.Config{
Value: &cfg.MaxCachedKeystones,
DefaultValue: int(1e5),
Help: "maximum cached keystones during indexing",
Print: config.PrintAll,
},
"TBC_MAX_CACHED_TXS": config.Config{
Value: &cfg.MaxCachedTxs,
DefaultValue: int(1e6),
Expand Down
7 changes: 6 additions & 1 deletion database/level/level.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2024 Hemi Labs, Inc.
// Copyright (c) 2024-2025 Hemi Labs, Inc.
// Use of this source code is governed by the MIT License,
// which can be found in the LICENSE file.

Expand Down Expand Up @@ -29,6 +29,7 @@ const (
BlockHeadersDB = "blockheaders"
BlocksMissingDB = "blocksmissing"
MetadataDB = "metadata"
KeystonesDB = "keystones"
HeightHashDB = "heighthash"
PeersDB = "peers"
OutputsDB = "outputs"
Expand Down Expand Up @@ -221,6 +222,10 @@ func New(ctx context.Context, home string, version int) (*Database, error) {
if err != nil {
return nil, fmt.Errorf("leveldb %v: %w", TransactionsDB, err)
}
err = l.openDB(KeystonesDB, nil)
if err != nil {
return nil, fmt.Errorf("leveldb %v: %w", KeystonesDB, err)
}

// Blocks database is special
err = l.openRawDB(BlocksDB, rawdb.DefaultMaxFileSize)
Expand Down
23 changes: 16 additions & 7 deletions database/tbcd/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,15 @@ type Database interface {
ScriptHashByOutpoint(ctx context.Context, op Outpoint) (*ScriptHash, error)
UtxosByScriptHash(ctx context.Context, sh ScriptHash, start uint64, count uint64) ([]Utxo, error)
UtxosByScriptHashCount(ctx context.Context, sh ScriptHash) (uint64, error)

// Hemi
BlockKeystoneUpdate(ctx context.Context, direction int, keystones map[chainhash.Hash]Keystone) error
BlockKeystoneByL2KeystoneAbrevHash(ctx context.Context, abrevhash chainhash.Hash) (*Keystone, error)
}

type Keystone struct {
BlockHash chainhash.Hash // Block that contains abbreviated keystone
AbbreviatedKeystone []byte // Abbreviated keystone
}

// XXX there exist various types in this file that need to be reevaluated.
Expand Down Expand Up @@ -225,13 +234,13 @@ func NewOutpoint(txid [32]byte, index uint32) (op Outpoint) {
return
}

// CacheOutput is a densely packed representation of a bitcoin UTXo. The fields are
// script_hash + value + out_index. It is packed for
// memory conservation reasons.
// CacheOutput is a densely packed representation of a bitcoin UTXo. The fields
// are script_hash + value + out_index. It is packed for memory conservation
// reasons.
type CacheOutput [32 + 8 + 4]byte // script_hash + value + out_idx

// String returns pretty printable CacheOutput. Hash is not reversed since it is an
// opaque pointer. It prints satoshis@script_hash:output_index
// String returns pretty printable CacheOutput. Hash is not reversed since it
// is an opaque pointer. It prints satoshis@script_hash:output_index
func (c CacheOutput) String() string {
return fmt.Sprintf("%d @ %x:%d", binary.BigEndian.Uint64(c[32:40]),
c[0:32], binary.BigEndian.Uint32(c[40:]))
Expand Down Expand Up @@ -292,8 +301,8 @@ func NewDeleteCacheOutput(hash [32]byte, outIndex uint32) (co CacheOutput) {
// Utxo packs a transaction id, the value and the out index.
type Utxo [32 + 8 + 4]byte // tx_id + value + out_idx

// String returns pretty printable CacheOutput. Hash is not reversed since it is an
// opaque pointer. It prints satoshis@script_hash:output_index
// String returns pretty printable CacheOutput. Hash is not reversed since it
// is an opaque pointer. It prints satoshis@script_hash:output_index
func (u Utxo) String() string {
ch, _ := chainhash.NewHash(u[0:32])
return fmt.Sprintf("%d @ %v:%d", binary.BigEndian.Uint64(u[32:40]),
Expand Down
82 changes: 82 additions & 0 deletions database/tbcd/level/level.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/hemilabs/heminetwork/database"
"github.com/hemilabs/heminetwork/database/level"
"github.com/hemilabs/heminetwork/database/tbcd"
"github.com/hemilabs/heminetwork/hemi"
)

// Locking order:
Expand Down Expand Up @@ -282,6 +283,27 @@ func (l *ldb) MetadataBatchGet(ctx context.Context, allOrNone bool, keys [][]byt
return l.transactionBatchGet(ctx, mdDB, allOrNone, keys)
}

func (l *ldb) BlockKeystoneByL2KeystoneAbrevHash(ctx context.Context, abrevhash chainhash.Hash) (*tbcd.Keystone, error) {
log.Tracef("BlockKeystoneByL2KeystoneAbrevHash")
defer log.Tracef("BlockKeystoneByL2KeystoneAbrevHash exit")

kssDB := l.pool[level.KeystonesDB]
marcopeereboom marked this conversation as resolved.
Show resolved Hide resolved
eks, err := kssDB.Get(abrevhash.CloneBytes(), nil)
if err != nil {
if errors.Is(err, leveldb.ErrNotFound) {
return nil, database.NotFoundError(fmt.Sprintf("l2 keystone not found: %v", abrevhash))
}
return nil, fmt.Errorf("l2 keystone get: %w", err)
}

ks := tbcd.Keystone{
BlockHash: chainhash.Hash(eks[:32]),
AbbreviatedKeystone: eks[32:],
}

return &ks, nil
}

// BatchAppend appends rows to batch b.
func BatchAppend(ctx context.Context, b *leveldb.Batch, rows []tbcd.Row) {
log.Tracef("BatchAppend")
Expand Down Expand Up @@ -1707,6 +1729,66 @@ func (l *ldb) BlockTxUpdate(ctx context.Context, direction int, txs map[tbcd.TxK
return nil
}

func (l *ldb) BlockKeystoneUpdate(ctx context.Context, direction int, keystones map[chainhash.Hash]tbcd.Keystone) error {
AL-CT marked this conversation as resolved.
Show resolved Hide resolved
log.Tracef("BlockKeystoneUpdate")
defer log.Tracef("BlockKeystoneUpdate exit")

if !(direction == 1 || direction == -1) {
marcopeereboom marked this conversation as resolved.
Show resolved Hide resolved
return fmt.Errorf("invalid direction: %v", direction)
}

// keystones
kssTx, kssCommit, kssDiscard, err := l.startTransaction(level.KeystonesDB)
if err != nil {
return fmt.Errorf("keystones open db transaction: %w", err)
}
defer kssDiscard()

kssBatch := new(leveldb.Batch)
for k, v := range keystones {
switch direction {
case -1:
foundKeystone, err := kssTx.Get(k[:], nil)
if err != nil {
continue
}
fbh, err := chainhash.NewHash(foundKeystone[0:32])
if err != nil {
return err // XXX really can't happen
}
// Only delete keystone if it is in the previously found block.
if fbh.IsEqual(&v.BlockHash) {
kssBatch.Delete(k[:])
}
case 1:
has, err := kssTx.Has(k[:], nil)
if err != nil {
return fmt.Errorf("keystone update has: %w", err)
}
if has {
// Only store unknown keystones
continue
}
var value [chainhash.HashSize + hemi.L2KeystoneAbrevSize]byte
marcopeereboom marked this conversation as resolved.
Show resolved Hide resolved
copy(value[0:32], v.BlockHash[:])
copy(value[32:], v.AbbreviatedKeystone)
kssBatch.Put(k[:], value[:])
}
}

// Write keystones batch
if err = kssTx.Write(kssBatch, nil); err != nil {
return fmt.Errorf("keystones insert: %w", err)
}

// keystones commit
if err = kssCommit(); err != nil {
return fmt.Errorf("keystones commit: %w", err)
}

return nil
}

func (l *ldb) BlockHeaderCacheStats() tbcd.CacheStats {
if l.cfg.blockheaderCacheSize == 0 {
return noStats
Expand Down
Loading
Loading