Skip to content

Commit

Permalink
Enable treasury vote choices.
Browse files Browse the repository at this point in the history
This adds both tSpend and treasury policies to be set on a per-ticket basis.

Any request which alters treasury/tspend policy will be stored in the database using the existing accountability system.

This does not include consistency checking. Will need to be added later when dcrwallet has an RPC to retrieve policies in batches.
  • Loading branch information
jholdstock committed Feb 2, 2022
1 parent dcbf8a2 commit 47838fe
Show file tree
Hide file tree
Showing 17 changed files with 375 additions and 41 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
ISC License

Copyright (c) 2020-2021 The Decred developers
Copyright (c) 2020-2022 The Decred developers

Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
Expand Down
23 changes: 22 additions & 1 deletion background/background.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2020-2021 The Decred developers
// Copyright (c) 2020-2022 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

Expand Down Expand Up @@ -244,6 +244,25 @@ func blockConnected() {
}
}
}

// Set tspend policy on voting wallets.
for tspend, policy := range ticket.TSpendPolicy {
err = walletClient.SetTSpendPolicy(tspend, policy, ticket.Hash)
if err != nil {
log.Errorf("%s: dcrwallet.SetTSpendPolicy failed (wallet=%s, ticketHash=%s): %v",
funcName, walletClient.String(), ticket.Hash, err)
}
}

// Set treasury policy on voting wallets.
for key, policy := range ticket.TreasuryPolicy {
err = walletClient.SetTreasuryPolicy(key, policy, ticket.Hash)
if err != nil {
log.Errorf("%s: dcrwallet.SetTreasuryPolicy failed (wallet=%s, ticketHash=%s): %v",
funcName, walletClient.String(), ticket.Hash, err)
}
}

log.Infof("%s: Ticket added to voting wallet (wallet=%s, ticketHash=%s)",
funcName, walletClient.String(), ticket.Hash)
}
Expand Down Expand Up @@ -557,6 +576,8 @@ func checkWalletConsistency() {
}
}
}

// TODO - tspend and treasury policy consistency checking.
}
}
}
Expand Down
56 changes: 48 additions & 8 deletions database/ticket.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2020-2021 The Decred developers
// Copyright (c) 2020-2022 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

