Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
43 changes: 43 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,46 @@ jobs:

- name: Run linter
run: nix develop --command make lint

conformance:
name: Conformance Tests
strategy:
fail-fast: false
matrix:
include:
- os: macos-latest
platform: darwin_arm64
- os: macos-15-intel
platform: darwin_amd64
- os: ubuntu-latest
platform: linux_amd64
- os: ubuntu-24.04-arm
platform: linux_arm64

runs-on: ${{ matrix.os }}

steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.23.12'

- name: Install dependencies (Ubuntu)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y libboost-all-dev

- name: Install dependencies (macOS)
if: runner.os == 'macOS'
run: |
brew install boost

- name: Build Kernel
run: make build-kernel

- name: Run conformance tests
working-directory: cmd/conformance-handler
run: make test
2 changes: 2 additions & 0 deletions cmd/conformance-handler/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.conformance-tests
handler
79 changes: 79 additions & 0 deletions cmd/conformance-handler/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Conformance Handler Makefile

# Test suite configuration
TEST_VERSION := 0.0.3-alpha.3
TEST_REPO := stringintech/kernel-bindings-tests
TEST_DIR := .conformance-tests

# Platform detection
UNAME_S := $(shell uname -s)
UNAME_M := $(shell uname -m)

ifeq ($(UNAME_S),Darwin)
ifeq ($(UNAME_M),arm64)
PLATFORM := darwin_arm64
else
PLATFORM := darwin_amd64
endif
else ifeq ($(UNAME_S),Linux)
ifeq ($(UNAME_M),x86_64)
PLATFORM := linux_amd64
else ifeq ($(UNAME_M),aarch64)
PLATFORM := linux_arm64
else
PLATFORM := linux_amd64
endif
else
$(error Unsupported platform: $(UNAME_S) $(UNAME_M))
endif

# Binary names
TEST_RUNNER := $(TEST_DIR)/runner
HANDLER_BIN := handler

.PHONY: all build download-tests test clean help

all: build test

help:
@echo "Conformance Handler Makefile"
@echo ""
@echo "Targets:"
@echo " build - Build the conformance handler binary"
@echo " download-tests - Download the test suite for your platform"
@echo " test - Run conformance tests against the handler"
@echo " clean - Remove built binaries and downloaded tests"
@echo " help - Show this help message"
@echo ""
@echo "Configuration:"
@echo " Test Version: $(TEST_VERSION)"
@echo " Platform: $(PLATFORM)"

build:
@echo "Building conformance handler..."
go build -o $(HANDLER_BIN) .

