Skip to content

Commit a1bdba0

Browse files
committed
Implement conformance handler for v0.0.2 tests
1 parent 88105c6 commit a1bdba0

File tree

11 files changed

+545
-23
lines changed

11 files changed

+545
-23
lines changed

.github/workflows/ci.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,46 @@ jobs:
119119

120120
- name: Run linter
121121
run: nix develop --command make lint
122+
123+
conformance:
124+
name: Conformance Tests
125+
strategy:
126+
fail-fast: false
127+
matrix:
128+
include:
129+
- os: macos-latest
130+
platform: darwin_arm64
131+
- os: macos-13
132+
platform: darwin_amd64
133+
- os: ubuntu-latest
134+
platform: linux_amd64
135+
- os: ubuntu-24.04-arm
136+
platform: linux_arm64
137+
138+
runs-on: ${{ matrix.os }}
139+
140+
steps:
141+
- uses: actions/checkout@v4
142+
143+
- name: Set up Go
144+
uses: actions/setup-go@v5
145+
with:
146+
go-version: '1.23.12'
147+
148+
- name: Install dependencies (Ubuntu)
149+
if: runner.os == 'Linux'
150+
run: |
151+
sudo apt-get update
152+
sudo apt-get install -y libboost-all-dev
153+
154+
- name: Install dependencies (macOS)
155+
if: runner.os == 'macOS'
156+
run: |
157+
brew install boost
158+
159+
- name: Build Kernel
160+
run: make build-kernel
161+
162+
- name: Run conformance tests
163+
working-directory: cmd/conformance-handler
164+
run: make test

cmd/conformance-handler/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.conformance-tests
2+
handler

