Skip to content

Commit 06510cc

Browse files
committed
Add block header stuff
1 parent d444305 commit 06510cc

File tree

7 files changed

+538
-34
lines changed

7 files changed

+538
-34
lines changed

cmd/main.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/bsv-blockchain/teranode-p2p-poc/pkg/http"
77
"github.com/bsv-blockchain/teranode-p2p-poc/pkg/model"
88
"github.com/bsv-blockchain/teranode-p2p-poc/pkg/parser"
9+
"github.com/bsv-blockchain/teranode-p2p-poc/pkg/service"
910
"github.com/bsv-blockchain/teranode-p2p-poc/pkg/websocket"
1011
"github.com/sirupsen/logrus"
1112
"strings"
@@ -232,6 +233,25 @@ func main() {
232233
// Start HTTP server for querying messages
233234
go http.InitServer(log, db)
234235

236+
// Initialize coinbase service
237+
coinbaseService := service.NewCoinbaseService(db, log)
238+
239+
// Start background processing of coinbase data
240+
go func() {
241+
ticker := time.NewTicker(5 * time.Minute)
242+
defer ticker.Stop()
243+
244+
// Process once on startup
245+
coinbaseService.ProcessPendingBlocks()
246+
247+
for {
248+
select {
249+
case <-ticker.C:
250+
coinbaseService.ProcessPendingBlocks()
251+
}
252+
}
253+
}()
254+
235255
go func() {
236256
ticker := time.NewTicker(2 * time.Minute)
237257
defer ticker.Stop()

frontend-react/src/components/BlockDetailsModal.tsx

Lines changed: 67 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState, useEffect } from 'react';
1+
import React from 'react';
22
import { BlockHeader } from '../types/Message';
33

44
interface BlockDetailsModalProps {
@@ -8,17 +8,6 @@ interface BlockDetailsModalProps {
88
}
99

1010
export const BlockDetailsModal: React.FC<BlockDetailsModalProps> = ({ blockHeader, isOpen, onClose }) => {
11-
const [coinbaseData, setCoinbaseData] = useState<any>(null);
12-
const [loading, setLoading] = useState(false);
13-
14-
useEffect(() => {
15-
if (blockHeader && isOpen) {
16-
// TODO: Fetch coinbase transaction data when API is available
17-
// For now, we'll display the block header information
18-
setCoinbaseData(null);
19-
}
20-
}, [blockHeader, isOpen]);
21-
2211
if (!isOpen || !blockHeader) return null;
2312

2413
const formatTimestamp = (timestamp: number) => {
@@ -182,24 +171,79 @@ export const BlockDetailsModal: React.FC<BlockDetailsModalProps> = ({ blockHeade
182171
Coinbase Transaction
183172
</h4>
184173

185-
{loading ? (
186-
<div className="flex items-center justify-center py-8">
187-
<div className="animate-spin rounded-full h-8 w-8 border-2 border-[#1B1EA9] border-t-transparent"></div>
188-
</div>
189-
) : coinbaseData ? (
190-
<div className="space-y-3">
191-
{/* Coinbase data will be displayed here when API is available */}
192-
<div className="text-gray-600">Coinbase transaction details will be available soon.</div>
174+
{blockHeader.CoinbaseTxID ? (
175+
<div className="space-y-4">
176+
{/* Transaction ID */}
177+
<div>
178+
<label className="text-sm text-gray-500">Transaction ID</label>
179+
<div className="flex items-center gap-2">
180+
<p className="font-mono text-sm text-gray-900 break-all">{blockHeader.CoinbaseTxID}</p>
181+
<button
182+
onClick={() => navigator.clipboard.writeText(blockHeader.CoinbaseTxID || '')}
183+
className="flex-shrink-0 text-gray-400 hover:text-gray-600"
184+
>
185+
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
186+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
187+
</svg>
188+
</button>
189+
</div>
190+
</div>
191+
192+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
193+
{/* Block Reward */}
194+
<div className="bg-gradient-to-br from-green-50 to-green-100 rounded-lg p-4">
195+
<label className="text-sm text-green-700 font-medium">Total Output Value</label>
196+
<p className="text-2xl font-bold text-green-900">
197+
{blockHeader.CoinbaseValue ? (blockHeader.CoinbaseValue / 100000000).toFixed(8) : '0'} BSV
198+
</p>
199+
<p className="text-sm text-green-600 mt-1">
200+
{blockHeader.CoinbaseValue?.toLocaleString()} satoshis
201+
</p>
202+
</div>
203+
204+
{/* Miner Info */}
205+
<div className="bg-gradient-to-br from-blue-50 to-blue-100 rounded-lg p-4">
206+
<label className="text-sm text-blue-700 font-medium">Miner</label>
207+
<p className="text-lg font-semibold text-blue-900">
208+
{blockHeader.MinerAddress || 'Unknown'}
209+
</p>
210+
</div>
211+
</div>
212+
213+
{/* Coinbase Text */}
214+
{blockHeader.CoinbaseText && (
215+
<div>
216+
<label className="text-sm text-gray-500">Coinbase Message</label>
217+
<div className="bg-gray-50 rounded-lg p-3 mt-1">
218+
<p className="text-sm text-gray-700 font-mono break-all">{blockHeader.CoinbaseText}</p>
219+
</div>
220+
</div>
221+
)}
222+
223+
{/* Coinbase Script */}
224+
{blockHeader.CoinbaseScript && (
225+
<details className="group">
226+
<summary className="cursor-pointer text-sm text-gray-600 hover:text-gray-900 flex items-center gap-2">
227+
<svg className="w-4 h-4 transform group-open:rotate-90 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
228+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
229+
</svg>
230+
View Raw Coinbase Script
231+
</summary>
232+
<div className="mt-2 bg-gray-100 rounded-lg p-3">
233+
<p className="text-xs font-mono text-gray-600 break-all">{blockHeader.CoinbaseScript}</p>
234+
</div>
235+
</details>
236+
)}
193237
</div>
194238
) : (
195239
<div className="text-center py-8">
196240
<div className="inline-flex items-center justify-center w-16 h-16 bg-gray-100 rounded-full mb-4">
197241
<svg className="w-8 h-8 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
198-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
242+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
199243
</svg>
200244
</div>
201-
<p className="text-gray-600">Coinbase transaction data not yet available</p>
202-
<p className="text-sm text-gray-500 mt-2">This feature requires additional API endpoints</p>
245+
<p className="text-gray-600">Coinbase data pending</p>
246+
<p className="text-sm text-gray-500 mt-2">Check back in a few moments</p>
203247
</div>
204248
)}
205249
</div>

frontend-react/src/types/Message.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,12 @@ export interface BlockHeader {
120120
Bits: number;
121121
Nonce: number;
122122
ReceivedAt: string;
123+
// Coinbase transaction fields
124+
CoinbaseValue?: number;
125+
CoinbaseScript?: string;
126+
MinerAddress?: string;
127+
CoinbaseTxID?: string;
128+
CoinbaseText?: string;
123129
}
124130

125131
export interface MiningOn {

pkg/datahub/client.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package datahub
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"io"
7+
"net/http"
8+
"time"
9+
)
10+
11+
// Client represents a client for fetching data from BSV data hubs
12+
type Client struct {
13+
httpClient *http.Client
14+
}
15+
16+
// NewClient creates a new data hub client
17+
func NewClient() *Client {
18+
return &Client{
19+
httpClient: &http.Client{
20+
Timeout: 30 * time.Second,
21+
},
22+
}
23+
}
24+
25+
// BlockResponse represents the response from data hub for a block
26+
type BlockResponse struct {
27+
Hash string `json:"hash"`
28+
Height int `json:"height"`
29+
Version int `json:"version"`
30+
Size int `json:"size"`
31+
Timestamp int64 `json:"timestamp"`
32+
Transactions []Transaction `json:"transactions"`
33+
}
34+
35+
// Transaction represents a transaction in the block
36+
type Transaction struct {
37+
TxID string `json:"txid"`
38+
Version int `json:"version"`
39+
Size int `json:"size"`
40+
Inputs []Input `json:"inputs"`
41+
Outputs []Output `json:"outputs"`
42+
LockTime int `json:"locktime"`
43+
}
44+
45+
// Input represents a transaction input
46+
type Input struct {
47+
Coinbase string `json:"coinbase,omitempty"` // Only present in coinbase transactions
48+
TxID string `json:"txid,omitempty"`
49+
Vout int `json:"vout,omitempty"`
50+
Script string `json:"script,omitempty"`
51+
Sequence uint32 `json:"sequence"`
52+
}
53+
54+
// Output represents a transaction output
55+
type Output struct {
56+
Value uint64 `json:"value"` // Value in satoshis
57+
Script string `json:"script"`
58+
ScriptPubKey struct {
59+
Type string `json:"type"`
60+
Addresses []string `json:"addresses,omitempty"`
61+
} `json:"scriptPubKey,omitempty"`
62+
}
63+
64+
// GetBlock fetches block data from the data hub
65+
func (c *Client) GetBlock(dataHubURL string, blockHash string) (*BlockResponse, error) {
66+
if dataHubURL == "" {
67+
return nil, fmt.Errorf("data hub URL is empty")
68+
}
69+
70+
// Construct the URL for fetching block data
71+
// Note: The exact endpoint format depends on the data hub implementation
72+
// This is a common pattern for BSV data hubs
73+
url := fmt.Sprintf("%s/block/%s", dataHubURL, blockHash)
74+
75+
resp, err := c.httpClient.Get(url)
76+
if err != nil {
77+
return nil, fmt.Errorf("failed to fetch block: %w", err)
78+
}
79+
defer resp.Body.Close()
80+
81+
if resp.StatusCode != http.StatusOK {
82+
body, _ := io.ReadAll(resp.Body)
83+
return nil, fmt.Errorf("unexpected status code %d: %s", resp.StatusCode, string(body))
84+
}
85+
86+
var block BlockResponse
87+
if err := json.NewDecoder(resp.Body).Decode(&block); err != nil {
88+
return nil, fmt.Errorf("failed to decode block response: %w", err)
89+
}
90+
91+
return &block, nil
92+
}
93+
94+
// GetCoinbaseTransaction extracts the coinbase transaction from a block
95+
func GetCoinbaseTransaction(block *BlockResponse) *Transaction {
96+
if len(block.Transactions) == 0 {
97+
return nil
98+
}
99+
100+
// The first transaction in a block is always the coinbase transaction
101+
tx := &block.Transactions[0]
102+
103+
// Verify it's a coinbase transaction
104+
if len(tx.Inputs) == 1 && tx.Inputs[0].Coinbase != "" {
105+
return tx
106+
}
107+
108+
return nil
109+
}

pkg/model/block.go

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,21 @@ type Block struct {
1717
}
1818

1919
type BlockHeader struct {
20-
ID uint `gorm:"primaryKey" json:"ID"`
21-
Network string `gorm:"index;not null" json:"Network"`
22-
Hash string `gorm:"index;not null" json:"Hash"`
23-
Height uint32 `gorm:"index;not null" json:"Height"`
24-
Version int32 `gorm:"not null" json:"Version"`
25-
PreviousHash string `gorm:"index" json:"PreviousHash"`
26-
MerkleRoot string `gorm:"not null" json:"MerkleRoot"`
27-
Timestamp uint32 `gorm:"index;not null" json:"Timestamp"` // Unix timestamp
28-
Bits uint32 `gorm:"not null" json:"Bits"` // Difficulty target
29-
Nonce uint32 `gorm:"not null" json:"Nonce"`
30-
ReceivedAt time.Time `gorm:"index;not null" json:"ReceivedAt"`
20+
ID uint `gorm:"primaryKey" json:"ID"`
21+
Network string `gorm:"index;not null" json:"Network"`
22+
Hash string `gorm:"index;not null" json:"Hash"`
23+
Height uint32 `gorm:"index;not null" json:"Height"`
24+
Version int32 `gorm:"not null" json:"Version"`
25+
PreviousHash string `gorm:"index" json:"PreviousHash"`
26+
MerkleRoot string `gorm:"not null" json:"MerkleRoot"`
27+
Timestamp uint32 `gorm:"index;not null" json:"Timestamp"` // Unix timestamp
28+
Bits uint32 `gorm:"not null" json:"Bits"` // Difficulty target
29+
Nonce uint32 `gorm:"not null" json:"Nonce"`
30+
ReceivedAt time.Time `gorm:"index;not null" json:"ReceivedAt"`
31+
// Coinbase transaction fields
32+
CoinbaseValue uint64 `gorm:"default:0" json:"CoinbaseValue"` // Total output value in satoshis
33+
CoinbaseScript string `gorm:"type:text" json:"CoinbaseScript"` // Hex-encoded coinbase script
34+
MinerAddress string `gorm:"index" json:"MinerAddress"` // Miner address if identifiable
35+
CoinbaseTxID string `gorm:"index" json:"CoinbaseTxID"` // Coinbase transaction ID
36+
CoinbaseText string `gorm:"type:text" json:"CoinbaseText"` // Decoded ASCII text from coinbase
3137
}

0 commit comments

Comments
 (0)