Upcoming changes constitute breaking public API changes to both the client and types modules, therefore this bumps the version numbers of both modules and adds local replacements to go.mod files such that the new versions can be used before they are publicly tagged.
250 lines
5.5 KiB
Go
250 lines
5.5 KiB
Go
// Copyright (c) 2022-2024 The Decred developers
|
|
// Use of this source code is governed by an ISC
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/decred/slog"
|
|
"github.com/decred/vspd/client/v4"
|
|
"github.com/decred/vspd/internal/config"
|
|
"github.com/decred/vspd/internal/signal"
|
|
"github.com/decred/vspd/types/v3"
|
|
)
|
|
|
|
const (
|
|
vspdURL = "http://localhost:8800"
|
|
// dcrwallet RPC.
|
|
rpcURL = "wss://localhost:19110/ws"
|
|
rpcUser = "user"
|
|
rpcPass = "pass"
|
|
)
|
|
|
|
func getVspPubKey(url string) ([]byte, error) {
|
|
resp, err := http.Get(url + "/api/v3/vspinfo")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
b, err := io.ReadAll(resp.Body)
|
|
resp.Body.Close()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var j types.VspInfoResponse
|
|
err = json.Unmarshal(b, &j)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = client.ValidateServerSignature(resp, b, j.PubKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return j.PubKey, nil
|
|
}
|
|
|
|
func run() int {
|
|
log := slog.NewBackend(os.Stdout).Logger("")
|
|
log.SetLevel(slog.LevelTrace)
|
|
|
|
ctx := signal.ShutdownListener(log)
|
|
|
|
walletRPC, err := newWalletRPC(ctx, rpcURL, rpcUser, rpcPass)
|
|
if err != nil {
|
|
if errors.Is(err, context.Canceled) {
|
|
return 0
|
|
}
|
|
log.Errorf("%v", err)
|
|
return 1
|
|
}
|
|
defer walletRPC.Close()
|
|
|
|
log.Infof("vpsd url: %s", vspdURL)
|
|
|
|
pubKey, err := getVspPubKey(vspdURL)
|
|
if err != nil {
|
|
log.Errorf("%v", err)
|
|
return 1
|
|
}
|
|
|
|
log.Infof("vspd pubkey: %x", pubKey)
|
|
|
|
vClient := client.Client{
|
|
URL: vspdURL,
|
|
PubKey: pubKey,
|
|
Sign: walletRPC.SignMessage,
|
|
Log: log,
|
|
}
|
|
|
|
// Get list of tickets
|
|
tickets, err := walletRPC.getTickets(ctx)
|
|
if err != nil {
|
|
if errors.Is(err, context.Canceled) {
|
|
return 0
|
|
}
|
|
log.Errorf("%v", err)
|
|
return 1
|
|
}
|
|
|
|
if len(tickets.Hashes) == 0 {
|
|
log.Errorf("wallet owns no tickets")
|
|
return 1
|
|
}
|
|
|
|
log.Infof("wallet returned %d ticket(s):", len(tickets.Hashes))
|
|
for _, tkt := range tickets.Hashes {
|
|
log.Infof(" %s", tkt)
|
|
}
|
|
|
|
for i := 0; i < len(tickets.Hashes); i++ {
|
|
// Stop if shutdown requested.
|
|
if ctx.Err() != nil {
|
|
return 0
|
|
}
|
|
|
|
ticketHash := tickets.Hashes[i]
|
|
hex, privKeyStr, commitmentAddr, err := walletRPC.getTicketDetails(ctx, ticketHash)
|
|
if err != nil {
|
|
if errors.Is(err, context.Canceled) {
|
|
return 0
|
|
}
|
|
log.Errorf("%v", err)
|
|
return 1
|
|
}
|
|
|
|
log.Infof("")
|
|
log.Infof("Processing ticket %d of %d:", i+1, len(tickets.Hashes))
|
|
log.Infof(" Hash: %s", ticketHash)
|
|
log.Infof(" privKeyStr: %s", privKeyStr)
|
|
log.Infof(" commitmentAddr: %s", commitmentAddr)
|
|
log.Infof("")
|
|
|
|
feeAddrReq := types.FeeAddressRequest{
|
|
TicketHex: hex,
|
|
// Hack for ParentHex, can't be bothered to get the real one. It doesn't
|
|
// make a difference when testing locally anyway.
|
|
ParentHex: hex,
|
|
TicketHash: ticketHash,
|
|
Timestamp: time.Now().Unix(),
|
|
}
|
|
|
|
feeAddrResp, err := vClient.FeeAddress(ctx, feeAddrReq, commitmentAddr)
|
|
if err != nil {
|
|
if errors.Is(err, context.Canceled) {
|
|
return 0
|
|
}
|
|
log.Errorf("getFeeAddress error: %v", err)
|
|
return 1
|
|
}
|
|
|
|
log.Infof("feeAddress: %v", feeAddrResp.FeeAddress)
|
|
log.Infof("privKeyStr: %v", privKeyStr)
|
|
|
|
feeTx, err := walletRPC.createFeeTx(ctx, feeAddrResp.FeeAddress, feeAddrResp.FeeAmount)
|
|
if err != nil {
|
|
if errors.Is(err, context.Canceled) {
|
|
return 0
|
|
}
|
|
log.Errorf("createFeeTx error: %v", err)
|
|
return 1
|
|
}
|
|
|
|
// Grab an agenda ID from the current vote version.
|
|
network := config.TestNet3
|
|
voteVersion := network.CurrentVoteVersion()
|
|
agendaID := network.Deployments[voteVersion][0].Vote.Id
|
|
|
|
voteChoices := map[string]string{agendaID: "no"}
|
|
tspend := map[string]string{
|
|
"6c78690fa2fa31803df0376897725704e9dc19ecbdf80061e79b69de93ca1360": "no",
|
|
"abb86660dda1f1b66544bab24a823a22e9213ada48649f0d913623f49e17dacb": "yes",
|
|
}
|
|
treasury := map[string]string{
|
|
"03f6e7041f1cf51ee10e0a01cd2b0385ce3cd9debaabb2296f7e9dee9329da946c": "no",
|
|
"0319a37405cb4d1691971847d7719cfce70857c0f6e97d7c9174a3998cf0ab86dd": "yes",
|
|
}
|
|
|
|
payFeeReq := types.PayFeeRequest{
|
|
FeeTx: feeTx,
|
|
VotingKey: privKeyStr,
|
|
TicketHash: ticketHash,
|
|
Timestamp: time.Now().Unix(),
|
|
VoteChoices: voteChoices,
|
|
TSpendPolicy: tspend,
|
|
TreasuryPolicy: treasury,
|
|
}
|
|
|
|
_, err = vClient.PayFee(ctx, payFeeReq, commitmentAddr)
|
|
if err != nil {
|
|
if errors.Is(err, context.Canceled) {
|
|
return 0
|
|
}
|
|
log.Errorf("payFee error: %v", err)
|
|
continue
|
|
}
|
|
|
|
ticketStatusReq := types.TicketStatusRequest{
|
|
TicketHash: ticketHash,
|
|
}
|
|
|
|
_, err = vClient.TicketStatus(ctx, ticketStatusReq, commitmentAddr)
|
|
if err != nil {
|
|
if errors.Is(err, context.Canceled) {
|
|
return 0
|
|
}
|
|
log.Errorf("getTicketStatus error: %v", err)
|
|
return 1
|
|
}
|
|
|
|
voteChoices[agendaID] = "yes"
|
|
|
|
// Sleep to ensure a new timestamp. vspd will reject old/reused timestamps.
|
|
time.Sleep(1001 * time.Millisecond)
|
|
|
|
voteChoiceReq := types.SetVoteChoicesRequest{
|
|
Timestamp: time.Now().Unix(),
|
|
TicketHash: ticketHash,
|
|
VoteChoices: voteChoices,
|
|
TSpendPolicy: tspend,
|
|
TreasuryPolicy: treasury,
|
|
}
|
|
|
|
_, err = vClient.SetVoteChoices(ctx, voteChoiceReq, commitmentAddr)
|
|
if err != nil {
|
|
if errors.Is(err, context.Canceled) {
|
|
return 0
|
|
}
|
|
log.Errorf("setVoteChoices error: %v", err)
|
|
return 1
|
|
}
|
|
|
|
_, err = vClient.TicketStatus(ctx, ticketStatusReq, commitmentAddr)
|
|
if err != nil {
|
|
if errors.Is(err, context.Canceled) {
|
|
return 0
|
|
}
|
|
log.Errorf("getTicketStatus error: %v", err)
|
|
return 1
|
|
}
|
|
|
|
time.Sleep(1 * time.Second)
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
func main() {
|
|
os.Exit(run())
|
|
}
|