Skip to content

Commit 0edb833

Browse files
committed
Orchestrator and go-handler
0 parents  commit 0edb833

File tree

17 files changed

+981
-0
lines changed

17 files changed

+981
-0
lines changed

.gitmodules

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[submodule "go-handler/go-bitcoinkernel"]
2+
path = go-handler/go-bitcoinkernel
3+
url = https://github.com/stringintech/go-bitcoinkernel.git
4+
branch = main

go-handler/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
bin/

go-handler/chainstate.go

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
package main
2+
3+
import (
4+
"encoding/hex"
5+
"encoding/json"
6+
"os"
7+
8+
"github.com/stringintech/go-bitcoinkernel/kernel"
9+
)
10+
11+
// handleChainstateSetup initializes a chainstate and imports blocks
12+
func handleChainstateSetup(req Request, state *SessionState) Response {
13+
var params struct {
14+
ChainType string `json:"chain_type"`
15+
BlocksHex []string `json:"blocks_hex"`
16+
}
17+
18+
if err := json.Unmarshal(req.Params, &params); err != nil {
19+
return NewErrorResponse(req.ID, ErrInvalidParams, "Failed to parse params: "+err.Error())
20+
}
21+
22+
// Clean up any existing state
23+
state.Cleanup()
24+
25+
// Create temp directory
26+
tempDir, err := os.MkdirTemp("", "conformance_test_")
27+
if err != nil {
28+
return NewErrorResponse(req.ID, ErrInternalError, "Failed to create temp dir: "+err.Error())
29+
}
30+
state.tempDir = tempDir
31+
32+
// Parse chain type
33+
var chainType kernel.ChainType
34+
switch params.ChainType {
35+
case "mainnet":
36+
chainType = kernel.ChainTypeMainnet
37+
case "testnet":
38+
chainType = kernel.ChainTypeTestnet
39+
case "testnet4":
40+
chainType = kernel.ChainTypeTestnet4
41+
case "signet":
42+
chainType = kernel.ChainTypeSignet
43+
case "regtest":
44+
chainType = kernel.ChainTypeRegtest
45+
default:
46+
state.Cleanup()
47+
return NewErrorResponse(req.ID, ErrInvalidParams, "Unknown chain type: "+params.ChainType)
48+
}
49+
50+
// Create chain parameters
51+
chainParams, err := kernel.NewChainParameters(chainType)
52+
if err != nil {
53+
state.Cleanup()
54+
return NewErrorResponse(req.ID, ErrKernel, "Failed to create chain parameters: "+err.Error())
55+
}
56+
defer chainParams.Destroy()
57+
58+
// Create context options
59+
contextOpts := kernel.NewContextOptions()
60+
contextOpts.SetChainParams(chainParams)
61+
62+
// Create context
63+
ctx, err := kernel.NewContext(contextOpts)
64+
if err != nil {
65+
state.Cleanup()
66+
return NewErrorResponse(req.ID, ErrKernel, "Failed to create context: "+err.Error())
67+
}
68+
defer ctx.Destroy()
69+
70+
// Create chainstate manager options
71+
opts, err := kernel.NewChainstateManagerOptions(ctx, state.tempDir, state.tempDir+"/blocks")
72+
if err != nil {
73+
state.Cleanup()
74+
return NewErrorResponse(req.ID, ErrKernel, "Failed to create options: "+err.Error())
75+
}
76+
defer opts.Destroy()
77+
78+
// Configure for in-memory testing
79+
opts.SetWorkerThreads(1)
80+
opts.UpdateBlockTreeDBInMemory(true)
81+
opts.UpdateChainstateDBInMemory(true)
82+
if err := opts.SetWipeDBs(true, true); err != nil {
83+
state.Cleanup()
84+
return NewErrorResponse(req.ID, ErrKernel, "Failed to set wipe DBs: "+err.Error())
85+
}
86+
87+
// Create chainstate manager
88+
manager, err := kernel.NewChainstateManager(opts)
89+
if err != nil {
90+
state.Cleanup()
91+
return NewErrorResponse(req.ID, ErrKernel, "Failed to create manager: "+err.Error())
92+
}
93+
state.chainstateManager = manager
94+
95+
// Initialize empty databases
96+
if err := manager.ImportBlocks(nil); err != nil {
97+
state.Cleanup()
98+
return NewErrorResponse(req.ID, ErrKernel, "Failed to initialize: "+err.Error())
99+
}
100+
101+
// Process blocks
102+
blocksImported := 0
103+
for i, blockHex := range params.BlocksHex {
104+
blockBytes, err := hex.DecodeString(blockHex)
105+
if err != nil {
106+
return NewErrorResponse(req.ID, ErrInvalidParams, "Invalid block hex at index "+string(rune(i))+": "+err.Error())
107+
}
108+
109+
block, err := kernel.NewBlock(blockBytes)
110+
if err != nil {
111+
return NewErrorResponse(req.ID, ErrKernel, "Failed to create block at index "+string(rune(i))+": "+err.Error())
112+
}
113+
114+
ok, duplicate := manager.ProcessBlock(block)
115+
block.Destroy()
116+
117+
if !ok || duplicate {
118+
return NewErrorResponse(req.ID, ErrKernel, "Failed to process block at index "+string(rune(i)))
119+
}
120+
121+
blocksImported++
122+
}
123+
124+
// Get tip height
125+
chain := manager.GetActiveChain()
126+
tipHeight := chain.GetHeight()
127+
128+
result := map[string]interface{}{
129+
"blocks_imported": blocksImported,
130+
"tip_height": tipHeight,
131+
}
132+
133+
return NewSuccessResponse(req.ID, result)
134+
}
135+
136+
// handleChainstateReadBlock reads a block by height or tip
137+
func handleChainstateReadBlock(req Request, state *SessionState) Response {
138+
if state.chainstateManager == nil {
139+
return NewErrorResponse(req.ID, ErrInternalError, "Chainstate not initialized")
140+
}
141+
142+
var params struct {
143+
Height *int32 `json:"height,omitempty"`
144+
Tip *bool `json:"tip,omitempty"`
145+
}
146+
147+
if err := json.Unmarshal(req.Params, &params); err != nil {
148+
return NewErrorResponse(req.ID, ErrInvalidParams, "Failed to parse params: "+err.Error())
149+
}
150+
151+
chain := state.chainstateManager.GetActiveChain()
152+
153+
var blockIndex *kernel.BlockTreeEntry
154+
if params.Tip != nil && *params.Tip {
155+
blockIndex = chain.GetTip()
156+
} else if params.Height != nil {
157+
blockIndex = chain.GetByHeight(*params.Height)
158+
} else {
159+
return NewErrorResponse(req.ID, ErrInvalidParams, "Must specify either height or tip")
160+
}
161+
162+
if blockIndex == nil {
163+
return NewErrorResponse(req.ID, ErrKernel, "Block not found")
164+
}
165+
166+
block, err := state.chainstateManager.ReadBlock(blockIndex)
167+
if err != nil {
168+
return NewErrorResponse(req.ID, ErrKernel, "Failed to read block: "+err.Error())
169+
}
170+
defer block.Destroy()
171+
172+
blockBytes, err := block.Bytes()
173+
if err != nil {
174+
return NewErrorResponse(req.ID, ErrKernel, "Failed to serialize block: "+err.Error())
175+
}
176+
177+
result := map[string]interface{}{
178+
"block_hex": hex.EncodeToString(blockBytes),
179+
"height": blockIndex.Height(),
180+
}
181+
182+
return NewSuccessResponse(req.ID, result)
183+
}
184+
185+
// handleChainstateTeardown cleans up chainstate resources
186+
func handleChainstateTeardown(req Request, state *SessionState) Response {
187+
state.Cleanup()
188+
result := map[string]interface{}{
189+
"success": true,
190+
}
191+
return NewSuccessResponse(req.ID, result)
192+
}

