Skip to content

Commit

Permalink
finish bonds import/export
Browse files Browse the repository at this point in the history
  • Loading branch information
chappjc committed Mar 17, 2023
1 parent 8cf67fa commit a133419
Show file tree
Hide file tree
Showing 8 changed files with 78 additions and 28 deletions.
2 changes: 1 addition & 1 deletion client/asset/dcr/dcr.go
Original file line number Diff line number Diff line change
Expand Up @@ -3746,7 +3746,7 @@ func (dcr *ExchangeWallet) makeBondRefundTxV0(txid *chainhash.Hash, vout uint32,
pk := priv.PubKey().SerializeCompressed()
pkh := stdaddr.Hash160(pk)
if !bytes.Equal(pkh, pkhPush) {
return nil, fmt.Errorf("incorrect private key to spend the bond output")
return nil, asset.ErrIncorrectBondKey
}

redeemMsgTx := wire.NewMsgTx()
Expand Down
5 changes: 4 additions & 1 deletion client/asset/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,12 +157,15 @@ const (
// ErrNotEnoughConfirms is returned when a transaction is confirmed,
// but does not have enough confirmations to be trusted.
ErrNotEnoughConfirms = dex.ErrorKind("transaction does not have enough confirmations")
// ErrWalletTypeDisabled inidicates that a wallet type is no longer
// ErrWalletTypeDisabled indicates that a wallet type is no longer
// available.
ErrWalletTypeDisabled = dex.ErrorKind("wallet type has been disabled")
// ErrInsufficientBalance is returned when there is insufficient available
// balance for an operation, such as reserving funds for future bonds.
ErrInsufficientBalance = dex.ErrorKind("insufficient available balance")
// ErrIncorrectBondKey is returned when a provided private key is incorrect
// for a bond output.
ErrIncorrectBondKey = dex.ErrorKind("incorrect private key")

// InternalNodeLoggerName is the name for a logger that is used to fine
// tune log levels for only loggers using this name.
Expand Down
49 changes: 39 additions & 10 deletions client/core/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,16 +139,45 @@ func (c *Core) AccountImport(pw []byte, acct *Account, bonds []*db.Bond) error {
}

// 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")
// know, but try to import bonds.
if acctInfo, err := c.db.Account(host); err == nil {
haveBond := func(bond *db.Bond) bool {
for _, haveBond := range acctInfo.Bonds {
if bytes.Equal(haveBond.UniqueID(), bond.UniqueID()) {
return true
}
}
return false
}
var newLiveBonds int
for _, bond := range bonds {
if haveBond(bond) {
continue
}
if err = c.db.AddBond(host, bond); err != nil {
return fmt.Errorf("importing bond: %v", err)
}
acctInfo.Bonds = append(acctInfo.Bonds, bond) // for connectDEX below
if !bond.Refunded {
newLiveBonds++
}
}
if newLiveBonds == 0 {
return nil
}
c.log.Infof("Imported %d new unspent bonds", newLiveBonds)
if dc, connected, _ := c.dex(host); connected {
c.disconnectDEX(dc)
// TODO: less heavy handed approach to append to
// dc.acct.{bonds,pendingBonds,expiredBonds}, using server config...
}
dc, err := c.connectDEX(acctInfo)
if err != nil {
return err
}
c.addDexConnection(dc)
c.initializeDEXConnections(crypter)
return nil
}

accountInfo := db.AccountInfo{
Expand Down
19 changes: 15 additions & 4 deletions client/core/bond.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,11 +345,22 @@ func (c *Core) rotateBonds(ctx context.Context) {
refundCoin, err := wallet.RefundBond(ctx, bond.Version, bond.CoinID, bond.Data, bond.Amount, priv)
priv.Zero()
bondAlreadySpent = errors.Is(err, asset.CoinNotFoundError) // or never mined!
if err != nil && !bondAlreadySpent {
c.log.Errorf("Failed to generate bond refund tx: %v", err)
continue
if err != nil {
if errors.Is(err, asset.ErrIncorrectBondKey) { // imported account and app seed is different
c.log.Warnf("Private key to spend bond %v is not available. Broadcasting backup refund tx.", bondIDStr)
refundCoinID, err := wallet.SendTransaction(bond.RefundTx)
if err != nil {
c.log.Errorf("Failed to broadcast bond refund txn %x: %v", bond.RefundTx, err)
continue
}
refundCoinStr, _ = asset.DecodeCoinID(bond.AssetID, refundCoinID)
} else if !bondAlreadySpent {
c.log.Errorf("Failed to generate bond refund tx: %v", err)
continue
}
} else {
refundCoinStr, refundVal = refundCoin.String(), refundCoin.Value()
}
refundCoinStr, refundVal = refundCoin.String(), refundCoin.Value()
}
// RefundBond increases reserves when it spends the bond, adding to
// the wallet's balance (available or immature).
Expand Down
23 changes: 12 additions & 11 deletions client/db/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,18 +91,19 @@ func BondUID(assetID uint32, bondCoinID []byte) []byte {
return hashKey(append(uint32Bytes(assetID), bondCoinID...))
}

// Bond is stored in a sub-bucket of an account bucket.
// Bond is stored in a sub-bucket of an account bucket. The dex.Bytes type is
// used for certain fields so that the data marshals to/from hexadecimal.
type Bond struct {
Version uint16 `json:"ver"`
AssetID uint32 `json:"asset"`
CoinID []byte `json:"coinID"`
UnsignedTx []byte `json:"utx"`
SignedTx []byte `json:"stx"` // can be obtained from msgjson.Bond.CoinID
Data []byte `json:"data"` // e.g. redeem script
Amount uint64 `json:"amt"`
LockTime uint64 `json:"lockTime"`
KeyIndex uint32 `json:"keyIndex"` // child key index for HD path: m / hdKeyPurposeBonds / assetID' / bondIndex
RefundTx []byte `json:"refundTx"` // pays to wallet that created it - only a backup for emergency!
Version uint16 `json:"ver"`
AssetID uint32 `json:"asset"`
CoinID dex.Bytes `json:"coinID"`
UnsignedTx dex.Bytes `json:"utx"`
SignedTx dex.Bytes `json:"stx"` // can be obtained from msgjson.Bond.CoinID
Data dex.Bytes `json:"data"` // e.g. redeem script
Amount uint64 `json:"amt"`
LockTime uint64 `json:"lockTime"`
KeyIndex uint32 `json:"keyIndex"` // child key index for HD path: m / hdKeyPurposeBonds / assetID' / bondIndex
RefundTx dex.Bytes `json:"refundTx"` // pays to wallet that created it - only a backup for emergency!

Confirmed bool `json:"confirmed"` // if reached required confs according to server, not in serialization
Refunded bool `json:"refunded"` // not in serialization
Expand Down
3 changes: 3 additions & 0 deletions client/webserver/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,9 @@ func (s *WebServer) apiAccountExport(w http.ResponseWriter, r *http.Request) {
s.writeAPIError(w, fmt.Errorf("error exporting account: %w", err))
return
}
if bonds == nil {
bonds = make([]*db.Bond, 0) // marshal to [], not null
}
w.Header().Set("Connection", "close")
res := &struct {
OK bool `json:"ok"`
Expand Down
1 change: 1 addition & 0 deletions client/webserver/site/src/js/dexsettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ export default class DexSettingsPage extends BasePage {
Doc.show(page.exportAccountErr)
return
}
res.account.bonds = res.bonds // maintain backward compat of JSON file
const accountForExport = JSON.parse(JSON.stringify(res.account))
const a = document.createElement('a')
a.setAttribute('download', 'dcrAccount-' + host + '.json')
Expand Down
4 changes: 3 additions & 1 deletion client/webserver/site/src/js/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,9 +244,11 @@ export default class SettingsPage extends BasePage {
Doc.show(page.importAccountErr)
return
}
const { bonds = [], ...acctInf } = account
const req = {
pw: pw,
account: account
account: acctInf,
bonds: bonds
}
const loaded = app().loading(this.body)
const importResponse = await postJSON('/api/importaccount', req)
Expand Down

0 comments on commit a133419

Please sign in to comment.