Skip to content

fidelity bonds foundation: integration, client db#1820

Merged
chappjc merged 5 commits into
decred:masterfrom
chappjc:bond-client-integ
Dec 2, 2022
Merged

fidelity bonds foundation: integration, client db#1820
chappjc merged 5 commits into
decred:masterfrom
chappjc:bond-client-integ

Conversation

@chappjc

@chappjc chappjc commented Aug 24, 2022

Copy link
Copy Markdown
Member

Further breaking up #1480 for feasible review, this piece builds on both #1818 and #1819, and deals with:

  • Updating client core with methods to create and post bond using new asset methods from 1818 and comms from 1819, and machinery for monitoring and refunding active bonds (see client/core/bond.go)
  • Update client DB to manage bonds
  • Add dexc RPCs and HTTP handlers for posting bond

These are the commits beginning with client/db: storing, confirming, and refunding bonds.

To make this transition smoother, the client and server retain all the legacy registration fee machinery, and the client's continues to use the legacy registration system.

For follow-up work

Auto add bond in client.Core. There are comments in the code regarding an option to automatically postbond when previous bonds are about to expire on DEX. There are questions pertaining to timing, UI, and UX. Presently this PR requires using dexcctl to call the postbond RPC (or a direct http API call to the endpoint of the same name, which is created for said UI plans).

As suggested by @buck54321 on the prerequisite PRs, an asset.RenewBond method could be devised for said bond maintenance code to do teh refund and post of a new bond in a single txn.

See the project board at DCRDEX Fidelity Bonds.

@chappjc

chappjc commented Sep 16, 2022

Copy link
Copy Markdown
Member Author

This PR is based on both #1818 and #1819, and it can be used to test those pieces with dexcctl e.g. ./dexcctl --simnet -p a postbond 127.0.0.1:17273 1000000000 42.
As such, taking out of draft.
There are clearly many steps that need to follow this work to have bonds working in product, as described in the PRs, including bond maintenance, a possible RenewBond method, UI components, et.

@chappjc
chappjc marked this pull request as ready for review September 16, 2022 14:47
@chappjc
chappjc marked this pull request as draft October 12, 2022 16:10

@buck54321 buck54321 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I know this is still draft, but I was browsing and made some notes. Conceptually, this looks good. I didn't dig too deeply in the DB bits, but the interface looks about right, I think.

Comment thread dex/msgjson/types.go Outdated
DEXPubKey Bytes `json:"pubkey"`

RegFees map[string]*FeeAsset `json:"regFees"`
BondExpiry uint64 `json:"bondExpiry"`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does this need to be specified. We have the dex package consts.

@chappjc chappjc Nov 17, 2022

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Please see new comment here: #1819 (comment)

EDIT: wow this reply was part of a pending review for several days. GitHub can be so irritating.