go-handler/go-bitcoinkernel

Submodule go-bitcoinkernel added at 4e19882

go-handler/go.mod

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module github.com/stringintech/go-bitcoinkernel/conformance/go-handler
2+
3+
go 1.23.3
4+
5+
replace github.com/stringintech/go-bitcoinkernel => ./go-bitcoinkernel
6+
7+
require github.com/stringintech/go-bitcoinkernel v0.0.0-00010101000000-000000000000

go-handler/handler.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package main
2+
3+
import "fmt"
4+
5+
// handleRequest dispatches a request to the appropriate handler
6+
func handleRequest(req Request, state *SessionState) (resp Response) {
7+
// Recover from panics and return error response
8+
defer func() {
9+
if r := recover(); r != nil {
10+
resp = NewErrorResponse(req.ID, ErrInternalError, fmt.Sprintf("Internal error (panic): %v", r))
11+
}
12+
}()
13+
14+
switch req.Method {
15+
// ScriptPubkey
16+
case "script_pubkey.verify":
17+
return handleScriptPubkeyVerify(req)
18+
19+
// Chainstate
20+
case "chainstate.setup":
21+
return handleChainstateSetup(req, state)
22+
case "chainstate.read_block":
23+
return handleChainstateReadBlock(req, state)
24+
case "chainstate.teardown":
25+
return handleChainstateTeardown(req, state)
26+
27+
default:
28+
return NewErrorResponse(req.ID, ErrMethodNotFound, "Unknown method: "+req.Method)
29+
}
30+
}