Expand Down Expand Up @@ -51,6 +51,8 @@ var (
confirmedK = []byte("Confirmed")
votingWIFK = []byte("VotingWIF")
voteChoicesK = []byte("VoteChoices")
tSpendPolicyK = []byte("TSpendPolicy")
treasuryPolicyK = []byte("TreasuryPolicy")
feeTxHexK = []byte("FeeTxHex")
feeTxHashK = []byte("FeeTxHash")
feeTxStatusK = []byte("FeeTxStatus")
Expand All @@ -72,9 +74,11 @@ type Ticket struct {
// VotingWIF is set in /payfee.
VotingWIF string

// VoteChoices is initially set in /payfee, but can be updated in
// /setvotechoices.
VoteChoices map[string]string
// VoteChoices, TSpendPolicy and TreasuryPolicy are initially set in
// /payfee, but can be updated in /setvotechoices.
VoteChoices map[string]string
TSpendPolicy map[string]string
TreasuryPolicy map[string]string

// FeeTxHex and FeeTxHash will be set when the fee tx has been received.
FeeTxHex string
Expand Down Expand Up @@ -176,6 +180,22 @@ func putTicketInBucket(bkt *bolt.Bucket, ticket Ticket) error {
return err
}

jsonTSpend, err := json.Marshal(ticket.TSpendPolicy)
if err != nil {
return err
}
if err = bkt.Put(tSpendPolicyK, jsonTSpend); err != nil {
return err
}

jsonTreasury, err := json.Marshal(ticket.TreasuryPolicy)
if err != nil {
return err
}
if err = bkt.Put(treasuryPolicyK, jsonTreasury); err != nil {
return err
}

jsonVoteChoices, err := json.Marshal(ticket.VoteChoices)
if err != nil {
return err
Expand All @@ -202,10 +222,30 @@ func getTicketFromBkt(bkt *bolt.Bucket) (Ticket, error) {

ticket.Confirmed = bytesToBool(bkt.Get(confirmedK))

ticket.VoteChoices = make(map[string]string)
err := json.Unmarshal(bkt.Get(voteChoicesK), &ticket.VoteChoices)
if err != nil {
return ticket, err
var err error

voteChoicesB := bkt.Get(voteChoicesK)
if voteChoicesB != nil {
err = json.Unmarshal(voteChoicesB, &ticket.VoteChoices)
if err != nil {
return ticket, fmt.Errorf("unmarshal VoteChoices err: %w", err)
}
}

tSpendPolicyB := bkt.Get(tSpendPolicyK)
if tSpendPolicyB != nil {
err = json.Unmarshal(tSpendPolicyB, &ticket.TSpendPolicy)
if err != nil {
return ticket, fmt.Errorf("unmarshal TSpendPolicy err: %w", err)
}
}

treasuryPolicyB := bkt.Get(treasuryPolicyK)
if treasuryPolicyB != nil {
err = json.Unmarshal(treasuryPolicyB, &ticket.TreasuryPolicy)
if err != nil {
return ticket, fmt.Errorf("unmarshal TreasuryPolicy err: %w", err)
}
}

return ticket, nil
Expand Down
4 changes: 3 additions & 1 deletion database/ticket_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2020-2021 The Decred developers
// Copyright (c) 2020-2022 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

Expand All @@ -22,6 +22,8 @@ func exampleTicket() Ticket {
FeeExpiration: 4,
Confirmed: false,
VoteChoices: map[string]string{"AgendaID": "yes"},
TSpendPolicy: map[string]string{randString(64, hexCharset): "no"},
TreasuryPolicy: map[string]string{randString(66, hexCharset): "abstain"},
VotingWIF: randString(53, addrCharset),
FeeTxHex: randString(504, hexCharset),
FeeTxHash: randString(64, hexCharset),
Expand Down
19 changes: 17 additions & 2 deletions database/upgrade_v3.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2021 The Decred developers
// Copyright (c) 2021-2022 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

Expand Down Expand Up @@ -52,7 +52,22 @@ func ticketBucketUpgrade(db *bolt.DB) error {
return fmt.Errorf("could not create new ticket bucket: %w", err)
}

err = putTicketInBucket(newBkt, Ticket(ticket))
err = putTicketInBucket(newBkt, Ticket{
Hash: ticket.Hash,
PurchaseHeight: ticket.PurchaseHeight,
CommitmentAddress: ticket.CommitmentAddress,
FeeAddressIndex: ticket.FeeAddressIndex,
FeeAddress: ticket.FeeAddress,
FeeAmount: ticket.FeeAmount,
FeeExpiration: ticket.FeeExpiration,
Confirmed: ticket.Confirmed,
VotingWIF: ticket.VotingWIF,
VoteChoices: ticket.VoteChoices,
FeeTxHex: ticket.FeeTxHex,
FeeTxHash: ticket.FeeTxHash,
FeeTxStatus: ticket.FeeTxStatus,
Outcome: ticket.Outcome,
})
if err != nil {
return fmt.Errorf("could not put new ticket in bucket: %w", err)
}
Expand Down
12 changes: 10 additions & 2 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,9 @@ for the specified ticket.
"tickethash":"484a68f7148e55d05f0b64a29fe7b148572cb5272d1ce2438cf15466d347f4f4",
"feetx":"010000000125...737b266ffb9a93",
"votingkey":"PtWUJWhSXsM9ztPkdtH8REe91z7uoidX8dsMChJUZ2spagm7YvrNm",
"votechoices":{"headercommitments":"yes"}
"votechoices":{"headercommitments":"yes"},
"tspendpolicy":{"<tspend tx hash>":"yes"},
"treasurypolicy":{"<treasury spending key>":"no"}
}
```

Expand Down Expand Up @@ -212,6 +214,8 @@ its `feetxstatus` is `confirmed`.
"feetxhash":"e1c02b04b5bbdae66cf8e3c88366c4918d458a2d27a26144df37f54a2bc956ac",
"altsignaddress":"Tsfkn6k9AoYgVZRV6ZzcgmuVSgCdJQt9JY2",
"votechoices":{"headercommitments":"no"},
"tspendpolicy":{"<tspend tx hash>":"yes"},
"treasurypolicy":{"<treasury spending key>":"no"},
"request": {"<Copy of request body>"}
}
```
Expand All @@ -220,6 +224,8 @@ its `feetxstatus` is `confirmed`.

Clients can update the voting preferences of their ticket at any time after
after calling `/payfee`.
This call can be used to update consensus, treasury spend key, and tspend voting
preferences.

- `POST /api/v3/setvotechoices`

Expand All @@ -229,7 +235,9 @@ after calling `/payfee`.
{
"timestamp":1590509066,
"tickethash":"484a68f7148e55d05f0b64a29fe7b148572cb5272d1ce2438cf15466d347f4f4",
"votechoices":{"headercommitments":"no"}
"votechoices":{"headercommitments":"no"},
"tspendpolicy":{"<tspend tx hash>":"yes"},
"treasurypolicy":{"<treasury spending key>":"no"}
}
```

Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ module github.com/decred/vspd
go 1.16

require (
decred.org/dcrwallet/v2 v2.0.0-rc3
decred.org/dcrwallet/v2 v2.0.0
github.com/decred/dcrd/blockchain/stake/v4 v4.0.0
github.com/decred/dcrd/blockchain/v4 v4.0.0
github.com/decred/dcrd/chaincfg/chainhash v1.0.3
github.com/decred/dcrd/chaincfg/v3 v3.1.1
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1
github.com/decred/dcrd/dcrutil/v4 v4.0.0
github.com/decred/dcrd/hdkeychain/v3 v3.1.0
github.com/decred/dcrd/rpc/jsonrpc/types/v3 v3.0.0
Expand Down
6 changes: 3 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
decred.org/cspp/v2 v2.0.0-20220117153402-4f26c92d52a3/go.mod h1:USyJS44Kqxz2wT/VaNsf9iTAONegO/qKXRdLg1nvrWI=
decred.org/dcrwallet/v2 v2.0.0-rc3 h1:xxmZndkTheyfORD16nf8fUsevR3dBHdI8hhdAUFumZE=
decred.org/dcrwallet/v2 v2.0.0-rc3/go.mod h1:NJnnnOrPISrCt5oxrkXgTu0feCnxcLUsbqsvdXtFVBI=
decred.org/cspp/v2 v2.0.0/go.mod h1:0shJWKTWY3LxZEWGxtbER1Y45+HVjC0WZtj4bctSzCI=
decred.org/dcrwallet/v2 v2.0.0 h1:nOGChwR5RM4QLmbm/Krit6/eQryv/RvcpUxxmUThfKI=
decred.org/dcrwallet/v2 v2.0.0/go.mod h1:lZXgx5OcLDaWyNWFkBekqER1gdqiVwua1w68SFC1/Nk=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI=
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0=
Expand Down
14 changes: 13 additions & 1 deletion rpc/dcrwallet.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2020 The Decred developers
// Copyright (c) 2020-2022 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

Expand Down Expand Up @@ -219,3 +219,15 @@ func (c *WalletRPC) TicketInfo(startHeight int64) (map[string]*wallettypes.Ticke
func (c *WalletRPC) RescanFrom(fromHeight int64) error {
return c.Call(c.ctx, "rescanwallet", nil, fromHeight)
}

// SetTreasuryPolicy sets the specified tickets voting policy for all tspends
// published by the given treasury key.
func (c *WalletRPC) SetTreasuryPolicy(key, policy, ticket string) error {
return c.Call(c.ctx, "settreasurypolicy", nil, key, policy, ticket)
}

// SetTSpendPolicy sets the specified tickets voting policy for a single tspend
// identified by its hash.
func (c *WalletRPC) SetTSpendPolicy(tSpend, policy, ticket string) error {
return c.Call(c.ctx, "settspendpolicy", nil, tSpend, policy, ticket)
}
55 changes: 54 additions & 1 deletion webapi/helpers.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2020 The Decred developers
// Copyright (c) 2020-2022 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

Expand All @@ -10,7 +10,9 @@ import (
"fmt"

"github.com/decred/dcrd/blockchain/stake/v4"
"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/wire"
"github.com/gin-gonic/gin"
Expand Down Expand Up @@ -52,6 +54,57 @@ agendaLoop:
return nil
}

func validTreasuryPolicy(policy map[string]string) error {
for key, choice := range policy {
pikey, err := hex.DecodeString(key)
if err != nil {
return fmt.Errorf("error decoding treasury key %q: %w", key, err)
}
if len(pikey) != secp256k1.PubKeyBytesLenCompressed {
return fmt.Errorf("treasury key %q is not 33 bytes", key)
}

err = validPolicyOption(choice)
if err != nil {
return err
}
}

return nil
}

func validTSpendPolicy(policy map[string]string) error {
for hash, choice := range policy {
if len(hash) != chainhash.MaxHashStringSize {
return fmt.Errorf("wrong tspend hash length, expected %d got %d",
chainhash.MaxHashStringSize, len(hash))
}

_, err := chainhash.NewHashFromStr(hash)
if err != nil {
return fmt.Errorf("error decoding tspend hash %q: %w", hash, err)
}

err = validPolicyOption(choice)
if err != nil {
return err
}
}

return nil
}

// validPolicyOption checks that policy is one of the valid values accepted by
// dcrwallet RPCs. Invalid values return an error.
func validPolicyOption(policy string) error {
switch policy {
case "yes", "no", "abstain", "invalid", "":
return nil
default:
return fmt.Errorf("%q is not a valid policy option", policy)
}
}

func validateSignature(reqBytes []byte, commitmentAddress string, c *gin.Context) error {
// Ensure a signature is provided.
signature := c.GetHeader("VSP-Client-Signature")
Expand Down
Loading

0 comments on commit 47838fe

Please sign in to comment.