client: Automatic fee payment from dcrwallet (#382)
This commit is contained in:
parent
799041a1e5
commit
a5003c046b
@ -27,3 +27,18 @@ linters:
|
|||||||
- unparam
|
- unparam
|
||||||
- unused
|
- unused
|
||||||
- vetshadow
|
- vetshadow
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
# Disable rule SA1019 on staticcheck, it causes the build to fail if any
|
||||||
|
# deprecated func/var/const are referenced.
|
||||||
|
staticcheck:
|
||||||
|
checks: ["all", "-SA1019"]
|
||||||
|
|
||||||
|
exhaustive:
|
||||||
|
check:
|
||||||
|
- switch
|
||||||
|
- map
|
||||||
|
# Presence of "default" case in switch statements satisfies exhaustiveness,
|
||||||
|
# even if all enum members are not listed.
|
||||||
|
# Default: false
|
||||||
|
default-signifies-exhaustive: true
|
||||||
|
|||||||
447
client/autoclient.go
Normal file
447
client/autoclient.go
Normal file
@ -0,0 +1,447 @@
|
|||||||
|
// Copyright (c) 2022-2023 The Decred developers
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"decred.org/dcrwallet/v3/errors"
|
||||||
|
"decred.org/dcrwallet/v3/wallet"
|
||||||
|
"decred.org/dcrwallet/v3/wallet/udb"
|
||||||
|
"github.com/decred/dcrd/chaincfg/chainhash"
|
||||||
|
"github.com/decred/dcrd/chaincfg/v3"
|
||||||
|
"github.com/decred/dcrd/dcrutil/v4"
|
||||||
|
"github.com/decred/dcrd/txscript/v4"
|
||||||
|
"github.com/decred/dcrd/txscript/v4/stdaddr"
|
||||||
|
"github.com/decred/dcrd/wire"
|
||||||
|
"github.com/decred/slog"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DialFunc func(ctx context.Context, network, addr string) (net.Conn, error)
|
||||||
|
|
||||||
|
type Policy struct {
|
||||||
|
MaxFee dcrutil.Amount
|
||||||
|
ChangeAcct uint32 // to derive fee addresses
|
||||||
|
FeeAcct uint32 // to pay fees from, if inputs are not provided to Process
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure dcrwallet satisfies the Wallet interface.
|
||||||
|
var _ Wallet = (*wallet.Wallet)(nil)
|
||||||
|
|
||||||
|
type Wallet interface {
|
||||||
|
Spender(ctx context.Context, out *wire.OutPoint) (*wire.MsgTx, uint32, error)
|
||||||
|
MainChainTip(ctx context.Context) (hash chainhash.Hash, height int32)
|
||||||
|
ChainParams() *chaincfg.Params
|
||||||
|
TxBlock(ctx context.Context, hash *chainhash.Hash) (chainhash.Hash, int32, error)
|
||||||
|
DumpWIFPrivateKey(ctx context.Context, addr stdaddr.Address) (string, error)
|
||||||
|
VSPFeeHashForTicket(ctx context.Context, ticketHash *chainhash.Hash) (chainhash.Hash, error)
|
||||||
|
UpdateVspTicketFeeToStarted(ctx context.Context, ticketHash, feeHash *chainhash.Hash, host string, pubkey []byte) error
|
||||||
|
GetTransactionsByHashes(ctx context.Context, txHashes []*chainhash.Hash) (txs []*wire.MsgTx, notFound []*wire.InvVect, err error)
|
||||||
|
ReserveOutputsForAmount(ctx context.Context, account uint32, amount dcrutil.Amount, minconf int32) ([]wallet.Input, error)
|
||||||
|
UnlockOutpoint(txHash *chainhash.Hash, index uint32)
|
||||||
|
NewChangeAddress(ctx context.Context, account uint32) (stdaddr.Address, error)
|
||||||
|
RelayFee() dcrutil.Amount
|
||||||
|
SignTransaction(ctx context.Context, tx *wire.MsgTx, hashType txscript.SigHashType, additionalPrevScripts map[wire.OutPoint][]byte,
|
||||||
|
additionalKeysByAddress map[string]*dcrutil.WIF, p2shRedeemScriptsByAddress map[string][]byte) ([]wallet.SignatureError, error)
|
||||||
|
SetPublished(ctx context.Context, hash *chainhash.Hash, published bool) error
|
||||||
|
AddTransaction(ctx context.Context, tx *wire.MsgTx, blockHash *chainhash.Hash) error
|
||||||
|
UpdateVspTicketFeeToPaid(ctx context.Context, ticketHash, feeHash *chainhash.Hash, host string, pubkey []byte) error
|
||||||
|
UpdateVspTicketFeeToErrored(ctx context.Context, ticketHash *chainhash.Hash, host string, pubkey []byte) error
|
||||||
|
AgendaChoices(ctx context.Context, ticketHash *chainhash.Hash) (choices wallet.AgendaChoices, voteBits uint16, err error)
|
||||||
|
TSpendPolicyForTicket(ticketHash *chainhash.Hash) map[string]string
|
||||||
|
TreasuryKeyPolicyForTicket(ticketHash *chainhash.Hash) map[string]string
|
||||||
|
AbandonTransaction(ctx context.Context, hash *chainhash.Hash) error
|
||||||
|
TxConfirms(ctx context.Context, hash *chainhash.Hash) (int32, error)
|
||||||
|
ForUnspentUnexpiredTickets(ctx context.Context, f func(hash *chainhash.Hash) error) error
|
||||||
|
IsVSPTicketConfirmed(ctx context.Context, ticketHash *chainhash.Hash) (bool, error)
|
||||||
|
UpdateVspTicketFeeToConfirmed(ctx context.Context, ticketHash, feeHash *chainhash.Hash, host string, pubkey []byte) error
|
||||||
|
VSPTicketInfo(ctx context.Context, ticketHash *chainhash.Hash) (*wallet.VSPTicket, error)
|
||||||
|
SignMessage(ctx context.Context, msg string, addr stdaddr.Address) (sig []byte, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type AutoClient struct {
|
||||||
|
wallet Wallet
|
||||||
|
policy *Policy
|
||||||
|
*Client
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
jobs map[chainhash.Hash]*feePayment
|
||||||
|
|
||||||
|
log slog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
// URL specifies the base URL of the VSP
|
||||||
|
URL string
|
||||||
|
|
||||||
|
// PubKey specifies the VSP's base64 encoded public key
|
||||||
|
PubKey string
|
||||||
|
|
||||||
|
// Dialer specifies an optional dialer when connecting to the VSP.
|
||||||
|
Dialer DialFunc
|
||||||
|
|
||||||
|
// Wallet specifies a loaded wallet.
|
||||||
|
Wallet Wallet
|
||||||
|
|
||||||
|
// Default policy for fee payments unless another is provided by the
|
||||||
|
// caller.
|
||||||
|
Policy *Policy
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(cfg Config, log slog.Logger) (*AutoClient, error) {
|
||||||
|
u, err := url.Parse(cfg.URL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pubKey, err := base64.StdEncoding.DecodeString(cfg.PubKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if cfg.Wallet == nil {
|
||||||
|
return nil, fmt.Errorf("wallet option not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &Client{
|
||||||
|
URL: u.String(),
|
||||||
|
PubKey: pubKey,
|
||||||
|
Sign: cfg.Wallet.SignMessage,
|
||||||
|
Log: log,
|
||||||
|
}
|
||||||
|
client.Transport = &http.Transport{
|
||||||
|
DialContext: cfg.Dialer,
|
||||||
|
}
|
||||||
|
|
||||||
|
v := &AutoClient{
|
||||||
|
wallet: cfg.Wallet,
|
||||||
|
policy: cfg.Policy,
|
||||||
|
Client: client,
|
||||||
|
jobs: make(map[chainhash.Hash]*feePayment),
|
||||||
|
log: log,
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AutoClient) FeePercentage(ctx context.Context) (float64, error) {
|
||||||
|
resp, err := c.Client.VspInfo(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
return resp.FeePercentage, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessUnprocessedTickets processes all tickets that don't currently have
|
||||||
|
// any association with a VSP.
|
||||||
|
func (c *AutoClient) ProcessUnprocessedTickets(ctx context.Context) {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
_ = c.wallet.ForUnspentUnexpiredTickets(ctx, func(hash *chainhash.Hash) error {
|
||||||
|
// Skip tickets which have a fee tx already associated with
|
||||||
|
// them; they are already processed by some vsp.
|
||||||
|
_, err := c.wallet.VSPFeeHashForTicket(ctx, hash)
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
confirmed, err := c.wallet.IsVSPTicketConfirmed(ctx, hash)
|
||||||
|
if err != nil && !errors.Is(err, errors.NotExist) {
|
||||||
|
c.log.Error(err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if confirmed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
c.mu.Lock()
|
||||||
|
fp := c.jobs[*hash]
|
||||||
|
c.mu.Unlock()
|
||||||
|
if fp != nil {
|
||||||
|
// Already processing this ticket with the VSP.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start processing in the background.
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
err := c.Process(ctx, hash, nil)
|
||||||
|
if err != nil {
|
||||||
|
c.log.Error(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessTicket attempts to process a given ticket based on the hash provided.
|
||||||
|
func (c *AutoClient) ProcessTicket(ctx context.Context, hash *chainhash.Hash) error {
|
||||||
|
err := c.Process(ctx, hash, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessManagedTickets discovers tickets which were previously registered with
|
||||||
|
// a VSP and begins syncing them in the background. This is used to recover VSP
|
||||||
|
// tracking after seed restores, and is only performed on unspent and unexpired
|
||||||
|
// tickets.
|
||||||
|
func (c *AutoClient) ProcessManagedTickets(ctx context.Context) error {
|
||||||
|
err := c.wallet.ForUnspentUnexpiredTickets(ctx, func(hash *chainhash.Hash) error {
|
||||||
|
// We only want to process tickets that haven't been confirmed yet.
|
||||||
|
confirmed, err := c.wallet.IsVSPTicketConfirmed(ctx, hash)
|
||||||
|
if err != nil && !errors.Is(err, errors.NotExist) {
|
||||||
|
c.log.Error(err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if confirmed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
c.mu.Lock()
|
||||||
|
_, ok := c.jobs[*hash]
|
||||||
|
c.mu.Unlock()
|
||||||
|
if ok {
|
||||||
|
// Already processing this ticket with the VSP.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make ticketstatus api call and only continue if ticket is
|
||||||
|
// found managed by this vsp. The rest is the same codepath as
|
||||||
|
// for processing a new ticket.
|
||||||
|
status, err := c.status(ctx, hash)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, errors.Locked) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if status.FeeTxStatus == "confirmed" {
|
||||||
|
feeHash, err := chainhash.NewHashFromStr(status.FeeTxHash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = c.wallet.UpdateVspTicketFeeToConfirmed(ctx, hash, feeHash, c.Client.URL, c.Client.PubKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
} else if status.FeeTxHash != "" {
|
||||||
|
feeHash, err := chainhash.NewHashFromStr(status.FeeTxHash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = c.wallet.UpdateVspTicketFeeToPaid(ctx, hash, feeHash, c.Client.URL, c.Client.PubKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_ = c.feePayment(ctx, hash, true)
|
||||||
|
} else {
|
||||||
|
// Fee hasn't been paid at the provided VSP, so this should do that if needed.
|
||||||
|
_ = c.feePayment(ctx, hash, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process begins processing a VSP fee payment for a ticket. If feeTx contains
|
||||||
|
// inputs, is used to pay the VSP fee. Otherwise, new inputs are selected and
|
||||||
|
// locked to prevent double spending the fee.
|
||||||
|
//
|
||||||
|
// feeTx must not be nil, but may point to an empty transaction, and is modified
|
||||||
|
// with the inputs and the fee and change outputs before returning without an
|
||||||
|
// error. The fee transaction is also recorded as unpublised in the wallet, and
|
||||||
|
// the fee hash is associated with the ticket.
|
||||||
|
func (c *AutoClient) Process(ctx context.Context, ticketHash *chainhash.Hash, feeTx *wire.MsgTx) error {
|
||||||
|
vspTicket, err := c.wallet.VSPTicketInfo(ctx, ticketHash)
|
||||||
|
if err != nil && !errors.Is(err, errors.NotExist) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
feeStatus := udb.VSPFeeProcessStarted // Will be used if the ticket isn't registered to the vsp yet.
|
||||||
|
if vspTicket != nil {
|
||||||
|
feeStatus = udb.FeeStatus(vspTicket.FeeTxStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch feeStatus {
|
||||||
|
case udb.VSPFeeProcessStarted, udb.VSPFeeProcessErrored:
|
||||||
|
// If VSPTicket has been started or errored then attempt to create a new fee
|
||||||
|
// transaction, submit it then confirm.
|
||||||
|
fp := c.feePayment(ctx, ticketHash, false)
|
||||||
|
if fp == nil {
|
||||||
|
err := c.wallet.UpdateVspTicketFeeToErrored(ctx, ticketHash, c.Client.URL, c.Client.PubKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fmt.Errorf("fee payment cannot be processed")
|
||||||
|
}
|
||||||
|
fp.mu.Lock()
|
||||||
|
if fp.feeTx == nil {
|
||||||
|
fp.feeTx = feeTx
|
||||||
|
}
|
||||||
|
fp.mu.Unlock()
|
||||||
|
err := fp.receiveFeeAddress()
|
||||||
|
if err != nil {
|
||||||
|
err := c.wallet.UpdateVspTicketFeeToErrored(ctx, ticketHash, c.Client.URL, c.Client.PubKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// XXX, retry? (old Process retried)
|
||||||
|
// but this may not be necessary any longer as the parent of
|
||||||
|
// the ticket is always relayed to the vsp as well.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = fp.makeFeeTx(feeTx)
|
||||||
|
if err != nil {
|
||||||
|
err := c.wallet.UpdateVspTicketFeeToErrored(ctx, ticketHash, c.Client.URL, c.Client.PubKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fp.submitPayment()
|
||||||
|
case udb.VSPFeeProcessPaid:
|
||||||
|
// If a VSP ticket has been paid, but confirm payment.
|
||||||
|
if len(vspTicket.Host) > 0 && vspTicket.Host != c.Client.URL {
|
||||||
|
// Cannot confirm a paid ticket that is already with another VSP.
|
||||||
|
return fmt.Errorf("ticket already paid or confirmed with another vsp")
|
||||||
|
}
|
||||||
|
fp := c.feePayment(ctx, ticketHash, true)
|
||||||
|
if fp == nil {
|
||||||
|
// Don't update VSPStatus to Errored if it was already paid or
|
||||||
|
// confirmed.
|
||||||
|
return fmt.Errorf("fee payment cannot be processed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return fp.confirmPayment()
|
||||||
|
case udb.VSPFeeProcessConfirmed:
|
||||||
|
// VSPTicket has already been confirmed, there is nothing to process.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetVoteChoice takes the provided consensus, tspend and treasury key voting
|
||||||
|
// preferences, and checks if they match the status of the specified ticket from
|
||||||
|
// the connected VSP. The status provides the current voting preferences so we
|
||||||
|
// can just update from there if need be.
|
||||||
|
func (c *AutoClient) SetVoteChoice(ctx context.Context, hash *chainhash.Hash,
|
||||||
|
choices map[string]string, tspendPolicy map[string]string, treasuryPolicy map[string]string) error {
|
||||||
|
|
||||||
|
// Retrieve current voting preferences from VSP.
|
||||||
|
status, err := c.status(ctx, hash)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, errors.Locked) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.log.Errorf("Could not check status of VSP ticket %s: %v", hash, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for any mismatch between the provided voting preferences and the
|
||||||
|
// VSP preferences to determine if VSP needs to be updated.
|
||||||
|
update := false
|
||||||
|
|
||||||
|
// Check consensus vote choices.
|
||||||
|
for newAgenda, newChoice := range choices {
|
||||||
|
vspChoice, ok := status.VoteChoices[newAgenda]
|
||||||
|
if !ok {
|
||||||
|
update = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if vspChoice != newChoice {
|
||||||
|
update = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check tspend policies.
|
||||||
|
for newTSpend, newChoice := range tspendPolicy {
|
||||||
|
vspChoice, ok := status.TSpendPolicy[newTSpend]
|
||||||
|
if !ok {
|
||||||
|
update = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if vspChoice != newChoice {
|
||||||
|
update = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check treasury policies.
|
||||||
|
for newKey, newChoice := range treasuryPolicy {
|
||||||
|
vspChoice, ok := status.TSpendPolicy[newKey]
|
||||||
|
if !ok {
|
||||||
|
update = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if vspChoice != newChoice {
|
||||||
|
update = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !update {
|
||||||
|
c.log.Debugf("VSP already has correct vote choices for ticket %s", hash)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
c.log.Debugf("Updating vote choices on VSP for ticket %s", hash)
|
||||||
|
err = c.setVoteChoices(ctx, hash, choices, tspendPolicy, treasuryPolicy)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TicketInfo stores per-ticket info tracked by a VSP Client instance.
|
||||||
|
type TicketInfo struct {
|
||||||
|
TicketHash chainhash.Hash
|
||||||
|
CommitmentAddr stdaddr.StakeAddress
|
||||||
|
VotingAddr stdaddr.StakeAddress
|
||||||
|
State State
|
||||||
|
Fee dcrutil.Amount
|
||||||
|
FeeHash chainhash.Hash
|
||||||
|
|
||||||
|
// TODO: include stuff returned by the status() call?
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrackedTickets returns information about all outstanding tickets tracked by
|
||||||
|
// a vsp.Client instance.
|
||||||
|
//
|
||||||
|
// Currently this returns only info about tickets which fee hasn't been paid or
|
||||||
|
// confirmed at enough depth to be considered committed to.
|
||||||
|
func (c *AutoClient) TrackedTickets() []*TicketInfo {
|
||||||
|
// Collect all jobs first, to avoid working under two different locks.
|
||||||
|
c.mu.Lock()
|
||||||
|
jobs := make([]*feePayment, 0, len(c.jobs))
|
||||||
|
for _, job := range c.jobs {
|
||||||
|
jobs = append(jobs, job)
|
||||||
|
}
|
||||||
|
c.mu.Unlock()
|
||||||
|
|
||||||
|
tickets := make([]*TicketInfo, 0, len(jobs))
|
||||||
|
for _, job := range jobs {
|
||||||
|
job.mu.Lock()
|
||||||
|
tickets = append(tickets, &TicketInfo{
|
||||||
|
TicketHash: job.ticketHash,
|
||||||
|
CommitmentAddr: job.commitmentAddr,
|
||||||
|
VotingAddr: job.votingAddr,
|
||||||
|
State: job.state,
|
||||||
|
Fee: job.fee,
|
||||||
|
FeeHash: job.feeHash,
|
||||||
|
})
|
||||||
|
job.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
return tickets
|
||||||
|
}
|
||||||
@ -1,3 +1,7 @@
|
|||||||
|
// Copyright (c) 2022-2023 The Decred developers
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@ -1,3 +1,7 @@
|
|||||||
|
// Copyright (c) 2022-2023 The Decred developers
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
937
client/feepayment.go
Normal file
937
client/feepayment.go
Normal file
@ -0,0 +1,937 @@
|
|||||||
|
// Copyright (c) 2022-2023 The Decred developers
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
wallet_errs "decred.org/dcrwallet/v3/errors"
|
||||||
|
"decred.org/dcrwallet/v3/wallet"
|
||||||
|
"decred.org/dcrwallet/v3/wallet/txrules"
|
||||||
|
"decred.org/dcrwallet/v3/wallet/txsizes"
|
||||||
|
"github.com/decred/dcrd/blockchain/stake/v5"
|
||||||
|
"github.com/decred/dcrd/chaincfg/chainhash"
|
||||||
|
"github.com/decred/dcrd/chaincfg/v3"
|
||||||
|
"github.com/decred/dcrd/dcrutil/v4"
|
||||||
|
"github.com/decred/dcrd/txscript/v4"
|
||||||
|
"github.com/decred/dcrd/txscript/v4/stdaddr"
|
||||||
|
"github.com/decred/dcrd/txscript/v4/stdscript"
|
||||||
|
"github.com/decred/dcrd/wire"
|
||||||
|
"github.com/decred/slog"
|
||||||
|
"github.com/decred/vspd/types/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// randomInt64 returns a random int64 in [0,n).
|
||||||
|
func randomInt64(n int64) int64 {
|
||||||
|
i, err := rand.Int(rand.Reader, big.NewInt(n))
|
||||||
|
if err != nil {
|
||||||
|
// crypto/rand should never return an error if running on a supported platform.
|
||||||
|
panic(fmt.Sprintf("unhandled crypto/rand error: %v", err))
|
||||||
|
}
|
||||||
|
return i.Int64()
|
||||||
|
}
|
||||||
|
|
||||||
|
// randomDuration returns a random time.Duration in [0,d).
|
||||||
|
func randomDuration(d time.Duration) time.Duration {
|
||||||
|
return time.Duration(randomInt64(int64(d)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// coinflip returns a random bool.
|
||||||
|
func coinflip() bool {
|
||||||
|
return randomInt64(2) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
errStopped = errors.New("fee processing stopped")
|
||||||
|
errNotSolo = errors.New("not a solo ticket")
|
||||||
|
)
|
||||||
|
|
||||||
|
// A random amount of delay (between zero and these jitter constants) is added
|
||||||
|
// before performing some background action with the VSP. The delay is reduced
|
||||||
|
// when a ticket is currently live, as it may be called to vote any time.
|
||||||
|
const (
|
||||||
|
immatureJitter = time.Hour
|
||||||
|
liveJitter = 5 * time.Minute
|
||||||
|
unminedJitter = 2 * time.Minute
|
||||||
|
)
|
||||||
|
|
||||||
|
type feePayment struct {
|
||||||
|
client *AutoClient
|
||||||
|
ctx context.Context
|
||||||
|
|
||||||
|
// Set at feepayment creation and never changes
|
||||||
|
ticketHash chainhash.Hash
|
||||||
|
commitmentAddr stdaddr.StakeAddress
|
||||||
|
votingAddr stdaddr.StakeAddress
|
||||||
|
policy *Policy
|
||||||
|
|
||||||
|
// Requires locking for all access outside of Client.feePayment
|
||||||
|
mu sync.Mutex
|
||||||
|
votingKey string
|
||||||
|
ticketLive int32
|
||||||
|
ticketExpires int32
|
||||||
|
fee dcrutil.Amount
|
||||||
|
feeAddr stdaddr.Address
|
||||||
|
feeHash chainhash.Hash
|
||||||
|
feeTx *wire.MsgTx
|
||||||
|
state State
|
||||||
|
err error
|
||||||
|
|
||||||
|
timerMu sync.Mutex
|
||||||
|
timer *time.Timer
|
||||||
|
|
||||||
|
log slog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
type State uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
_ State = iota
|
||||||
|
Unprocessed
|
||||||
|
FeePublished
|
||||||
|
_ // ...
|
||||||
|
TicketSpent
|
||||||
|
)
|
||||||
|
|
||||||
|
func parseTicket(ticket *wire.MsgTx, params *chaincfg.Params) (
|
||||||
|
votingAddr, commitmentAddr stdaddr.StakeAddress, err error) {
|
||||||
|
fail := func(err error) (_, _ stdaddr.StakeAddress, _ error) {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if !stake.IsSStx(ticket) {
|
||||||
|
return fail(fmt.Errorf("%v is not a ticket", ticket))
|
||||||
|
}
|
||||||
|
_, addrs := stdscript.ExtractAddrs(ticket.TxOut[0].Version, ticket.TxOut[0].PkScript, params)
|
||||||
|
if len(addrs) != 1 {
|
||||||
|
return fail(fmt.Errorf("cannot parse voting addr"))
|
||||||
|
}
|
||||||
|
switch addr := addrs[0].(type) {
|
||||||
|
case stdaddr.StakeAddress:
|
||||||
|
votingAddr = addr
|
||||||
|
default:
|
||||||
|
return fail(fmt.Errorf("address cannot be used for voting rights: %v", err))
|
||||||
|
}
|
||||||
|
commitmentAddr, err = stake.AddrFromSStxPkScrCommitment(ticket.TxOut[1].PkScript, params)
|
||||||
|
if err != nil {
|
||||||
|
return fail(fmt.Errorf("cannot parse commitment address: %w", err))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fp *feePayment) ticketSpent() bool {
|
||||||
|
ctx := fp.ctx
|
||||||
|
ticketOut := wire.OutPoint{Hash: fp.ticketHash, Index: 0, Tree: 1}
|
||||||
|
_, _, err := fp.client.wallet.Spender(ctx, &ticketOut)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fp *feePayment) ticketExpired() bool {
|
||||||
|
ctx := fp.ctx
|
||||||
|
w := fp.client.wallet
|
||||||
|
_, tipHeight := w.MainChainTip(ctx)
|
||||||
|
|
||||||
|
fp.mu.Lock()
|
||||||
|
expires := fp.ticketExpires
|
||||||
|
fp.mu.Unlock()
|
||||||
|
|
||||||
|
return expires > 0 && tipHeight >= expires
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fp *feePayment) removedExpiredOrSpent() bool {
|
||||||
|
var reason string
|
||||||
|
switch {
|
||||||
|
case fp.ticketExpired():
|
||||||
|
reason = "expired"
|
||||||
|
case fp.ticketSpent():
|
||||||
|
reason = "spent"
|
||||||
|
}
|
||||||
|
if reason != "" {
|
||||||
|
fp.remove(reason)
|
||||||
|
// nothing scheduled
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fp *feePayment) remove(reason string) {
|
||||||
|
fp.stop()
|
||||||
|
fp.log.Infof("ticket %v is %s; removing from VSP client", &fp.ticketHash, reason)
|
||||||
|
fp.client.mu.Lock()
|
||||||
|
delete(fp.client.jobs, fp.ticketHash)
|
||||||
|
fp.client.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// feePayment returns an existing managed fee payment, or creates and begins
|
||||||
|
// processing a fee payment for a ticket.
|
||||||
|
func (c *AutoClient) feePayment(ctx context.Context, ticketHash *chainhash.Hash, paidConfirmed bool) (fp *feePayment) {
|
||||||
|
c.mu.Lock()
|
||||||
|
fp = c.jobs[*ticketHash]
|
||||||
|
c.mu.Unlock()
|
||||||
|
if fp != nil {
|
||||||
|
return fp
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if fp == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var schedule bool
|
||||||
|
c.mu.Lock()
|
||||||
|
fp2 := c.jobs[*ticketHash]
|
||||||
|
if fp2 != nil {
|
||||||
|
fp.stop()
|
||||||
|
fp = fp2
|
||||||
|
} else {
|
||||||
|
c.jobs[*ticketHash] = fp
|
||||||
|
schedule = true
|
||||||
|
}
|
||||||
|
c.mu.Unlock()
|
||||||
|
if schedule {
|
||||||
|
fp.schedule("reconcile payment", fp.reconcilePayment)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
w := c.wallet
|
||||||
|
params := w.ChainParams()
|
||||||
|
|
||||||
|
fp = &feePayment{
|
||||||
|
client: c,
|
||||||
|
ctx: ctx,
|
||||||
|
ticketHash: *ticketHash,
|
||||||
|
policy: c.policy,
|
||||||
|
log: c.log,
|
||||||
|
}
|
||||||
|
|
||||||
|
// No VSP interaction is required for spent tickets.
|
||||||
|
if fp.ticketSpent() {
|
||||||
|
fp.state = TicketSpent
|
||||||
|
return fp
|
||||||
|
}
|
||||||
|
|
||||||
|
ticket, err := c.tx(ctx, ticketHash)
|
||||||
|
if err != nil {
|
||||||
|
fp.log.Warnf("no ticket found for %v", ticketHash)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ticketHeight, err := w.TxBlock(ctx, ticketHash)
|
||||||
|
if err != nil {
|
||||||
|
// This is not expected to ever error, as the ticket was fetched
|
||||||
|
// from the wallet in the above call.
|
||||||
|
fp.log.Errorf("failed to query block which mines ticket: %v", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if ticketHeight >= 2 {
|
||||||
|
// Note the off-by-one; this is correct. Tickets become live
|
||||||
|
// one block after the params would indicate.
|
||||||
|
fp.ticketLive = ticketHeight + int32(params.TicketMaturity) + 1
|
||||||
|
fp.ticketExpires = fp.ticketLive + int32(params.TicketExpiry)
|
||||||
|
}
|
||||||
|
|
||||||
|
fp.votingAddr, fp.commitmentAddr, err = parseTicket(ticket, params)
|
||||||
|
if err != nil {
|
||||||
|
fp.log.Errorf("%v is not a ticket: %v", ticketHash, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Try to access the voting key, ignore error unless the wallet is
|
||||||
|
// locked.
|
||||||
|
fp.votingKey, err = w.DumpWIFPrivateKey(ctx, fp.votingAddr)
|
||||||
|
if err != nil && !errors.Is(err, wallet_errs.Locked) {
|
||||||
|
fp.log.Errorf("no voting key for ticket %v: %v", ticketHash, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
feeHash, err := w.VSPFeeHashForTicket(ctx, ticketHash)
|
||||||
|
if err != nil {
|
||||||
|
// caller must schedule next method, as paying the fee may
|
||||||
|
// require using provided transaction inputs.
|
||||||
|
return fp
|
||||||
|
}
|
||||||
|
|
||||||
|
fee, err := c.tx(ctx, &feeHash)
|
||||||
|
if err != nil {
|
||||||
|
// A fee hash is recorded for this ticket, but was not found in
|
||||||
|
// the wallet. This should not happen and may require manual
|
||||||
|
// intervention.
|
||||||
|
//
|
||||||
|
// XXX should check ticketinfo and see if fee is not paid. if
|
||||||
|
// possible, update it with a new fee.
|
||||||
|
fp.err = fmt.Errorf("fee transaction not found in wallet: %w", err)
|
||||||
|
return fp
|
||||||
|
}
|
||||||
|
|
||||||
|
fp.feeTx = fee
|
||||||
|
fp.feeHash = feeHash
|
||||||
|
|
||||||
|
// If database has been updated to paid or confirmed status, we can forgo
|
||||||
|
// this step.
|
||||||
|
if !paidConfirmed {
|
||||||
|
err = w.UpdateVspTicketFeeToStarted(ctx, ticketHash, &feeHash, c.Client.URL, c.Client.PubKey)
|
||||||
|
if err != nil {
|
||||||
|
return fp
|
||||||
|
}
|
||||||
|
|
||||||
|
fp.state = Unprocessed // XXX fee created, but perhaps not submitted with vsp.
|
||||||
|
fp.fee = -1 // XXX fee amount (not needed anymore?)
|
||||||
|
}
|
||||||
|
return fp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AutoClient) tx(ctx context.Context, hash *chainhash.Hash) (*wire.MsgTx, error) {
|
||||||
|
txs, _, err := c.wallet.GetTransactionsByHashes(ctx, []*chainhash.Hash{hash})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return txs[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schedule a method to be executed.
|
||||||
|
// Any currently-scheduled method is replaced.
|
||||||
|
func (fp *feePayment) schedule(name string, method func() error) {
|
||||||
|
var delay time.Duration
|
||||||
|
if method != nil {
|
||||||
|
delay = fp.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
fp.timerMu.Lock()
|
||||||
|
defer fp.timerMu.Unlock()
|
||||||
|
if fp.timer != nil {
|
||||||
|
fp.timer.Stop()
|
||||||
|
fp.timer = nil
|
||||||
|
}
|
||||||
|
if method != nil {
|
||||||
|
fp.log.Debugf("scheduling %q for ticket %s in %v", name, &fp.ticketHash, delay)
|
||||||
|
fp.timer = time.AfterFunc(delay, fp.task(name, method))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fp *feePayment) next() time.Duration {
|
||||||
|
w := fp.client.wallet
|
||||||
|
params := w.ChainParams()
|
||||||
|
_, tipHeight := w.MainChainTip(fp.ctx)
|
||||||
|
|
||||||
|
fp.mu.Lock()
|
||||||
|
ticketLive := fp.ticketLive
|
||||||
|
ticketExpires := fp.ticketExpires
|
||||||
|
fp.mu.Unlock()
|
||||||
|
|
||||||
|
var jitter time.Duration
|
||||||
|
switch {
|
||||||
|
case tipHeight < ticketLive: // immature, mined ticket
|
||||||
|
blocksUntilLive := ticketExpires - tipHeight
|
||||||
|
jitter = params.TargetTimePerBlock * time.Duration(blocksUntilLive)
|
||||||
|
if jitter > immatureJitter {
|
||||||
|
jitter = immatureJitter
|
||||||
|
}
|
||||||
|
case tipHeight < ticketExpires: // live ticket
|
||||||
|
jitter = liveJitter
|
||||||
|
default: // unmined ticket
|
||||||
|
jitter = unminedJitter
|
||||||
|
}
|
||||||
|
|
||||||
|
return randomDuration(jitter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// task returns a function running a feePayment method.
|
||||||
|
// If the method errors, the error is logged, and the payment is put
|
||||||
|
// in an errored state and may require manual processing.
|
||||||
|
func (fp *feePayment) task(name string, method func() error) func() {
|
||||||
|
return func() {
|
||||||
|
err := method()
|
||||||
|
fp.mu.Lock()
|
||||||
|
fp.err = err
|
||||||
|
fp.mu.Unlock()
|
||||||
|
if err != nil {
|
||||||
|
fp.log.Errorf("ticket %v: %v: %v", &fp.ticketHash, name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fp *feePayment) stop() {
|
||||||
|
fp.schedule("", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fp *feePayment) receiveFeeAddress() error {
|
||||||
|
ctx := fp.ctx
|
||||||
|
w := fp.client.wallet
|
||||||
|
params := w.ChainParams()
|
||||||
|
|
||||||
|
// stop processing if ticket is expired or spent
|
||||||
|
if fp.removedExpiredOrSpent() {
|
||||||
|
// nothing scheduled
|
||||||
|
return errStopped
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch ticket and its parent transaction (typically, a split
|
||||||
|
// transaction).
|
||||||
|
ticket, err := fp.client.tx(ctx, &fp.ticketHash)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to retrieve ticket: %w", err)
|
||||||
|
}
|
||||||
|
parentHash := &ticket.TxIn[0].PreviousOutPoint.Hash
|
||||||
|
parent, err := fp.client.tx(ctx, parentHash)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to retrieve parent %v of ticket: %w",
|
||||||
|
parentHash, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ticketHex, err := marshalTx(ticket)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
parentHex, err := marshalTx(parent)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := types.FeeAddressRequest{
|
||||||
|
Timestamp: time.Now().Unix(),
|
||||||
|
TicketHash: fp.ticketHash.String(),
|
||||||
|
TicketHex: ticketHex,
|
||||||
|
ParentHex: parentHex,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := fp.client.FeeAddress(ctx, req, fp.commitmentAddr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
feeAmount := dcrutil.Amount(resp.FeeAmount)
|
||||||
|
feeAddr, err := stdaddr.DecodeAddress(resp.FeeAddress, params)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("server fee address invalid: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fp.log.Infof("VSP requires fee %v", feeAmount)
|
||||||
|
if feeAmount > fp.policy.MaxFee {
|
||||||
|
return fmt.Errorf("server fee amount too high: %v > %v",
|
||||||
|
feeAmount, fp.policy.MaxFee)
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX validate server timestamp?
|
||||||
|
|
||||||
|
fp.mu.Lock()
|
||||||
|
fp.fee = feeAmount
|
||||||
|
fp.feeAddr = feeAddr
|
||||||
|
fp.mu.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeFeeTx adds outputs to tx to pay a VSP fee, optionally adding inputs as
|
||||||
|
// well to fund the transaction if no input value is already provided in the
|
||||||
|
// transaction.
|
||||||
|
//
|
||||||
|
// If tx is nil, fp.feeTx may be assigned or modified, but the pointer will not
|
||||||
|
// be dereferenced.
|
||||||
|
func (fp *feePayment) makeFeeTx(tx *wire.MsgTx) error {
|
||||||
|
ctx := fp.ctx
|
||||||
|
w := fp.client.wallet
|
||||||
|
|
||||||
|
fp.mu.Lock()
|
||||||
|
fee := fp.fee
|
||||||
|
fpFeeTx := fp.feeTx
|
||||||
|
feeAddr := fp.feeAddr
|
||||||
|
fp.mu.Unlock()
|
||||||
|
|
||||||
|
// The rest of this function will operate on the tx pointer, with fp.feeTx
|
||||||
|
// assigned to the result on success.
|
||||||
|
// Update tx to use the partially created fpFeeTx if any has been started.
|
||||||
|
// The transaction pointed to by the caller will be dereferenced and modified
|
||||||
|
// when non-nil.
|
||||||
|
if fpFeeTx != nil {
|
||||||
|
if tx != nil {
|
||||||
|
*tx = *fpFeeTx
|
||||||
|
} else {
|
||||||
|
tx = fpFeeTx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fee transaction with outputs is already finished.
|
||||||
|
if fpFeeTx != nil && len(fpFeeTx.TxOut) != 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// When both transactions are nil, create a new empty transaction.
|
||||||
|
if tx == nil {
|
||||||
|
tx = wire.NewMsgTx()
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX fp.fee == -1?
|
||||||
|
if fee == 0 {
|
||||||
|
err := fp.receiveFeeAddress()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fp.mu.Lock()
|
||||||
|
fee = fp.fee
|
||||||
|
feeAddr = fp.feeAddr
|
||||||
|
fp.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reserve new outputs to pay the fee if outputs have not already been
|
||||||
|
// reserved. This will be the case for fee payments that were begun on
|
||||||
|
// already purchased tickets, where the caller did not ensure that fee
|
||||||
|
// outputs would already be reserved.
|
||||||
|
if len(tx.TxIn) == 0 {
|
||||||
|
const minconf = 1
|
||||||
|
inputs, err := w.ReserveOutputsForAmount(ctx, fp.policy.FeeAcct, fee, minconf)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to reserve enough output value to "+
|
||||||
|
"pay VSP fee for ticket %v: %w", fp.ticketHash, err)
|
||||||
|
}
|
||||||
|
for _, in := range inputs {
|
||||||
|
tx.AddTxIn(wire.NewTxIn(&in.OutPoint, in.PrevOut.Value, nil))
|
||||||
|
}
|
||||||
|
// The transaction will be added to the wallet in an unpublished
|
||||||
|
// state, so there is no need to leave the outputs locked.
|
||||||
|
defer func() {
|
||||||
|
for _, in := range inputs {
|
||||||
|
w.UnlockOutpoint(&in.OutPoint.Hash, in.OutPoint.Index)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
var input int64
|
||||||
|
for _, in := range tx.TxIn {
|
||||||
|
input += in.ValueIn
|
||||||
|
}
|
||||||
|
if input < int64(fee) {
|
||||||
|
err := fmt.Errorf("not enough input value to pay fee: %v < %v",
|
||||||
|
dcrutil.Amount(input), fee)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
vers, feeScript := feeAddr.PaymentScript()
|
||||||
|
|
||||||
|
addr, err := w.NewChangeAddress(ctx, fp.policy.ChangeAcct)
|
||||||
|
if err != nil {
|
||||||
|
fp.log.Warnf("failed to get new change address: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var changeOut *wire.TxOut
|
||||||
|
switch addr := addr.(type) {
|
||||||
|
case wallet.Address:
|
||||||
|
vers, script := addr.PaymentScript()
|
||||||
|
changeOut = &wire.TxOut{PkScript: script, Version: vers}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("failed to convert '%T' to wallet.Address", addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.TxOut = append(tx.TxOut[:0], &wire.TxOut{
|
||||||
|
Value: int64(fee),
|
||||||
|
Version: vers,
|
||||||
|
PkScript: feeScript,
|
||||||
|
})
|
||||||
|
feeRate := w.RelayFee()
|
||||||
|
scriptSizes := make([]int, len(tx.TxIn))
|
||||||
|
for i := range scriptSizes {
|
||||||
|
scriptSizes[i] = txsizes.RedeemP2PKHSigScriptSize
|
||||||
|
}
|
||||||
|
est := txsizes.EstimateSerializeSize(scriptSizes, tx.TxOut, txsizes.P2PKHPkScriptSize)
|
||||||
|
change := input
|
||||||
|
change -= tx.TxOut[0].Value
|
||||||
|
change -= int64(txrules.FeeForSerializeSize(feeRate, est))
|
||||||
|
if !txrules.IsDustAmount(dcrutil.Amount(change), txsizes.P2PKHPkScriptSize, feeRate) {
|
||||||
|
changeOut.Value = change
|
||||||
|
tx.TxOut = append(tx.TxOut, changeOut)
|
||||||
|
// randomize position
|
||||||
|
if coinflip() {
|
||||||
|
tx.TxOut[0], tx.TxOut[1] = tx.TxOut[1], tx.TxOut[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
feeHash := tx.TxHash()
|
||||||
|
|
||||||
|
// sign
|
||||||
|
sigErrs, err := w.SignTransaction(ctx, tx, txscript.SigHashAll, nil, nil, nil)
|
||||||
|
if err != nil || len(sigErrs) > 0 {
|
||||||
|
fp.log.Errorf("failed to sign transaction: %v", err)
|
||||||
|
sigErrStr := ""
|
||||||
|
for _, sigErr := range sigErrs {
|
||||||
|
fp.log.Errorf("\t%v", sigErr)
|
||||||
|
sigErrStr = fmt.Sprintf("\t%v", sigErr) + " "
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fmt.Errorf(sigErrStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.SetPublished(ctx, &feeHash, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = w.AddTransaction(ctx, tx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = w.UpdateVspTicketFeeToPaid(ctx, &fp.ticketHash, &feeHash, fp.client.URL, fp.client.PubKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fp.mu.Lock()
|
||||||
|
fp.feeTx = tx
|
||||||
|
fp.feeHash = feeHash
|
||||||
|
fp.mu.Unlock()
|
||||||
|
|
||||||
|
// nothing scheduled
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AutoClient) status(ctx context.Context, ticketHash *chainhash.Hash) (*types.TicketStatusResponse, error) {
|
||||||
|
w := c.wallet
|
||||||
|
params := w.ChainParams()
|
||||||
|
|
||||||
|
ticketTx, err := c.tx(ctx, ticketHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to retrieve ticket %v: %w", ticketHash, err)
|
||||||
|
}
|
||||||
|
if len(ticketTx.TxOut) != 3 {
|
||||||
|
return nil, fmt.Errorf("ticket %v has multiple commitments: %w", ticketHash, errNotSolo)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !stake.IsSStx(ticketTx) {
|
||||||
|
return nil, fmt.Errorf("%v is not a ticket", ticketHash)
|
||||||
|
}
|
||||||
|
commitmentAddr, err := stake.AddrFromSStxPkScrCommitment(ticketTx.TxOut[1].PkScript, params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to extract commitment address from %v: %w",
|
||||||
|
ticketHash, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req := types.TicketStatusRequest{
|
||||||
|
TicketHash: ticketHash.String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.Client.TicketStatus(ctx, req, commitmentAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX validate server timestamp?
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AutoClient) setVoteChoices(ctx context.Context, ticketHash *chainhash.Hash,
|
||||||
|
choices map[string]string, tspendPolicy map[string]string, treasuryPolicy map[string]string) error {
|
||||||
|
w := c.wallet
|
||||||
|
params := w.ChainParams()
|
||||||
|
|
||||||
|
ticketTx, err := c.tx(ctx, ticketHash)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to retrieve ticket %v: %w", ticketHash, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !stake.IsSStx(ticketTx) {
|
||||||
|
return fmt.Errorf("%v is not a ticket", ticketHash)
|
||||||
|
}
|
||||||
|
if len(ticketTx.TxOut) != 3 {
|
||||||
|
return fmt.Errorf("ticket %v has multiple commitments: %w", ticketHash, errNotSolo)
|
||||||
|
}
|
||||||
|
|
||||||
|
commitmentAddr, err := stake.AddrFromSStxPkScrCommitment(ticketTx.TxOut[1].PkScript, params)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to extract commitment address from %v: %w",
|
||||||
|
ticketHash, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req := types.SetVoteChoicesRequest{
|
||||||
|
Timestamp: time.Now().Unix(),
|
||||||
|
TicketHash: ticketHash.String(),
|
||||||
|
VoteChoices: choices,
|
||||||
|
TSpendPolicy: tspendPolicy,
|
||||||
|
TreasuryPolicy: treasuryPolicy,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = c.Client.SetVoteChoices(ctx, req, commitmentAddr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX validate server timestamp?
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fp *feePayment) reconcilePayment() error {
|
||||||
|
ctx := fp.ctx
|
||||||
|
w := fp.client.wallet
|
||||||
|
|
||||||
|
// stop processing if ticket is expired or spent
|
||||||
|
// XXX if ticket is no longer saved by wallet (because the tx expired,
|
||||||
|
// or was double spent, etc) remove the fee payment.
|
||||||
|
if fp.removedExpiredOrSpent() {
|
||||||
|
// nothing scheduled
|
||||||
|
return errStopped
|
||||||
|
}
|
||||||
|
|
||||||
|
// A fee amount and address must have been created by this point.
|
||||||
|
// Ensure that the fee transaction can be created, otherwise reschedule
|
||||||
|
// this method until it is. There is no need to check the wallet for a
|
||||||
|
// fee transaction matching a known hash; this is performed when
|
||||||
|
// creating the feePayment.
|
||||||
|
fp.mu.Lock()
|
||||||
|
feeTx := fp.feeTx
|
||||||
|
fp.mu.Unlock()
|
||||||
|
if feeTx == nil || len(feeTx.TxOut) == 0 {
|
||||||
|
err := fp.makeFeeTx(nil)
|
||||||
|
if err != nil {
|
||||||
|
var apiErr types.ErrorResponse
|
||||||
|
if errors.As(err, &apiErr) && apiErr.Code == types.ErrTicketCannotVote {
|
||||||
|
fp.remove("ticket cannot vote")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A fee address has been obtained, and the fee transaction has been
|
||||||
|
// created, but it is unknown if the VSP has received the fee and will
|
||||||
|
// vote using the ticket.
|
||||||
|
//
|
||||||
|
// If the fee is mined, then check the status of the ticket and payment
|
||||||
|
// with the VSP, to ensure that it has marked the fee payment as paid.
|
||||||
|
//
|
||||||
|
// If the fee is not mined, an API call with the VSP is used so it may
|
||||||
|
// receive and publish the transaction. A follow up on the ticket
|
||||||
|
// status is scheduled for some time in the future.
|
||||||
|
|
||||||
|
err := fp.submitPayment()
|
||||||
|
fp.mu.Lock()
|
||||||
|
feeHash := fp.feeHash
|
||||||
|
fp.mu.Unlock()
|
||||||
|
var apiErr types.ErrorResponse
|
||||||
|
if errors.As(err, &apiErr) {
|
||||||
|
switch apiErr.Code {
|
||||||
|
case types.ErrFeeAlreadyReceived:
|
||||||
|
err = w.SetPublished(ctx, &feeHash, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = w.UpdateVspTicketFeeToPaid(ctx, &fp.ticketHash, &feeHash, fp.client.URL, fp.client.PubKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = nil
|
||||||
|
case types.ErrInvalidFeeTx, types.ErrCannotBroadcastFee:
|
||||||
|
err := w.UpdateVspTicketFeeToErrored(ctx, &fp.ticketHash, fp.client.URL, fp.client.PubKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Attempt to create a new fee transaction
|
||||||
|
fp.mu.Lock()
|
||||||
|
fp.feeHash = chainhash.Hash{}
|
||||||
|
fp.feeTx = nil
|
||||||
|
fp.mu.Unlock()
|
||||||
|
// err not nilled, so reconcile payment is rescheduled.
|
||||||
|
default:
|
||||||
|
// do nothing.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
// Nothing left to try except trying again.
|
||||||
|
fp.schedule("reconcile payment", fp.reconcilePayment)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.UpdateVspTicketFeeToPaid(ctx, &fp.ticketHash, &feeHash, fp.client.URL, fp.client.PubKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// confirmPayment will remove the fee payment processing when the fee
|
||||||
|
// has reached sufficient confirmations, and reschedule itself if the
|
||||||
|
// fee is not confirmed yet. If the fee tx is ever removed from the
|
||||||
|
// wallet, this will schedule another reconcile.
|
||||||
|
return fp.confirmPayment()
|
||||||
|
|
||||||
|
/*
|
||||||
|
// XXX? for each input, c.Wallet.UnlockOutpoint(&outpoint.Hash, outpoint.Index)
|
||||||
|
// xxx, or let the published tx replace the unpublished one, and unlock
|
||||||
|
// outpoints as it is processed.
|
||||||
|
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fp *feePayment) submitPayment() (err error) {
|
||||||
|
ctx := fp.ctx
|
||||||
|
w := fp.client.wallet
|
||||||
|
|
||||||
|
// stop processing if ticket is expired or spent
|
||||||
|
if fp.removedExpiredOrSpent() {
|
||||||
|
// nothing scheduled
|
||||||
|
return errStopped
|
||||||
|
}
|
||||||
|
|
||||||
|
// submitting a payment requires the fee tx to already be created.
|
||||||
|
fp.mu.Lock()
|
||||||
|
feeTx := fp.feeTx
|
||||||
|
votingKey := fp.votingKey
|
||||||
|
fp.mu.Unlock()
|
||||||
|
if feeTx == nil {
|
||||||
|
feeTx = new(wire.MsgTx)
|
||||||
|
}
|
||||||
|
if len(feeTx.TxOut) == 0 {
|
||||||
|
err := fp.makeFeeTx(feeTx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if votingKey == "" {
|
||||||
|
votingKey, err = w.DumpWIFPrivateKey(ctx, fp.votingAddr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fp.mu.Lock()
|
||||||
|
fp.votingKey = votingKey
|
||||||
|
fp.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve voting preferences
|
||||||
|
voteChoices := make(map[string]string)
|
||||||
|
agendaChoices, _, err := w.AgendaChoices(ctx, &fp.ticketHash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, agendaChoice := range agendaChoices {
|
||||||
|
voteChoices[agendaChoice.AgendaID] = agendaChoice.ChoiceID
|
||||||
|
}
|
||||||
|
|
||||||
|
feeTxHex, err := marshalTx(feeTx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := types.PayFeeRequest{
|
||||||
|
Timestamp: time.Now().Unix(),
|
||||||
|
TicketHash: fp.ticketHash.String(),
|
||||||
|
FeeTx: feeTxHex,
|
||||||
|
VotingKey: votingKey,
|
||||||
|
VoteChoices: voteChoices,
|
||||||
|
TSpendPolicy: w.TSpendPolicyForTicket(&fp.ticketHash),
|
||||||
|
TreasuryPolicy: w.TreasuryKeyPolicyForTicket(&fp.ticketHash),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = fp.client.PayFee(ctx, req, fp.commitmentAddr)
|
||||||
|
if err != nil {
|
||||||
|
var apiErr types.ErrorResponse
|
||||||
|
if errors.As(err, &apiErr) && apiErr.Code == types.ErrFeeExpired {
|
||||||
|
// Fee has been expired, so abandon current feetx, set fp.feeTx
|
||||||
|
// to nil and retry submit payment to make a new fee tx.
|
||||||
|
feeHash := feeTx.TxHash()
|
||||||
|
err := w.AbandonTransaction(ctx, &feeHash)
|
||||||
|
if err != nil {
|
||||||
|
fp.log.Errorf("error abandoning expired fee tx %v", err)
|
||||||
|
}
|
||||||
|
fp.mu.Lock()
|
||||||
|
fp.feeTx = nil
|
||||||
|
fp.mu.Unlock()
|
||||||
|
}
|
||||||
|
return fmt.Errorf("payfee: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO - validate server timestamp?
|
||||||
|
|
||||||
|
fp.log.Infof("successfully processed %v", fp.ticketHash)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fp *feePayment) confirmPayment() (err error) {
|
||||||
|
ctx := fp.ctx
|
||||||
|
w := fp.client.wallet
|
||||||
|
|
||||||
|
// stop processing if ticket is expired or spent
|
||||||
|
if fp.removedExpiredOrSpent() {
|
||||||
|
// nothing scheduled
|
||||||
|
return errStopped
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err != nil && !errors.Is(err, errStopped) {
|
||||||
|
fp.schedule("reconcile payment", fp.reconcilePayment)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
status, err := fp.client.status(ctx, &fp.ticketHash)
|
||||||
|
// Suppress log if the wallet is currently locked.
|
||||||
|
if err != nil && !errors.Is(err, wallet_errs.Locked) {
|
||||||
|
fp.log.Warnf("Rescheduling status check for %v: %v", &fp.ticketHash, err)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
// Stop processing if the status check cannot be performed, but
|
||||||
|
// a significant amount of confirmations are observed on the fee
|
||||||
|
// transaction.
|
||||||
|
//
|
||||||
|
// Otherwise, chedule another confirmation check, in case the
|
||||||
|
// status API can be performed at a later time or more
|
||||||
|
// confirmations are observed.
|
||||||
|
fp.mu.Lock()
|
||||||
|
feeHash := fp.feeHash
|
||||||
|
fp.mu.Unlock()
|
||||||
|
confs, err := w.TxConfirms(ctx, &feeHash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if confs >= 6 {
|
||||||
|
fp.remove("confirmed")
|
||||||
|
err = w.UpdateVspTicketFeeToConfirmed(ctx, &fp.ticketHash, &feeHash, fp.client.URL, fp.client.PubKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
fp.schedule("confirm payment", fp.confirmPayment)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch status.FeeTxStatus {
|
||||||
|
case "received":
|
||||||
|
// VSP has received the fee tx but has not yet broadcast it.
|
||||||
|
// VSP will only broadcast the tx when ticket has 6+ confirmations.
|
||||||
|
fp.schedule("confirm payment", fp.confirmPayment)
|
||||||
|
return nil
|
||||||
|
case "broadcast":
|
||||||
|
fp.log.Infof("VSP has successfully sent the fee tx for %v", &fp.ticketHash)
|
||||||
|
// Broadcasted, but not confirmed.
|
||||||
|
fp.schedule("confirm payment", fp.confirmPayment)
|
||||||
|
return nil
|
||||||
|
case "confirmed":
|
||||||
|
fp.remove("confirmed by VSP")
|
||||||
|
// nothing scheduled
|
||||||
|
fp.mu.Lock()
|
||||||
|
feeHash := fp.feeHash
|
||||||
|
fp.mu.Unlock()
|
||||||
|
err = w.UpdateVspTicketFeeToConfirmed(ctx, &fp.ticketHash, &feeHash, fp.client.URL, fp.client.PubKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case "error":
|
||||||
|
fp.log.Warnf("VSP failed to broadcast feetx for %v -- restarting payment",
|
||||||
|
&fp.ticketHash)
|
||||||
|
fp.schedule("reconcile payment", fp.reconcilePayment)
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
// XXX put in unknown state
|
||||||
|
fp.log.Warnf("VSP responded with %v for %v", status.FeeTxStatus,
|
||||||
|
&fp.ticketHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func marshalTx(tx *wire.MsgTx) (string, error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
buf.Grow(tx.SerializeSize() * 2)
|
||||||
|
err := tx.Serialize(hex.NewEncoder(&buf))
|
||||||
|
return buf.String(), err
|
||||||
|
}
|
||||||
@ -3,20 +3,47 @@ module github.com/decred/vspd/client/v2
|
|||||||
go 1.19
|
go 1.19
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
decred.org/dcrwallet/v3 v3.0.0-20230519033517-96817277627d
|
||||||
|
github.com/decred/dcrd/blockchain/stake/v5 v5.0.0
|
||||||
|
github.com/decred/dcrd/chaincfg/chainhash v1.0.4
|
||||||
|
github.com/decred/dcrd/chaincfg/v3 v3.1.2
|
||||||
|
github.com/decred/dcrd/dcrutil/v4 v4.0.0
|
||||||
github.com/decred/dcrd/txscript/v4 v4.0.0
|
github.com/decred/dcrd/txscript/v4 v4.0.0
|
||||||
|
github.com/decred/dcrd/wire v1.5.0
|
||||||
github.com/decred/slog v1.2.0
|
github.com/decred/slog v1.2.0
|
||||||
github.com/decred/vspd/types/v2 v2.0.0
|
github.com/decred/vspd/types/v2 v2.0.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
replace (
|
||||||
|
github.com/decred/dcrd/blockchain/stake/v5 => github.com/decred/dcrd/blockchain/stake/v5 v5.0.0-20221022042529-0a0cc3b3bf92
|
||||||
|
github.com/decred/dcrd/blockchain/standalone/v2 => github.com/decred/dcrd/blockchain/standalone/v2 v2.1.1-0.20230430213532-f95870f9c6af
|
||||||
|
github.com/decred/dcrd/chaincfg/v3 => github.com/decred/dcrd/chaincfg/v3 v3.1.2-0.20230412145739-9aa79ec168f6
|
||||||
|
github.com/decred/dcrd/gcs/v4 => github.com/decred/dcrd/gcs/v4 v4.0.0-20221022042529-0a0cc3b3bf92
|
||||||
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
decred.org/cspp/v2 v2.0.0 // indirect
|
||||||
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect
|
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect
|
||||||
github.com/dchest/siphash v1.2.2 // indirect
|
github.com/companyzero/sntrup4591761 v0.0.0-20200131011700-2b0d299dbd22 // indirect
|
||||||
github.com/decred/base58 v1.0.3 // indirect
|
github.com/dchest/siphash v1.2.3 // indirect
|
||||||
github.com/decred/dcrd/chaincfg/chainhash v1.0.3 // indirect
|
github.com/decred/base58 v1.0.4 // indirect
|
||||||
github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect
|
github.com/decred/dcrd/blockchain/standalone/v2 v2.1.1-0.20230430213532-f95870f9c6af // indirect
|
||||||
|
github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect
|
||||||
github.com/decred/dcrd/crypto/ripemd160 v1.0.1 // indirect
|
github.com/decred/dcrd/crypto/ripemd160 v1.0.1 // indirect
|
||||||
|
github.com/decred/dcrd/database/v3 v3.0.0 // indirect
|
||||||
github.com/decred/dcrd/dcrec v1.0.0 // indirect
|
github.com/decred/dcrd/dcrec v1.0.0 // indirect
|
||||||
github.com/decred/dcrd/dcrec/edwards/v2 v2.0.2 // indirect
|
github.com/decred/dcrd/dcrec/edwards/v2 v2.0.2 // indirect
|
||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
|
||||||
github.com/decred/dcrd/wire v1.5.0 // indirect
|
github.com/decred/dcrd/dcrjson/v4 v4.0.1 // indirect
|
||||||
|
github.com/decred/dcrd/gcs/v4 v4.0.0 // indirect
|
||||||
|
github.com/decred/dcrd/hdkeychain/v3 v3.1.0 // indirect
|
||||||
|
github.com/decred/dcrd/rpc/jsonrpc/types/v4 v4.0.0 // indirect
|
||||||
|
github.com/decred/go-socks v1.1.0 // indirect
|
||||||
|
github.com/gorilla/websocket v1.5.0 // indirect
|
||||||
|
github.com/jrick/bitset v1.0.0 // indirect
|
||||||
|
github.com/jrick/wsrpc/v2 v2.3.5 // indirect
|
||||||
|
go.etcd.io/bbolt v1.3.7 // indirect
|
||||||
|
golang.org/x/crypto v0.6.0 // indirect
|
||||||
|
golang.org/x/sync v0.1.0 // indirect
|
||||||
|
golang.org/x/sys v0.5.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
123
client/go.sum
123
client/go.sum
@ -1,31 +1,140 @@
|
|||||||
|
decred.org/cspp/v2 v2.0.0 h1:b4fZrElRufz30rYnBZ2shhC8AjNVTN4i6TMzDi+hk44=
|
||||||
|
decred.org/cspp/v2 v2.0.0/go.mod h1:0shJWKTWY3LxZEWGxtbER1Y45+HVjC0WZtj4bctSzCI=
|
||||||
|
decred.org/dcrwallet/v3 v3.0.0-20230519033517-96817277627d h1:Gov8SXYPsw6eyIBhzLP6+kjfDHdv6Q/ifIMt8fKtoYc=
|
||||||
|
decred.org/dcrwallet/v3 v3.0.0-20230519033517-96817277627d/go.mod h1:JGNFyWBroylUUf973eIHsdHN7PuJdCUVkIOMyWVv+y0=
|
||||||
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI=
|
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI=
|
||||||
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0=
|
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0=
|
||||||
|
github.com/companyzero/sntrup4591761 v0.0.0-20200131011700-2b0d299dbd22 h1:vfqLMkB1UqwJliW0I/34oscQawInrVfL1uPjGEEt2YY=
|
||||||
|
github.com/companyzero/sntrup4591761 v0.0.0-20200131011700-2b0d299dbd22/go.mod h1:LoZJNGDWmVPqMEHmeJzj4Weq4Stjc6FKY6FVpY3Hem0=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dchest/siphash v1.2.2 h1:9DFz8tQwl9pTVt5iok/9zKyzA1Q6bRGiF3HPiEEVr9I=
|
|
||||||
github.com/dchest/siphash v1.2.2/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4=
|
github.com/dchest/siphash v1.2.2/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4=
|
||||||
github.com/decred/base58 v1.0.3 h1:KGZuh8d1WEMIrK0leQRM47W85KqCAdl2N+uagbctdDI=
|
github.com/dchest/siphash v1.2.3 h1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA=
|
||||||
|
github.com/dchest/siphash v1.2.3/go.mod h1:0NvQU092bT0ipiFN++/rXm69QG9tVxLAlQHIXMPAkHc=
|
||||||
github.com/decred/base58 v1.0.3/go.mod h1:pXP9cXCfM2sFLb2viz2FNIdeMWmZDBKG3ZBYbiSM78E=
|
github.com/decred/base58 v1.0.3/go.mod h1:pXP9cXCfM2sFLb2viz2FNIdeMWmZDBKG3ZBYbiSM78E=
|
||||||
|
github.com/decred/base58 v1.0.4 h1:QJC6B0E0rXOPA8U/kw2rP+qiRJsUaE2Er+pYb3siUeA=
|
||||||
|
github.com/decred/base58 v1.0.4/go.mod h1:jJswKPEdvpFpvf7dsDvFZyLT22xZ9lWqEByX38oGd9E=
|
||||||
|
github.com/decred/dcrd/blockchain/stake/v5 v5.0.0-20221022042529-0a0cc3b3bf92 h1:GTIg6r54cgNhUyZMNmTsmxM8OneEJ8t6QcWQCqu+al0=
|
||||||
|
github.com/decred/dcrd/blockchain/stake/v5 v5.0.0-20221022042529-0a0cc3b3bf92/go.mod h1:fij5xS9IBfJ5e/F5ytp/g/TWjrETEMXUFlE6C7KYOvA=
|
||||||
|
github.com/decred/dcrd/blockchain/standalone/v2 v2.1.1-0.20230430213532-f95870f9c6af h1:tmfTIkxkq7NdeLj3mrMk0uEdRyU1/oO1MWx8dfwOmm8=
|
||||||
|
github.com/decred/dcrd/blockchain/standalone/v2 v2.1.1-0.20230430213532-f95870f9c6af/go.mod h1:PpM/jdMaD5MnBcSoFd+rJZE4q8tU0xPTTAyVzgegLQI=
|
||||||
|
github.com/decred/dcrd/blockchain/v5 v5.0.0-20221022042529-0a0cc3b3bf92 h1:AYgHfuWXQh5NTv3mciQleTm2K1IuM7ozgfXy1Vg6V7k=
|
||||||
github.com/decred/dcrd/chaincfg/chainhash v1.0.2/go.mod h1:BpbrGgrPTr3YJYRN3Bm+D9NuaFd+zGyNeIKgrhCXK60=
|
github.com/decred/dcrd/chaincfg/chainhash v1.0.2/go.mod h1:BpbrGgrPTr3YJYRN3Bm+D9NuaFd+zGyNeIKgrhCXK60=
|
||||||
github.com/decred/dcrd/chaincfg/chainhash v1.0.3 h1:PF2czcYZGW3dz4i/35AUfVAgnqHl9TMNQt1ADTYGOoE=
|
|
||||||
github.com/decred/dcrd/chaincfg/chainhash v1.0.3/go.mod h1:BpbrGgrPTr3YJYRN3Bm+D9NuaFd+zGyNeIKgrhCXK60=
|
github.com/decred/dcrd/chaincfg/chainhash v1.0.3/go.mod h1:BpbrGgrPTr3YJYRN3Bm+D9NuaFd+zGyNeIKgrhCXK60=
|
||||||
github.com/decred/dcrd/chaincfg/v3 v3.1.0 h1:u8l+E6ryv8E0WY69pM/lUI36UeAVcLKBwD/Q3xPiuog=
|
github.com/decred/dcrd/chaincfg/chainhash v1.0.4 h1:zRCv6tdncLfLTKYqu7hrXvs7hW+8FO/NvwoFvGsrluU=
|
||||||
github.com/decred/dcrd/chaincfg/v3 v3.1.0/go.mod h1:4XF9nlx2NeGD4xzw1+L0DGICZMl0a5rKV8nnuHLgk8o=
|
github.com/decred/dcrd/chaincfg/chainhash v1.0.4/go.mod h1:hA86XxlBWwHivMvxzXTSD0ZCG/LoYsFdWnCekkTMCqY=
|
||||||
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
|
github.com/decred/dcrd/chaincfg/v3 v3.1.2-0.20230412145739-9aa79ec168f6 h1:rKAzrv3gIEQaEQpnWU4IKxXrvx6QfXkdiOUKLvwEpQw=
|
||||||
|
github.com/decred/dcrd/chaincfg/v3 v3.1.2-0.20230412145739-9aa79ec168f6/go.mod h1:aEEti0kQSBFAlzHln4FB+3L30k9ZN1M7YDfYuK5VWtc=
|
||||||
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
|
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
|
||||||
|
github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y=
|
||||||
|
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
|
||||||
github.com/decred/dcrd/crypto/ripemd160 v1.0.1 h1:TjRL4LfftzTjXzaufov96iDAkbY2R3aTvH2YMYa1IOc=
|
github.com/decred/dcrd/crypto/ripemd160 v1.0.1 h1:TjRL4LfftzTjXzaufov96iDAkbY2R3aTvH2YMYa1IOc=
|
||||||
github.com/decred/dcrd/crypto/ripemd160 v1.0.1/go.mod h1:F0H8cjIuWTRoixr/LM3REB8obcWkmYx0gbxpQWR8RPg=
|
github.com/decred/dcrd/crypto/ripemd160 v1.0.1/go.mod h1:F0H8cjIuWTRoixr/LM3REB8obcWkmYx0gbxpQWR8RPg=
|
||||||
|
github.com/decred/dcrd/database/v3 v3.0.0 h1:7VVN2sWjKB934jvXzjnyGJFUVH9d8Qh5VULi+NMRjek=
|
||||||
|
github.com/decred/dcrd/database/v3 v3.0.0/go.mod h1:8EyKddB8rXDi6/CDOdYc/7qL1//sb6iwg9DctP0ZJF4=
|
||||||
github.com/decred/dcrd/dcrec v1.0.0 h1:W+z6Es+Rai3MXYVoPAxYr5U1DGis0Co33scJ6uH2J6o=
|
github.com/decred/dcrd/dcrec v1.0.0 h1:W+z6Es+Rai3MXYVoPAxYr5U1DGis0Co33scJ6uH2J6o=
|
||||||
github.com/decred/dcrd/dcrec v1.0.0/go.mod h1:HIaqbEJQ+PDzQcORxnqen5/V1FR3B4VpIfmePklt8Q8=
|
github.com/decred/dcrd/dcrec v1.0.0/go.mod h1:HIaqbEJQ+PDzQcORxnqen5/V1FR3B4VpIfmePklt8Q8=
|
||||||
github.com/decred/dcrd/dcrec/edwards/v2 v2.0.2 h1:bX7rtGTMBDJxujZ29GNqtn7YCAdINjHKnA6J6tBBv6s=
|
github.com/decred/dcrd/dcrec/edwards/v2 v2.0.2 h1:bX7rtGTMBDJxujZ29GNqtn7YCAdINjHKnA6J6tBBv6s=
|
||||||
github.com/decred/dcrd/dcrec/edwards/v2 v2.0.2/go.mod h1:d0H8xGMWbiIQP7gN3v2rByWUcuZPm9YsgmnfoxgbINc=
|
github.com/decred/dcrd/dcrec/edwards/v2 v2.0.2/go.mod h1:d0H8xGMWbiIQP7gN3v2rByWUcuZPm9YsgmnfoxgbINc=
|
||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
|
|
||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
|
||||||
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4=
|
||||||
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc=
|
||||||
|
github.com/decred/dcrd/dcrjson/v4 v4.0.1 h1:vyQuB1miwGqbCVNm8P6br3V65WQ6wyrh0LycMkvaBBg=
|
||||||
|
github.com/decred/dcrd/dcrjson/v4 v4.0.1/go.mod h1:2qVikafVF9/X3PngQVmqkbUbyAl32uik0k/kydgtqMc=
|
||||||
|
github.com/decred/dcrd/dcrutil/v4 v4.0.0 h1:AY00fWy/ETrMHN0DNV3XUbH1aip2RG1AoTy5dp0+sJE=
|
||||||
|
github.com/decred/dcrd/dcrutil/v4 v4.0.0/go.mod h1:QQpX5WVH3/ixVtiW15xZMe+neugXX3l2bsrYgq6nz4M=
|
||||||
|
github.com/decred/dcrd/gcs/v4 v4.0.0-20221022042529-0a0cc3b3bf92 h1:DiAdpAQg54JL5iWFdB+DEdK/xuKIjTyL6mlR7iIumPQ=
|
||||||
|
github.com/decred/dcrd/gcs/v4 v4.0.0-20221022042529-0a0cc3b3bf92/go.mod h1:2SpSpCW0vOWlACQNAn7mPuIb3Vet070zfs1SpcSEv8o=
|
||||||
|
github.com/decred/dcrd/hdkeychain/v3 v3.1.0 h1:NlUjzPMzexbk1PyJu6vrQaiilep5WsEPB0KdhLYrEcE=
|
||||||
|
github.com/decred/dcrd/hdkeychain/v3 v3.1.0/go.mod h1:rDCdqwGkcTfEyRheG1g8Wc38appT2C9+D1XTlLy21lo=
|
||||||
|
github.com/decred/dcrd/rpc/jsonrpc/types/v4 v4.0.0 h1:4YUKsWKrKlkhVMYGRB6G0XI6QfwUnwEH18eoEbM1/+M=
|
||||||
|
github.com/decred/dcrd/rpc/jsonrpc/types/v4 v4.0.0/go.mod h1:dDHO7ivrPAhZjFD3LoOJN/kdq5gi0sxie6zCsWHAiUo=
|
||||||
github.com/decred/dcrd/txscript/v4 v4.0.0 h1:BwaBUCMCmg58MCYoBhxVjL8ZZKUIfoJuxu/djmh8h58=
|
github.com/decred/dcrd/txscript/v4 v4.0.0 h1:BwaBUCMCmg58MCYoBhxVjL8ZZKUIfoJuxu/djmh8h58=
|
||||||
github.com/decred/dcrd/txscript/v4 v4.0.0/go.mod h1:OJtxNc5RqwQyfrRnG2gG8uMeNPo8IAJp+TD1UKXkqk8=
|
github.com/decred/dcrd/txscript/v4 v4.0.0/go.mod h1:OJtxNc5RqwQyfrRnG2gG8uMeNPo8IAJp+TD1UKXkqk8=
|
||||||
github.com/decred/dcrd/wire v1.5.0 h1:3SgcEzSjqAMQvOugP0a8iX7yQSpiVT1yNi9bc4iOXVg=
|
github.com/decred/dcrd/wire v1.5.0 h1:3SgcEzSjqAMQvOugP0a8iX7yQSpiVT1yNi9bc4iOXVg=
|
||||||
github.com/decred/dcrd/wire v1.5.0/go.mod h1:fzAjVqw32LkbAZIt5mnrvBR751GTa3e0rRQdOIhPY3w=
|
github.com/decred/dcrd/wire v1.5.0/go.mod h1:fzAjVqw32LkbAZIt5mnrvBR751GTa3e0rRQdOIhPY3w=
|
||||||
|
github.com/decred/go-socks v1.1.0 h1:dnENcc0KIqQo3HSXdgboXAHgqsCIutkqq6ntQjYtm2U=
|
||||||
|
github.com/decred/go-socks v1.1.0/go.mod h1:sDhHqkZH0X4JjSa02oYOGhcGHYp12FsY1jQ/meV8md0=
|
||||||
github.com/decred/slog v1.2.0 h1:soHAxV52B54Di3WtKLfPum9OFfWqwtf/ygf9njdfnPM=
|
github.com/decred/slog v1.2.0 h1:soHAxV52B54Di3WtKLfPum9OFfWqwtf/ygf9njdfnPM=
|
||||||
github.com/decred/slog v1.2.0/go.mod h1:kVXlGnt6DHy2fV5OjSeuvCJ0OmlmTF6LFpEPMu/fOY0=
|
github.com/decred/slog v1.2.0/go.mod h1:kVXlGnt6DHy2fV5OjSeuvCJ0OmlmTF6LFpEPMu/fOY0=
|
||||||
github.com/decred/vspd/types/v2 v2.0.0 h1:FaPA+W4OOMRWK+Vk4fyyYdXoVLRMMRQsxzsnSjJjOnI=
|
github.com/decred/vspd/types/v2 v2.0.0 h1:FaPA+W4OOMRWK+Vk4fyyYdXoVLRMMRQsxzsnSjJjOnI=
|
||||||
github.com/decred/vspd/types/v2 v2.0.0/go.mod h1:2xnNqedkt9GuL+pK8uIzDxqYxFlwLRflYFJH64b76n0=
|
github.com/decred/vspd/types/v2 v2.0.0/go.mod h1:2xnNqedkt9GuL+pK8uIzDxqYxFlwLRflYFJH64b76n0=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||||
|
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
|
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
|
github.com/jrick/bitset v1.0.0 h1:Ws0PXV3PwXqWK2n7Vz6idCdrV/9OrBXgHEJi27ZB9Dw=
|
||||||
|
github.com/jrick/bitset v1.0.0/go.mod h1:ZOYB5Uvkla7wIEY4FEssPVi3IQXa02arznRaYaAEPe4=
|
||||||
|
github.com/jrick/wsrpc/v2 v2.3.4/go.mod h1:XPYs8BnRWl99lCvXRM5SLpZmTPqWpSOPkDIqYTwDPfU=
|
||||||
|
github.com/jrick/wsrpc/v2 v2.3.5 h1:CwdycaR/df09iGkPMXs1FxqAHMCQbdAiTGoHfOrtuds=
|
||||||
|
github.com/jrick/wsrpc/v2 v2.3.5/go.mod h1:7oBeDM/xMF6Yqy4GDAjpppuOf1hm6lWsaG3EaMrm+aA=
|
||||||
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||||
|
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||||
|
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||||
|
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||||
|
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
|
||||||
|
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
|
||||||
|
go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
|
||||||
|
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
|
||||||
|
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||||
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||||
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
|||||||
16
go.mod
16
go.mod
@ -6,9 +6,9 @@ require (
|
|||||||
decred.org/dcrwallet/v3 v3.0.0
|
decred.org/dcrwallet/v3 v3.0.0
|
||||||
github.com/decred/dcrd/blockchain/stake/v5 v5.0.0
|
github.com/decred/dcrd/blockchain/stake/v5 v5.0.0
|
||||||
github.com/decred/dcrd/blockchain/standalone/v2 v2.1.1
|
github.com/decred/dcrd/blockchain/standalone/v2 v2.1.1
|
||||||
github.com/decred/dcrd/chaincfg/chainhash v1.0.3
|
github.com/decred/dcrd/chaincfg/chainhash v1.0.4
|
||||||
github.com/decred/dcrd/chaincfg/v3 v3.1.1
|
github.com/decred/dcrd/chaincfg/v3 v3.1.2
|
||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0
|
||||||
github.com/decred/dcrd/dcrutil/v4 v4.0.0
|
github.com/decred/dcrd/dcrutil/v4 v4.0.0
|
||||||
github.com/decred/dcrd/hdkeychain/v3 v3.1.0
|
github.com/decred/dcrd/hdkeychain/v3 v3.1.0
|
||||||
github.com/decred/dcrd/rpc/jsonrpc/types/v4 v4.0.0
|
github.com/decred/dcrd/rpc/jsonrpc/types/v4 v4.0.0
|
||||||
@ -28,23 +28,23 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
replace (
|
replace (
|
||||||
decred.org/dcrwallet/v3 => decred.org/dcrwallet/v3 v3.0.0-20230406144806-dc82294b976a
|
decred.org/dcrwallet/v3 => decred.org/dcrwallet/v3 v3.0.0-20230519033517-96817277627d
|
||||||
github.com/decred/dcrd/blockchain/stake/v5 => github.com/decred/dcrd/blockchain/stake/v5 v5.0.0-20221022042529-0a0cc3b3bf92
|
github.com/decred/dcrd/blockchain/stake/v5 => github.com/decred/dcrd/blockchain/stake/v5 v5.0.0-20221022042529-0a0cc3b3bf92
|
||||||
github.com/decred/dcrd/blockchain/standalone/v2 => github.com/decred/dcrd/blockchain/standalone/v2 v2.1.1-0.20230411184711-ce46220cf772
|
github.com/decred/dcrd/blockchain/standalone/v2 => github.com/decred/dcrd/blockchain/standalone/v2 v2.1.1-0.20230430213532-f95870f9c6af
|
||||||
|
github.com/decred/dcrd/chaincfg/v3 => github.com/decred/dcrd/chaincfg/v3 v3.1.2-0.20230412145739-9aa79ec168f6
|
||||||
github.com/decred/dcrd/gcs/v4 => github.com/decred/dcrd/gcs/v4 v4.0.0-20221022042529-0a0cc3b3bf92
|
github.com/decred/dcrd/gcs/v4 => github.com/decred/dcrd/gcs/v4 v4.0.0-20221022042529-0a0cc3b3bf92
|
||||||
github.com/decred/dcrd/rpc/jsonrpc/types/v4 => github.com/decred/dcrd/rpc/jsonrpc/types/v4 v4.0.0-20221022042529-0a0cc3b3bf92
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect
|
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect
|
||||||
github.com/dchest/siphash v1.2.3 // indirect
|
github.com/dchest/siphash v1.2.3 // indirect
|
||||||
github.com/decred/base58 v1.0.4 // indirect
|
github.com/decred/base58 v1.0.4 // indirect
|
||||||
github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect
|
github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect
|
||||||
github.com/decred/dcrd/crypto/ripemd160 v1.0.1 // indirect
|
github.com/decred/dcrd/crypto/ripemd160 v1.0.1 // indirect
|
||||||
github.com/decred/dcrd/database/v3 v3.0.0 // indirect
|
github.com/decred/dcrd/database/v3 v3.0.0 // indirect
|
||||||
github.com/decred/dcrd/dcrec v1.0.0 // indirect
|
github.com/decred/dcrd/dcrec v1.0.0 // indirect
|
||||||
github.com/decred/dcrd/dcrec/edwards/v2 v2.0.2 // indirect
|
github.com/decred/dcrd/dcrec/edwards/v2 v2.0.2 // indirect
|
||||||
github.com/decred/dcrd/dcrjson/v4 v4.0.0 // indirect
|
github.com/decred/dcrd/dcrjson/v4 v4.0.1 // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
github.com/go-playground/locales v0.14.0 // indirect
|
github.com/go-playground/locales v0.14.0 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.0 // indirect
|
github.com/go-playground/universal-translator v0.18.0 // indirect
|
||||||
|
|||||||
31
go.sum
31
go.sum
@ -1,5 +1,5 @@
|
|||||||
decred.org/dcrwallet/v3 v3.0.0-20230406144806-dc82294b976a h1:gssWH7pL1maf4qOGt2vkIcaXvz8GVAvR9sltU6W7JOQ=
|
decred.org/dcrwallet/v3 v3.0.0-20230519033517-96817277627d h1:Gov8SXYPsw6eyIBhzLP6+kjfDHdv6Q/ifIMt8fKtoYc=
|
||||||
decred.org/dcrwallet/v3 v3.0.0-20230406144806-dc82294b976a/go.mod h1:hXxn7XBmvCZQ4D0uMdMVCzAsV258WoDIl+/UrABEB4o=
|
decred.org/dcrwallet/v3 v3.0.0-20230519033517-96817277627d/go.mod h1:JGNFyWBroylUUf973eIHsdHN7PuJdCUVkIOMyWVv+y0=
|
||||||
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI=
|
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI=
|
||||||
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0=
|
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
@ -14,16 +14,17 @@ github.com/decred/base58 v1.0.4 h1:QJC6B0E0rXOPA8U/kw2rP+qiRJsUaE2Er+pYb3siUeA=
|
|||||||
github.com/decred/base58 v1.0.4/go.mod h1:jJswKPEdvpFpvf7dsDvFZyLT22xZ9lWqEByX38oGd9E=
|
github.com/decred/base58 v1.0.4/go.mod h1:jJswKPEdvpFpvf7dsDvFZyLT22xZ9lWqEByX38oGd9E=
|
||||||
github.com/decred/dcrd/blockchain/stake/v5 v5.0.0-20221022042529-0a0cc3b3bf92 h1:GTIg6r54cgNhUyZMNmTsmxM8OneEJ8t6QcWQCqu+al0=
|
github.com/decred/dcrd/blockchain/stake/v5 v5.0.0-20221022042529-0a0cc3b3bf92 h1:GTIg6r54cgNhUyZMNmTsmxM8OneEJ8t6QcWQCqu+al0=
|
||||||
github.com/decred/dcrd/blockchain/stake/v5 v5.0.0-20221022042529-0a0cc3b3bf92/go.mod h1:fij5xS9IBfJ5e/F5ytp/g/TWjrETEMXUFlE6C7KYOvA=
|
github.com/decred/dcrd/blockchain/stake/v5 v5.0.0-20221022042529-0a0cc3b3bf92/go.mod h1:fij5xS9IBfJ5e/F5ytp/g/TWjrETEMXUFlE6C7KYOvA=
|
||||||
github.com/decred/dcrd/blockchain/standalone/v2 v2.1.1-0.20230411184711-ce46220cf772 h1:LNE2EluIv1R4R933HJhS8rpgOqfpPAeAKkPq0Bf7cP8=
|
github.com/decred/dcrd/blockchain/standalone/v2 v2.1.1-0.20230430213532-f95870f9c6af h1:tmfTIkxkq7NdeLj3mrMk0uEdRyU1/oO1MWx8dfwOmm8=
|
||||||
github.com/decred/dcrd/blockchain/standalone/v2 v2.1.1-0.20230411184711-ce46220cf772/go.mod h1:PpM/jdMaD5MnBcSoFd+rJZE4q8tU0xPTTAyVzgegLQI=
|
github.com/decred/dcrd/blockchain/standalone/v2 v2.1.1-0.20230430213532-f95870f9c6af/go.mod h1:PpM/jdMaD5MnBcSoFd+rJZE4q8tU0xPTTAyVzgegLQI=
|
||||||
github.com/decred/dcrd/chaincfg/chainhash v1.0.2/go.mod h1:BpbrGgrPTr3YJYRN3Bm+D9NuaFd+zGyNeIKgrhCXK60=
|
github.com/decred/dcrd/chaincfg/chainhash v1.0.2/go.mod h1:BpbrGgrPTr3YJYRN3Bm+D9NuaFd+zGyNeIKgrhCXK60=
|
||||||
github.com/decred/dcrd/chaincfg/chainhash v1.0.3 h1:PF2czcYZGW3dz4i/35AUfVAgnqHl9TMNQt1ADTYGOoE=
|
|
||||||
github.com/decred/dcrd/chaincfg/chainhash v1.0.3/go.mod h1:BpbrGgrPTr3YJYRN3Bm+D9NuaFd+zGyNeIKgrhCXK60=
|
github.com/decred/dcrd/chaincfg/chainhash v1.0.3/go.mod h1:BpbrGgrPTr3YJYRN3Bm+D9NuaFd+zGyNeIKgrhCXK60=
|
||||||
github.com/decred/dcrd/chaincfg/v3 v3.1.0/go.mod h1:4XF9nlx2NeGD4xzw1+L0DGICZMl0a5rKV8nnuHLgk8o=
|
github.com/decred/dcrd/chaincfg/chainhash v1.0.4 h1:zRCv6tdncLfLTKYqu7hrXvs7hW+8FO/NvwoFvGsrluU=
|
||||||
github.com/decred/dcrd/chaincfg/v3 v3.1.1 h1:Ki8kq5IXGmjriiQyPCrCTF1aZSBiORb91/Sr5xW4otw=
|
github.com/decred/dcrd/chaincfg/chainhash v1.0.4/go.mod h1:hA86XxlBWwHivMvxzXTSD0ZCG/LoYsFdWnCekkTMCqY=
|
||||||
github.com/decred/dcrd/chaincfg/v3 v3.1.1/go.mod h1:4XF9nlx2NeGD4xzw1+L0DGICZMl0a5rKV8nnuHLgk8o=
|
github.com/decred/dcrd/chaincfg/v3 v3.1.2-0.20230412145739-9aa79ec168f6 h1:rKAzrv3gIEQaEQpnWU4IKxXrvx6QfXkdiOUKLvwEpQw=
|
||||||
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
|
github.com/decred/dcrd/chaincfg/v3 v3.1.2-0.20230412145739-9aa79ec168f6/go.mod h1:aEEti0kQSBFAlzHln4FB+3L30k9ZN1M7YDfYuK5VWtc=
|
||||||
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
|
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
|
||||||
|
github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y=
|
||||||
|
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
|
||||||
github.com/decred/dcrd/crypto/ripemd160 v1.0.1 h1:TjRL4LfftzTjXzaufov96iDAkbY2R3aTvH2YMYa1IOc=
|
github.com/decred/dcrd/crypto/ripemd160 v1.0.1 h1:TjRL4LfftzTjXzaufov96iDAkbY2R3aTvH2YMYa1IOc=
|
||||||
github.com/decred/dcrd/crypto/ripemd160 v1.0.1/go.mod h1:F0H8cjIuWTRoixr/LM3REB8obcWkmYx0gbxpQWR8RPg=
|
github.com/decred/dcrd/crypto/ripemd160 v1.0.1/go.mod h1:F0H8cjIuWTRoixr/LM3REB8obcWkmYx0gbxpQWR8RPg=
|
||||||
github.com/decred/dcrd/database/v3 v3.0.0 h1:7VVN2sWjKB934jvXzjnyGJFUVH9d8Qh5VULi+NMRjek=
|
github.com/decred/dcrd/database/v3 v3.0.0 h1:7VVN2sWjKB934jvXzjnyGJFUVH9d8Qh5VULi+NMRjek=
|
||||||
@ -33,16 +34,16 @@ github.com/decred/dcrd/dcrec v1.0.0/go.mod h1:HIaqbEJQ+PDzQcORxnqen5/V1FR3B4VpIf
|
|||||||
github.com/decred/dcrd/dcrec/edwards/v2 v2.0.2 h1:bX7rtGTMBDJxujZ29GNqtn7YCAdINjHKnA6J6tBBv6s=
|
github.com/decred/dcrd/dcrec/edwards/v2 v2.0.2 h1:bX7rtGTMBDJxujZ29GNqtn7YCAdINjHKnA6J6tBBv6s=
|
||||||
github.com/decred/dcrd/dcrec/edwards/v2 v2.0.2/go.mod h1:d0H8xGMWbiIQP7gN3v2rByWUcuZPm9YsgmnfoxgbINc=
|
github.com/decred/dcrd/dcrec/edwards/v2 v2.0.2/go.mod h1:d0H8xGMWbiIQP7gN3v2rByWUcuZPm9YsgmnfoxgbINc=
|
||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
|
||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4=
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
|
||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc=
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
|
||||||
github.com/decred/dcrd/dcrjson/v4 v4.0.0 h1:KsaFhHAYO+vLYz7Qmx/fs1gOY5ouTEz8hRuDm8jmJtU=
|
github.com/decred/dcrd/dcrjson/v4 v4.0.1 h1:vyQuB1miwGqbCVNm8P6br3V65WQ6wyrh0LycMkvaBBg=
|
||||||
github.com/decred/dcrd/dcrjson/v4 v4.0.0/go.mod h1:DMnSpU8lsVh+Nt5kHl63tkrjBDA7UIs4+ov8Kwwgvjs=
|
github.com/decred/dcrd/dcrjson/v4 v4.0.1/go.mod h1:2qVikafVF9/X3PngQVmqkbUbyAl32uik0k/kydgtqMc=
|
||||||
github.com/decred/dcrd/dcrutil/v4 v4.0.0 h1:AY00fWy/ETrMHN0DNV3XUbH1aip2RG1AoTy5dp0+sJE=
|
github.com/decred/dcrd/dcrutil/v4 v4.0.0 h1:AY00fWy/ETrMHN0DNV3XUbH1aip2RG1AoTy5dp0+sJE=
|
||||||
github.com/decred/dcrd/dcrutil/v4 v4.0.0/go.mod h1:QQpX5WVH3/ixVtiW15xZMe+neugXX3l2bsrYgq6nz4M=
|
github.com/decred/dcrd/dcrutil/v4 v4.0.0/go.mod h1:QQpX5WVH3/ixVtiW15xZMe+neugXX3l2bsrYgq6nz4M=
|
||||||
github.com/decred/dcrd/hdkeychain/v3 v3.1.0 h1:NlUjzPMzexbk1PyJu6vrQaiilep5WsEPB0KdhLYrEcE=
|
github.com/decred/dcrd/hdkeychain/v3 v3.1.0 h1:NlUjzPMzexbk1PyJu6vrQaiilep5WsEPB0KdhLYrEcE=
|
||||||
github.com/decred/dcrd/hdkeychain/v3 v3.1.0/go.mod h1:rDCdqwGkcTfEyRheG1g8Wc38appT2C9+D1XTlLy21lo=
|
github.com/decred/dcrd/hdkeychain/v3 v3.1.0/go.mod h1:rDCdqwGkcTfEyRheG1g8Wc38appT2C9+D1XTlLy21lo=
|
||||||
github.com/decred/dcrd/rpc/jsonrpc/types/v4 v4.0.0-20221022042529-0a0cc3b3bf92 h1:R32+XN8qM6kB7qUHfbkskPAldTPXWIhxMk+e3UBNwyY=
|
github.com/decred/dcrd/rpc/jsonrpc/types/v4 v4.0.0 h1:4YUKsWKrKlkhVMYGRB6G0XI6QfwUnwEH18eoEbM1/+M=
|
||||||
github.com/decred/dcrd/rpc/jsonrpc/types/v4 v4.0.0-20221022042529-0a0cc3b3bf92/go.mod h1:x1zG8D4HRmUCDtAioDe/QQ/PazzFXcYtSrbasX3FHoE=
|
github.com/decred/dcrd/rpc/jsonrpc/types/v4 v4.0.0/go.mod h1:dDHO7ivrPAhZjFD3LoOJN/kdq5gi0sxie6zCsWHAiUo=
|
||||||
github.com/decred/dcrd/txscript/v4 v4.0.0 h1:BwaBUCMCmg58MCYoBhxVjL8ZZKUIfoJuxu/djmh8h58=
|
github.com/decred/dcrd/txscript/v4 v4.0.0 h1:BwaBUCMCmg58MCYoBhxVjL8ZZKUIfoJuxu/djmh8h58=
|
||||||
github.com/decred/dcrd/txscript/v4 v4.0.0/go.mod h1:OJtxNc5RqwQyfrRnG2gG8uMeNPo8IAJp+TD1UKXkqk8=
|
github.com/decred/dcrd/txscript/v4 v4.0.0/go.mod h1:OJtxNc5RqwQyfrRnG2gG8uMeNPo8IAJp+TD1UKXkqk8=
|
||||||
github.com/decred/dcrd/wire v1.5.0 h1:3SgcEzSjqAMQvOugP0a8iX7yQSpiVT1yNi9bc4iOXVg=
|
github.com/decred/dcrd/wire v1.5.0 h1:3SgcEzSjqAMQvOugP0a8iX7yQSpiVT1yNi9bc4iOXVg=
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user