mirror of
https://github.com/QuilibriumNetwork/ceremonyclient.git
synced 2026-03-05 16:27:28 +08:00
527 lines
12 KiB
Go
527 lines
12 KiB
Go
//go:build !js && !wasm
|
||
|
||
package main
|
||
|
||
import (
|
||
"encoding/binary"
|
||
"encoding/hex"
|
||
"flag"
|
||
"fmt"
|
||
"io/fs"
|
||
"math/big"
|
||
"os"
|
||
"os/exec"
|
||
"path/filepath"
|
||
"runtime"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"go.uber.org/zap"
|
||
"golang.org/x/crypto/sha3"
|
||
"google.golang.org/protobuf/proto"
|
||
"source.quilibrium.com/quilibrium/monorepo/node/protobufs"
|
||
"source.quilibrium.com/quilibrium/monorepo/node/store"
|
||
"source.quilibrium.com/quilibrium/monorepo/node/utils"
|
||
|
||
"github.com/libp2p/go-libp2p/core/crypto"
|
||
"github.com/libp2p/go-libp2p/core/peer"
|
||
"github.com/pbnjay/memory"
|
||
"github.com/pkg/errors"
|
||
"source.quilibrium.com/quilibrium/monorepo/node/app"
|
||
"source.quilibrium.com/quilibrium/monorepo/node/config"
|
||
qcrypto "source.quilibrium.com/quilibrium/monorepo/node/crypto"
|
||
"source.quilibrium.com/quilibrium/monorepo/node/crypto/kzg"
|
||
)
|
||
|
||
var (
|
||
configDirectory = flag.String(
|
||
"config",
|
||
filepath.Join(".", ".config"),
|
||
"the configuration directory",
|
||
)
|
||
emergencyRepair = flag.Bool(
|
||
"emergency-repair",
|
||
false,
|
||
"performs an attempt at emergency repair. extremely dangerous, take a backup of your store before running.",
|
||
)
|
||
)
|
||
|
||
func main() {
|
||
flag.Parse()
|
||
|
||
nodeConfig, err := config.LoadConfig(*configDirectory, "", false)
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
|
||
if *emergencyRepair {
|
||
fmt.Println("Emergency Repair Mode")
|
||
fmt.Println("WARNING")
|
||
fmt.Println("WARNING")
|
||
fmt.Println("WARNING")
|
||
fmt.Println(
|
||
"This operation will try an attempt at repairing your 1.4.21.1 store. " +
|
||
"It is not guaranteed to work, and may make things worse. Before you " +
|
||
"run this, please take a backup of your store. Proofs generated by " +
|
||
"this repair tool will evaluate at single core, and earn less QUIL " +
|
||
"for the proofs produced than you would have previously earned with a " +
|
||
"valid backup. Do you wish to proceed?",
|
||
)
|
||
fmt.Println("WARNING")
|
||
fmt.Println("WARNING")
|
||
fmt.Println("WARNING")
|
||
|
||
fmt.Printf("Proceed? (Y/N): ")
|
||
|
||
var response string
|
||
_, err := fmt.Scanln(&response)
|
||
if err != nil {
|
||
fmt.Println("Invalid response, exiting without running repair.")
|
||
os.Exit(1)
|
||
}
|
||
|
||
response = strings.ToUpper(strings.TrimSpace(response))
|
||
if response == "Y" || response == "YES" {
|
||
runEmergencyRepair(nodeConfig)
|
||
} else {
|
||
fmt.Println(
|
||
"Did not receive confirmation, exiting without running repair.",
|
||
)
|
||
os.Exit(0)
|
||
}
|
||
}
|
||
|
||
fmt.Println("This is not a normal node distribution, this is a repair tool. To run repair, use --emergency-repair.")
|
||
}
|
||
|
||
func check(pstore store.DataProofStore, peerId []byte, i uint32) bool {
|
||
defer func() {
|
||
if err := recover(); err != nil {
|
||
fmt.Println(err)
|
||
}
|
||
}()
|
||
fmt.Println("Checking increment", i)
|
||
_, _, _, _, err := pstore.GetDataTimeProof(
|
||
[]byte(peerId),
|
||
uint32(i),
|
||
)
|
||
|
||
if err != nil {
|
||
if !errors.Is(err, store.ErrNotFound) {
|
||
fmt.Println("Uncorrectable error detected: ", err)
|
||
}
|
||
|
||
fmt.Println("Missing record at increment", i, " – adding to repair set")
|
||
return true
|
||
}
|
||
|
||
return false
|
||
}
|
||
|
||
func runEmergencyRepair(cfg *config.Config) {
|
||
fmt.Println("Starting emergency repair.")
|
||
kzg.Init()
|
||
fmt.Println(
|
||
"Opening pebble database. If you see a invalid chunk error, your " +
|
||
"database is corrupted beyond the abilities of this tool to repair.",
|
||
)
|
||
|
||
db := store.NewPebbleDB(cfg.DB)
|
||
defer db.Close()
|
||
fmt.Println("Scanning for gaps in record...")
|
||
|
||
logger, err := zap.NewDevelopment()
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
|
||
pstore := store.NewPebbleDataProofStore(db, logger)
|
||
peerId := getPeerID(cfg.P2P)
|
||
|
||
increment, _, _, err := pstore.GetLatestDataTimeProof([]byte(peerId))
|
||
if err != nil {
|
||
fmt.Println(
|
||
"Could not find latest proof. Please ensure you are using the correct " +
|
||
"config.yml and the path to the store in the config is correct. (Hint: " +
|
||
"try an absolute path for the store)",
|
||
)
|
||
os.Exit(1)
|
||
}
|
||
|
||
fmt.Println(
|
||
"Latest proof found, increment:", increment, " – iterating to find gaps...",
|
||
)
|
||
|
||
gapStarts := []uint32{}
|
||
|
||
for i := uint32(0); i < increment; i++ {
|
||
if check(pstore, []byte(peerId), i) {
|
||
gapStarts = append(gapStarts, i-1)
|
||
}
|
||
}
|
||
|
||
if len(gapStarts) == 0 {
|
||
fmt.Println("No gaps found, quitting.")
|
||
os.Exit(0)
|
||
}
|
||
|
||
kprover := qcrypto.NewKZGInclusionProver(logger)
|
||
wprover := qcrypto.NewWesolowskiFrameProver(logger)
|
||
|
||
for _, gapPredecessor := range gapStarts {
|
||
prevIndex := -1
|
||
hashes := []byte{}
|
||
previousCommitment := []byte{}
|
||
proofs := [][]byte{}
|
||
commitment := []byte{}
|
||
_, _, _, previousOutput, err := pstore.GetDataTimeProof(
|
||
[]byte(peerId),
|
||
gapPredecessor,
|
||
)
|
||
if err != nil {
|
||
if errors.Is(err, store.ErrNotFound) && len(gapStarts) > 1 &&
|
||
gapPredecessor == uint32(0xFFFFFFFF) {
|
||
fmt.Println(
|
||
"Could not load predecessor data time proof, store is severely "+
|
||
"corrupted. Please review the logs above. If you encounter this "+
|
||
"scenario starting from increment 0 -",
|
||
gapStarts[len(gapStarts)-1],
|
||
"create a new 1.4.21.1 store, keeping this config.yml and "+
|
||
"keys.yml, and run the node up to",
|
||
gapStarts[len(gapStarts)-1],
|
||
)
|
||
}
|
||
fmt.Println("Uncorrectable error detected: ", err)
|
||
os.Exit(1)
|
||
}
|
||
_, _, previousCommitment, _ = app.GetOutputs(previousOutput)
|
||
|
||
fmt.Println(
|
||
"Missing record at increment", gapPredecessor+1, "– repairing...",
|
||
)
|
||
|
||
input := []byte{}
|
||
input = append(input, []byte(peerId)...)
|
||
input = append(input, previousCommitment...)
|
||
proof, _ := wprover.RecalculatePreDuskChallengeProof(
|
||
input,
|
||
0,
|
||
gapPredecessor+1,
|
||
)
|
||
proofs = append(proofs, proof)
|
||
|
||
hashes, commitment, prevIndex = performDataCommitment(
|
||
kprover,
|
||
proofs,
|
||
1,
|
||
uint64(128),
|
||
)
|
||
|
||
p, err := kprover.ProveRaw(
|
||
hashes,
|
||
0,
|
||
uint64(128),
|
||
)
|
||
if err != nil {
|
||
fmt.Println("Error while proving", err, "– stopping")
|
||
os.Exit(1)
|
||
}
|
||
|
||
output := serializeOutput(
|
||
uint32(prevIndex),
|
||
proofs,
|
||
commitment,
|
||
p,
|
||
)
|
||
|
||
txn, err := pstore.NewTransaction()
|
||
if err != nil {
|
||
fmt.Println("Error while preparing transaction", err, "– stopping")
|
||
os.Exit(1)
|
||
}
|
||
|
||
fmt.Println("Storing repaired proof, increment:", gapPredecessor+1)
|
||
err = pstore.PutDataTimeProof(
|
||
txn,
|
||
1,
|
||
[]byte(peerId),
|
||
gapPredecessor+1,
|
||
previousCommitment,
|
||
output,
|
||
true,
|
||
)
|
||
if err != nil {
|
||
fmt.Println("Error while saving proof", err, "– stopping")
|
||
os.Exit(1)
|
||
}
|
||
|
||
if err := txn.Commit(); err != nil {
|
||
fmt.Println("Error while committing transaction", err, "– stopping")
|
||
os.Exit(1)
|
||
}
|
||
}
|
||
|
||
fmt.Println("Emergency repair completed successfully.")
|
||
os.Exit(0)
|
||
}
|
||
|
||
func serializeOutput(
|
||
previousIndex uint32,
|
||
previousOutputs [][]byte,
|
||
kzgCommitment []byte,
|
||
kzgProof []byte,
|
||
) []byte {
|
||
serializedOutput := []byte{}
|
||
serializedOutput = binary.BigEndian.AppendUint32(
|
||
serializedOutput,
|
||
previousIndex,
|
||
)
|
||
serializedOutput = append(serializedOutput, previousOutputs[previousIndex]...)
|
||
serializedOutput = append(serializedOutput, kzgCommitment...)
|
||
serializedOutput = append(serializedOutput, kzgProof...)
|
||
return serializedOutput
|
||
}
|
||
|
||
func performDataCommitment(
|
||
kprover *qcrypto.KZGInclusionProver,
|
||
proofs [][]byte,
|
||
parallelism int,
|
||
polySize uint64,
|
||
) ([]byte, []byte, int) {
|
||
// Take the VDF outputs and generate some deterministic outputs to feed
|
||
// into a KZG commitment:
|
||
output := []byte{}
|
||
for i := 0; i < len(proofs); i++ {
|
||
h := sha3.Sum512(proofs[i])
|
||
output = append(output, h[:]...)
|
||
}
|
||
nextInput, err := kprover.CommitRaw(output, polySize)
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
inputHash := sha3.Sum256(nextInput)
|
||
inputHashBI := big.NewInt(0).SetBytes(inputHash[:])
|
||
prevIndex := int(inputHashBI.Mod(
|
||
inputHashBI,
|
||
big.NewInt(int64(parallelism)),
|
||
).Int64())
|
||
return output, nextInput, prevIndex
|
||
}
|
||
|
||
var dataWorkers []*exec.Cmd
|
||
|
||
func spawnDataWorkers(nodeConfig *config.Config) {
|
||
if len(nodeConfig.Engine.DataWorkerMultiaddrs) != 0 {
|
||
fmt.Println(
|
||
"Data workers configured by multiaddr, be sure these are running...",
|
||
)
|
||
return
|
||
}
|
||
|
||
process, err := os.Executable()
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
|
||
cores := runtime.GOMAXPROCS(0)
|
||
dataWorkers = make([]*exec.Cmd, cores-1)
|
||
fmt.Printf("Spawning %d data workers...\n", cores-1)
|
||
|
||
for i := 1; i <= cores-1; i++ {
|
||
i := i
|
||
go func() {
|
||
for {
|
||
args := []string{
|
||
fmt.Sprintf("--core=%d", i),
|
||
fmt.Sprintf("--parent-process=%d", os.Getpid()),
|
||
}
|
||
args = append(args, os.Args[1:]...)
|
||
cmd := exec.Command(process, args...)
|
||
cmd.Stdout = os.Stdout
|
||
cmd.Stderr = os.Stdout
|
||
err := cmd.Start()
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
|
||
dataWorkers[i-1] = cmd
|
||
cmd.Wait()
|
||
time.Sleep(25 * time.Millisecond)
|
||
fmt.Printf("Data worker %d stopped, restarting...\n", i)
|
||
}
|
||
}()
|
||
}
|
||
}
|
||
|
||
func stopDataWorkers() {
|
||
for i := 0; i < len(dataWorkers); i++ {
|
||
err := dataWorkers[i].Process.Signal(os.Kill)
|
||
if err != nil {
|
||
fmt.Printf(
|
||
"fatal: unable to kill worker with pid %d, please kill this process!\n",
|
||
dataWorkers[i].Process.Pid,
|
||
)
|
||
}
|
||
}
|
||
}
|
||
|
||
func RunSelfTestIfNeeded(
|
||
configDir string,
|
||
nodeConfig *config.Config,
|
||
) *protobufs.SelfTestReport {
|
||
logger, _ := zap.NewProduction()
|
||
|
||
cores := runtime.GOMAXPROCS(0)
|
||
if len(nodeConfig.Engine.DataWorkerMultiaddrs) != 0 {
|
||
cores = len(nodeConfig.Engine.DataWorkerMultiaddrs) + 1
|
||
}
|
||
|
||
memory := memory.TotalMemory()
|
||
d, err := os.Stat(filepath.Join(configDir, "store"))
|
||
if d == nil {
|
||
err := os.Mkdir(filepath.Join(configDir, "store"), 0755)
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
}
|
||
|
||
report := &protobufs.SelfTestReport{}
|
||
|
||
report.Cores = uint32(cores)
|
||
report.Memory = binary.BigEndian.AppendUint64([]byte{}, memory)
|
||
disk := utils.GetDiskSpace(nodeConfig.DB.Path)
|
||
report.Storage = binary.BigEndian.AppendUint64([]byte{}, disk)
|
||
logger.Info("writing report")
|
||
|
||
report.Capabilities = []*protobufs.Capability{
|
||
{
|
||
ProtocolIdentifier: 0x020000,
|
||
},
|
||
}
|
||
reportBytes, err := proto.Marshal(report)
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
|
||
err = os.WriteFile(
|
||
filepath.Join(configDir, "SELF_TEST"),
|
||
reportBytes,
|
||
fs.FileMode(0600),
|
||
)
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
|
||
return report
|
||
}
|
||
|
||
func clearIfTestData(configDir string, nodeConfig *config.Config) {
|
||
_, err := os.Stat(filepath.Join(configDir, "RELEASE_VERSION"))
|
||
if os.IsNotExist(err) {
|
||
fmt.Println("Clearing test data...")
|
||
err := os.RemoveAll(nodeConfig.DB.Path)
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
|
||
versionFile, err := os.OpenFile(
|
||
filepath.Join(configDir, "RELEASE_VERSION"),
|
||
os.O_CREATE|os.O_RDWR,
|
||
fs.FileMode(0600),
|
||
)
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
|
||
_, err = versionFile.Write([]byte{0x01, 0x00, 0x00})
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
|
||
err = versionFile.Close()
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
}
|
||
}
|
||
|
||
func printBalance(config *config.Config) {
|
||
if config.ListenGRPCMultiaddr == "" {
|
||
_, _ = fmt.Fprintf(os.Stderr, "gRPC Not Enabled, Please Configure\n")
|
||
os.Exit(1)
|
||
}
|
||
|
||
conn, err := app.ConnectToNode(config)
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
defer conn.Close()
|
||
|
||
client := protobufs.NewNodeServiceClient(conn)
|
||
|
||
balance, err := app.FetchTokenBalance(client)
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
|
||
conversionFactor, _ := new(big.Int).SetString("1DCD65000", 16)
|
||
r := new(big.Rat).SetFrac(balance.Owned, conversionFactor)
|
||
fmt.Println("Owned balance:", r.FloatString(12), "QUIL")
|
||
fmt.Println("Note: bridged balance is not reflected here, you must bridge back to QUIL to use QUIL on mainnet.")
|
||
}
|
||
|
||
func getPeerID(p2pConfig *config.P2PConfig) peer.ID {
|
||
peerPrivKey, err := hex.DecodeString(p2pConfig.PeerPrivKey)
|
||
if err != nil {
|
||
panic(errors.Wrap(err, "error unmarshaling peerkey"))
|
||
}
|
||
|
||
privKey, err := crypto.UnmarshalEd448PrivateKey(peerPrivKey)
|
||
if err != nil {
|
||
panic(errors.Wrap(err, "error unmarshaling peerkey"))
|
||
}
|
||
|
||
pub := privKey.GetPublic()
|
||
id, err := peer.IDFromPublicKey(pub)
|
||
if err != nil {
|
||
panic(errors.Wrap(err, "error getting peer id"))
|
||
}
|
||
|
||
return id
|
||
}
|
||
|
||
func printPeerID(p2pConfig *config.P2PConfig) {
|
||
id := getPeerID(p2pConfig)
|
||
|
||
fmt.Println("Peer ID: " + id.String())
|
||
}
|
||
|
||
func printNodeInfo(cfg *config.Config) {
|
||
if cfg.ListenGRPCMultiaddr == "" {
|
||
_, _ = fmt.Fprintf(os.Stderr, "gRPC Not Enabled, Please Configure\n")
|
||
os.Exit(1)
|
||
}
|
||
|
||
printPeerID(cfg.P2P)
|
||
|
||
conn, err := app.ConnectToNode(cfg)
|
||
if err != nil {
|
||
fmt.Println("Could not connect to node. If it is still booting, please wait.")
|
||
os.Exit(1)
|
||
}
|
||
defer conn.Close()
|
||
|
||
client := protobufs.NewNodeServiceClient(conn)
|
||
|
||
nodeInfo, err := app.FetchNodeInfo(client)
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
|
||
fmt.Println("Version: " + config.FormatVersion(nodeInfo.Version))
|
||
fmt.Println("Max Frame: " + strconv.FormatUint(nodeInfo.GetMaxFrame(), 10))
|
||
fmt.Println("Peer Score: " + strconv.FormatUint(nodeInfo.GetPeerScore(), 10))
|
||
printBalance(cfg)
|
||
}
|