download-tests:
@echo "Downloading test suite $(TEST_VERSION) for $(PLATFORM)..."
@mkdir -p $(TEST_DIR)
$(eval DOWNLOAD_URL := https://github.com/$(TEST_REPO)/releases/download/v$(TEST_VERSION)/kernel-bindings-tests_$(TEST_VERSION)_$(PLATFORM).tar.gz)
@echo "URL: $(DOWNLOAD_URL)"
@curl -L -o $(TEST_DIR)/test-runner.tar.gz "$(DOWNLOAD_URL)"
@echo "Extracting test runner..."
@tar -xzf $(TEST_DIR)/test-runner.tar.gz -C $(TEST_DIR)
@chmod +x $(TEST_RUNNER)
@rm $(TEST_DIR)/test-runner.tar.gz
@echo "Test runner downloaded to $(TEST_RUNNER)"

test: build
@if [ ! -f "$(TEST_RUNNER)" ]; then \
echo "Test runner not found. Downloading..."; \
$(MAKE) download-tests; \
fi
@echo "Running conformance tests..."
$(TEST_RUNNER) --handler ./$(HANDLER_BIN) -vv

clean:
@echo "Cleaning up..."
rm -f $(HANDLER_BIN)
rm -rf $(TEST_DIR)
33 changes: 33 additions & 0 deletions cmd/conformance-handler/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Conformance Handler

This binary implements the JSON protocol required by the [kernel-bindings-spec](https://github.com/stringintech/kernel-bindings-spec) conformance testing framework.

## Purpose

The conformance handler acts as a bridge between the test runner and the Go Bitcoin Kernel bindings. It:

- Reads test requests from stdin (JSON protocol)
- Executes operations using the Go binding API
- Returns responses to stdout (JSON protocol)

## Testing

This handler is designed to work with the conformance test suite. The easiest way to run tests is using the Makefile:

```bash
# Run conformance tests (builds handler and downloads test runner automatically)
make test

# Or manually build and run
make build
make download-tests
./.conformance-tests/runner --handler ./handler
```

The test suite is automatically downloaded for your platform (darwin_arm64, darwin_amd64, linux_amd64, or linux_arm64).

## Pinned Test Version

This handler is compatible with:
- Test Suite Version: `0.0.3-alpha.3`
- Test Repository: [stringintech/kernel-bindings-tests](https://github.com/stringintech/kernel-bindings-tests)
63 changes: 63 additions & 0 deletions cmd/conformance-handler/block.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package main

import (
"encoding/hex"
"encoding/json"

"github.com/stringintech/go-bitcoinkernel/kernel"
)

// handleBlockCreate creates a block from raw hex data
func handleBlockCreate(registry *Registry, req Request) Response {
var params struct {
RawBlock string `json:"raw_block"`
}

if err := json.Unmarshal(req.Params, &params); err != nil {
return NewInvalidParamsResponse(req.ID, "failed to parse params")
}

if req.Ref == "" {
return NewInvalidParamsResponse(req.ID, "ref field is required")
}

// Decode hex to bytes
blockBytes, err := hex.DecodeString(params.RawBlock)
if err != nil {
return NewInvalidParamsResponse(req.ID, "raw_block must be valid hex")
}

// Create block
block, err := kernel.NewBlock(blockBytes)
if err != nil {
return NewEmptyErrorResponse(req.ID)
}

registry.Store(req.Ref, block)

return NewSuccessResponseWithRef(req.ID, req.Ref)
}

// handleBlockTreeEntryGetBlockHash gets the block hash from a block tree entry
func handleBlockTreeEntryGetBlockHash(registry *Registry, req Request) Response {
var params struct {
BlockTreeEntry RefObject `json:"block_tree_entry"`
}

if err := json.Unmarshal(req.Params, &params); err != nil {
return NewInvalidParamsResponse(req.ID, "failed to parse params")
}

// Get block tree entry from registry
entry, err := registry.GetBlockTreeEntry(params.BlockTreeEntry.Ref)
if err != nil {
return NewInvalidParamsResponse(req.ID, err.Error())
}

// Get block hash and convert to string (handles display order conversion)
hashView := entry.Hash()
hashString := hashView.String()

// Return hash as string
return NewSuccessResponse(req.ID, hashString)
}
90 changes: 90 additions & 0 deletions cmd/conformance-handler/chain.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package main

import (
"encoding/json"
)

// handleChainGetHeight gets the current height of the chain
func handleChainGetHeight(registry *Registry, req Request) Response {
var params struct {
Chain RefObject `json:"chain"`
}

if err := json.Unmarshal(req.Params, &params); err != nil {
return NewInvalidParamsResponse(req.ID, "failed to parse params")
}

// Get chain from registry
chain, err := registry.GetChain(params.Chain.Ref)
if err != nil {
return NewInvalidParamsResponse(req.ID, err.Error())
}

// Get height
height := chain.GetHeight()

// Return height as integer
return NewSuccessResponse(req.ID, height)
}

// handleChainGetByHeight gets a block tree entry at the specified height
func handleChainGetByHeight(registry *Registry, req Request) Response {
var params struct {
Chain RefObject `json:"chain"`
BlockHeight int32 `json:"block_height"`
}

if err := json.Unmarshal(req.Params, &params); err != nil {
return NewInvalidParamsResponse(req.ID, "failed to parse params")
}

if req.Ref == "" {
return NewInvalidParamsResponse(req.ID, "ref field is required")
}

// Get chain from registry
chain, err := registry.GetChain(params.Chain.Ref)
if err != nil {
return NewInvalidParamsResponse(req.ID, err.Error())
}

// Get block tree entry at height
entry := chain.GetByHeight(params.BlockHeight)
if entry == nil {
return NewEmptyErrorResponse(req.ID)
}

registry.Store(req.Ref, entry)

return NewSuccessResponseWithRef(req.ID, req.Ref)
}

// handleChainContains checks if a block tree entry is in the active chain
func handleChainContains(registry *Registry, req Request) Response {
var params struct {
Chain RefObject `json:"chain"`
BlockTreeEntry RefObject `json:"block_tree_entry"`
}

if err := json.Unmarshal(req.Params, &params); err != nil {
return NewInvalidParamsResponse(req.ID, "failed to parse params")
}

// Get chain from registry
chain, err := registry.GetChain(params.Chain.Ref)
if err != nil {
return NewInvalidParamsResponse(req.ID, err.Error())
}

// Get block tree entry from registry
entry, err := registry.GetBlockTreeEntry(params.BlockTreeEntry.Ref)
if err != nil {
return NewInvalidParamsResponse(req.ID, err.Error())
}

// Check if chain contains the entry
contains := chain.Contains(entry)

// Return boolean result
return NewSuccessResponse(req.ID, contains)
}
Loading
Loading