Files
opentimestamps/cmd/ots/upgrade.go
2025-04-11 13:31:02 +02:00

139 lines
3.9 KiB
Go

package main
import (
"context"
"log/slog"
"os"
"time"
"git.intruders.space/public/opentimestamps"
"github.com/spf13/cobra"
)
var (
// Upgrade command flags
upgradeOutput string
upgradeTimeout time.Duration
upgradeDryRun bool
)
// upgradeCmd represents the upgrade command
var upgradeCmd = &cobra.Command{
Use: "upgrade [flags] <file.ots>",
Short: "Upgrade a timestamp",
Long: `Upgrade a timestamp by checking if pending attestations have been confirmed in the Bitcoin blockchain.
If confirmed, the timestamp will be updated with the Bitcoin block information.`,
Args: cobra.ExactArgs(1),
Run: runUpgradeCmd,
}
func init() {
// Local flags for the upgrade command
upgradeCmd.Flags().StringVarP(&upgradeOutput, "output", "o", "", "Output filename (default: overwrites the input file)")
upgradeCmd.Flags().DurationVar(&upgradeTimeout, "timeout", 30*time.Second, "Timeout for calendar server connections")
upgradeCmd.Flags().BoolVar(&upgradeDryRun, "dry-run", false, "Don't write output file, just check if upgrade is possible")
}
func runUpgradeCmd(cmd *cobra.Command, args []string) {
inputPath := args[0]
// Determine output file path
outputPath := upgradeOutput
if outputPath == "" {
outputPath = inputPath
}
// Read and parse the OTS file
otsData, err := os.ReadFile(inputPath)
if err != nil {
slog.Error("Failed to read OTS file", "file", inputPath, "error", err)
os.Exit(1)
}
timestampFile, err := opentimestamps.ReadFromFile(otsData)
if err != nil {
slog.Error("Failed to parse OTS file", "file", inputPath, "error", err)
os.Exit(1)
}
// Get pending sequences to upgrade
pendingSequences := timestampFile.GetPendingSequences()
if len(pendingSequences) == 0 {
slog.Info("No pending timestamps found, file is already fully upgraded", "file", inputPath)
os.Exit(0)
}
slog.Info("Found pending timestamps", "count", len(pendingSequences))
// Create context with timeout
ctx, cancel := context.WithTimeout(context.Background(), upgradeTimeout)
defer cancel()
// Try to upgrade each pending sequence
upgradedCount := 0
for i, seq := range pendingSequences {
att := seq.GetAttestation()
slog.Info("Attempting to upgrade timestamp",
"index", i+1,
"calendar", att.CalendarServerURL)
upgraded, err := opentimestamps.UpgradeSequence(ctx, seq, timestampFile.Digest)
if err != nil {
slog.Warn("Upgrade failed",
"calendar", att.CalendarServerURL,
"error", err)
continue
}
// Replace the pending sequence with the upgraded one
for j, origSeq := range timestampFile.Sequences {
if origSeq[len(origSeq)-1].Attestation != nil &&
origSeq[len(origSeq)-1].Attestation.CalendarServerURL == att.CalendarServerURL {
timestampFile.Sequences[j] = upgraded
break
}
}
upgradedCount++
newAtt := upgraded.GetAttestation()
if newAtt.BitcoinBlockHeight > 0 {
slog.Info("Timestamp upgraded successfully",
"calendar", att.CalendarServerURL,
"block", newAtt.BitcoinBlockHeight)
} else {
slog.Info("Timestamp replaced but still pending", "calendar", att.CalendarServerURL)
}
}
if upgradedCount == 0 {
slog.Warn("No timestamps could be upgraded at this time. Try again later.", "file", inputPath)
if !upgradeDryRun {
os.Exit(1)
}
os.Exit(0)
}
// In dry run mode, don't write the file
if upgradeDryRun {
slog.Info("Dry run completed", "upgraded", upgradedCount, "total", len(pendingSequences))
os.Exit(0)
}
// Write the updated OTS file
newOtsData := timestampFile.SerializeToFile()
err = os.WriteFile(outputPath, newOtsData, 0644)
if err != nil {
slog.Error("Failed to write updated OTS file", "file", outputPath, "error", err)
os.Exit(1)
}
slog.Info("Timestamp file upgraded successfully",
"file", outputPath,
"upgraded", upgradedCount,
"total", len(pendingSequences))
// Print human-readable representation
slog.Debug("Updated timestamp details", "info", timestampFile.Human(false))
}