Skip to content

Commit

Permalink
use hostd store
Browse files Browse the repository at this point in the history
  • Loading branch information
chris124567 committed Dec 21, 2023
1 parent 21d1d82 commit df60088
Show file tree
Hide file tree
Showing 12 changed files with 606 additions and 90 deletions.
50 changes: 35 additions & 15 deletions cmd/explored/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"os/signal"
"runtime/debug"

"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"golang.org/x/term"
)

Expand Down Expand Up @@ -36,21 +38,21 @@ func init() {
}
}

func check(context string, err error) {
func check(context string, err error, logger *zap.Logger) {
if err != nil {
log.Fatalf("%v: %v", context, err)
}
}

func getAPIPassword() string {
func getAPIPassword(logger *zap.Logger) string {
apiPassword := os.Getenv("EXPLORED_API_PASSWORD")
if apiPassword != "" {
fmt.Println("env: Using EXPLORED_API_PASSWORD environment variable")
logger.Info("env: Using EXPLORED_API_PASSWORD environment variable")
} else {
fmt.Print("Enter API password: ")
pw, err := term.ReadPassword(int(os.Stdin.Fd()))
fmt.Println()
check("Could not read API password:", err)
check("Could not read API password:", err, logger)
if err != nil {
log.Fatal(err)
}
Expand All @@ -60,39 +62,57 @@ func getAPIPassword() string {
}

func main() {
log.SetFlags(0)
// configure console logging note: this is configured before anything else
// to have consistent logging. File logging will be added after the cli
// flags and config is parsed
consoleCfg := zap.NewProductionEncoderConfig()
consoleCfg.TimeKey = "" // prevent duplicate timestamps
consoleCfg.EncodeTime = zapcore.RFC3339TimeEncoder
consoleCfg.EncodeDuration = zapcore.StringDurationEncoder
consoleCfg.EncodeLevel = zapcore.CapitalColorLevelEncoder
consoleCfg.StacktraceKey = ""
consoleCfg.CallerKey = ""
consoleEncoder := zapcore.NewConsoleEncoder(consoleCfg)

// only log info messages to console unless stdout logging is enabled
consoleCore := zapcore.NewCore(consoleEncoder, zapcore.Lock(os.Stdout), zap.NewAtomicLevelAt(zap.InfoLevel))
log := zap.New(consoleCore, zap.AddCaller())
defer log.Sync()
// redirect stdlib log to zap
zap.RedirectStdLog(log.Named("stdlib"))

gatewayAddr := flag.String("addr", ":9981", "p2p address to listen on")
apiAddr := flag.String("http", "localhost:9980", "address to serve API on")
dir := flag.String("dir", ".", "directory to store node state in")
network := flag.String("network", "mainnet", "network to connect to")
upnp := flag.Bool("upnp", true, "attempt to forward ports and discover IP with UPnP")
flag.Parse()

log.Println("explored v0.0.0")
log.Info("explored v0.0.0")
if flag.Arg(0) == "version" {
log.Println("Commit Hash:", commit)
log.Println("Commit Date:", timestamp)
log.Info("Commit Hash:", zap.String("hash", commit))
log.Info("Commit Date:", zap.String("date", timestamp))
return
}

apiPassword := getAPIPassword()
apiPassword := getAPIPassword(log)
l, err := net.Listen("tcp", *apiAddr)
if err != nil {
log.Fatal(err)
log.Fatal("Failed to create listener", zap.Error(err))
}

n, err := newNode(*gatewayAddr, *dir, *network, *upnp)
n, err := newNode(*gatewayAddr, *dir, *network, *upnp, log)
if err != nil {
log.Fatal(err)
log.Fatal("Failed to create node", zap.Error(err))
}
log.Println("p2p: Listening on", n.s.Addr())
log.Info("p2p: Listening on", zap.String("addr", n.s.Addr()))
stop := n.Start()
log.Println("api: Listening on", l.Addr())
log.Info("api: Listening on", zap.String("addr", l.Addr().String()))
go startWeb(l, n, apiPassword)

signalCh := make(chan os.Signal, 1)
signal.Notify(signalCh, os.Interrupt)
<-signalCh
log.Println("Shutting down...")
log.Info("Shutting down...")
stop()
}
21 changes: 11 additions & 10 deletions cmd/explored/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ import (
"go.sia.tech/core/gateway"
"go.sia.tech/core/types"
"go.sia.tech/explored/explorer"
"go.sia.tech/explored/internal/exploreutil"
"go.sia.tech/explored/internal/syncerutil"
"go.sia.tech/explored/persist/sqlite"
"go.sia.tech/explored/syncer"
"go.uber.org/zap"
"lukechampine.com/upnp"
)

Expand Down Expand Up @@ -129,7 +130,7 @@ type node struct {
Start func() (stop func())
}

func newNode(addr, dir string, chainNetwork string, useUPNP bool) (*node, error) {
func newNode(addr, dir string, chainNetwork string, useUPNP bool, logger *zap.Logger) (*node, error) {
var network *consensus.Network
var genesisBlock types.Block
var bootstrapPeers []string
Expand All @@ -146,7 +147,7 @@ func newNode(addr, dir string, chainNetwork string, useUPNP bool) (*node, error)

bdb, err := bolt.Open(filepath.Join(dir, "consensus.db"), 0600, nil)
if err != nil {
log.Fatal(err)
return nil, err
}
db := &boltDB{db: bdb}
dbstore, tipState, err := chain.NewDBStore(db, network, genesisBlock)
Expand All @@ -155,7 +156,7 @@ func newNode(addr, dir string, chainNetwork string, useUPNP bool) (*node, error)
}
cm := chain.NewManager(dbstore, tipState)

store, err := exploreutil.NewStore("./explore.db")
store, err := sqlite.OpenDatabase("./explore.db", logger)
if err != nil {
panic(err)
}
Expand All @@ -170,21 +171,21 @@ func newNode(addr, dir string, chainNetwork string, useUPNP bool) (*node, error)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if d, err := upnp.Discover(ctx); err != nil {
log.Println("WARN: couldn't discover UPnP device:", err)
logger.Warn("WARN: couldn't discover UPnP device:", zap.Error(err))
} else {
_, portStr, _ := net.SplitHostPort(addr)
port, _ := strconv.Atoi(portStr)
if !d.IsForwarded(uint16(port), "TCP") {
if err := d.Forward(uint16(port), "TCP", "explored"); err != nil {
log.Println("WARN: couldn't forward port:", err)
logger.Warn("WARN: couldn't forward port:", zap.Error(err))
} else {
log.Println("p2p: Forwarded port", port)
logger.Info("p2p: Forwarded port", zap.Int("port", port))
}
}
if ip, err := d.ExternalIP(); err != nil {
log.Println("WARN: couldn't determine external IP:", err)
logger.Warn("WARN: couldn't determine external IP:", zap.Error(err))
} else {
log.Println("p2p: External IP is", ip)
logger.Info("p2p: External IP is", zap.String("ip", ip))
syncerAddr = net.JoinHostPort(ip, portStr)
}
}
Expand All @@ -197,7 +198,7 @@ func newNode(addr, dir string, chainNetwork string, useUPNP bool) (*node, error)

ps, err := syncerutil.NewJSONPeerStore(filepath.Join(dir, "peers.json"))
if err != nil {
log.Fatal(err)
return nil, err
}
for _, peer := range bootstrapPeers {
ps.AddPeer(peer)
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ require (
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
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
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ 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/mux v1.2.0 h1:ofa1Us9mdymBbGMY2XH/lSpY8itFsKIo/Aq8zwe+GHU=
go.sia.tech/mux v1.2.0/go.mod h1:Yyo6wZelOYTyvrHmJZ6aQfRoer3o4xyKQ4NmQLJrBSo=
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=
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122 h1:NvGWuYG8dkDHFSKksI1P9faiVJ9rayE6l0+ouWVIDs8=
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
Expand Down
65 changes: 0 additions & 65 deletions internal/exploreutil/store.go

This file was deleted.

12 changes: 12 additions & 0 deletions persist/sqlite/consts_default.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//go:build !testing

package sqlite

const (
busyTimeout = 5000 // 5 seconds
retryAttempts = 15 // 15 attempts
factor = 2.0 // factor ^ retryAttempts = backoff time in milliseconds

// the number of records to limit long-running sector queries to
sqlSectorBatchSize = 256 // 1 GiB
)
12 changes: 12 additions & 0 deletions persist/sqlite/consts_testing.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//go:build testing

package sqlite

const (
busyTimeout = 5 // 5ms
retryAttempts = 10 // 10 attempts
factor = 2.0 // factor ^ retryAttempts = backoff time in milliseconds

// the number of records to limit long-running sector queries to
sqlSectorBatchSize = 5 // 20 MiB
)
83 changes: 83 additions & 0 deletions persist/sqlite/init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package sqlite

import (
"database/sql"
_ "embed" // for init.sql
"errors"
"time"

"fmt"

"go.uber.org/zap"
)

// init queries are run when the database is first created.
//
//go:embed init.sql
var initDatabase string

func (s *Store) initNewDatabase(target int64) error {
return s.transaction(func(tx txn) error {
if _, err := tx.Exec(initDatabase); err != nil {
return fmt.Errorf("failed to initialize database: %w", err)
} else if err := setDBVersion(tx, target); err != nil {
return fmt.Errorf("failed to set initial database version: %w", err)
}
return nil
})
}

func (s *Store) upgradeDatabase(current, target int64) error {
log := s.log.Named("migrations")
log.Info("migrating database", zap.Int64("current", current), zap.Int64("target", target))

// disable foreign key constraints during migration
if _, err := s.db.Exec("PRAGMA foreign_keys = OFF"); err != nil {
return fmt.Errorf("failed to disable foreign key constraints: %w", err)
}
defer func() {
// re-enable foreign key constraints
if _, err := s.db.Exec("PRAGMA foreign_keys = ON"); err != nil {
log.Panic("failed to enable foreign key constraints", zap.Error(err))
}
}()

return s.transaction(func(tx txn) error {
for _, fn := range migrations[current-1:] {
current++
start := time.Now()
if err := fn(tx, log.With(zap.Int64("version", current))); err != nil {
return fmt.Errorf("failed to migrate database to version %v: %w", current, err)
}
// check that no foreign key constraints were violated
if err := tx.QueryRow("PRAGMA foreign_key_check").Scan(); !errors.Is(err, sql.ErrNoRows) {
return fmt.Errorf("foreign key constraints are not satisfied")
}
log.Debug("migration complete", zap.Int64("current", current), zap.Int64("target", target), zap.Duration("elapsed", time.Since(start)))
}

// set the final database version
return setDBVersion(tx, target)
})
}

func (s *Store) init() error {
// calculate the expected final database version
target := int64(len(migrations) + 1)
// disable foreign key constraints during migration
if _, err := s.db.Exec("PRAGMA foreign_keys = OFF"); err != nil {
return fmt.Errorf("failed to disable foreign key constraints: %w", err)
}

version := getDBVersion(s.db)
switch {
case version == 0:
return s.initNewDatabase(target)
case version < target:
return s.upgradeDatabase(version, target)
case version > target:
return fmt.Errorf("database version %v is newer than expected %v. database downgrades are not supported", version, target)
}
// nothing to do
return nil
}
7 changes: 7 additions & 0 deletions persist/sqlite/init.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
CREATE TABLE global_settings (
id INTEGER PRIMARY KEY NOT NULL DEFAULT 0 CHECK (id = 0), -- enforce a single row
db_version INTEGER NOT NULL -- used for migrations
);

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

import "go.uber.org/zap"

var migrations []func(tx txn, log *zap.Logger) error
Loading

0 comments on commit df60088

Please sign in to comment.