From ce32862ef975be44d1370b19382bc543dbc814bb Mon Sep 17 00:00:00 2001 From: JoeGruff Date: Wed, 7 Jun 2023 20:32:03 +0900 Subject: [PATCH] client/dcr: Add staking methods. --- client/asset/dcr/dcr.go | 162 +++++++++++++++++ client/asset/dcr/dcr_test.go | 22 +++ client/asset/dcr/rpcwallet.go | 147 +++++++++++++++ client/asset/dcr/simnet_test.go | 121 ++++++++++++- client/asset/dcr/spv.go | 295 ++++++++++++++++++++++++++++++- client/asset/dcr/spv_test.go | 143 ++++++++++++++- client/asset/dcr/wallet.go | 16 +- client/asset/interface.go | 85 +++++++++ client/cmd/dexc-desktop/go.mod | 18 +- client/cmd/dexc-desktop/go.sum | 41 +++-- dex/testing/dcr/create-vspd.sh | 55 ++++++ dex/testing/dcr/create-wallet.sh | 13 +- dex/testing/dcr/harness.sh | 56 ++++-- dex/testing/loadbot/go.mod | 16 +- dex/testing/loadbot/go.sum | 41 +++-- go.mod | 16 +- go.sum | 41 +++-- 17 files changed, 1195 insertions(+), 93 deletions(-) create mode 100755 dex/testing/dcr/create-vspd.sh diff --git a/client/asset/dcr/dcr.go b/client/asset/dcr/dcr.go index 7bc1d446a0..a9939d7af9 100644 --- a/client/asset/dcr/dcr.go +++ b/client/asset/dcr/dcr.go @@ -7,6 +7,7 @@ import ( "bytes" "context" "crypto/sha256" + "encoding/base64" "encoding/binary" "encoding/hex" "encoding/json" @@ -15,6 +16,8 @@ import ( "io" "math" "net/http" + neturl "net/url" + "os" "path/filepath" "sort" "strconv" @@ -44,6 +47,7 @@ import ( "github.com/decred/dcrd/txscript/v4/stdaddr" "github.com/decred/dcrd/txscript/v4/stdscript" "github.com/decred/dcrd/wire" + vspdjson "github.com/decred/vspd/types" ) const ( @@ -94,6 +98,8 @@ const ( // monitored until this number of confirms is reached. Two to make sure // the block containing the redeem is stakeholder-approved requiredRedeemConfirms = 2 + + vspFileName = "vsp.json" ) var ( @@ -555,6 +561,14 @@ type mempoolRedeem struct { firstSeen time.Time } +// vsp holds info needed for purchasing tickes from a vsp. pub is from the vsp +// and is used for verifying communications. +type vsp struct { + Url string `json:"url"` + FeePercentage float64 `json:"feepercent"` + PubKey string `json:"pubkey"` +} + // ExchangeWallet is a wallet backend for Decred. The backend is how the DEX // client app communicates with the Decred blockchain and wallet. ExchangeWallet // satisfies the dex.Wallet interface. @@ -586,6 +600,8 @@ type ExchangeWallet struct { tipChange func(error) lastPeerCount uint32 peersChange func(uint32, error) + dir string + walletType string oracleFeesMtx sync.Mutex oracleFees map[uint64]feeStamped // conf target => fee rate @@ -607,6 +623,11 @@ type ExchangeWallet struct { // TODO: Consider persisting mempool redeems on file. mempoolRedeemsMtx sync.RWMutex mempoolRedeems map[[32]byte]*mempoolRedeem // keyed by secret hash + + vspV atomic.Value // *vsp + vspInfo func(url string) (*vspdjson.VspInfoResponse, error) + + connected atomic.Bool } func (dcr *ExchangeWallet) config() *exchangeWalletConfig { @@ -684,6 +705,7 @@ var _ asset.LiveReconfigurer = (*ExchangeWallet)(nil) var _ asset.TxFeeEstimator = (*ExchangeWallet)(nil) var _ asset.Bonder = (*ExchangeWallet)(nil) var _ asset.Authenticator = (*ExchangeWallet)(nil) +var _ asset.TicketBuyer = (*ExchangeWallet)(nil) type block struct { height int64 @@ -825,6 +847,11 @@ func unconnectedWallet(cfg *asset.WalletConfig, dcrCfg *walletConfig, chainParam return nil, err } + dir := filepath.Join(cfg.DataDir, chainParams.Name) + if err := os.MkdirAll(dir, 0755); err != nil { + return nil, fmt.Errorf("unable to create wallet dir: %v", err) + } + w := &ExchangeWallet{ log: logger, chainParams: chainParams, @@ -836,6 +863,22 @@ func unconnectedWallet(cfg *asset.WalletConfig, dcrCfg *walletConfig, chainParam externalTxCache: make(map[chainhash.Hash]*externalTx), oracleFees: make(map[uint64]feeStamped), mempoolRedeems: make(map[[32]byte]*mempoolRedeem), + dir: dir, + vspInfo: vspInfo, + walletType: cfg.Type, + } + + if b, err := os.ReadFile(filepath.Join(dir, vspFileName)); err != nil { + if !errors.Is(err, os.ErrNotExist) { + return nil, fmt.Errorf("unable to read vsp file: %v", err) + } + } else { + var v vsp + err = json.Unmarshal(b, &v) + if err != nil { + return nil, fmt.Errorf("unable to unmarshal vsp file: %v", err) + } + w.vspV.Store(&v) } w.cfgV.Store(walletCfg) @@ -900,6 +943,8 @@ func (dcr *ExchangeWallet) Connect(ctx context.Context) (*sync.WaitGroup, error) defer func() { if !success { dcr.wallet.Disconnect() + } else { + dcr.connected.Store(true) } }() @@ -4546,6 +4591,123 @@ func (dcr *ExchangeWallet) EstimateSendTxFee(address string, sendAmount, feeRate return finalFee, isValidAddress, nil } +func (dcr *ExchangeWallet) isInternal() bool { + return dcr.walletType == walletTypeSPV +} + +func (dcr *ExchangeWallet) StakeStatus() (*asset.TicketStakingStatus, error) { + if !dcr.connected.Load() { + return nil, errors.New("not connected, login first") + } + sdiff, err := dcr.wallet.StakeDiff(dcr.ctx) + if err != nil { + return nil, err + } + isRPC := !dcr.isInternal() + var vspURL string + if !isRPC { + if v := dcr.vspV.Load(); v != nil { + vspURL = v.(*vsp).Url + } + } + tickets, err := dcr.wallet.Tickets(dcr.ctx) + if err != nil { + return nil, fmt.Errorf("error retrieving tickets: %w", err) + } + voteChoices, tSpendPolicy, treasuryPolicy, err := dcr.wallet.VotingPreferences(dcr.ctx) + if err != nil { + return nil, fmt.Errorf("error retrieving stances: %w", err) + } + return &asset.TicketStakingStatus{ + TicketPrice: uint64(sdiff), + VSP: vspURL, + IsRPC: isRPC, + Tickets: tickets, + Stances: asset.Stances{ + VoteChoices: voteChoices, + TSpendPolicy: tSpendPolicy, + TreasuryPolicy: treasuryPolicy, + }, + }, nil +} + +func vspInfo(url string) (*vspdjson.VspInfoResponse, error) { + suffix := "/api/v3/vspinfo" + path, err := neturl.JoinPath(url, suffix) + if err != nil { + return nil, err + } + resp, err := http.Get(path) + if err != nil { + return nil, fmt.Errorf("http get error: %v", err) + } + b, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + var info vspdjson.VspInfoResponse + err = json.Unmarshal(b, &info) + if err != nil { + return nil, err + } + return &info, nil +} + +// SetVSP sets the VSP provider. Ability to set should be checked with CanSetVSP +// first. Part of the asset.TicketBuyer interface. +func (dcr *ExchangeWallet) SetVSP(url string) error { + if !dcr.isInternal() { + return errors.New("cannot set vsp for external wallet") + } + info, err := dcr.vspInfo(url) + if err != nil { + return err + } + v := vsp{ + Url: url, + PubKey: base64.StdEncoding.EncodeToString(info.PubKey), + FeePercentage: info.FeePercentage, + } + b, err := json.Marshal(&v) + if err != nil { + return err + } + if err := os.WriteFile(filepath.Join(dcr.dir, vspFileName), b, 0666); err != nil { + return err + } + dcr.vspV.Store(&v) + return nil +} + +// PurchaseTickets purchases n amout of tickets. Part of the asset.TicketBuyer +// interface. +func (dcr *ExchangeWallet) PurchaseTickets(n int) ([]string, error) { + if n < 1 { + return nil, nil + } + if !dcr.connected.Load() { + return nil, errors.New("not connected, login first") + } + if !dcr.isInternal() { + return dcr.wallet.PurchaseTickets(dcr.ctx, n, "", "") + } + v := dcr.vspV.Load() + if v == nil { + return nil, errors.New("no vsp set") + } + return dcr.wallet.PurchaseTickets(dcr.ctx, n, v.(*vsp).Url, v.(*vsp).PubKey) +} + +// SetVotingPreferences sets default voting settings for all active tickets and +// future tickets. Nil maps can be provided for no change. Part of the +// asset.TicketBuyer interface. +func (dcr *ExchangeWallet) SetVotingPreferences(choices map[string]string, tspendPolicy map[string]string, treasuryPolicy map[string]string) error { + if !dcr.connected.Load() { + return errors.New("not connected, login first") + } + return dcr.wallet.SetVotingPreferences(dcr.ctx, choices, tspendPolicy, treasuryPolicy, dcr.vspInfo) +} + func (dcr *ExchangeWallet) broadcastTx(signedTx *wire.MsgTx) (*chainhash.Hash, error) { txHash, err := dcr.wallet.SendRawTransaction(dcr.ctx, signedTx, false) if err != nil { diff --git a/client/asset/dcr/dcr_test.go b/client/asset/dcr/dcr_test.go index 8f8ff6e492..dcb1c25ca5 100644 --- a/client/asset/dcr/dcr_test.go +++ b/client/asset/dcr/dcr_test.go @@ -511,6 +511,28 @@ func (c *tRPCClient) Disconnected() bool { return c.disconnected } +func (c *tRPCClient) GetStakeInfo(ctx context.Context) (*walletjson.GetStakeInfoResult, error) { + return nil, nil +} + +func (c *tRPCClient) PurchaseTicket(ctx context.Context, fromAccount string, spendLimit dcrutil.Amount, minConf *int, + ticketAddress stdaddr.Address, numTickets *int, poolAddress stdaddr.Address, poolFees *dcrutil.Amount, + expiry *int, ticketChange *bool, ticketFee *dcrutil.Amount) ([]*chainhash.Hash, error) { + return nil, nil +} + +func (c *tRPCClient) GetTickets(ctx context.Context, includeImmature bool) ([]*chainhash.Hash, error) { + return nil, nil +} + +func (c *tRPCClient) GetVoteChoices(ctx context.Context) (*walletjson.GetVoteChoicesResult, error) { + return nil, nil +} + +func (c *tRPCClient) SetVoteChoice(ctx context.Context, agendaID, choiceID string) error { + return nil +} + func (c *tRPCClient) RawRequest(_ context.Context, method string, params []json.RawMessage) (json.RawMessage, error) { if rr, found := c.rawRes[method]; found { return rr, c.rawErr[method] // err probably should be nil, but respect the config diff --git a/client/asset/dcr/rpcwallet.go b/client/asset/dcr/rpcwallet.go index eea7e63eed..8f05dfb6a0 100644 --- a/client/asset/dcr/rpcwallet.go +++ b/client/asset/dcr/rpcwallet.go @@ -31,6 +31,7 @@ import ( "github.com/decred/dcrd/rpcclient/v8" "github.com/decred/dcrd/txscript/v4/stdaddr" "github.com/decred/dcrd/wire" + vspdjson "github.com/decred/vspd/types" ) var ( @@ -138,6 +139,13 @@ type rpcClient interface { RawRequest(ctx context.Context, method string, params []json.RawMessage) (json.RawMessage, error) WalletInfo(ctx context.Context) (*walletjson.WalletInfoResult, error) ValidateAddress(ctx context.Context, address stdaddr.Address) (*walletjson.ValidateAddressWalletResult, error) + GetStakeInfo(ctx context.Context) (*walletjson.GetStakeInfoResult, error) + PurchaseTicket(ctx context.Context, fromAccount string, spendLimit dcrutil.Amount, minConf *int, + ticketAddress stdaddr.Address, numTickets *int, poolAddress stdaddr.Address, poolFees *dcrutil.Amount, + expiry *int, ticketChange *bool, ticketFee *dcrutil.Amount) ([]*chainhash.Hash, error) + GetTickets(ctx context.Context, includeImmature bool) ([]*chainhash.Hash, error) + GetVoteChoices(ctx context.Context) (*walletjson.GetVoteChoicesResult, error) + SetVoteChoice(ctx context.Context, agendaID, choiceID string) error } // newRPCWallet creates an rpcClient and uses it to construct a new instance @@ -864,6 +872,145 @@ func (w *rpcWallet) AddressPrivKey(ctx context.Context, address stdaddr.Address) return &priv, nil } +// StakeDiff returns the current stake difficulty. +func (w *rpcWallet) StakeDiff(ctx context.Context) (dcrutil.Amount, error) { + si, err := w.rpcClient.GetStakeInfo(ctx) + if err != nil { + return 0, err + } + amt, err := dcrutil.NewAmount(si.Difficulty) + if err != nil { + return 0, err + } + return amt, nil +} + +// PurchaseTickets purchases n amout of tickets. Returns the purchased ticket +// hashes if successful. +func (w *rpcWallet) PurchaseTickets(ctx context.Context, n int, _, _ string) ([]string, error) { + hashes, err := w.rpcClient.PurchaseTicket(ctx, "default", 0 /*spendLimit dcrutil.Amount*/, nil, /*minConf *int*/ + nil /*ticketAddress stdaddr.Address*/, &n, nil /*poolAddress stdaddr.Address*/, nil, /*poolFees *dcrutil.Amount*/ + nil /*expiry *int*/, nil /*ticketChange *bool*/, nil /*ticketFee *dcrutil.Amount*/) + hashStrs := make([]string, len(hashes)) + for i := range hashes { + hashStrs[i] = hashes[i].String() + } + return hashStrs, err +} + +// Tickets returns active tickets. +func (w *rpcWallet) Tickets(ctx context.Context) ([]*asset.Ticket, error) { + const includeImmature = true + // GetTickets only works for clients with a dcrd backend. + hashes, err := w.rpcClient.GetTickets(ctx, includeImmature) + if err != nil { + return nil, err + } + tickets := make([]*asset.Ticket, 0, len(hashes)) + for _, h := range hashes { + tx, err := w.client().GetTransaction(ctx, h) + if err != nil { + w.log.Errorf("GetTransaction error for ticket %s: %v", h, err) + continue + } + msgTx, err := msgTxFromHex(tx.Hex) + if err != nil { + w.log.Errorf("Error decoding ticket %s tx hex: %v", h, err) + continue + } + + if len(msgTx.TxOut) < 1 { + w.log.Errorf("No outputs for ticket %s", h) + continue + } + + feeAmt, _ := dcrutil.NewAmount(tx.Fee) + + tickets = append(tickets, &asset.Ticket{ + Ticket: asset.TicketTransaction{ + Hash: h.String(), + TicketPrice: uint64(msgTx.TxOut[0].Value), + Fees: uint64(feeAmt), + Stamp: uint64(tx.Time), + BlockHeight: tx.BlockIndex, + }, + // The walletjson.GetTransactionResult returned from GetTransaction + // actually has a TicketStatus string field, but it doesn't appear + // to ever be populated by dcrwallet. + // Status: somehowConvertFromString(tx.TicketStatus), + + // Not sure how to get the spender through RPC. + // Spender: ?, + }) + } + + return tickets, nil +} + +// VotingPreferences returns current wallet voting preferences. +func (w *rpcWallet) VotingPreferences(ctx context.Context) ([]*walletjson.VoteChoice, []*walletjson.TSpendPolicyResult, []*walletjson.TreasuryPolicyResult, error) { + // Get consensus vote choices. + choices, err := w.rpcClient.GetVoteChoices(ctx) + if err != nil { + return nil, nil, nil, fmt.Errorf("unable to get vote choices: %v", err) + } + voteChoices := make([]*walletjson.VoteChoice, len(choices.Choices)) + for i, v := range choices.Choices { + vc := v + voteChoices[i] = &vc + } + // Get tspend voting policy. + const tSpendPolicyMethod = "tspendpolicy" + var tSpendRes []walletjson.TSpendPolicyResult + err = w.rpcClientRawRequest(ctx, tSpendPolicyMethod, nil, &tSpendRes) + if err != nil { + return nil, nil, nil, fmt.Errorf("unable to get treasury spend policy: %v", err) + } + tSpendPolicy := make([]*walletjson.TSpendPolicyResult, len(tSpendRes)) + for i, v := range tSpendRes { + tp := v + tSpendPolicy[i] = &tp + } + // Get treasury voting policy. + const treasuryPolicyMethod = "treasurypolicy" + var treasuryRes []walletjson.TreasuryPolicyResult + err = w.rpcClientRawRequest(ctx, treasuryPolicyMethod, nil, &treasuryRes) + if err != nil { + return nil, nil, nil, fmt.Errorf("unable to get treasury policy: %v", err) + } + treasuryPolicy := make([]*walletjson.TreasuryPolicyResult, len(treasuryRes)) + for i, v := range treasuryRes { + tp := v + treasuryPolicy[i] = &tp + } + return voteChoices, tSpendPolicy, treasuryPolicy, nil +} + +// SetVotingPreferences sets voting preferences. +// +// NOTE: Will fail for communication problems with VSPs unlike internal wallets. +func (w *rpcWallet) SetVotingPreferences(ctx context.Context, choices, tSpendPolicy, + treasuryPolicy map[string]string, _ func(url string) (*vspdjson.VspInfoResponse, error)) error { + for k, v := range choices { + if err := w.rpcClient.SetVoteChoice(ctx, k, v); err != nil { + return fmt.Errorf("unable to set vote choice: %v", err) + } + } + const setTSpendPolicyMethod = "settspendpolicy" + for k, v := range tSpendPolicy { + if err := w.rpcClientRawRequest(ctx, setTSpendPolicyMethod, anylist{k, v}, nil); err != nil { + return fmt.Errorf("unable to set tspend policy: %v", err) + } + } + const setTreasuryPolicyMethod = "settreasurypolicy" + for k, v := range treasuryPolicy { + if err := w.rpcClientRawRequest(ctx, setTreasuryPolicyMethod, anylist{k, v}, nil); err != nil { + return fmt.Errorf("unable to set treasury policy: %v", err) + } + } + return nil +} + // anylist is a list of RPC parameters to be converted to []json.RawMessage and // sent via nodeRawRequest. type anylist []interface{} diff --git a/client/asset/dcr/simnet_test.go b/client/asset/dcr/simnet_test.go index 074e6d30a8..d286012043 100644 --- a/client/asset/dcr/simnet_test.go +++ b/client/asset/dcr/simnet_test.go @@ -16,6 +16,7 @@ import ( "bytes" "context" "crypto/sha256" + "encoding/hex" "math/rand" "os" "os/exec" @@ -28,6 +29,8 @@ import ( "decred.org/dcrdex/dex" "decred.org/dcrdex/dex/config" dexdcr "decred.org/dcrdex/dex/networks/dcr" + "github.com/davecgh/go-spew/spew" + "github.com/decred/dcrd/chaincfg/v3" "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/decred/dcrd/dcrutil/v4" ) @@ -35,6 +38,8 @@ import ( const ( alphaAddress = "SsWKp7wtdTZYabYFYSc9cnxhwFEjA5g4pFc" betaAddress = "Ssge52jCzbixgFC736RSTrwAnvH3a4hcPRX" + gammaSeed = "1285a47d6a59f9c548b2a72c2c34a2de97967bede3844090102bbba76707fe9d" + vspAddr = "http://127.0.0.1:19591" ) var ( @@ -58,18 +63,22 @@ func mineAlpha() error { return exec.Command("tmux", "send-keys", "-t", "dcr-harness:0", "./mine-alpha 1", "C-m").Run() } -func tBackend(t *testing.T, name string, blkFunc func(string, error)) (*ExchangeWallet, *dex.ConnectionMaster) { +func tBackend(t *testing.T, name string, isInternal bool, blkFunc func(string, error)) (*ExchangeWallet, *dex.ConnectionMaster) { t.Helper() user, err := user.Current() if err != nil { t.Fatalf("error getting current user: %v", err) } - cfgPath := filepath.Join(user.HomeDir, "dextest", "dcr", name, name+".conf") - settings, err := config.Parse(cfgPath) - if err != nil { - t.Fatalf("error reading config options: %v", err) + settings := make(map[string]string) + if !isInternal { + cfgPath := filepath.Join(user.HomeDir, "dextest", "dcr", name, name+".conf") + var err error + settings, err = config.Parse(cfgPath) + if err != nil { + t.Fatalf("error reading config options: %v", err) + } + settings["account"] = "default" } - settings["account"] = "default" walletCfg := &asset.WalletConfig{ Settings: settings, TipChange: func(err error) { @@ -79,6 +88,16 @@ func tBackend(t *testing.T, name string, blkFunc func(string, error)) (*Exchange t.Logf("peer count = %d, err = %v", num, err) }, } + if isInternal { + seed, err := hex.DecodeString(gammaSeed) + if err != nil { + t.Fatal(err) + } + dataDir := t.TempDir() + createSPVWallet(walletPassword, seed, dataDir, 0, 0, chaincfg.SimNetParams()) + walletCfg.Type = walletTypeSPV + walletCfg.DataDir = dataDir + } var backend asset.Wallet backend, err = NewWallet(walletCfg, tLogger, dex.Simnet) if err != nil { @@ -89,6 +108,23 @@ func tBackend(t *testing.T, name string, blkFunc func(string, error)) (*Exchange if err != nil { t.Fatalf("error connecting backend: %v", err) } + if isInternal { + i := 0 + for { + synced, _, err := backend.SyncStatus() + if err != nil { + t.Fatal(err) + } + if synced { + break + } + if i == 5 { + t.Fatal("spv wallet not synced after 5 seconds") + } + i++ + time.Sleep(time.Second) + } + } return backend.(*ExchangeWallet), cm } @@ -103,17 +139,27 @@ func newTestRig(t *testing.T, blkFunc func(string, error)) *testRig { backends: make(map[string]*ExchangeWallet), connectionMasters: make(map[string]*dex.ConnectionMaster, 3), } - rig.backends["alpha"], rig.connectionMasters["alpha"] = tBackend(t, "alpha", blkFunc) - rig.backends["beta"], rig.connectionMasters["beta"] = tBackend(t, "beta", blkFunc) + rig.backends["alpha"], rig.connectionMasters["alpha"] = tBackend(t, "alpha", false, blkFunc) + rig.backends["beta"], rig.connectionMasters["beta"] = tBackend(t, "beta", false, blkFunc) + rig.backends["gamma"], rig.connectionMasters["gamma"] = tBackend(t, "gamma", true, blkFunc) return rig } +// alpha is an external wallet connected to dcrd. func (rig *testRig) alpha() *ExchangeWallet { return rig.backends["alpha"] } + +// beta is an external spv wallet. func (rig *testRig) beta() *ExchangeWallet { return rig.backends["beta"] } + +// gamma is an internal spv wallet. +func (rig *testRig) gamma() *ExchangeWallet { + return rig.backends["gamma"] +} + func (rig *testRig) close(t *testing.T) { t.Helper() for name, cm := range rig.connectionMasters { @@ -531,3 +577,62 @@ func runTest(t *testing.T, splitTx bool) { t.Fatalf("error locking wallet: %v", err) } } + +func TestTickets(t *testing.T) { + rig := newTestRig(t, func(name string, err error) { + tLogger.Infof("%s has reported a new block, error = %v", name, err) + }) + defer rig.close(t) + tLogger.Info("Testing ticket methods with the alpha rig.") + testTickets(t, false, rig.alpha()) + // TODO: beta wallet is spv but has no vps in its config. Add it and + // test here. + tLogger.Info("Testing ticket methods with the gamma rig.") + testTickets(t, true, rig.gamma()) +} + +func testTickets(t *testing.T, isInternal bool, ew *ExchangeWallet) { + + // Test StakeStatus. + ss, err := ew.StakeStatus() + if err != nil { + t.Fatalf("unable to get stake status: %v", err) + } + tLogger.Info("The following are stake status before setting vsp or purchasing tickets.") + spew.Dump(ss) + + // Test SetVSP. + err = ew.SetVSP(vspAddr) + if isInternal { + if err != nil { + t.Fatalf("unexected error setting spv for internal wallet: %v", err) + } + } else { + if err == nil { + t.Fatal("exected error setting spv for external wallet") + } + } + + // Test PurchaseTickets. + if err := ew.Unlock(walletPassword); err != nil { + t.Fatalf("unable to unlock wallet: %v", err) + } + tickets, err := ew.PurchaseTickets(3) + if err != nil { + t.Fatalf("error purchasing tickets: %v", err) + } + tLogger.Infof("Purchased the following tickets: %v", tickets) + + // Test SetVotingPreferences. + if err := ew.SetVotingPreferences(nil, nil, nil); err != nil { + t.Fatalf("error setting voting preferences: %v", err) + } + + // Test StakeStatus again. + ss, err = ew.StakeStatus() + if err != nil { + t.Fatalf("unable to get stake status: %v", err) + } + tLogger.Info("The following are stake status after setting vsp and purchasing tickets.") + spew.Dump(ss) +} diff --git a/client/asset/dcr/spv.go b/client/asset/dcr/spv.go index 3eb01fa0c1..3dbcfb5f7a 100644 --- a/client/asset/dcr/spv.go +++ b/client/asset/dcr/spv.go @@ -5,6 +5,7 @@ package dcr import ( "context" + "encoding/base64" "encoding/hex" "errors" "fmt" @@ -40,6 +41,8 @@ import ( "github.com/decred/dcrd/txscript/v4/stdaddr" "github.com/decred/dcrd/wire" "github.com/decred/slog" + vspclient "github.com/decred/vspd/client/v2" + vspdjson "github.com/decred/vspd/types" "github.com/jrick/logrotate/rotator" ) @@ -63,14 +66,11 @@ type dcrWallet interface { AccountBalance(ctx context.Context, account uint32, confirms int32) (wallet.Balances, error) LockedOutpoints(ctx context.Context, accountName string) ([]chainjson.TransactionInput, error) ListUnspent(ctx context.Context, minconf, maxconf int32, addresses map[string]struct{}, accountName string) ([]*walletjson.ListUnspentResult, error) - UnlockOutpoint(txHash *chainhash.Hash, index uint32) LockOutpoint(txHash *chainhash.Hash, index uint32) ListTransactionDetails(ctx context.Context, txHash *chainhash.Hash) ([]walletjson.ListTransactionsResult, error) MainChainTip(ctx context.Context) (hash chainhash.Hash, height int32) NewExternalAddress(ctx context.Context, account uint32, callOpts ...wallet.NextAddressCallOption) (stdaddr.Address, error) NewInternalAddress(ctx context.Context, account uint32, callOpts ...wallet.NextAddressCallOption) (stdaddr.Address, error) - 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) PublishTransaction(ctx context.Context, tx *wire.MsgTx, n wallet.NetworkBackend) (*chainhash.Hash, error) BlockHeader(ctx context.Context, blockHash *chainhash.Hash) (*wire.BlockHeader, error) BlockInMainChain(ctx context.Context, hash *chainhash.Hash) (haveBlock, invalidated bool, err error) @@ -82,6 +82,18 @@ type dcrWallet interface { LoadPrivateKey(ctx context.Context, addr stdaddr.Address) (key *secp256k1.PrivateKey, zero func(), err error) TxDetails(ctx context.Context, txHash *chainhash.Hash) (*udb.TxDetails, error) GetTransactionsByHashes(ctx context.Context, txHashes []*chainhash.Hash) (txs []*wire.MsgTx, notFound []*wire.InvVect, err error) + StakeInfo(ctx context.Context) (*wallet.StakeInfoData, error) + PurchaseTickets(ctx context.Context, n wallet.NetworkBackend, req *wallet.PurchaseTicketsRequest) (*wallet.PurchaseTicketsResponse, error) + ForUnspentUnexpiredTickets(ctx context.Context, f func(hash *chainhash.Hash) error) error + GetTickets(ctx context.Context, f func([]*wallet.TicketSummary, *wire.BlockHeader) (bool, error), startBlock, endBlock *wallet.BlockIdentifier) error + TreasuryKeyPolicies() []wallet.TreasuryKeyPolicy + GetAllTSpends(ctx context.Context) []*wire.MsgTx + TSpendPolicy(tspendHash, ticketHash *chainhash.Hash) stake.TreasuryVoteT + VSPHostForTicket(ctx context.Context, ticketHash *chainhash.Hash) (string, error) + SetAgendaChoices(ctx context.Context, ticketHash *chainhash.Hash, choices ...wallet.AgendaChoice) (voteBits uint16, err error) + SetTSpendPolicy(ctx context.Context, tspendHash *chainhash.Hash, policy stake.TreasuryVoteT, ticketHash *chainhash.Hash) error + SetTreasuryKeyPolicy(ctx context.Context, pikey []byte, policy stake.TreasuryVoteT, ticketHash *chainhash.Hash) error + vspclient.Wallet // TODO: Rescan and DiscoverActiveAddresses can be used for a Rescanner. } @@ -823,6 +835,283 @@ func (w *spvWallet) AddressPrivKey(ctx context.Context, addr stdaddr.Address) (* return privKey, err } +// StakeDiff returns the current stake difficulty. +func (w *spvWallet) StakeDiff(ctx context.Context) (dcrutil.Amount, error) { + si, err := w.dcrWallet.StakeInfo(ctx) + if err != nil { + return 0, err + } + return si.Sdiff, nil +} + +func newVSPClient(w vspclient.Wallet, vspHost, vspPubKey string, log dex.Logger) (*vspclient.AutoClient, error) { + return vspclient.New(vspclient.Config{ + URL: vspHost, + PubKey: vspPubKey, + Dialer: new(net.Dialer).DialContext, + Wallet: w, + Policy: &vspclient.Policy{ + MaxFee: 0.2e8, + FeeAcct: 0, + ChangeAcct: 0, + }, + }, log) +} + +// PurchaseTickets purchases n tickets, tells the provided vspd to monitor the +// ticket, and pays the vsp fee. +func (w *spvWallet) PurchaseTickets(ctx context.Context, n int, vspHost, vspPubKey string) ([]string, error) { + vspClient, err := newVSPClient(w.dcrWallet, vspHost, vspPubKey, w.log.SubLogger("VSP")) + if err != nil { + return nil, err + } + request := &wallet.PurchaseTicketsRequest{ + Count: n, + MinConf: 1, + VSPFeePaymentProcess: vspClient.Process, + VSPFeeProcess: vspClient.FeePercentage, + // TODO: CSPP/mixing + } + res, err := w.dcrWallet.PurchaseTickets(ctx, w.spv, request) + if err != nil { + return nil, err + } + hashes := res.TicketHashes + hashStrs := make([]string, len(hashes)) + for i := range hashes { + hashStrs[i] = hashes[i].String() + } + return hashStrs, err +} + +// Tickets returns current active tickets. +func (w *spvWallet) Tickets(ctx context.Context) ([]*asset.Ticket, error) { + return w.ticketsInRange(ctx, 0, 0) +} + +func (w *spvWallet) ticketsInRange(ctx context.Context, fromHeight, toHeight int32) ([]*asset.Ticket, error) { + params := w.ChainParams() + + tickets := make([]*asset.Ticket, 0) + + processTicket := func(ticketSummaries []*wallet.TicketSummary, hdr *wire.BlockHeader) (bool, error) { + for _, ticketSummary := range ticketSummaries { + spender := "" + if ticketSummary.Spender != nil { + spender = ticketSummary.Spender.Hash.String() + } + + if ticketSummary.Ticket == nil || len(ticketSummary.Ticket.MyOutputs) < 1 { + w.log.Errorf("No zeroth output") + } + + var blockHeight int64 = -1 + if hdr != nil { + blockHeight = int64(hdr.Height) + } + + tickets = append(tickets, &asset.Ticket{ + Ticket: asset.TicketTransaction{ + Hash: ticketSummary.Ticket.Hash.String(), + TicketPrice: uint64(ticketSummary.Ticket.MyOutputs[0].Amount), + Fees: uint64(ticketSummary.Ticket.Fee), + Stamp: uint64(ticketSummary.Ticket.Timestamp), + BlockHeight: blockHeight, + }, + Status: asset.TicketStatus(ticketSummary.Status), + Spender: spender, + }) + } + + return false, nil + } + + const requiredConfs = 6 + 2 + endBlockNum := toHeight + if endBlockNum == 0 { + _, endBlockNum = w.MainChainTip(ctx) + } + startBlockNum := fromHeight + if startBlockNum == 0 { + startBlockNum = endBlockNum - + int32(params.TicketExpiry+uint32(params.TicketMaturity)-requiredConfs) + } + startBlock := wallet.NewBlockIdentifierFromHeight(startBlockNum) + endBlock := wallet.NewBlockIdentifierFromHeight(endBlockNum) + if err := w.dcrWallet.GetTickets(ctx, processTicket, startBlock, endBlock); err != nil { + return nil, err + } + return tickets, nil +} + +// VotingPreferences returns current voting preferences. +func (w *spvWallet) VotingPreferences(ctx context.Context) ([]*walletjson.VoteChoice, []*walletjson.TSpendPolicyResult, []*walletjson.TreasuryPolicyResult, error) { + _, agendas := wallet.CurrentAgendas(w.chainParams) + + choices, _, err := w.dcrWallet.AgendaChoices(ctx, nil) + if err != nil { + return nil, nil, nil, fmt.Errorf("unable to get agenda choices: %v", err) + } + + voteChoices := make([]*walletjson.VoteChoice, len(choices)) + + for i := range choices { + voteChoices[i] = &walletjson.VoteChoice{ + AgendaID: choices[i].AgendaID, + AgendaDescription: agendas[i].Vote.Description, + ChoiceID: choices[i].ChoiceID, + } + for j := range agendas[i].Vote.Choices { + if choices[i].ChoiceID == agendas[i].Vote.Choices[j].Id { + voteChoices[i].ChoiceDescription = agendas[i].Vote.Choices[j].Description + break + } + } + } + policyToStr := func(p stake.TreasuryVoteT) string { + var policy string + switch p { + case stake.TreasuryVoteYes: + policy = "yes" + case stake.TreasuryVoteNo: + policy = "no" + } + return policy + } + tspends := w.dcrWallet.GetAllTSpends(ctx) + tSpendPolicy := make([]*walletjson.TSpendPolicyResult, 0, len(tspends)) + for i := range tspends { + tspendHash := tspends[i].TxHash() + p := w.dcrWallet.TSpendPolicy(&tspendHash, nil) + r := walletjson.TSpendPolicyResult{ + Hash: tspendHash.String(), + Policy: policyToStr(p), + } + tSpendPolicy = append(tSpendPolicy, &r) + } + + policies := w.dcrWallet.TreasuryKeyPolicies() + treasuryPolicy := make([]*walletjson.TreasuryPolicyResult, 0, len(policies)) + for i := range policies { + r := walletjson.TreasuryPolicyResult{ + Key: hex.EncodeToString(policies[i].PiKey), + Policy: policyToStr(policies[i].Policy), + } + if policies[i].Ticket != nil { + r.Ticket = policies[i].Ticket.String() + } + treasuryPolicy = append(treasuryPolicy, &r) + } + + return voteChoices, tSpendPolicy, treasuryPolicy, nil +} + +// SetVotingPreferences sets voting preferences for the wallet and for vsps with +// active tickets. +func (w *spvWallet) SetVotingPreferences(ctx context.Context, choices, tspendPolicy, + treasuryPolicy map[string]string, vspInfo func(url string) (*vspdjson.VspInfoResponse, error)) error { + // Set the consensus vote choices for the wallet. + agendaChoices := make([]wallet.AgendaChoice, 0, len(choices)) + for k, v := range choices { + choice := wallet.AgendaChoice{ + AgendaID: k, + ChoiceID: v, + } + agendaChoices = append(agendaChoices, choice) + } + if len(agendaChoices) > 0 { + _, err := w.SetAgendaChoices(ctx, nil, agendaChoices...) + if err != nil { + return err + } + } + strToPolicy := func(s, t string) (stake.TreasuryVoteT, error) { + var policy stake.TreasuryVoteT + switch s { + case "abstain", "invalid", "": + policy = stake.TreasuryVoteInvalid + case "yes": + policy = stake.TreasuryVoteYes + case "no": + policy = stake.TreasuryVoteNo + default: + return 0, fmt.Errorf("unknown %s policy %q", t, s) + } + return policy, nil + } + // Set the tspend policy for the wallet. + for k, v := range tspendPolicy { + if len(k) != chainhash.MaxHashStringSize { + return fmt.Errorf("invalid tspend hash length, expected %d got %d", + chainhash.MaxHashStringSize, len(k)) + } + hash, err := chainhash.NewHashFromStr(k) + if err != nil { + return fmt.Errorf("invalid hash %s: %v", k, err) + } + policy, err := strToPolicy(v, "tspend") + if err != nil { + return err + } + err = w.dcrWallet.SetTSpendPolicy(ctx, hash, policy, nil) + if err != nil { + return err + } + } + // Set the treasury policy for the wallet. + for k, v := range treasuryPolicy { + pikey, err := hex.DecodeString(k) + if err != nil { + return fmt.Errorf("unable to decode pi key %s: %v", k, err) + } + if len(pikey) != secp256k1.PubKeyBytesLenCompressed { + return fmt.Errorf("treasury key %s must be 33 bytes", k) + } + policy, err := strToPolicy(v, "treasury") + if err != nil { + return err + } + err = w.dcrWallet.SetTreasuryKeyPolicy(ctx, pikey, policy, nil) + if err != nil { + return err + } + } + clientCache := make(map[string]*vspclient.AutoClient) + // Set voting preferences for VSPs. Continuing for all errors. + return w.dcrWallet.ForUnspentUnexpiredTickets(ctx, func(hash *chainhash.Hash) error { + vspHost, err := w.dcrWallet.VSPHostForTicket(ctx, hash) + if err != nil { + if errors.Is(err, walleterrors.NotExist) { + w.log.Warnf("ticket %s is not associated with a VSP", hash) + return nil + } + w.log.Warnf("unable to get VSP associated with ticket %s: %v", hash, err) + return nil + } + vspClient, have := clientCache[vspHost] + if !have { + info, err := vspInfo(vspHost) + if err != nil { + w.log.Warnf("unable to get info from vsp at %s for ticket %s: %v", vspHost, hash, err) + return nil + } + vspPubKey := base64.StdEncoding.EncodeToString(info.PubKey) + vspClient, err = newVSPClient(w.dcrWallet, vspHost, vspPubKey, w.log.SubLogger("VSP")) + if err != nil { + w.log.Warnf("unable to load vsp at %s for ticket %s: %v", vspHost, hash, err) + return nil + } + } + // Never return errors here, so all tickets are tried. + // The first error will be returned to the user. + err = vspClient.SetVoteChoice(ctx, hash, choices, tspendPolicy, treasuryPolicy) + if err != nil { + w.log.Warnf("unable to set vote for vsp at %s for ticket %s: %v", vspHost, hash, err) + } + return nil + }) +} + // cacheBlock caches a block for future use. The block has a lastAccess stamp // added, and will be discarded if not accessed again within 2 hours. func (w *spvWallet) cacheBlock(block *wire.MsgBlock) { diff --git a/client/asset/dcr/spv_test.go b/client/asset/dcr/spv_test.go index 7fa033d4b4..4d3922fc08 100644 --- a/client/asset/dcr/spv_test.go +++ b/client/asset/dcr/spv_test.go @@ -18,6 +18,7 @@ import ( "decred.org/dcrwallet/v3/wallet/udb" "github.com/decred/dcrd/blockchain/stake/v5" "github.com/decred/dcrd/chaincfg/chainhash" + "github.com/decred/dcrd/chaincfg/v3" "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/decred/dcrd/dcrutil/v4" "github.com/decred/dcrd/gcs/v4" @@ -28,7 +29,7 @@ import ( ) type tDcrWallet struct { - wallet.NetworkBackend + spvSyncer knownAddr wallet.KnownAddress knownAddrErr error txsByHash []*wire.MsgTx @@ -192,6 +193,146 @@ func (w *tDcrWallet) GetTransactionsByHashes(ctx context.Context, txHashes []*ch return w.txsByHash, nil, w.txsByHashErr } +func (w *tDcrWallet) StakeInfo(ctx context.Context) (*wallet.StakeInfoData, error) { + return nil, nil +} + +func (w *tDcrWallet) PurchaseTickets(context.Context, wallet.NetworkBackend, *wallet.PurchaseTicketsRequest) (*wallet.PurchaseTicketsResponse, error) { + return nil, nil +} + +func (w *tDcrWallet) ForUnspentUnexpiredTickets(ctx context.Context, f func(hash *chainhash.Hash) error) error { + return nil +} + +func (w *tDcrWallet) GetTickets(ctx context.Context, f func([]*wallet.TicketSummary, *wire.BlockHeader) (bool, error), startBlock, endBlock *wallet.BlockIdentifier) error { + return nil +} + +func (w *tDcrWallet) AgendaChoices(ctx context.Context, ticketHash *chainhash.Hash) (choices wallet.AgendaChoices, voteBits uint16, err error) { + return nil, 0, nil +} + +func (w *tDcrWallet) TreasuryKeyPolicies() []wallet.TreasuryKeyPolicy { + return nil +} + +func (w *tDcrWallet) GetAllTSpends(ctx context.Context) []*wire.MsgTx { + return nil +} + +func (w *tDcrWallet) TSpendPolicy(tspendHash, ticketHash *chainhash.Hash) stake.TreasuryVoteT { + return 0 +} + +func (w *tDcrWallet) VSPHostForTicket(ctx context.Context, ticketHash *chainhash.Hash) (string, error) { + return "", nil +} + +func (w *tDcrWallet) SetAgendaChoices(ctx context.Context, ticketHash *chainhash.Hash, choices ...wallet.AgendaChoice) (voteBits uint16, err error) { + return 0, nil +} + +func (w *tDcrWallet) SetTSpendPolicy(ctx context.Context, tspendHash *chainhash.Hash, policy stake.TreasuryVoteT, ticketHash *chainhash.Hash) error { + return nil +} + +func (w *tDcrWallet) SetTreasuryKeyPolicy(ctx context.Context, pikey []byte, policy stake.TreasuryVoteT, ticketHash *chainhash.Hash) error { + return nil +} + +func (w *tDcrWallet) Spender(ctx context.Context, out *wire.OutPoint) (*wire.MsgTx, uint32, error) { + return nil, 0, nil +} + +func (w *tDcrWallet) ChainParams() *chaincfg.Params { + return nil +} + +func (w *tDcrWallet) TxBlock(ctx context.Context, hash *chainhash.Hash) (chainhash.Hash, int32, error) { + return chainhash.Hash{}, 0, nil +} + +func (w *tDcrWallet) DumpWIFPrivateKey(ctx context.Context, addr stdaddr.Address) (string, error) { + return "", nil +} + +func (w *tDcrWallet) VSPFeeHashForTicket(ctx context.Context, ticketHash *chainhash.Hash) (chainhash.Hash, error) { + return chainhash.Hash{}, nil +} + +func (w *tDcrWallet) UpdateVspTicketFeeToStarted(ctx context.Context, ticketHash, feeHash *chainhash.Hash, host string, pubkey []byte) error { + return nil +} + +func (w *tDcrWallet) ReserveOutputsForAmount(ctx context.Context, account uint32, amount dcrutil.Amount, minconf int32) ([]wallet.Input, error) { + return nil, nil +} + +func (w *tDcrWallet) NewChangeAddress(ctx context.Context, account uint32) (stdaddr.Address, error) { + return nil, nil +} + +func (w *tDcrWallet) RelayFee() dcrutil.Amount { + return 0 +} + +func (w *tDcrWallet) SetPublished(ctx context.Context, hash *chainhash.Hash, published bool) error { + return nil +} + +func (w *tDcrWallet) AddTransaction(ctx context.Context, tx *wire.MsgTx, blockHash *chainhash.Hash) error { + return nil +} + +func (w *tDcrWallet) UpdateVspTicketFeeToPaid(ctx context.Context, ticketHash, feeHash *chainhash.Hash, host string, pubkey []byte) error { + return nil +} + +func (w *tDcrWallet) NetworkBackend() (wallet.NetworkBackend, error) { + return nil, nil +} + +func (w *tDcrWallet) RevokeTickets(ctx context.Context, rpcCaller wallet.Caller) error { + return nil +} + +func (w *tDcrWallet) UpdateVspTicketFeeToErrored(ctx context.Context, ticketHash *chainhash.Hash, host string, pubkey []byte) error { + return nil +} + +func (w *tDcrWallet) TSpendPolicyForTicket(ticketHash *chainhash.Hash) map[string]string { + return nil +} + +func (w *tDcrWallet) TreasuryKeyPolicyForTicket(ticketHash *chainhash.Hash) map[string]string { + return nil +} + +func (w *tDcrWallet) AbandonTransaction(ctx context.Context, hash *chainhash.Hash) error { + return nil +} + +func (w *tDcrWallet) TxConfirms(ctx context.Context, hash *chainhash.Hash) (int32, error) { + return 0, nil +} + +func (w *tDcrWallet) IsVSPTicketConfirmed(ctx context.Context, ticketHash *chainhash.Hash) (bool, error) { + return false, nil +} + +func (w *tDcrWallet) UpdateVspTicketFeeToConfirmed(ctx context.Context, ticketHash, feeHash *chainhash.Hash, host string, pubkey []byte) error { + return nil +} + +func (w *tDcrWallet) VSPTicketInfo(ctx context.Context, ticketHash *chainhash.Hash) (*wallet.VSPTicket, error) { + return nil, nil +} + +func (w *tDcrWallet) SignMessage(ctx context.Context, msg string, addr stdaddr.Address) (sig []byte, err error) { + return nil, nil +} + func tNewSpvWallet() (*spvWallet, *tDcrWallet) { dcrw := &tDcrWallet{ blockHeader: make(map[chainhash.Hash]*wire.BlockHeader), diff --git a/client/asset/dcr/wallet.go b/client/asset/dcr/wallet.go index 665e70b22c..ba24e27e74 100644 --- a/client/asset/dcr/wallet.go +++ b/client/asset/dcr/wallet.go @@ -13,9 +13,11 @@ import ( "github.com/decred/dcrd/chaincfg/chainhash" "github.com/decred/dcrd/chaincfg/v3" "github.com/decred/dcrd/dcrec/secp256k1/v4" + "github.com/decred/dcrd/dcrutil/v4" chainjson "github.com/decred/dcrd/rpc/jsonrpc/types/v4" "github.com/decred/dcrd/txscript/v4/stdaddr" "github.com/decred/dcrd/wire" + vspdjson "github.com/decred/vspd/types" ) // WalletConstructor defines a function that can be invoked to create a custom @@ -143,7 +145,19 @@ type Wallet interface { PeerCount(ctx context.Context) (uint32, error) // AddressPrivKey fetches the privkey for the specified address. AddressPrivKey(ctx context.Context, address stdaddr.Address) (*secp256k1.PrivateKey, error) - + // StakeDiff gets the stake difficulty. + StakeDiff(ctx context.Context) (dcrutil.Amount, error) + // PurchaseTickets purchases n tickets. vsp arguments only needed for + // internal wallets. + PurchaseTickets(ctx context.Context, n int, vspHost, vspPubKey string) ([]string, error) + // Tickets returns current active ticket hashes up until they are voted + // or revoked. Includes unconfirmed tickets. + Tickets(ctx context.Context) ([]*asset.Ticket, error) + // VotingPreferences returns current voting preferences. + VotingPreferences(ctx context.Context) ([]*walletjson.VoteChoice, []*walletjson.TSpendPolicyResult, []*walletjson.TreasuryPolicyResult, error) + // SetVotingPreferences sets preferences used when a ticket is chosen to + // be voted on. + SetVotingPreferences(ctx context.Context, choices, tspendPolicy, treasuryPolicy map[string]string, vspInfo func(url string) (*vspdjson.VspInfoResponse, error)) error Reconfigure(ctx context.Context, cfg *asset.WalletConfig, net dex.Network, currentAddress, depositAccount string) (restart bool, err error) } diff --git a/client/asset/interface.go b/client/asset/interface.go index 6cf96191bf..f2c91396b4 100644 --- a/client/asset/interface.go +++ b/client/asset/interface.go @@ -8,6 +8,7 @@ import ( "time" "decred.org/dcrdex/dex" + dcrwalletjson "decred.org/dcrwallet/v3/rpc/jsonrpc/types" "github.com/decred/dcrd/dcrec/secp256k1/v4" ) @@ -30,6 +31,7 @@ const ( WalletTraitAuthenticator // The wallet require authentication. WalletTraitShielded // The wallet is ShieldedWallet (e.g. ZCash) WalletTraitTokenApprover // The wallet is a TokenApprover + WalletTraitTicketBuyer // The wallet can participate in decred staking. ) // IsRescanner tests if the WalletTrait has the WalletTraitRescanner bit set. @@ -116,6 +118,12 @@ func (wt WalletTrait) IsTokenApprover() bool { return wt&WalletTraitTokenApprover != 0 } +// IsTicketBuyer tests if the WalletTrait has the WalletTraitDCRStaker bit set, +// which indicates the wallet implements the TicketBuyer interface. +func (wt WalletTrait) IsTicketBuyer() bool { + return wt&WalletTraitTicketBuyer != 0 +} + // DetermineWalletTraits returns the WalletTrait bitset for the provided Wallet. func DetermineWalletTraits(w Wallet) (t WalletTrait) { if _, is := w.(Rescanner); is { @@ -160,6 +168,9 @@ func DetermineWalletTraits(w Wallet) (t WalletTrait) { if _, is := w.(TokenApprover); is { t |= WalletTraitTokenApprover } + if _, is := w.(TicketBuyer); is { + t |= WalletTraitTicketBuyer + } return t } @@ -860,6 +871,80 @@ type TokenApprover interface { ApprovalFee(assetVer uint32, approval bool) (uint64, error) } +// TicketTransaction represents a ticket transaction. +type TicketTransaction struct { + Hash string `json:"hash"` + TicketPrice uint64 `json:"ticketPrice"` + Fees uint64 `json:"fees"` + Stamp uint64 `json:"stamp"` + BlockHeight int64 `json:"blockHeight"` +} + +// TicketStatus from dcrwallet. +type TicketStatus uint + +// Copy of wallet.TicketStatus +const ( + TicketStatusUnknown TicketStatus = iota + TicketStatusUnmined + TicketStatusImmature + TicketStatusLive + TicketStatusVoted + TicketStatusMissed + TicketStatusExpired + TicketStatusUnspent + TicketStatusRevoked +) + +// Ticket holds information about a decred ticket. +type Ticket struct { + Ticket TicketTransaction `json:"ticket"` + Status TicketStatus `json:"status"` + Spender string `json:"spender"` +} + +// Stances are vote choices. +type Stances struct { + VoteChoices []*dcrwalletjson.VoteChoice `json:"voteChoices"` + TSpendPolicy []*dcrwalletjson.TSpendPolicyResult `json:"tSpendPolicy"` + TreasuryPolicy []*dcrwalletjson.TreasuryPolicyResult `json:"treasuryPolicy"` +} + +// TicketStakingStatus holds various stake information from the wallet. +type TicketStakingStatus struct { + // TicketPrice is the current price of one ticket. Also known as the + // stake difficulty. + TicketPrice uint64 `json:"ticketPrice"` + // VSP is the currently set VSP address and fee. + VSP string `json:"vsp"` + // IsRPC will be true if this is an RPC wallet, in which case we can't + // set a new VSP and some other information may not be available. + IsRPC bool `json:"isRPC"` + // Tickets returns current active tickets up until they are voted or + // revoked. Includes unconfirmed tickets. + Tickets []*Ticket `json:"tickets"` + // Stances returns current voting preferences. + Stances Stances `json:"stances"` +} + +// TicketBuyer is a wallet that can participate in decred staking. +// +// TODO: Consider adding (*AutoClient).ProcessUnprocessedTickets/ProcessManagedTickets +// to be used when restoring wallet from seed. +type TicketBuyer interface { + // StakeStatus returns current staking statuses such as currently owned + // tickets, ticket price, and current voting preferences. + StakeStatus() (*TicketStakingStatus, error) + // SetVSP sets the VSP provider. + SetVSP(addr string) error + // PurchaseTickets purchases n amout of tickets. Returns the purchased + // ticket hashes if successful. + PurchaseTickets(n int) ([]string, error) + // SetVotingPreferences sets default voting settings for all active + // tickets and future tickets. Nil maps can be provided for no change. + SetVotingPreferences(choices, tSpendPolicy, treasuryPolicy map[string]string) error +} + // Bond is the fidelity bond info generated for a certain account ID, amount, // and lock time. These data are intended for the "post bond" request, in which // the server pre-validates the unsigned transaction, the client then publishes diff --git a/client/cmd/dexc-desktop/go.mod b/client/cmd/dexc-desktop/go.mod index dbeac5d9dc..0cba960ba2 100644 --- a/client/cmd/dexc-desktop/go.mod +++ b/client/cmd/dexc-desktop/go.mod @@ -5,7 +5,7 @@ go 1.18 replace decred.org/dcrdex => ../../.. require ( - decred.org/dcrdex v0.6.1 + decred.org/dcrdex v0.6.0 fyne.io/systray v1.10.1-0.20230403195833-7dc3c09283d6 github.com/gen2brain/beeep v0.0.0-20220909211152-5a9ec94374f6 github.com/webview/webview v0.0.0-20230415172654-8387ff8945fc @@ -21,13 +21,13 @@ require ( github.com/aead/siphash v1.0.1 // indirect github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/btcsuite/btcd v0.23.4 // indirect - github.com/btcsuite/btcd/btcec/v2 v2.2.2 // indirect - github.com/btcsuite/btcd/btcutil v1.1.3 // indirect - github.com/btcsuite/btcd/btcutil/psbt v1.1.8 // indirect + github.com/btcsuite/btcd v0.23.3 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.2.1 // indirect + github.com/btcsuite/btcd/btcutil v1.1.2 // indirect + github.com/btcsuite/btcd/btcutil/psbt v1.1.5 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect - github.com/btcsuite/btcwallet v0.16.10-0.20230530212348-2f8238da04ec // indirect + github.com/btcsuite/btcwallet v0.16.1 // indirect github.com/btcsuite/btcwallet/wallet/txauthor v1.3.2 // indirect github.com/btcsuite/btcwallet/wallet/txrules v1.2.0 // indirect github.com/btcsuite/btcwallet/wallet/txsizes v1.2.3 // indirect @@ -75,6 +75,9 @@ require ( github.com/decred/dcrd/wire v1.6.0 // indirect github.com/decred/go-socks v1.1.0 // indirect github.com/decred/slog v1.2.0 // indirect + github.com/decred/vspd/client/v2 v2.1.0 // indirect + github.com/decred/vspd/types v1.1.0 // indirect + github.com/decred/vspd/types/v2 v2.0.0 // indirect github.com/dgraph-io/badger v1.6.2 // indirect github.com/dgraph-io/ristretto v0.0.2 // indirect github.com/dustin/go-humanize v1.0.0 // indirect @@ -118,8 +121,7 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/lib/pq v1.10.4 // indirect github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf // indirect - github.com/lightninglabs/neutrino v0.15.0 // indirect - github.com/lightninglabs/neutrino/cache v1.1.0 // indirect + github.com/lightninglabs/neutrino v0.14.3-0.20221024182812-792af8548c14 // indirect github.com/lightningnetwork/lnd/clock v1.0.1 // indirect github.com/lightningnetwork/lnd/queue v1.0.1 // indirect github.com/lightningnetwork/lnd/ticker v1.0.0 // indirect diff --git a/client/cmd/dexc-desktop/go.sum b/client/cmd/dexc-desktop/go.sum index 1bd9f1393e..4bed7f0d22 100644 --- a/client/cmd/dexc-desktop/go.sum +++ b/client/cmd/dexc-desktop/go.sum @@ -121,38 +121,43 @@ github.com/bkielbasa/cyclop v1.2.0/go.mod h1:qOI0yy6A7dYC4Zgsa72Ppm9kONl0RoIlPbz github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/bombsimon/wsl/v3 v3.3.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc= +github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= github.com/btcsuite/btcd v0.22.0-beta.0.20220204213055-eaf0459ff879/go.mod h1:osu7EoKiL36UThEgzYPqdRaxeo0NU8VoXqgcnwpey0g= github.com/btcsuite/btcd v0.22.0-beta.0.20220207191057-4dc4ff7963b4/go.mod h1:7alexyj/lHlOtr2PJK7L/+HDJZpcGDn/pAU98r7DY08= +github.com/btcsuite/btcd v0.22.0-beta.0.20220316175102-8d5c75c28923/go.mod h1:taIcYprAW2g6Z9S0gGUxyR+zDwimyDMK5ePOX+iJ2ds= github.com/btcsuite/btcd v0.23.0/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY= github.com/btcsuite/btcd v0.23.1/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY= -github.com/btcsuite/btcd v0.23.4 h1:IzV6qqkfwbItOS/sg/aDfPDsjPP8twrCOE2R93hxMlQ= -github.com/btcsuite/btcd v0.23.4/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY= +github.com/btcsuite/btcd v0.23.3 h1:4KH/JKy9WiCd+iUS9Mu0Zp7Dnj17TGdKrg9xc/FGj24= +github.com/btcsuite/btcd v0.23.3/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY= github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= github.com/btcsuite/btcd/btcec/v2 v2.1.1/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= -github.com/btcsuite/btcd/btcec/v2 v2.2.2 h1:5uxe5YjoCq+JeOpg0gZSNHuFgeogrocBYxvg6w9sAgc= -github.com/btcsuite/btcd/btcec/v2 v2.2.2/go.mod h1:9/CSmJxmuvqzX9Wh2fXMWToLOHhPd11lSPuIupwTkI8= +github.com/btcsuite/btcd/btcec/v2 v2.2.1 h1:xP60mv8fvp+0khmrN0zTdPC3cNm24rfeE6lh2R/Yv3E= +github.com/btcsuite/btcd/btcec/v2 v2.2.1/go.mod h1:9/CSmJxmuvqzX9Wh2fXMWToLOHhPd11lSPuIupwTkI8= github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= github.com/btcsuite/btcd/btcutil v1.1.1/go.mod h1:nbKlBMNm9FGsdvKvu0essceubPiAcI57pYBNnsLAa34= -github.com/btcsuite/btcd/btcutil v1.1.3 h1:xfbtw8lwpp0G6NwSHb+UE67ryTFHJAiNuipusjXSohQ= -github.com/btcsuite/btcd/btcutil v1.1.3/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0= -github.com/btcsuite/btcd/btcutil/psbt v1.1.8 h1:4voqtT8UppT7nmKQkXV+T9K8UyQjKOn2z/ycpmJK8wg= -github.com/btcsuite/btcd/btcutil/psbt v1.1.8/go.mod h1:kA6FLH/JfUx++j9pYU0pyu+Z8XGBQuuTmuKYUf6q7/U= +github.com/btcsuite/btcd/btcutil v1.1.2 h1:XLMbX8JQEiwMcYft2EGi8zPUkoa0abKIU6/BJSRsjzQ= +github.com/btcsuite/btcd/btcutil v1.1.2/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0= +github.com/btcsuite/btcd/btcutil/psbt v1.1.4/go.mod h1:9AyU6EQVJ9Iw9zPyNT1lcdHd6cnEZdno5wLu5FY74os= +github.com/btcsuite/btcd/btcutil/psbt v1.1.5 h1:x0ZRrYY8j75ThV6xBz86CkYAG82F5bzay4H5D1c8b/U= +github.com/btcsuite/btcd/btcutil/psbt v1.1.5/go.mod h1:kA6FLH/JfUx++j9pYU0pyu+Z8XGBQuuTmuKYUf6q7/U= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= -github.com/btcsuite/btcwallet v0.16.10-0.20230530212348-2f8238da04ec h1:fTze926fMVDtZPNUp1HJQXFaBbvaaCKw4tnJkuUbJUM= -github.com/btcsuite/btcwallet v0.16.10-0.20230530212348-2f8238da04ec/go.mod h1:T3DjEAMZYIqQ28l+ixlB6DX4mFJXCX8Pzz+yACQcLsc= +github.com/btcsuite/btcwallet v0.16.1 h1:nD8qXJeAU8c7a0Jlx5jwI2ufbf/9ouy29XGapRLTmos= +github.com/btcsuite/btcwallet v0.16.1/go.mod h1:NCO8+5rIcbUm5CtVNSQM0xrtK4iYprlyuvpGzhkejaM= +github.com/btcsuite/btcwallet/wallet/txauthor v1.2.3/go.mod h1:T2xSiKGpUkSLCh68aF+FMXmKK9mFqNdHl9VaqOr+JjU= github.com/btcsuite/btcwallet/wallet/txauthor v1.3.2 h1:etuLgGEojecsDOYTII8rYiGHjGyV5xTqsXi+ZQ715UU= github.com/btcsuite/btcwallet/wallet/txauthor v1.3.2/go.mod h1:Zpk/LOb2sKqwP2lmHjaZT9AdaKsHPSbNLm2Uql5IQ/0= github.com/btcsuite/btcwallet/wallet/txrules v1.2.0 h1:BtEN5Empw62/RVnZ0VcJaVtVlBijnLlJY+dwjAye2Bg= github.com/btcsuite/btcwallet/wallet/txrules v1.2.0/go.mod h1:AtkqiL7ccKWxuLYtZm8Bu8G6q82w4yIZdgq6riy60z0= +github.com/btcsuite/btcwallet/wallet/txsizes v1.1.0/go.mod h1:pauEU8UuMFiThe5PB3EO+gO5kx87Me5NvdQDsTuq6cs= github.com/btcsuite/btcwallet/wallet/txsizes v1.2.2/go.mod h1:q08Rms52VyWyXcp5zDc4tdFRKkFgNsMQrv3/LvE1448= github.com/btcsuite/btcwallet/wallet/txsizes v1.2.3 h1:PszOub7iXVYbtGybym5TGCp9Dv1h1iX4rIC3HICZGLg= github.com/btcsuite/btcwallet/wallet/txsizes v1.2.3/go.mod h1:q08Rms52VyWyXcp5zDc4tdFRKkFgNsMQrv3/LvE1448= @@ -310,6 +315,12 @@ 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/go.mod h1:kVXlGnt6DHy2fV5OjSeuvCJ0OmlmTF6LFpEPMu/fOY0= +github.com/decred/vspd/client/v2 v2.1.0 h1:RzwmM/FCvpJDskNMeqeJ8UNnlR7kLCl3JlG8iZiLbG0= +github.com/decred/vspd/client/v2 v2.1.0/go.mod h1:r/CtdQF7TmuoIaFuanHtUMYYlQxWgRBGapdn4b+Bouc= +github.com/decred/vspd/types v1.1.0 h1:hTeqQwgRUN2FGIbuCIdyzBejKV+jxKrmEIcLKxpsB1g= +github.com/decred/vspd/types v1.1.0/go.mod h1:THsO8aBSwWBq6ZsIG25cNqbkNb+EEASXzLhFvODVc0s= +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/denis-tingajkin/go-header v0.4.2/go.mod h1:eLRHAVXzE5atsKAnNRDB90WHCFFnBUn4RN0nRcs1LJA= github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE= github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= @@ -780,10 +791,9 @@ github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf h1:HZKvJUHlcXI/f/O0Avg7t8sqkPo78HFzjmeYFl6DPnc= github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk= -github.com/lightninglabs/neutrino v0.15.0 h1:yr3uz36fLAq8hyM0TRUVlef1TRNoWAqpmmNlVtKUDtI= -github.com/lightninglabs/neutrino v0.15.0/go.mod h1:pmjwElN/091TErtSE9Vd5W4hpxoG2/+xlb+HoPm9Gug= -github.com/lightninglabs/neutrino/cache v1.1.0 h1:szZIhVabiQIsGzJjhvo76sj05Au+zVotj2M34EquGME= -github.com/lightninglabs/neutrino/cache v1.1.0/go.mod h1:XJNcgdOw1LQnanGjw8Vj44CvguYA25IMKjWFZczwZuo= +github.com/lightninglabs/neutrino v0.14.2/go.mod h1:OICUeTCn+4Tu27YRJIpWvvqySxx4oH4vgdP33Sw9RDc= +github.com/lightninglabs/neutrino v0.14.3-0.20221024182812-792af8548c14 h1:ieYiTZabh03Rjn+W8//z7z8xwaEjXE8lWulVWV4Sqns= +github.com/lightninglabs/neutrino v0.14.3-0.20221024182812-792af8548c14/go.mod h1:OICUeTCn+4Tu27YRJIpWvvqySxx4oH4vgdP33Sw9RDc= github.com/lightningnetwork/lnd/clock v1.0.1 h1:QQod8+m3KgqHdvVMV+2DRNNZS1GRFir8mHZYA+Z2hFo= github.com/lightningnetwork/lnd/clock v1.0.1/go.mod h1:KnQudQ6w0IAMZi1SgvecLZQZ43ra2vpDNj7H/aasemg= github.com/lightningnetwork/lnd/queue v1.0.1 h1:jzJKcTy3Nj5lQrooJ3aaw9Lau3I0IwvQR5sqtjdv2R0= @@ -1252,6 +1262,7 @@ golang.org/x/crypto v0.0.0-20180501155221-613d6eafa307/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -1326,6 +1337,7 @@ golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190206173232-65e2d4e15006/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -1494,6 +1506,7 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= diff --git a/dex/testing/dcr/create-vspd.sh b/dex/testing/dcr/create-vspd.sh new file mode 100755 index 0000000000..acea65663a --- /dev/null +++ b/dex/testing/dcr/create-vspd.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# Script for creating dcr vspd, dcr harness and wallets should be running before executing. +set -e + +# The following are required script arguments +TMUX_WIN_ID=$1 +PORT=$2 +FEE_XPUB=$3 + +VSPD_DIR="${NODES_ROOT}/vspd" +VSPD_WEBAPI_DIR="${VSPD_DIR}/webapi" +mkdir -p ${VSPD_WEBAPI_DIR} + +VSPD_TEMPLATES_FROM="${HARNESS_DIR}/vspdtemplates" +VSPD_TEMPLATES_TO="${VSPD_WEBAPI_DIR}/templates" +cp -a "${VSPD_TEMPLATES_FROM}" "${VSPD_TEMPLATES_TO}" + +DCRD_PORT="${ALPHA_NODE_RPC_PORT}" +DCRD_CERT="${NODES_ROOT}/alpha/rpc.cert" +USER="${RPC_USER}" +PASS="${RPC_PASS}" +WALLET_PORT="${VSPD_WALLET_RPC_PORT}" +DCRWALLET_RPC_PORT="${ALPHA_WALLET_RPC_PORT}" + +WALLET_CERT="${NODES_ROOT}/vspdwallet/rpc.cert" + +# vspd config +cat > "${VSPD_DIR}/vspd.conf" <> "${WALLET_DIR}/${NAME}.conf" +cat >> "${WALLET_DIR}/${NAME}.conf" <> "${WALLET_DIR}/${NAME}.conf" <> "${WALLET_DIR}/${NAME}.conf" fi diff --git a/dex/testing/dcr/harness.sh b/dex/testing/dcr/harness.sh index a1a5be1c7f..a05f85a786 100755 --- a/dex/testing/dcr/harness.sh +++ b/dex/testing/dcr/harness.sh @@ -15,7 +15,7 @@ export BETA_NODE_RPC_PORT="19571" ALPHA_WALLET_SEED="b280922d2cffda44648346412c5ec97f429938105003730414f10b01e1402eac" ALPHA_MINING_ADDR="SsXciQNTo3HuV5tX3yy4hXndRWgLMRVC7Ah" -ALPHA_WALLET_RPC_PORT="19562" +export ALPHA_WALLET_RPC_PORT="19562" ALPHA_WALLET_HTTPPROF_PORT="19563" # The alpha wallet clone uses the same seed as the alpha wallet. @@ -34,6 +34,14 @@ TRADING_WALLET2_SEED="3db72efa55b9e6cce9c27dde9bea848c6199004f9b1ae2add3b0438949 TRADING_WALLET2_ADDRESS="SsYW5LPmGCvvHuWok8U9FQu1kotv8LpvoEt" TRADING_WALLET2_PORT="19582" +VSPD_WALLET_SEED="2db72efa55b9e6cce9c27dde9bea848c6199004f9b1ae2add3b04389495edb9c" +export VSPD_WALLET_RPC_PORT="19590" + +# Address for testing with an internal wallet in client/assets/dcr +VSP_HARNESS_TEST_ADDR="SsXsicdfL1jB2Rzu2UA7P2B9gnpqA9YGypw" + +VSPD_PORT="19591" + # WAIT can be used in a send-keys call along with a `wait-for donedcr` command to # wait for process termination. WAIT="; tmux wait-for -S donedcr" @@ -143,7 +151,7 @@ chmod +x "${NODES_ROOT}/harness-ctl/mine-beta" # alpha wallet clone does not need to purchase tickets or do anything else # really other than just voting on tickets purchased with the alpha wallet. cat > "${NODES_ROOT}/harness-ctl/clone-w-alpha" <