initial scaffold

- module layout: cmd/mithril-go, internal/{aggregator,artifact,verify,networks}
- aggregator REST client, list command working against mainnet
- download/extract/verify stubbed
- no deps yet, pure stdlib
This commit is contained in:
Kayos 2026-04-23 15:12:39 -07:00
commit f87b7fc3c4
8 changed files with 450 additions and 0 deletions

147
cmd/mithril-go/main.go Normal file
View file

@ -0,0 +1,147 @@
// Command mithril-go is a pure-Go client for the Cardano Mithril protocol.
//
// It downloads, verifies, and extracts Mithril-certified snapshots of the
// Cardano database without requiring the upstream Rust mithril-client.
//
// Subcommands:
// list — list available cardano-database snapshots on an aggregator
// download — fetch a snapshot (verify + extract optional)
// verify — verify an already-downloaded snapshot
// info — show aggregator + network details
package main
import (
"context"
"flag"
"fmt"
"os"
"text/tabwriter"
"git.sulkta.coop/Sulkta-Coop/mithril-go/internal/aggregator"
"git.sulkta.coop/Sulkta-Coop/mithril-go/internal/networks"
)
const version = "0.0.1-dev"
func main() {
if len(os.Args) < 2 {
usage()
os.Exit(2)
}
cmd := os.Args[1]
args := os.Args[2:]
switch cmd {
case "list":
os.Exit(cmdList(args))
case "download":
os.Exit(cmdDownload(args))
case "verify":
os.Exit(cmdVerify(args))
case "info":
os.Exit(cmdInfo(args))
case "version", "--version", "-v":
fmt.Println("mithril-go", version)
case "help", "--help", "-h":
usage()
default:
fmt.Fprintf(os.Stderr, "unknown command: %s\n\n", cmd)
usage()
os.Exit(2)
}
}
func usage() {
fmt.Fprintln(os.Stderr, `mithril-go pure Go Mithril snapshot client
Usage:
mithril-go <command> [flags]
Commands:
list List available cardano-database snapshots
download Download + verify + extract a snapshot
verify Verify an already-downloaded snapshot
info Show network + aggregator info
version Print version
help Show this help
Common flags:
-network mainnet | preprod | preview (default: preprod)`)
}
func resolveNetwork(fs *flag.FlagSet, args []string) (networks.Network, []string, error) {
networkName := fs.String("network", "preprod", "Cardano network: mainnet | preprod | preview")
if err := fs.Parse(args); err != nil {
return networks.Network{}, nil, err
}
n, ok := networks.ByName(*networkName)
if !ok {
return networks.Network{}, nil, fmt.Errorf("unknown network: %s", *networkName)
}
return n, fs.Args(), nil
}
func cmdList(args []string) int {
fs := flag.NewFlagSet("list", flag.ExitOnError)
n, _, err := resolveNetwork(fs, args)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return 2
}
client := aggregator.New(n.AggregatorURL)
snaps, err := client.ListCardanoDBSnapshots(context.Background())
if err != nil {
fmt.Fprintln(os.Stderr, "list:", err)
return 1
}
tw := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintln(tw, "HASH\tEPOCH\tIMMUTABLE\tSIZE\tCREATED")
for _, s := range snaps {
fmt.Fprintf(tw, "%.16s\t%d\t%d\t%s\t%s\n",
s.Hash, s.Beacon.Epoch, s.Beacon.ImmutableFileNumber,
humanSize(s.TotalDBSizeUncompressed),
s.CreatedAt.UTC().Format("2006-01-02 15:04 MST"))
}
if err := tw.Flush(); err != nil {
fmt.Fprintln(os.Stderr, "flush:", err)
return 1
}
return 0
}
func cmdDownload(args []string) int {
fmt.Fprintln(os.Stderr, "download: not yet implemented")
return 1
}
func cmdVerify(args []string) int {
fmt.Fprintln(os.Stderr, "verify: not yet implemented")
return 1
}
func cmdInfo(args []string) int {
fs := flag.NewFlagSet("info", flag.ExitOnError)
n, _, err := resolveNetwork(fs, args)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return 2
}
fmt.Printf("network: %s\n", n.Name)
fmt.Printf("aggregator: %s\n", n.AggregatorURL)
fmt.Printf("genesis verify key: %s…\n", n.GenesisVerifyKey[:16])
return 0
}
func humanSize(b uint64) string {
const k = 1024
if b < k {
return fmt.Sprintf("%dB", b)
}
units := []string{"K", "M", "G", "T"}
v := float64(b)
u := 0
for v >= k && u < len(units)-1 {
v /= k
u++
}
return fmt.Sprintf("%.1f%s", v, units[u])
}