cmd/conformance-handler/Makefile

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Conformance Handler Makefile
2+
3+
# Test suite configuration
4+
TEST_VERSION := 0.0.2
5+
TEST_REPO := stringintech/kernel-bindings-tests
6+
TEST_DIR := .conformance-tests
7+
8+
# Platform detection
9+
UNAME_S := $(shell uname -s)
10+
UNAME_M := $(shell uname -m)
11+
12+
ifeq ($(UNAME_S),Darwin)
13+
ifeq ($(UNAME_M),arm64)
14+
PLATFORM := darwin_arm64
15+
else
16+
PLATFORM := darwin_amd64
17+
endif
18+
else ifeq ($(UNAME_S),Linux)
19+
ifeq ($(UNAME_M),x86_64)
20+
PLATFORM := linux_amd64
21+
else ifeq ($(UNAME_M),aarch64)
22+
PLATFORM := linux_arm64
23+
else
24+
PLATFORM := linux_amd64
25+
endif
26+
else
27+
$(error Unsupported platform: $(UNAME_S) $(UNAME_M))
28+
endif
29+
30+
# Binary names
31+
TEST_RUNNER := $(TEST_DIR)/runner
32+
HANDLER_BIN := handler
33+
34+
.PHONY: all build download-tests test clean help
35+
36+
all: build test
37+
38+
help:
39+
@echo "Conformance Handler Makefile"
40+
@echo ""
41+
@echo "Targets:"
42+
@echo " build - Build the conformance handler binary"
43+
@echo " download-tests - Download the test suite for your platform"
44+
@echo " test - Run conformance tests against the handler"
45+
@echo " clean - Remove built binaries and downloaded tests"
46+
@echo " help - Show this help message"
47+
@echo ""
48+
@echo "Configuration:"
49+
@echo " Test Version: $(TEST_VERSION)"
50+
@echo " Platform: $(PLATFORM)"
51+
52+
build:
53+
@echo "Building conformance handler..."
54+
go build -o $(HANDLER_BIN) .
55+
56+
download-tests:
57+
@echo "Downloading test suite $(TEST_VERSION) for $(PLATFORM)..."
58+
@mkdir -p $(TEST_DIR)
59+
$(eval DOWNLOAD_URL := https://github.com/$(TEST_REPO)/releases/download/v$(TEST_VERSION)/kernel-bindings-tests_$(TEST_VERSION)_$(PLATFORM).tar.gz)
60+
@echo "URL: $(DOWNLOAD_URL)"
61+
@curl -L -o $(TEST_DIR)/test-runner.tar.gz "$(DOWNLOAD_URL)"
62+
@echo "Extracting test runner..."
63+
@tar -xzf $(TEST_DIR)/test-runner.tar.gz -C $(TEST_DIR)
64+
@chmod +x $(TEST_RUNNER)
65+
@rm $(TEST_DIR)/test-runner.tar.gz
66+
@echo "Test runner downloaded to $(TEST_RUNNER)"
67+
68+
test: build
69+
@if [ ! -f "$(TEST_RUNNER)" ]; then \
70+
echo "Test runner not found. Downloading..."; \
71+
$(MAKE) download-tests; \
72+
fi
73+
@echo "Running conformance tests..."
74+
$(TEST_RUNNER) --handler ./$(HANDLER_BIN)
75+
76+
clean:
77+
@echo "Cleaning up..."
78+
rm -f $(HANDLER_BIN)
79+
rm -rf $(TEST_DIR)

cmd/conformance-handler/README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Conformance Handler
2+
3+
This binary implements the JSON protocol required by the [kernel-bindings-spec](https://github.com/stringintech/kernel-bindings-spec) conformance testing framework.
4+
5+
## Purpose
6+
7+
The conformance handler acts as a bridge between the test runner and the Go Bitcoin Kernel bindings. It:
8+
9+
- Reads test requests from stdin (JSON protocol)
10+
- Executes operations using the Go binding API
11+
- Returns responses to stdout (JSON protocol)
12+
13+
## Testing
14+
15+
This handler is designed to work with the conformance test suite. The easiest way to run tests is using the Makefile:
16+
17+
```bash
18+
# Run conformance tests (builds handler and downloads test runner automatically)
19+
make test
20+
21+
# Or manually build and run
22+
make build
23+
make download-tests
24+
./.conformance-tests/runner --handler ./handler
25+
```
26+
27+
The test suite is automatically downloaded for your platform (darwin_arm64, darwin_amd64, linux_amd64, or linux_arm64).
28+
29+
## Pinned Test Version
30+
31+
This handler is compatible with:
32+
- Test Suite Version: `0.0.2`
33+
- Test Repository: [stringintech/kernel-bindings-tests](https://github.com/stringintech/kernel-bindings-tests)

cmd/conformance-handler/handler.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package main
2+
3+
import "fmt"
4+
5+
// handleRequest dispatches a request to the appropriate handler
6+
func handleRequest(req Request) (resp Response) {
7+
defer func() {
8+
if r := recover(); r != nil {
9+
resp = NewHandlerErrorResponse(req.ID, "INTERNAL_ERROR", fmt.Sprintf("%v", r))
10+
}
11+
}()
12+
13+
switch req.Method {
14+
case "btck_script_pubkey_verify":
15+
return handleScriptPubkeyVerify(req)
16+
default:
17+
return NewHandlerErrorResponse(req.ID, "METHOD_NOT_FOUND", "")
18+
}
19+
}

cmd/conformance-handler/main.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"encoding/json"
6+
"fmt"
7+
"os"
8+
)
9+
10+
func main() {
11+
// Read requests from stdin line by line
12+
scanner := bufio.NewScanner(os.Stdin)
13+
for scanner.Scan() {
14+
line := scanner.Text()
15+
16+
// Parse request
17+
var req Request
18+
if err := json.Unmarshal([]byte(line), &req); err != nil {
19+
sendResponse(NewHandlerErrorResponse("", "INVALID_REQUEST", ""))
20+
continue
21+
}
22+
23+
resp := handleRequest(req)
24+
sendResponse(resp)
25+
}
26+
27+
if err := scanner.Err(); err != nil {
28+
fmt.Fprintf(os.Stderr, "Error reading stdin: %v\n", err)
29+
os.Exit(1)
30+
}
31+
}
32+
33+
// sendResponse writes a response to stdout as JSON
34+
func sendResponse(resp Response) {
35+
data, err := json.Marshal(resp)
36+
if err != nil {
37+
fmt.Fprintf(os.Stderr, "Error marshaling response: %v\n", err)
38+
return
39+
}
40+
41+
fmt.Println(string(data))
42+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
)
7+
8+
type Request struct {
9+
ID string `json:"id"`
10+
Method string `json:"method"`
11+
Params json.RawMessage `json:"params"`
12+
}
13+
14+
type Response struct {
15+
ID string `json:"id"`
16+
Result json.RawMessage `json:"result,omitempty"`
17+
Error *Error `json:"error,omitempty"`
18+
}
19+
20+
type Error struct {
21+
Code ErrorCode `json:"code"`
22+
}
23+
24+
type ErrorCode struct {
25+
Type string `json:"type"`
26+
Member string `json:"member"`
27+
}
28+
29+
// NewErrorResponse creates an error response with the given code type and member.
30+
// Use directly for C API error codes (e.g., "btck_ScriptVerifyStatus").
31+
// For handler errors, use NewHandlerErrorResponse.
32+
func NewErrorResponse(id, codeType, codeMember string) Response {
33+
return Response{
34+
ID: id,
35+
Error: &Error{
36+
Code: ErrorCode{
37+
Type: codeType,
38+
Member: codeMember,
39+
},
40+
},
41+
}
42+
}
43+
44+
// NewHandlerErrorResponse creates an error response for handler layer errors.
45+
// Use for request validation, method routing, and parameter parsing errors.
46+
// Optional detail parameter adds context to the error (e.g., "INVALID_PARAMS (missing field 'foo')").
47+
func NewHandlerErrorResponse(id, codeMember, detail string) Response {
48+
member := codeMember
49+
if detail != "" {
50+
member += fmt.Sprintf(" (%s)", detail)
51+
}
52+
return NewErrorResponse(id, "Handler", member)
53+
}
54+
55+
// NewInvalidParamsResponse creates an INVALID_PARAMS error with optional detail.
56+
// Use when request parameters are malformed or missing. Detail provides context about the issue.
57+
func NewInvalidParamsResponse(id, detail string) Response {
58+
return NewHandlerErrorResponse(id, "INVALID_PARAMS", detail)
59+
}

0 commit comments

Comments
 (0)