Skip to content

Commit

Permalink
client/zcash: replace deprecated address methods with unified address…
Browse files Browse the repository at this point in the history
… parsing (#2237)

getnewaddress and getrawchangeaddress RPCs are deprecated. There is
no way to get a transparent address directly. Instead, get a unified
address, parse "receivers", and pick out the transparent address.

require 5.4.2 and handle RPC deprecations in harness.
  • Loading branch information
buck54321 authored Apr 10, 2023
1 parent 7d16f7f commit d1c4581
Show file tree
Hide file tree
Showing 8 changed files with 719 additions and 5 deletions.
21 changes: 21 additions & 0 deletions client/asset/btc/btc.go
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,10 @@ type BTCCloneCFG struct {
// inputs. This is used to accurately determine the size of the first swap
// in a chain when considered with the actual inputs.
InitTxSizeBase uint32
// AddrFunc is an optional function to produce new addresses. If AddrFunc
// is provided, the regular getnewaddress and getrawchangeaddress methods
// will not be used, and AddrFunc will be used instead.
AddrFunc func() (btcutil.Address, error)
// AddressDecoder is an optional argument that can decode an address string
// into btcutil.Address. If AddressDecoder is not supplied,
// btcutil.DecodeAddress will be used.
Expand All @@ -341,6 +345,10 @@ type BTCCloneCFG struct {
// NonSegwitSigner can be true if the transaction signature hash data is not
// the standard for non-segwit Bitcoin. If nil, txscript.
NonSegwitSigner TxInSigner
// ConnectFunc, if provided, is called by the RPC client at the end of the
// (*rpcClient).connect method. Errors returned by ConnectFunc will preclude
// the starting of goroutines associated with block and peer monitoring.
ConnectFunc func() error
// FeeEstimator provides a way to get fees given an RawRequest-enabled
// client and a confirmation target.
FeeEstimator func(context.Context, RawRequester, uint64) (uint64, error)
Expand Down Expand Up @@ -1117,6 +1125,8 @@ func newRPCWallet(requester RawRequester, cfg *BTCCloneCFG, parsedCfg *RPCWallet
legacyValidateAddressRPC: cfg.LegacyValidateAddressRPC,
manualMedianTime: cfg.ManualMedianTime,
omitRPCOptionsArg: cfg.OmitRPCOptionsArg,
addrFunc: cfg.AddrFunc,
connectFunc: cfg.ConnectFunc,
}
core.requesterV.Store(requester)
node := newRPCClient(core)
Expand Down Expand Up @@ -5589,6 +5599,17 @@ func (btc *baseWallet) scriptHashScript(contract []byte) ([]byte, error) {
return txscript.PayToAddrScript(addr)
}

// CallRPC is a method for making RPC calls directly on an underlying RPC
// client. CallRPC is not part of the wallet interface. Its intended use is for
// clone wallets to implement custom functionality.
func (btc *baseWallet) CallRPC(method string, args []interface{}, thing interface{}) error {
rpcCl, is := btc.node.(*rpcClient)
if !is {
return errors.New("wallet is not RPC")
}
return rpcCl.call(method, args, thing)
}

func scriptHashAddress(segwit bool, contract []byte, chainParams *chaincfg.Params) (btcutil.Address, error) {
if segwit {
return btcutil.NewAddressWitnessScriptHash(hashContract(segwit, contract), chainParams)
Expand Down
12 changes: 12 additions & 0 deletions client/asset/btc/rpcclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ type rpcCore struct {
legacyValidateAddressRPC bool
manualMedianTime bool
omitRPCOptionsArg bool
addrFunc func() (btcutil.Address, error)
connectFunc func() error
}

func (c *rpcCore) requester() RawRequester {
Expand Down Expand Up @@ -167,6 +169,11 @@ func (wc *rpcClient) connect(ctx context.Context, _ *sync.WaitGroup) error {
}
wc.log.Debug("Using a descriptor wallet.")
}
if wc.connectFunc != nil {
if err := wc.connectFunc(); err != nil {
return err
}
}
return nil
}

Expand Down Expand Up @@ -518,6 +525,8 @@ func (wc *rpcClient) changeAddress() (btcutil.Address, error) {
var addrStr string
var err error
switch {
case wc.addrFunc != nil:
return wc.addrFunc()
case wc.omitAddressType:
err = wc.call(methodChangeAddress, nil, &addrStr)
case wc.segwit:
Expand All @@ -532,6 +541,9 @@ func (wc *rpcClient) changeAddress() (btcutil.Address, error) {
}

func (wc *rpcClient) externalAddress() (btcutil.Address, error) {
if wc.addrFunc != nil {
return wc.addrFunc()
}
if wc.segwit {
return wc.address("bech32")
}
Expand Down
58 changes: 58 additions & 0 deletions client/asset/zec/shielded_rpc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// This code is available on the terms of the project LICENSE.md file,
// also available online at https://blueoakcouncil.org/license/1.0.0.

package zec

const (
methodZGetAddressForAccount = "z_getaddressforaccount"
methodZListUnifiedReceivers = "z_listunifiedreceivers"
methodZListAccounts = "z_listaccounts"
methodZGetNewAccount = "z_getnewaccount"
)

type zListAccountsResult struct {
Number uint32 `json:"account"`
Addresses []struct {
Diversifier uint32 `json:"diversifier"`
UnifiedAddr string `json:"ua"`
} `json:"addresses"`
}

// z_listaccounts
func zListAccounts(c rpcCaller) (accts []zListAccountsResult, err error) {
return accts, c.CallRPC(methodZListAccounts, nil, &accts)
}

// z_getnewaccount
func zGetNewAccount(c rpcCaller) (uint32, error) {
var res struct {
Number uint32 `json:"account"`
}
if err := c.CallRPC(methodZGetNewAccount, nil, &res); err != nil {
return 0, err
}
return res.Number, nil
}

type zGetAddressForAccountResult struct {
Account uint32 `json:"account"`
DiversifierIndex uint32 `json:"diversifier_index"`
ReceiverTypes []string `json:"receiver_types"`
Address string `json:"address"`
}

// z_getaddressforaccount account ( ["receiver_type", ...] diversifier_index )
func zGetAddressForAccount(c rpcCaller, acctNumber uint32, addrTypes []string) (unified *zGetAddressForAccountResult, err error) {
return unified, c.CallRPC(methodZGetAddressForAccount, []interface{}{acctNumber, addrTypes}, &unified)
}

type unifiedReceivers struct {
Transparent string `json:"p2pkh"`
Orchard string `json:"orchard"`
Sapling string `json:"sapling"`
}

// z_listunifiedreceivers unified_address
func zGetUnifiedReceivers(c rpcCaller, unifiedAddr string) (receivers *unifiedReceivers, err error) {
return receivers, c.CallRPC(methodZListUnifiedReceivers, []interface{}{unifiedAddr}, &receivers)
}
68 changes: 67 additions & 1 deletion client/asset/zec/zec.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"fmt"
"math"
"strconv"
"strings"

"decred.org/dcrdex/client/asset"
"decred.org/dcrdex/client/asset/btc"
Expand All @@ -31,13 +32,16 @@ const (
// structure.
defaultFee = 10
defaultFeeRateLimit = 1000
minNetworkVersion = 5000025
minNetworkVersion = 5040250 // v5.4.2
walletTypeRPC = "zcashdRPC"

mainnetNU5ActivationHeight = 1687104
testnetNU5ActivationHeight = 1842420
testnetSaplingActivationHeight = 280000
testnetOverwinterActivationHeight = 207500

transparentAddressType = "p2pkh"
orchardAddressType = "orchard"
)

var (
Expand Down Expand Up @@ -184,6 +188,12 @@ func NewWallet(cfg *asset.WalletConfig, logger dex.Logger, net dex.Network) (ass
SingularWallet: true,
UnlockSpends: true,
FeeEstimator: estimateFee,
ConnectFunc: func() error {
return connect(w)
},
AddrFunc: func() (btcutil.Address, error) {
return transparentAddress(w, addrParams, btcParams)
},
AddressDecoder: func(addr string, net *chaincfg.Params) (btcutil.Address, error) {
return dexzec.DecodeAddress(addr, addrParams, btcParams)
},
Expand Down Expand Up @@ -227,6 +237,62 @@ func NewWallet(cfg *asset.WalletConfig, logger dex.Logger, net dex.Network) (ass
return w, err
}

type rpcCaller interface {
CallRPC(method string, args []interface{}, thing interface{}) error
}

func transparentAddress(c rpcCaller, addrParams *dexzec.AddressParams, btcParams *chaincfg.Params) (btcutil.Address, error) {
const zerothAccount = 0
// One of the address types MUST be shielded.
addrRes, err := zGetAddressForAccount(c, zerothAccount, []string{transparentAddressType, orchardAddressType})
if err != nil {
return nil, err
}
receivers, err := zGetUnifiedReceivers(c, addrRes.Address)
if err != nil {
return nil, err
}
return dexzec.DecodeAddress(receivers.Transparent, addrParams, btcParams)
}

// connect is ZCash's BTCCloneCFG.ConnectFunc. Ensures that accounts are set
// up correctly.
func connect(c rpcCaller) error {
// Make sure we have zeroth and first account or are able to create them.
accts, err := zListAccounts(c)
if err != nil {
return fmt.Errorf("error listing ZCash accounts: %w", err)
}

createAccount := func(n uint32) error {
for _, acct := range accts {
if acct.Number == n {
return nil
}
}
acctNumber, err := zGetNewAccount(c)
if err != nil {
if strings.Contains(err.Error(), "zcashd-wallet-tool") {
return fmt.Errorf("account %d does not exist and cannot be created because wallet seed backup has not been acknowledged with the zcashd-wallet-tool utility", n)
}
return fmt.Errorf("error creating account %d: %w", n, err)
}
if acctNumber != n {
return fmt.Errorf("no account %d found and newly created account has unexpected account number %d", n, acctNumber)
}
return nil
}
if err := createAccount(0); err != nil {
return err
}
// When shielded pools are implemented, we'll use a separate, dedicated
// account that uses unified addresses with only and Orchard receiver type.
// if err := createAccount(1); err != nil {
// return err
// }
return nil
}

func zecTx(tx *wire.MsgTx) *dexzec.Tx {
return dexzec.NewTxFromMsgTx(tx, dexzec.MaxExpiryHeight)
}
Expand Down
2 changes: 1 addition & 1 deletion dex/testing/dcrdex/harness.sh
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ if [ $ZEC_ON -eq 0 ]; then
{
"base": "ZEC_simnet",
"quote": "BTC_simnet",
"lotSize": 1000000,
"lotSize": 1000000000,
"rateStep": 1000,
"epochDuration": ${EPOCH_DURATION},
"marketBuyBuffer": 1.2
Expand Down
Loading

0 comments on commit d1c4581

Please sign in to comment.