Skip to content

Commit 65366da

Browse files
committed
Implement conformance handler
1 parent 88105c6 commit 65366da

8 files changed

Lines changed: 422 additions & 0 deletions

File tree

‎.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.1
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.1`
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 = NewErrorResponse(req.ID, "InternalError", fmt.Sprintf("%v", r))
10+
}
11+
}()
12+
13+
switch req.Method {
14+
case "script_pubkey.verify":
15+
return handleScriptPubkeyVerify(req)
16+
default:
17+
return NewErrorResponse(req.ID, "MethodNotFound", req.Method)
18+
}
19+
}

‎cmd/conformance-handler/main.go‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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+
resp := NewErrorResponse("", "InvalidRequest", err.Error())
20+
sendResponse(resp)
21+
continue
22+
}
23+
24+
resp := handleRequest(req)
25+
sendResponse(resp)
26+
}
27+
28+
if err := scanner.Err(); err != nil {
29+
fmt.Fprintf(os.Stderr, "Error reading stdin: %v\n", err)
30+
os.Exit(1)
31+
}
32+
}
33+
34+
// sendResponse writes a response to stdout as JSON
35+
func sendResponse(resp Response) {
36+
data, err := json.Marshal(resp)
37+
if err != nil {
38+
fmt.Fprintf(os.Stderr, "Error marshaling response: %v\n", err)
39+
return
40+
}
41+
42+
fmt.Println(string(data))
43+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
)
6+
7+
type Request struct {
8+
ID string `json:"id"`
9+
Method string `json:"method"`
10+
Params json.RawMessage `json:"params"`
11+
}
12+
13+
type Response struct {
14+
ID string `json:"id"`
15+
Success interface{} `json:"success,omitempty"`
16+
Error *Error `json:"error,omitempty"`
17+
}
18+
19+
type Error struct {
20+
Type string `json:"type"`
21+
Variant string `json:"variant,omitempty"`
22+
}
23+
24+
// NewErrorResponse creates an error response
25+
func NewErrorResponse(id, errorType, variant string) Response {
26+
return Response{
27+
ID: id,
28+
Error: &Error{
29+
Type: errorType,
30+
Variant: variant,
31+
},
32+
}
33+
}
34+
35+
// NewSuccessResponse creates a success response
36+
func NewSuccessResponse(id string, result interface{}) Response {
37+
return Response{
38+
ID: id,
39+
Success: result,
40+
}
41+
}
42+
43+
// NewEmptySuccessResponse creates an empty success response
44+
func NewEmptySuccessResponse(id string) Response {
45+
return NewSuccessResponse(id, map[string]interface{}{})
46+
}

0 commit comments

Comments
 (0)