Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fidelity bonds foundation: integration, client db #1820

Merged
merged 5 commits into from
Dec 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions client/asset/dcr/dcr.go
Original file line number Diff line number Diff line change
Expand Up @@ -3202,11 +3202,11 @@ func (dcr *ExchangeWallet) EstimateRegistrationTxFee(feeRate uint64) uint64 {
// Output 2 is change, if any.
//
// The bond output's redeem script, which is needed to spend the bond output, is
// returned as the BondData field of the Bond. The bond output pays to a
// pubkeyhash script for a wallet address. Bond.RedeemTx is a backup transaction
// that spends the bond output after lockTime passes, paying to an address for
// the current underlying wallet; the bond private key (BondPrivKey) should
// normally be used to author a new transaction paying to a new address instead.
// returned as the Data field of the Bond. The bond output pays to a pubkeyhash
// script for a wallet address. Bond.RedeemTx is a backup transaction that
// spends the bond output after lockTime passes, paying to an address for the
// current underlying wallet; the bond private key (BondPrivKey) should normally
// be used to author a new transaction paying to a new address instead.
func (dcr *ExchangeWallet) MakeBondTx(ver uint16, amt uint64, lockTime time.Time,
bondKey *secp256k1.PrivateKey, acctID []byte) (*asset.Bond, error) {
if ver != 0 {
Expand Down Expand Up @@ -3320,7 +3320,7 @@ func (dcr *ExchangeWallet) MakeBondTx(ver uint16, amt uint64, lockTime time.Time
AssetID: BipID,
Amount: amt,
CoinID: toCoinID(&txid, 0),
BondData: bondScript,
Data: bondScript,
BondPrivKey: bondKey.Serialize(),
SignedTx: signedTxBytes,
UnsignedTx: unsignedTxBytes,
Expand Down
6 changes: 3 additions & 3 deletions client/asset/dcr/simnet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ func TestMakeBondTx(t *testing.T) {
t.Logf("bond txid %v\n", coinhash)
t.Logf("signed tx: %x\n", bond.SignedTx)
t.Logf("unsigned tx: %x\n", bond.UnsignedTx)
t.Logf("bond script: %x\n", bond.BondData)
t.Logf("bond script: %x\n", bond.Data)
t.Logf("redeem tx: %x\n", bond.RedeemTx)
bondMsgTx, err := msgTxFromBytes(bond.SignedTx)
if err != nil {
Expand All @@ -199,7 +199,7 @@ func TestMakeBondTx(t *testing.T) {

pkh := dcrutil.Hash160(pubkey.SerializeCompressed())

lockTimeUint, pkhPush, err := dexdcr.ExtractBondDetailsV0(bondOutVersion, bond.BondData)
lockTimeUint, pkhPush, err := dexdcr.ExtractBondDetailsV0(bondOutVersion, bond.Data)
if err != nil {
t.Fatalf("ExtractBondDetailsV0: %v", err)
}
Expand Down Expand Up @@ -238,7 +238,7 @@ func TestMakeBondTx(t *testing.T) {
waitNetwork() // wait for beta to see the new block (bond must be mined for RefundBond)

refundTxNew, err := wallet.RefundBond(context.Background(), bondVer, bond.CoinID,
bond.BondData, bond.Amount, priv)
bond.Data, bond.Amount, priv)
if err != nil {
t.Fatalf("RefundBond: %v", err)
}
Expand Down
12 changes: 9 additions & 3 deletions client/asset/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -458,9 +458,17 @@ type TxFeeEstimator interface {
EstimateSendTxFee(address string, value, feeRate uint64, subtract bool) (fee uint64, isValidAddress bool, err error)
}

// Broadcaster is a wallet that can send a raw transaction on the asset network.
type Broadcaster interface {
// SendTransaction broadcasts a raw transaction, returning its coin ID.
SendTransaction(rawTx []byte) ([]byte, error)
}

// Bonder is a wallet capable of creating and redeeming time-locked fidelity
// bond transaction outputs.
type Bonder interface {
Broadcaster

// MakeBondTx authors a DEX time-locked fidelity bond transaction for the
// provided amount, lock time, and dex account ID. An explicit private key
// type is used to guarantee it's not bytes from something else like a
Expand All @@ -469,8 +477,6 @@ type Bonder interface {
// RefundBond will refund the bond given the full bond output details and
// private key to spend it.
RefundBond(ctx context.Context, ver uint16, coinID, script []byte, amt uint64, privKey *secp256k1.PrivateKey) ([]byte, error)
// SendTransaction broadcasts a raw transaction, returning its coin ID.
SendTransaction(rawTx []byte) ([]byte, error)

// A RefundBondByCoinID may be created in the future to attempt to refund a
// bond by locating it on chain, i.e. without providing the amount or
Expand Down Expand Up @@ -743,7 +749,7 @@ type Bond struct {
AssetID uint32
Amount uint64
CoinID []byte
BondData []byte // additional data to interpret the bond e.g. redeem script, bond contract, etc.
Data []byte // additional data to interpret the bond e.g. redeem script, bond contract, etc.
BondPrivKey []byte // caller provided, but kept with the output
// SignedTx and UnsignedTx are the opaque (raw bytes) signed and unsigned
// bond creation transactions, in whatever encoding and funding scheme for
Expand Down
3 changes: 3 additions & 0 deletions client/cmd/dexcctl/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ var promptPasswords = map[string][]string{
"newwallet": {"App password:", "Wallet password:"},
"openwallet": {"App password:"},
"register": {"App password:"},
"postbond": {"App password:"},
"trade": {"App password:"},
"withdraw": {"App password:"},
"send": {"App password:"},
Expand All @@ -65,6 +66,8 @@ var promptPasswords = map[string][]string{
// cmd args at the specified index.
var optionalTextFiles = map[string]int{
"discoveracct": 1,
"bondassets": 1,
"postbond": 4,
"getdexconfig": 1,
"register": 3,
"newwallet": 2,
Expand Down
87 changes: 46 additions & 41 deletions client/core/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"

"decred.org/dcrdex/client/db"
"decred.org/dcrdex/server/account"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
)

Expand Down Expand Up @@ -67,39 +68,37 @@ func (c *Core) AccountDisable(pw []byte, addr string) error {
}

// AccountExport is used to retrieve account by host for export.
func (c *Core) AccountExport(pw []byte, host string) (*Account, error) {
func (c *Core) AccountExport(pw []byte, host string) (*Account, []*db.Bond, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not stick the bonds into Account?

crypter, err := c.encryptionKey(pw)
if err != nil {
return nil, codedError(passwordErr, err)
return nil, nil, codedError(passwordErr, err)
}
defer crypter.Close()
host, err = addrHost(host)
if err != nil {
return nil, newError(addressParseErr, "error parsing address: %w", err)
return nil, nil, newError(addressParseErr, "error parsing address: %w", err)
}

// Get the dexConnection and the dex.Asset for each asset.
c.connMtx.RLock()
dc, found := c.conns[host]
c.connMtx.RUnlock()
if !found {
return nil, newError(unknownDEXErr, "DEX: %s", host)
// Load account info, including all bonds, from DB.
acctInf, err := c.db.Account(host)
if err != nil {
return nil, nil, newError(unknownDEXErr, "dex db load error: %w", err)
}

// Unlock account if it is locked so that account id and privKey can be retrieved.
if err = dc.acct.unlock(crypter); err != nil {
return nil, codedError(acctKeyErr, err)
keyB, err := crypter.Decrypt(acctInf.EncKey())
if err != nil {
return nil, nil, err
}
dc.acct.keyMtx.RLock()
privKey := hex.EncodeToString(dc.acct.privKey.Serialize())
dc.acct.keyMtx.RUnlock()
privKey := secp256k1.PrivKeyFromBytes(keyB)
pubKey := privKey.PubKey()
accountID := account.NewID(pubKey.SerializeCompressed())

feeProofSig := ""
var feeProofSig string
var feeProofStamp uint64
if dc.acct.isPaid {
if acctInf.LegacyFeePaid {
accountProof, err := c.db.AccountProof(host)
if err != nil {
return nil, codedError(accountProofErr, err)
return nil, nil, codedError(accountProofErr, err)
}
feeProofSig = hex.EncodeToString(accountProof.Sig)
feeProofStamp = accountProof.Stamp
Expand All @@ -108,22 +107,22 @@ func (c *Core) AccountExport(pw []byte, host string) (*Account, error) {
// Account ID is exported for informational purposes only, it is not used during import.
acct := &Account{
Host: host,
AccountID: dc.acct.id.String(),
AccountID: accountID.String(),
// PrivKey: Note that we don't differentiate between legacy and
// hierarchical private keys here. On import, all keys are treated as
// legacy keys.
PrivKey: privKey,
DEXPubKey: hex.EncodeToString(dc.acct.dexPubKey.SerializeCompressed()),
Cert: hex.EncodeToString(dc.acct.cert),
FeeCoin: hex.EncodeToString(dc.acct.feeCoin),
PrivKey: hex.EncodeToString(keyB),
DEXPubKey: hex.EncodeToString(acctInf.DEXPubKey.SerializeCompressed()),
Cert: hex.EncodeToString(acctInf.Cert),
FeeCoin: hex.EncodeToString(acctInf.LegacyFeeCoin),
FeeProofSig: feeProofSig,
FeeProofStamp: feeProofStamp,
}
return acct, nil
return acct, acctInf.Bonds, nil
}

// AccountImport is used import an existing account into the db.
func (c *Core) AccountImport(pw []byte, acct Account) error {
func (c *Core) AccountImport(pw []byte, acct *Account, bonds []*db.Bond) error {
crypter, err := c.encryptionKey(pw)
if err != nil {
return codedError(passwordErr, err)
Expand All @@ -133,7 +132,24 @@ func (c *Core) AccountImport(pw []byte, acct Account) error {
if err != nil {
return newError(addressParseErr, "error parsing address: %w", err)
}
accountInfo := db.AccountInfo{Host: host}

// Don't try to create and import an account for a DEX that we are already
// connected to.
c.connMtx.RLock()
_, connected := c.conns[host]
c.connMtx.RUnlock()
if connected {
return errors.New("already connected")
}
_, err = c.db.Account(host) // may just not be in the conns map
if err == nil {
return errors.New("account already exists")
}

accountInfo := db.AccountInfo{
Host: host,
Bonds: bonds,
}

DEXpubKey, err := hex.DecodeString(acct.DEXPubKey)
if err != nil {
Expand All @@ -149,7 +165,7 @@ func (c *Core) AccountImport(pw []byte, acct Account) error {
return codedError(decodeErr, err)
}

accountInfo.FeeCoin, err = hex.DecodeString(acct.FeeCoin)
accountInfo.LegacyFeeCoin, err = hex.DecodeString(acct.FeeCoin)
if err != nil {
return codedError(decodeErr, err)
}
Expand All @@ -163,25 +179,14 @@ func (c *Core) AccountImport(pw []byte, acct Account) error {
return codedError(encryptionErr, err)
}

accountInfo.Paid = acct.FeeProofSig != "" && acct.FeeProofStamp != 0

// Make a connection to the DEX.
if dc, connected := c.connectAccount(&accountInfo); !connected {
if dc != nil {
dc.connMaster.Disconnect() // stop reconnect loop
c.connMtx.Lock()
delete(c.conns, dc.acct.host)
c.connMtx.Unlock()
}
return newError(accountVerificationErr, "Account not verified for host: %s", host)
}
accountInfo.LegacyFeePaid = acct.FeeProofSig != "" && acct.FeeProofStamp != 0

err = c.db.CreateAccount(&accountInfo)
if err != nil {
return codedError(dbErr, err)
}

if accountInfo.Paid {
if accountInfo.LegacyFeePaid {
sig, err := hex.DecodeString(acct.FeeProofSig)
if err != nil {
return codedError(decodeErr, err)
Expand All @@ -191,7 +196,7 @@ func (c *Core) AccountImport(pw []byte, acct Account) error {
Stamp: acct.FeeProofStamp,
Sig: sig,
}
err = c.db.AccountPaid(&accountProof)
err = c.db.StoreAccountProof(&accountProof)
if err != nil {
return codedError(dbErr, err)
}
Expand Down
Loading