go-handler/main.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"encoding/json"
6+
"fmt"
7+
"os"
8+
)
9+
10+
func main() {
11+
// Create session state
12+
state := NewSessionState()
13+
defer state.Cleanup()
14+
15+
// Read requests from stdin line by line
16+
scanner := bufio.NewScanner(os.Stdin)
17+
for scanner.Scan() {
18+
line := scanner.Text()
19+
20+
// Parse request
21+
var req Request
22+
if err := json.Unmarshal([]byte(line), &req); err != nil {
23+
resp := NewErrorResponse("", ErrInvalidRequest, "Failed to parse JSON: "+err.Error())
24+
sendResponse(resp)
25+
continue
26+
}
27+
28+
resp := handleRequest(req, state)
29+
sendResponse(resp)
30+
}
31+
32+
if err := scanner.Err(); err != nil {
33+
fmt.Fprintf(os.Stderr, "Error reading stdin: %v\n", err)
34+
os.Exit(1)
35+
}
36+
}
37+
38+
// sendResponse writes a response to stdout as JSON
39+
func sendResponse(resp Response) {
40+
data, err := json.Marshal(resp)
41+
if err != nil {
42+
fmt.Fprintf(os.Stderr, "Error marshaling response: %v\n", err)
43+
return
44+
}
45+
46+
fmt.Println(string(data))
47+
}

go-handler/protocol.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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+
Result interface{} `json:"result,omitempty"`
16+
Error *ErrorObj `json:"error,omitempty"`
17+
}
18+
19+
type ErrorObj struct {
20+
Code string `json:"code"`
21+
Message string `json:"message"`
22+
}
23+
24+
// Standard error codes
25+
const (
26+
ErrInvalidRequest = "INVALID_REQUEST"
27+
ErrMethodNotFound = "METHOD_NOT_FOUND"
28+
ErrInvalidParams = "INVALID_PARAMS"
29+
ErrKernel = "KERNEL_ERROR"
30+
ErrScriptVerify = "SCRIPT_VERIFY_ERROR"
31+
ErrInternalError = "INTERNAL_ERROR"
32+
)
33+
34+
// NewErrorResponse creates an error response
35+
func NewErrorResponse(id, code, message string) Response {
36+
return Response{
37+
ID: id,
38+
Error: &ErrorObj{
39+
Code: code,
40+
Message: message,
41+
},
42+
}
43+
}
44+
45+
// NewSuccessResponse creates a success response
46+
func NewSuccessResponse(id string, result interface{}) Response {
47+
return Response{
48+
ID: id,
49+
Result: result,
50+
}
51+
}

0 commit comments

Comments
 (0)