Comment thread client/core/bond.go Outdated
Comment on lines +129 to +141
newRefundTx, err := wallet.RefundBond(ctx, bond.Version, bond.CoinID, bond.Script, bond.Amount, priv)
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 the user hasn't already manually refunded the bond, broadcast
// the refund txn. Mark it refunded and stop tracking regardless.
if bondAlreadySpent {
c.log.Warnf("Bond output not found, possibly already spent or never mined! "+
"Marking refunded. Backup refund transaction: %x", bond.RefundTx)
} else {
refundCoinID, err := wallet.SendTransaction(newRefundTx)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It's not clear to me what is the benefit of doing this in two steps instead of one.

RefundBond(...) (coinID string, err error)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It can be, but I was after a method that can prepare (as in advance of lock expiry) the bond transaction. The method is more versatile this way when you consider command line tools, recovery actions, bond export/backups etc. Not a big deal to change this later if needed though; it's just generally more useful this way, plus it is a bit more consistent with the mode of operation of the bond creation methods.

Comment thread client/core/bond.go
Comment on lines +299 to +343
if coinNotFound {
// Broadcast the bond and start waiting for confs.
c.log.Infof("Broadcasting bond %v (%s), script = %x.\n\n"+
"BACKUP refund tx paying to current wallet: %x\n\n",
coinIDStr, unbip(bond.AssetID), bond.BondData, bond.RedeemTx)
if _, err = wallet.SendTransaction(bond.SignedTx); err != nil {
c.log.Warnf("Failed to broadcast bond txn: %v")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Feels funny to use this as the indicator for whether to broadcast the bond. Lemme see if I've got the reasoning. If 1) this is the first time we're broadcasting, this should obviously work. If 2) we are picking up monitoring on a transaction that had already been broadcast, but has not been picked up by the server's node, then a) If we're using a full node, we should get an error, which will be logged as just a warning. If b) we're using an SPV node, there will be no error or log. In the end, the result is the same, worst that can happen is a warning. Sound right?

@chappjc chappjc Nov 5, 2022

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yah the reasoning is right, but fair point that this feels funny to start this off with a CoinNotFound -> YOLO broadcast. Since the broadcast should have been done in (c *Core) PostBond first, the actions here shouldn't be necessary except for weird edges like clearing full node mempool, spv wallet recovery (wiping data files), etc.
I suspect it would be alright without this though. Will revisit to see if I had other reasoning that I failed to doc.

Comment thread client/core/bond.go Outdated
@chappjc

chappjc commented Nov 5, 2022

Copy link
Copy Markdown
Member Author

I know this is still draft, but I was browsing and made some notes. Conceptually, this looks good. I didn't dig too deeply in the DB bits, but the interface looks about right, I think.

Thanks. It was ready for review previously, I just have it back in draft because I haven't re-tested. Very happy to have reviews, but won't be surprised if something small is broken and it doesn't work like it did before.

The two prior PRs were tested using this PR to actually use bonds to get registered on a server.

@chappjc

chappjc commented Nov 5, 2022

Copy link
Copy Markdown
Member Author

Only rebased, no substantive changes.

@chappjc

chappjc commented Nov 14, 2022

Copy link
Copy Markdown
Member Author

Just rebased and added some fixup commits, but not ready for re-review. Need to split the postbond route as per #1819 (comment)

@chappjc
chappjc force-pushed the bond-client-integ branch 5 times, most recently from 8fbcd9e to 47d86bc Compare November 15, 2022 16:54
@chappjc
chappjc marked this pull request as ready for review November 15, 2022 17:20
Comment thread client/core/core.go
Comment on lines +5770 to +5898
// Still pending on server. Start waiting for confs.
c.log.Debugf("Preparing to post pending bond %v (%s).", bondIDStr, symb)
c.monitorBondConfs(dc, assetBond(bond), bondAsset.Confs)

@chappjc chappjc Nov 15, 2022

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Tested this case by forcing a server side error on initial bond submission, then on reconnect, retry and success:

[INF] CORE: Authenticated connection to 127.0.0.1:17273, acct 9f8d5cbea78335c1c3c789ef1b08c193468adddc6a892e006433bd4ca82e5cb2, 0 active bonds, 0 active orders, 0 active matches, score -1, tier 
[DBG] CORE: Starting coin waiter for pending bond ca9a6c08dc4c973a0524b32a05fc9bf9b28182bed39223508aef190b18d229e3:0 (dcr), (known to server = false)
[DBG] CORE: Preparing to post pending bond ca9a6c08dc4c973a0524b32a05fc9bf9b28182bed39223508aef190b18d229e3:0 (dcr).
[DBG] CORE: Subscribing to the dcr_btc order book for 127.0.0.1:17273
[DBG] CORE[dcr_btc][book]: Processing 0 cached order notes
[INF] CORE: Bond confirmed ca9a6c08dc4c973a0524b32a05fc9bf9b28182bed39223508aef190b18d229e3:0 (dcr) with expire time of 2022-11-15 10:54:09 -0600 CST
[INF] CORE: Bond ca9a6c08dc4c973a0524b32a05fc9bf9b28182bed39223508aef190b18d229e3:0 (dcr) confirmed.
[INF] CORE: notify: |SUCCESS| (bondpost) BondConfirmed - New tier = 2.

Comment thread client/core/bond.go

reqConfs := bondAsset.Confs
bondCoinStr := coinIDString(bond.AssetID, bond.CoinID)
c.log.Infof("DEX %v has validated our bond %v (%s) with strength %d. %d confirmations required to trade.",

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

2022-11-15 10:50:09.795 [INF] CORE: DEX 127.0.0.1:17273 has validated our bond ca9a6c08dc4c973a0524b32a05fc9bf9b28182bed39223508aef190b18d229e3:0 (dcr) with strength 1. 1 confirmations required to trade.
2022-11-15 10:50:09.796 [INF] CORE: Broadcasting bond ca9a6c08dc4c973a0524b32a05fc9bf9b28182bed39223508aef190b18d229e3:0 (dcr) with lock time 2022-11-15 10:58:09 -0600 CST, data = 0421c57363b17576a914fc39ce555e3be56aca916c7f90d64ca0d2ecc9cd88ac.

BACKUP refund tx paying to current wallet: 0100000001e329d2180b19ef8a502392d3be8281b2f99bfc052ab324053a974cdc086c9aca0000000000feffffff0164b69a3b0000000000001976a914ad75136d6b4be5c97a2121028926db02414e3f3988ac21c57363000000000100ca9a3b0000000000000000ffffffff8c483045022100dfbf624943905b66aa86c74a2920ab6e0a66430599e278575798b63e49547d7f02204277efa7ae463af39a541078a4b38981c1e26bdf2b8dc989beddd6bf1fdd8868012102a9dbec0914201023d67974b8b6b225673cd7cb9e186e16e30936692980f56d70200421c57363b17576a914fc39ce555e3be56aca916c7f90d64ca0d2ecc9cd88ac

2022-11-15 10:50:09.800 [TRC] CORE: notify: |DATA| (balance) balance updated
2022-11-15 10:50:09.801 [INF] CORE: notify: |SUCCESS| (bondpost) BondConfirming - Waiting for 1 confirmations to post bond ca9a6c08dc4c973a0524b32a05fc9bf9b28182bed39223508aef190b18d229e3:0 (dcr) to 127.0.0.1:17273

Comment thread client/core/bond.go
c.log.Errorf("Failed to broadcast bond refund txn %v: %v", newRefundTx, err)
continue
}
c.log.Infof("Bond %v refunded in %v (%s)", bondIDStr, coinIDString(assetID, refundCoinID), unbip(bond.AssetID))

@chappjc chappjc Nov 15, 2022

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

[INF] CORE: Bond ca9a6c08dc4c973a0524b32a05fc9bf9b28182bed39223508aef190b18d229e3:0 (dcr) refunded in 0c21facb520a6e9ae79e81dbd9860a9c7e958b951cb12947e93a96b4c2ef07a3:0 (dcr)

Note that refundExpiredBond would be adapted to "bond maintenance" by renewing bonds directly instead of refunding.

Comment thread client/db/bolt/db.go
Comment on lines -106 to -112
redemptionReservesKey = []byte("redemptionReservesKey")
refundReservesKey = []byte("refundReservesKey")
byteTrue = encode.ByteTrue
backupDir = "backup"
disabledRateSourceKey = []byte("disabledRateSources")
walletDisabledKey = []byte("walletDisabled")
programKey = []byte("program")

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Sorry for bloating the diff here, but we had bucket keys, value keys, and values all mixed up in this var block.

@chappjc
chappjc force-pushed the bond-client-integ branch 4 times, most recently from 5647f97 to 86a584f Compare November 18, 2022 21:04

@martonp martonp left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

An issue I found:
If I initialize a new dexc, then post a bond I get no error. If I post a second bond, I will get this error:
failed to store account 6789063d3271c851419b0d7d6ddbd1d6392e5f61732c7bd0125013eea9997852 for dex 127.0.0.1:17273: failed to create account bucket

If I restart dexc, I can keep posting as many bonds as I want.

Comment thread client/core/types.go Outdated
Comment thread client/core/bond.go Outdated
Comment thread client/core/bond.go
return nil, newError(bondTimeErr, "lock time of %d has already passed the server's expiry time of %v (bond expiry %d)",
form.LockTime, expireTime, bondExpiry)
}
if lockTime.Add(-time.Minute).Before(expireTime) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Could use Sub instead of Add. Also, I think an hour gap would be better.. is a minute even enough to do a trade?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Could use Sub instead of Add. Also, I think an hour gap would be better.. is a minute even enough to do a trade?

