Skip to content

Commit

Permalink
Add tbcd, a small bitcoin daemon that participates on bitcoin p2p (#50)
Browse files Browse the repository at this point in the history
TBC is a Bitcoin indexer which Hemi embeds inside the Hemi virtual
machine for hVM Bitcoin Interoperability.

Features:
* Syncs the entire Bitcoin blockchain (headers and blocks) over P2P
* Indexes UTXOs and transactions
* Supports indexing up to specific height to ensure deterministic
  indexer state across multiple Hemi nodes

Indexer State Supported Queries:
* Headers by height/hash and for tip
* Height of header hash
* Balance by address/scripthash
* UTXOs by address/scripthash
* Transactions by TxID
* Block(s) containing transaction
* Scripthash from Outpoint (TxID + output index)

Co-authored-by: Joshua Sing <[email protected]>
Co-authored-by: John C. Vernaleo <[email protected]>
Co-authored-by: ClaytonNorthey92 <[email protected]>
Co-authored-by: Max Sanchez <[email protected]>
  • Loading branch information
5 people authored Apr 16, 2024
1 parent 1146a08 commit 88047e7
Show file tree
Hide file tree
Showing 35 changed files with 9,503 additions and 22 deletions.
1 change: 1 addition & 0 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ jobs:
if: (success() || failure()) && steps.deps.outcome == 'success'
env:
PGTESTURI: "postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable"
HEMI_DOCKER_TESTS: "1"
run: |
make
git diff --exit-code
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
/pkg/
/.gocache/

## Tests
/service/tbc/.testleveldb/

### Common editors
## Vim
# Swap
Expand Down
20 changes: 12 additions & 8 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,28 @@ project = heminetwork
version = $(shell git describe --tags 2>/dev/null || echo "v0.0.0")

cmds = \
bfgd \
bssd \
extool \
keygen \
popmd \
hemictl
bfgd \
bssd \
extool \
hemictl \
keygen \
popmd \
tbcd

.PHONY: all clean clean-dist deps $(cmds) build install lint lint-deps tidy race test vulncheck \
vulncheck-deps dist archive sources checksums networktest

all: lint tidy test build install

clean: clean-dist
clean: clean-dist clean-test
rm -rf $(GOBIN) $(GOCACHE) $(GOPKG)

clean-dist:
rm -rf $(DIST)

clean-test:
rm -rf $(PROJECTPATH)/service/tbc/.testleveldb/

deps: lint-deps vulncheck-deps
go mod download
go mod verify
Expand All @@ -54,7 +58,7 @@ lint:
$(shell go env GOPATH)/bin/goimports -local github.com/hemilabs/heminetwork -w -l .
$(shell go env GOPATH)/bin/gofumpt -w -l .
$(shell go env GOPATH)/bin/addlicense -c "Hemi Labs, Inc." -f $(PROJECTPATH)/license_header.txt \
-ignore "{.idea,.vscode}/**" -ignore ".github/release.yml" -ignore ".github/ISSUE_TEMPLATE/**" -v .
-ignore "{.idea,.vscode}/**" -ignore ".github/release.yml" -ignore ".github/ISSUE_TEMPLATE/**" .
go vet ./...

lint-deps:
Expand Down
2 changes: 1 addition & 1 deletion api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"strings"
)

// hexDecode decodes a string that may be prefixed with " and/or 0x. Thus
// hexDecode decodes a string that may be prefixed with " and/or 0x. Thus,
// "0x00" and 0x00 or 00 are all valid hex encodings. If length is provided the
// decoded size must exactly match. The length parameter will be ignored if it
// is less than 0.
Expand Down
249 changes: 249 additions & 0 deletions api/tbcapi/tbcapi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
// Copyright (c) 2024 Hemi Labs, Inc.
// Use of this source code is governed by the MIT License,
// which can be found in the LICENSE file.

package tbcapi

