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-13
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.2
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)

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.2`
- Test Repository: [stringintech/kernel-bindings-tests](https://github.com/stringintech/kernel-bindings-tests)
19 changes: 19 additions & 0 deletions cmd/conformance-handler/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package main

import "fmt"

// handleRequest dispatches a request to the appropriate handler
func handleRequest(req Request) (resp Response) {
defer func() {
if r := recover(); r != nil {
resp = NewHandlerErrorResponse(req.ID, "INTERNAL_ERROR", fmt.Sprintf("%v", r))
}
}()

switch req.Method {
case "btck_script_pubkey_verify":
return handleScriptPubkeyVerify(req)
default:
return NewHandlerErrorResponse(req.ID, "METHOD_NOT_FOUND", "")
}
}
42 changes: 42 additions & 0 deletions cmd/conformance-handler/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package main

import (
"bufio"
"encoding/json"
"fmt"
"os"
)

func main() {
// Read requests from stdin line by line
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
line := scanner.Text()

// Parse request
var req Request
if err := json.Unmarshal([]byte(line), &req); err != nil {
sendResponse(NewHandlerErrorResponse("", "INVALID_REQUEST", ""))
continue
}

resp := handleRequest(req)
sendResponse(resp)
}

if err := scanner.Err(); err != nil {
fmt.Fprintf(os.Stderr, "Error reading stdin: %v\n", err)
os.Exit(1)
}
}

// sendResponse writes a response to stdout as JSON
func sendResponse(resp Response) {
data, err := json.Marshal(resp)
if err != nil {
fmt.Fprintf(os.Stderr, "Error marshaling response: %v\n", err)
return
}

fmt.Println(string(data))
}
59 changes: 59 additions & 0 deletions cmd/conformance-handler/protocol.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package main

import (
"encoding/json"
"fmt"
)

type Request struct {
ID string `json:"id"`
Method string `json:"method"`
Params json.RawMessage `json:"params"`
}

type Response struct {
ID string `json:"id"`
Result json.RawMessage `json:"result,omitempty"`
Error *Error `json:"error,omitempty"`
}

type Error struct {
Code ErrorCode `json:"code"`
}

type ErrorCode struct {
Type string `json:"type"`
Member string `json:"member"`
}

// NewErrorResponse creates an error response with the given code type and member.
// Use directly for C API error codes (e.g., "btck_ScriptVerifyStatus").
// For handler errors, use NewHandlerErrorResponse.
func NewErrorResponse(id, codeType, codeMember string) Response {
return Response{
ID: id,
Error: &Error{
Code: ErrorCode{
Type: codeType,
Member: codeMember,
},
},
}
}

// NewHandlerErrorResponse creates an error response for handler layer errors.
// Use for request validation, method routing, and parameter parsing errors.
// Optional detail parameter adds context to the error (e.g., "INVALID_PARAMS (missing field 'foo')").
func NewHandlerErrorResponse(id, codeMember, detail string) Response {
member := codeMember
if detail != "" {
member += fmt.Sprintf(" (%s)", detail)
}
return NewErrorResponse(id, "Handler", member)
}

// NewInvalidParamsResponse creates an INVALID_PARAMS error with optional detail.
// Use when request parameters are malformed or missing. Detail provides context about the issue.
func NewInvalidParamsResponse(id, detail string) Response {
return NewHandlerErrorResponse(id, "INVALID_PARAMS", detail)
}
Loading
Loading