An hour sounds good.

(Time).Add(Duration) Time takes a duration and gets a Time so I can use Before to be expressive. But (Time).Sub(Time) Duration compares the two Times and then I'd have to do < or > with the Durations. Docs for Sub say // To compute t-d for a duration d, use t.Add(-d)., so even though adding a negative is odd, I prefer to return a Time so I can use Before.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Oh, I couldn't just switch to an hour because the times are meant to be fairly fast on simnet. I'll make some network dependent limits shortly.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ah yeah, better to leave it short on simnet.

Comment thread client/core/bond.go Outdated
Comment thread client/rpcserver/types.go Outdated
@chappjc

chappjc commented Nov 25, 2022

Copy link
Copy Markdown
Member Author

An issue I found: If I initialize a new dexc, then post a bond I get no error. If I post a second bond, I will get this error: failed to store account 6789063d3271c851419b0d7d6ddbd1d6392e5f61732c7bd0125013eea9997852 for dex 127.0.0.1:17273: failed to create account bucket

If I restart dexc, I can keep posting as many bonds as I want.

Good catch! Will fix and update tests for this. Thanks for reviewing and testing!

@martonp martonp left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Working well, no issues while testing.

Comment thread client/webserver/api.go Outdated
Comment thread client/core/account.go

// 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
Copy Markdown
Collaborator

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?