import (
"context"
"fmt"
"maps"
"reflect"

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

const (
APIVersion = 1

CmdPingRequest = "tbcapi-ping-request"
CmdPingResponse = "tbcapi-ping-response"

CmdBlockHeadersByHeightRawRequest = "tbcapi-block-headers-by-height-raw-request"
CmdBlockHeadersByHeightRawResponse = "tbcapi-block-headers-by-height-raw-response"

CmdBlockHeadersByHeightRequest = "tbcapi-block-headers-by-height-request"
CmdBlockHeadersByHeightResponse = "tbcapi-block-headers-by-height-response"

CmdBlockHeadersBestRawRequest = "tbcapi-block-headers-best-raw-request"
CmdBlockHeadersBestRawResponse = "tbcapi-block-headers-best-raw-response"

CmdBlockHeadersBestRequest = "tbcapi-block-headers-best-request"
CmdBlockHeadersBestResponse = "tbcapi-block-headers-best-response"

CmdBalanceByAddressRequest = "tbcapi-balance-by-address-request"
CmdBalanceByAddressResponse = "tbcapi-balance-by-address-response"

CmdUtxosByAddressRawRequest = "tbcapi-utxos-by-address-raw-request"
CmdUtxosByAddressRawResponse = "tbcapi-utxos-by-address-raw-response"

CmdUtxosByAddressRequest = "tbcapi-utxos-by-address-request"
CmdUtxosByAddressResponse = "tbcapi-utxos-by-address-response"

CmdTxByIdRawRequest = "tbcapi-tx-by-id-raw-request"
CmdTxByIdRawResponse = "tbcapi-tx-by-id-raw-response"

CmdTxByIdRequest = "tbcapi-tx-by-id-request"
CmdTxByIdResponse = "tbcapi-tx-by-id-response"
)

var (
APIVersionRoute = fmt.Sprintf("v%d", APIVersion)
RouteWebsocket = fmt.Sprintf("/%s/ws", APIVersionRoute)

DefaultListen = "localhost:8082"
DefaultURL = fmt.Sprintf("ws://%s/%s", DefaultListen, RouteWebsocket)
)

type (
PingRequest protocol.PingRequest
PingResponse protocol.PingResponse
)

type BlockHeader struct {
Version int32 `json:"version"`
PrevHash string `json:"prev_hash"`
MerkleRoot string `json:"merkle_root"`
Timestamp int64 `json:"timestamp"`
Bits string `json:"bits"`
Nonce uint32 `json:"nonce"`
}

type BlockHeadersByHeightRawRequest struct {
Height uint32 `json:"height"`
}

type BlockHeadersByHeightRawResponse struct {
BlockHeaders []api.ByteSlice `json:"block_headers"`
Error *protocol.Error `json:"error,omitempty"`
}

type BlockHeadersByHeightRequest struct {
Height uint32 `json:"height"`
}

type BlockHeadersByHeightResponse struct {
BlockHeaders []*BlockHeader `json:"block_headers"`
Error *protocol.Error `json:"error,omitempty"`
}

type BlockHeadersBestRawRequest struct{}

type BlockHeadersBestRawResponse struct {
Height uint64 `json:"height"`
BlockHeaders []api.ByteSlice `json:"block_headers"`
Error *protocol.Error `json:"error,omitempty"`
}

type BlockHeadersBestRequest struct{}

type BlockHeadersBestResponse struct {
Height uint64 `json:"height"`
BlockHeaders []*BlockHeader `json:"block_headers"`
Error *protocol.Error `json:"error,omitempty"`
}

type BalanceByAddressRequest struct {
Address string `json:"address"`
}

type BalanceByAddressResponse struct {
Balance uint64 `json:"balance"`
Error *protocol.Error `json:"error,omitempty"`
}

type UtxosByAddressRawRequest struct {
Address string `json:"address"`
Start uint `json:"start"`
Count uint `json:"count"`
}

type UtxosByAddressRawResponse struct {
Utxos []api.ByteSlice `json:"utxos"`
Error *protocol.Error `json:"error,omitempty"`
}

type UtxosByAddressRequest struct {
Address string `json:"address"`
Start uint `json:"start"`
Count uint `json:"count"`
}

type Utxo struct {
TxId api.ByteSlice `json:"tx_id"`
Value uint64 `json:"value"`
OutIndex uint32 `json:"out_index"`
}

type UtxosByAddressResponse struct {
Utxos []Utxo `json:"utxos"`
Error *protocol.Error `json:"error,omitempty"`
}

type TxByIdRawRequest struct {
TxId api.ByteSlice `json:"tx_id"`
}

type TxByIdRawResponse struct {
Tx api.ByteSlice `json:"tx"`
Error *protocol.Error `json:"error,omitempty"`
}

type TxByIdRequest struct {
TxId api.ByteSlice `json:"tx_id"`
}

type TxByIdResponse struct {
Tx Tx `json:"tx"`
Error *protocol.Error `json:"error,omitempty"`
}

type OutPoint struct {
Hash api.ByteSlice `json:"hash"`
Index uint32 `json:"index"`
}

type TxWitness []api.ByteSlice

type TxIn struct {
PreviousOutPoint OutPoint `json:"outpoint"`
SignatureScript api.ByteSlice `json:"signature_script"`
Witness TxWitness `json:"tx_witness"`
Sequence uint32 `json:"sequence"`
}

type TxOut struct {
Value int64 `json:"value"`
PkScript api.ByteSlice `json:"pk_script"`
}

type Tx struct {
Version int32 `json:"version"`
LockTime uint32 `json:"lock_time"`
TxIn []*TxIn `json:"tx_in"`
TxOut []*TxOut `json:"tx_out"`
}

var commands = map[protocol.Command]reflect.Type{
CmdPingRequest: reflect.TypeOf(PingRequest{}),
CmdPingResponse: reflect.TypeOf(PingResponse{}),
CmdBlockHeadersByHeightRawRequest: reflect.TypeOf(BlockHeadersByHeightRawRequest{}),
CmdBlockHeadersByHeightRawResponse: reflect.TypeOf(BlockHeadersByHeightRawResponse{}),
CmdBlockHeadersByHeightRequest: reflect.TypeOf(BlockHeadersByHeightRequest{}),
CmdBlockHeadersByHeightResponse: reflect.TypeOf(BlockHeadersByHeightResponse{}),
CmdBlockHeadersBestRawRequest: reflect.TypeOf(BlockHeadersBestRawRequest{}),
CmdBlockHeadersBestRawResponse: reflect.TypeOf(BlockHeadersBestRawResponse{}),
CmdBlockHeadersBestRequest: reflect.TypeOf(BlockHeadersBestRequest{}),
CmdBlockHeadersBestResponse: reflect.TypeOf(BlockHeadersBestResponse{}),
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{}),
}

