Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add block endpoints #1

Merged
merged 18 commits into from
Jan 8, 2024
Merged
Show file tree
Hide file tree
Changes from 12 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
8 changes: 6 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ on:
push:
branches:
- master
env:
CGO_ENABLED: 1

jobs:
test:
Expand All @@ -14,7 +16,7 @@ jobs:
strategy:
matrix:
os: [ ubuntu-latest , macos-latest, windows-latest ]
go-version: [ '1.19', '1.20' ]
go-version: [ '1.20', '1.21' ]
steps:
- name: Configure git
run: git config --global core.autocrlf false # required on Windows
Expand All @@ -25,4 +27,6 @@ jobs:
- name: Test
uses: ./.github/actions/test
- name: Build
run: go build -o bin/ ./cmd/explored
run: |
go install github.com/mattn/go-sqlite3
chris124567 marked this conversation as resolved.
Show resolved Hide resolved
go build -o bin/ ./cmd/explored
20 changes: 20 additions & 0 deletions api/client.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package api

import (
"fmt"

"go.sia.tech/core/consensus"
"go.sia.tech/core/types"
"go.sia.tech/jape"
Expand Down Expand Up @@ -58,3 +60,21 @@ func (c *Client) SyncerBroadcastBlock(b types.Block) (err error) {
err = c.c.POST("/syncer/broadcast/block", b, nil)
return
}

// Tip returns the current tip of the explorer.
func (c *Client) Tip() (resp types.ChainIndex, err error) {
err = c.c.GET("/explorer/tip", &resp)
return
}

// Block returns the block with the specified ID.
func (c *Client) Block(id types.BlockID) (resp types.Block, err error) {
err = c.c.GET(fmt.Sprintf("/explorer/block/id/%s", id), &resp)
return
}

// BlockHeight returns the block with the specified height.
func (c *Client) BlockHeight(height uint64) (resp types.Block, err error) {
err = c.c.GET(fmt.Sprintf("/explorer/block/height/%d", height), &resp)
return
}
47 changes: 46 additions & 1 deletion api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,18 @@ type (
BroadcastV2TransactionSet(txns []types.V2Transaction)
BroadcastV2BlockOutline(bo gateway.V2BlockOutline)
}

// Explorer implements a Sia explorer.
Explorer interface {
Tip() (types.ChainIndex, error)
Block(id types.BlockID) (types.Block, error)
BlockHeight(height uint64) (types.Block, error)
}
)

type server struct {
cm ChainManager
e Explorer
s Syncer

mu sync.Mutex
Expand Down Expand Up @@ -124,10 +132,43 @@ func (s *server) txpoolBroadcastHandler(jc jape.Context) {
}
}

func (s *server) explorerTipHandler(jc jape.Context) {
tip, err := s.e.Tip()
if jc.Check("failed to get tip", err) != nil {
return
}
jc.Encode(tip)
}

func (s *server) explorerBlockHandler(jc jape.Context) {
var id types.BlockID
if jc.DecodeParam("id", &id) != nil {
return
}
block, err := s.e.Block(id)
if jc.Check("failed to get block", err) != nil {
return
}
jc.Encode(block)
}

func (s *server) explorerBlockHeightHandler(jc jape.Context) {
var height uint64
if jc.DecodeParam("height", &height) != nil {
return
}
block, err := s.e.BlockHeight(height)
if jc.Check("failed to get block", err) != nil {
return
}
jc.Encode(block)
}

