Skip to content

Commit

Permalink
wallet: No taking voting addresses.
Browse files Browse the repository at this point in the history
Prevent methods from getting voting account addresses. These addresses
should not be sent funds and are intended to be used only for submission
scripts and signatures.
  • Loading branch information
JoeGruffins committed Feb 2, 2022
1 parent 9a8688e commit d994556
Show file tree
Hide file tree
Showing 2 changed files with 139 additions and 35 deletions.
142 changes: 113 additions & 29 deletions rpc/grpc_example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@ import (
"crypto/tls"
"crypto/x509"
"encoding/hex"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"

"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"

pb "decred.org/dcrwallet/v2/rpc/walletrpc"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
Expand All @@ -24,29 +29,35 @@ var (
)

func main() {
if err := run(); err != nil {
fmt.Println(err)
os.Exit(1)
}
os.Exit(0)
}

func run() error {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
serverCAs := x509.NewCertPool()
serverCert, err := ioutil.ReadFile(certificateFile)
if err != nil {
fmt.Println(err)
return
return err
}
if !serverCAs.AppendCertsFromPEM(serverCert) {
fmt.Printf("no certificates found in %s\n", certificateFile)
return
return fmt.Errorf("no certificates found in %s\n", certificateFile)
}
keypair, err := tls.LoadX509KeyPair(walletClientCertFile, walletClientKeyFile)
if err != nil {
fmt.Println(err)
return
return err
}
creds := credentials.NewTLS(&tls.Config{
Certificates: []tls.Certificate{keypair},
RootCAs: serverCAs,
})
conn, err := grpc.Dial("localhost:19111", grpc.WithTransportCredentials(creds))
if err != nil {
fmt.Println(err)
return
return err
}
defer conn.Close()
c := pb.NewWalletServiceClient(conn)
Expand All @@ -55,33 +66,34 @@ func main() {
AccountNumber: 0,
RequiredConfirmations: 1,
}
balanceResponse, err := c.Balance(context.Background(), balanceRequest)
balanceResponse, err := c.Balance(ctx, balanceRequest)
if err != nil {
fmt.Println(err)
return
return err
}

fmt.Println("Spendable balance: ", dcrutil.Amount(balanceResponse.Spendable))

decodedTx := func(tx string) {
decodedTx := func(tx string) error {
rawTx, _ := hex.DecodeString(tx)
dmClient := pb.NewDecodeMessageServiceClient(conn)
decodeRequest := &pb.DecodeRawTransactionRequest{
SerializedTransaction: rawTx,
}
decodeResponse, err := dmClient.DecodeRawTransaction(context.Background(), decodeRequest)
decodeResponse, err := dmClient.DecodeRawTransaction(ctx, decodeRequest)
if err != nil {
fmt.Println(err)
return
return err
}

// tj, _ := json.MarshalIndent(decodeResponse.Transaction, "", " ")
// fmt.Println(string(tj))
fmt.Println(prototext.MarshalOptions{Multiline: true}.Format(decodeResponse.Transaction))
return nil
}

for _, tx := range txns {
decodedTx(tx)
if err := decodedTx(tx); err != nil {
return err
}
}

wsClient := pb.NewWalletServiceClient(conn)
Expand All @@ -91,39 +103,98 @@ func main() {
// testing wallet.
scriptB, err := hex.DecodeString(script)
if err != nil {
fmt.Println(err)
return
return err
}
importScriptRequest := &pb.ImportScriptRequest{
Script: scriptB,
}
_, err = wsClient.ImportScript(context.Background(), importScriptRequest)
_, err = wsClient.ImportScript(ctx, importScriptRequest)
if err != nil {
fmt.Println(err)
return
return err
}
}

// Add an owned address to validated addresses.
nextAddressResp, err := wsClient.NextAddress(context.Background(), new(pb.NextAddressRequest))
const acctName = "testing_acct"
var (
seed [32]byte
acctPass = []byte("pass123")
acctN uint32
)
seed[31] = 1

// Import a voting account from seed.
importVAFSRequest := &pb.ImportVotingAccountFromSeedRequest{
Seed: seed[:],
Name: acctName,
Passphrase: acctPass,
}
importVAFSReqResp, err := wsClient.ImportVotingAccountFromSeed(ctx, importVAFSRequest)
if err != nil {
fmt.Println(err)
return
if status.Code(err) != codes.AlreadyExists {
return err
}
acctsResp, err := wsClient.Accounts(ctx, &pb.AccountsRequest{})
if err != nil {
return err
}
var found bool
for _, acct := range acctsResp.Accounts {
if acct.AccountName == acctName {
found = true
acctN = acct.AccountNumber
}
}
if !found {
return errors.New("testing account not found")
}
} else {
acctN = importVAFSReqResp.Account
}
validateAddrs = append(validateAddrs, nextAddressResp.Address)

fmt.Println("Testing account number is", acctN)
fmt.Println()

// Add requests for addresses from the voting account which should fail.
nextAddrs = append(nextAddrs,
nextAddr{name: "imported voting account external", acct: acctN, branch: 0, wantErr: true},
nextAddr{name: "imported voting account internal", acct: acctN, branch: 1, wantErr: true})

for _, addr := range nextAddrs {
// Add an owned address to validated addresses.
nextAddrReq := &pb.NextAddressRequest{
Account: addr.acct,
Kind: addr.branch,
GapPolicy: pb.NextAddressRequest_GAP_POLICY_IGNORE,
}
nextAddressResp, err := wsClient.NextAddress(context.Background(), nextAddrReq)
if addr.wantErr {
if err != nil {
continue
}
return fmt.Errorf("nextAddrs: expected error for %s", addr.name)
}
if err != nil {
return err
}
validateAddrs = append(validateAddrs, nextAddressResp.Address)
}

fmt.Println("ValidateAddress...")
fmt.Println()

for _, addr := range validateAddrs {
validateAddrRequest := &pb.ValidateAddressRequest{
Address: addr,
}
validateAddrResp, err := wsClient.ValidateAddress(context.Background(), validateAddrRequest)
validateAddrResp, err := wsClient.ValidateAddress(ctx, validateAddrRequest)
if err != nil {
fmt.Println(err)
return
return err
}
fmt.Println(validateAddrResp.ScriptType)
fmt.Println(prototext.MarshalOptions{Multiline: true}.Format(validateAddrResp))
}

return nil
}

var txns = []string{
Expand Down Expand Up @@ -168,4 +239,17 @@ var validateAddrs = []string{
"TcfdqCrK2fiFJBZnGj5N6xs6rMsbQBsJBYf", // TSPEND
"TcrzaAVMbFURm1PpukWru8yE2uBTjvQePoa", // 2 of 2 multisig
"TckSpBht36nMZgnDDjv7xaHUrgCyJpxQiLA", // NonStandard

"TsSAzyUaa2KSytEuu1hiGdeYJqu4om63ZQb", // Address at imported account/0/0
}

type nextAddr struct {
name string
acct uint32
branch pb.NextAddressRequest_Kind
wantErr bool
}

var nextAddrs = []nextAddr{{
name: "default external",
}}
32 changes: 26 additions & 6 deletions wallet/addresses.go
Original file line number Diff line number Diff line change
Expand Up @@ -742,22 +742,33 @@ func (w *Wallet) markUsedAddress(op errors.Op, dbtx walletdb.ReadWriteTx, addr u
func (w *Wallet) NewExternalAddress(ctx context.Context, account uint32, callOpts ...NextAddressCallOption) (stdaddr.Address, error) {
const op errors.Op = "wallet.NewExternalAddress"

// Imported voting accounts must not be used for normal transactions.
if err := w.notVotingAcct(ctx, op, account); err != nil {
return nil, err
}

accountName, _ := w.AccountName(ctx, account)
return w.nextAddress(ctx, op, w.persistReturnedChild(ctx, nil),
accountName, account, udb.ExternalBranch, callOpts...)
}

// NewInternalAddress returns an internal address.
func (w *Wallet) NewInternalAddress(ctx context.Context, account uint32, callOpts ...NextAddressCallOption) (stdaddr.Address, error) {
const op errors.Op = "wallet.NewExternalAddress"
const op errors.Op = "wallet.NewInternalAddress"

// Imported voting accounts must not be used for normal transactions.
if err := w.notVotingAcct(ctx, op, account); err != nil {
return nil, err
}

accountName, _ := w.AccountName(ctx, account)
return w.nextAddress(ctx, op, w.persistReturnedChild(ctx, nil),
accountName, account, udb.InternalBranch, callOpts...)
}

func (w *Wallet) newChangeAddress(ctx context.Context, op errors.Op, persist persistReturnedChildFunc,
accountName string, account uint32, gap gapPolicy) (stdaddr.Address, error) {
// notVotingAcct errors if an account is a special voting type. This account
// should not be used to receive funds.
func (w *Wallet) notVotingAcct(ctx context.Context, op errors.Op, account uint32) error {
var accountType uint8
if err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error {
ns := dbtx.ReadBucket(waddrmgrNamespaceKey)
Expand All @@ -768,11 +779,20 @@ func (w *Wallet) newChangeAddress(ctx context.Context, op errors.Op, persist per
accountType = props.AccountType
return nil
}); err != nil {
return nil, errors.E(op, err)
return errors.E(op, err)
}
// Imported voting accounts must not be used for change.
if udb.IsImportedVoting(accountType) {
return nil, errors.E(op, errors.Invalid, "cannot use voting accounts for change addresses")
return errors.E(op, errors.Invalid, "cannot use voting accounts for normal transactions")
}
return nil
}

func (w *Wallet) newChangeAddress(ctx context.Context, op errors.Op, persist persistReturnedChildFunc,
accountName string, account uint32, gap gapPolicy) (stdaddr.Address, error) {

// Imported voting accounts must not be used for change.
if err := w.notVotingAcct(ctx, op, account); err != nil {
return nil, err
}

// Addresses can not be generated for the imported account, so as a
Expand Down

0 comments on commit d994556

Please sign in to comment.