type tbcAPI struct{}

func (a *tbcAPI) Commands() map[protocol.Command]reflect.Type {
return commands
}

func APICommands() map[protocol.Command]reflect.Type {
return maps.Clone(commands)
}

// Write is the low level primitive of a protocol Write. One should generally
// not use this function and use WriteConn and Call instead.
func Write(ctx context.Context, c protocol.APIConn, id string, payload any) error {
return protocol.Write(ctx, c, &tbcAPI{}, id, payload)
}

// Read is the low level primitive of a protocol Read. One should generally
// not use this function and use ReadConn instead.
func Read(ctx context.Context, c protocol.APIConn) (protocol.Command, string, any, error) {
return protocol.Read(ctx, c, &tbcAPI{})
}

// Call is a blocking call. One should use ReadConn when using Call or else the
// completion will end up in the Read instead of being completed as expected.
func Call(ctx context.Context, c *protocol.Conn, payload any) (protocol.Command, string, any, error) {
return c.Call(ctx, &tbcAPI{}, payload)
}

// WriteConn writes to Conn. It is equivalent to Write but exists for symmetry
// reasons.
func WriteConn(ctx context.Context, c *protocol.Conn, id string, payload any) error {
return c.Write(ctx, &tbcAPI{}, id, payload)
}

// ReadConn reads from Conn and performs callbacks. One should use ReadConn over
// Read when mixing Write, WriteConn and Call.
func ReadConn(ctx context.Context, c *protocol.Conn) (protocol.Command, string, any, error) {
return c.Read(ctx, &tbcAPI{})
}
Loading

0 comments on commit 88047e7

Please sign in to comment.