Comment thread client/core/core.go Outdated
Comment thread client/core/core.go Outdated
Comment on lines +5900 to +5904
// EXPERIMENT: Server knows of a bond we do not! Store what we can for
// tier accounting, but we can't redeem it. We'll make en entry in
// dc.acct.bonds for tier accounting, but this may not work out. i.e.
// What handling in the bond monitor / refund loop do we need to discard
// these after expiry?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Shouldn't we make an attempt to find the keys that created this?
If it cannot be found, it could be removed when a tier change note or an expired bond note arrives.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Shouldn't we make an attempt to find the keys that created this?

We can search our DB, but they should have already been loaded into localBondMap, otherwise it would have already been redeemed and we don't need the keys, so this whole case feels like server spamming.

I think I just wanted to have it in our active bonds slice so we can locally match the bond-based tier that the server seems to have.

If it cannot be found, it could be removed when a tier change note or an expired bond note arrives.

That's a good plan. Just have the bond monitor / refund loop ignore anything without a PrivKey or a RefundTx, and let them stay until server says they are not longer active.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I meant to search using RefundBondByCoinID once it exists. But yes I think it's good to have in the active bonds slice.

Comment thread client/core/wallet.go
Comment thread client/db/bolt/db.go
Comment thread client/core/account.go Outdated
Comment thread client/core/bond.go Outdated
Comment thread client/core/bond.go
Comment thread client/core/bond.go Outdated
Comment thread client/core/bond.go Outdated
Comment thread client/core/core.go
Comment thread client/core/types.go Outdated
Comment thread client/db/bolt/db.go Outdated
Comment thread client/db/bolt/db.go
Comment thread client/db/bolt/db.go Outdated
@chappjc

chappjc commented Nov 28, 2022

Copy link
Copy Markdown
Member Author

Fix-ups just pushed are not yet tested but address review items.

@martonp martonp left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM

@chappjc

chappjc commented Dec 2, 2022

Copy link
Copy Markdown
Member Author

Rebased, squashed, and re-tested with both account creation with bond and adding bond to legacy accounts.

Existing legacy account

image

New account

image

Re-verified automatic refunds, and tier adjustment.

Next steps will be multifaceted. UI, updated messaging (suspended -> sub-tier or whatever), client bond maintenance, settings, switches, etc.

I will begin client db and core work for maintaining a certain tier with limits on total bonding amount.

@chappjc
chappjc merged commit 39030ca into decred:master Dec 2, 2022
@chappjc
chappjc deleted the bond-client-integ branch December 2, 2022 16:37
@chappjc chappjc added the bonds fidelity bonds label Feb 20, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bonds fidelity bonds

Projects

Development

Successfully merging this pull request may close these issues.

4 participants