// NewServer returns an HTTP handler that serves the explored API.
func NewServer(cm ChainManager, s Syncer) http.Handler {
func NewServer(e Explorer, cm ChainManager, s Syncer) http.Handler {
srv := server{
cm: cm,
e: e,
s: s,
}
return jape.Mux(map[string]jape.Handler{
Expand All @@ -138,5 +179,9 @@ func NewServer(cm ChainManager, s Syncer) http.Handler {
"GET /txpool/transactions": srv.txpoolTransactionsHandler,
"GET /txpool/fee": srv.txpoolFeeHandler,
"POST /txpool/broadcast": srv.txpoolBroadcastHandler,

"GET /explorer/tip": srv.explorerTipHandler,
"GET /explorer/block/id/:id": srv.explorerBlockHandler,
"GET /explorer/block/height/:height": srv.explorerBlockHeightHandler,
})
}
10 changes: 9 additions & 1 deletion cmd/explored/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,17 @@ func newNode(addr, dir string, chainNetwork string, useUPNP bool, logger *zap.Lo

store, err := sqlite.OpenDatabase("./explore.db", logger)
chris124567 marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
panic(err)
return nil, err
}
e := explorer.NewExplorer(store)
tip, err := store.Tip()
if err != nil {
chris124567 marked this conversation as resolved.
Show resolved Hide resolved
tip = types.ChainIndex{
ID: genesisBlock.ID(),
Height: 0,
}
}
cm.AddSubscriber(store, tip)

l, err := net.Listen("tcp", addr)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion cmd/explored/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
)

func startWeb(l net.Listener, node *node, password string) error {
renter := api.NewServer(node.cm, node.s)
renter := api.NewServer(node.e, node.cm, node.s)
api := jape.BasicAuth(password)(renter)
return http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.HasPrefix(r.URL.Path, "/api") {
Expand Down
32 changes: 23 additions & 9 deletions explorer/explorer.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
package explorer

import "go.sia.tech/core/chain"
import (
"go.sia.tech/core/chain"
"go.sia.tech/core/types"
)

// A Store is a database that stores information about elements, contracts,
// and blocks.
type Store interface{}
type Store interface {
chain.Subscriber

Tip() (types.ChainIndex, error)
Block(id types.BlockID) (types.Block, error)
BlockHeight(height uint64) (types.Block, error)
chris124567 marked this conversation as resolved.
Show resolved Hide resolved
}

// Explorer implements a Sia explorer.
type Explorer struct {
Expand All @@ -13,15 +22,20 @@ type Explorer struct {

// NewExplorer returns a Sia explorer.
func NewExplorer(s Store) *Explorer {
return &Explorer{s}
return &Explorer{s: s}
}

// Tip returns the tip of the best known valid chain.
func (e *Explorer) Tip() (types.ChainIndex, error) {
return e.s.Tip()
}

// ProcessChainApplyUpdate implements chain.Subscriber.
func (e *Explorer) ProcessChainApplyUpdate(cau *chain.ApplyUpdate, mayCommit bool) error {
return nil
// Block returns the block with the specified ID.
func (e *Explorer) Block(id types.BlockID) (types.Block, error) {
return e.s.Block(id)
}

// ProcessChainRevertUpdate implements chain.Subscriber.
func (e *Explorer) ProcessChainRevertUpdate(cru *chain.RevertUpdate) error {
return nil
// BlockHeight returns the block with the specified height.
func (e *Explorer) BlockHeight(height uint64) (types.Block, error) {
return e.s.BlockHeight(height)
}
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ require (
github.com/mattn/go-sqlite3 v1.14.19
go.etcd.io/bbolt v1.3.7
go.sia.tech/core v0.1.12-0.20231021194448-f1e65eb9f0d0
go.sia.tech/jape v0.9.0
go.sia.tech/jape v0.11.1
go.uber.org/zap v1.26.0
golang.org/x/term v0.6.0
lukechampine.com/frand v1.4.2
lukechampine.com/upnp v0.3.0
Expand All @@ -17,7 +18,6 @@ require (
github.com/julienschmidt/httprouter v1.3.0 // indirect
go.sia.tech/mux v1.2.0 // indirect
go.uber.org/multierr v1.10.0 // indirect
go.uber.org/zap v1.26.0 // indirect
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/tools v0.7.0 // indirect
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ go.sia.tech/core v0.1.12-0.20231021194448-f1e65eb9f0d0 h1:2nKOKa99g9h9m3hL5UortA
go.sia.tech/core v0.1.12-0.20231021194448-f1e65eb9f0d0/go.mod h1:3EoY+rR78w1/uGoXXVqcYdwSjSJKuEMI5bL7WROA27Q=
go.sia.tech/jape v0.9.0 h1:kWgMFqALYhLMJYOwWBgJda5ko/fi4iZzRxHRP7pp8NY=
go.sia.tech/jape v0.9.0/go.mod h1:4QqmBB+t3W7cNplXPj++ZqpoUb2PeiS66RLpXmEGap4=
go.sia.tech/jape v0.11.1 h1:M7IP+byXL7xOqzxcHUQuXW+q3sYMkYzmMlMw+q8ZZw0=
go.sia.tech/jape v0.11.1/go.mod h1:4QqmBB+t3W7cNplXPj++ZqpoUb2PeiS66RLpXmEGap4=
go.sia.tech/mux v1.2.0 h1:ofa1Us9mdymBbGMY2XH/lSpY8itFsKIo/Aq8zwe+GHU=
go.sia.tech/mux v1.2.0/go.mod h1:Yyo6wZelOYTyvrHmJZ6aQfRoer3o4xyKQ4NmQLJrBSo=
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
Expand Down
16 changes: 16 additions & 0 deletions persist/sqlite/init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,21 @@ CREATE TABLE global_settings (
db_version INTEGER NOT NULL -- used for migrations
);

CREATE TABLE Blocks (
id BINARY(32) NOT NULL PRIMARY KEY,
chris124567 marked this conversation as resolved.
Show resolved Hide resolved
height INTEGER NOT NULL,
parent_id BINARY(32) NOT NULL,
nonce BINARY(8) NOT NULL,
timestamp INTEGER NOT NULL
);

CREATE TABLE MinerPayouts (
block_id REFERENCES Blocks(id) ON DELETE CASCADE,
chris124567 marked this conversation as resolved.
Show resolved Hide resolved
block_order INTEGER NOT NULL,
address BINARY(32) NOT NULL,
value BINARY(16) NOT NULL
);
chris124567 marked this conversation as resolved.
Show resolved Hide resolved


-- initialize the global settings table
INSERT INTO global_settings (id, db_version) VALUES (0, 0); -- should not be changed
91 changes: 91 additions & 0 deletions persist/sqlite/query.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package sqlite

import (
"time"

"go.sia.tech/core/types"
)

func decode(obj types.DecoderFrom, data []byte) error {
d := types.NewBufDecoder(data)
obj.DecodeFrom(d)
return d.Err()
}

func decodeUint64(x *uint64, data []byte) error {
d := types.NewBufDecoder(data)
if x != nil {
*x = d.ReadUint64()
}
return d.Err()
}

// Tip implements explorer.Store.
func (s *Store) Tip() (result types.ChainIndex, err error) {
var data []byte
if err = s.queryRow("SELECT id, height FROM Blocks WHERE height = (SELECT MAX(height) from Blocks)").Scan(&data, &result.Height); err != nil {
return
}
if err = decode(&result.ID, data); err != nil {
return
}
return
}

// Block implements explorer.Store.
func (s *Store) Block(id types.BlockID) (result types.Block, err error) {
{
var timestamp int64
var parentID, nonce []byte
if err = s.queryRow("SELECT parent_id, nonce, timestamp FROM Blocks WHERE id = ?", encode(id)).Scan(&parentID, &nonce, &timestamp); err != nil {
chris124567 marked this conversation as resolved.
Show resolved Hide resolved
return
}
result.Timestamp = time.Unix(timestamp, 0).UTC()
if err = decode(&result.ParentID, parentID); err != nil {
return
}
if err = decodeUint64(&result.Nonce, nonce); err != nil {
return
}
}

{
var rows *loggedRows
if rows, err = s.query("SELECT address, value FROM MinerPayouts WHERE block_id = ? ORDER BY block_order", encode(id)); err != nil {
chris124567 marked this conversation as resolved.
Show resolved Hide resolved
return
}
chris124567 marked this conversation as resolved.
Show resolved Hide resolved
defer rows.Close()

var address, value []byte
for rows.Next() {
if err = rows.Scan(&address, &value); err != nil {
return
}
var minerPayout types.SiacoinOutput
if err = decode(&minerPayout.Address, address); err != nil {
return
}
if err = decode(&minerPayout.Value, value); err != nil {
return
}
result.MinerPayouts = append(result.MinerPayouts, minerPayout)
}
}

return
}

// BlockHeight implements explorer.Store.
func (s *Store) BlockHeight(height uint64) (result types.Block, err error) {
var data []byte
if err = s.queryRow("SELECT id FROM Blocks WHERE height = ?", height).Scan(&data); err != nil {
return
}

var bid types.BlockID
if err = decode(&bid, data); err != nil {
return
}
result, err = s.Block(bid)
return
}
5 changes: 5 additions & 0 deletions persist/sqlite/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ import (
"fmt"
"math"
"strings"
"sync"
"time"

"github.com/mattn/go-sqlite3"
"go.sia.tech/core/chain"
"go.uber.org/zap"
"lukechampine.com/frand"
)
Expand All @@ -19,6 +21,9 @@ type (
Store struct {
db *sql.DB
log *zap.Logger

mu sync.Mutex
pendingUpdates []*chain.ApplyUpdate
}
)

Expand Down
Loading