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 - DCR only, no frontend, manual bond replacement #1480

Closed
wants to merge 12 commits into from

Conversation

chappjc
Copy link
Member

@chappjc chappjc commented Feb 20, 2022

Summary

This begins advancing the proof-of-concept from #1120 to a proper implementation in the product. This covers the first several steps outlined in https://github.com/orgs/decred/projects/2
This will likely come out of draft in the next few days. I have not tested it since the initial draft in #1120, which used different routes and has significant differences with the present work.

To make this transition smoother, the client and server retain all the legacy registration fee machinery, and the client's frontend continues to use the legacy 'register' protocol, with the 'postbond' method only accessible via the rpcserver (dexcctl).

This PR replaces the registration fee, which was paid to an address provided by the server operator, with a time-locked fidelity bond, which is redeemable by the user who posted the bond after a certain time. This creates a time cost to use DCRDEX instead of a monetary cost.

The DEX considers a user's bond as active when the bond script's lockTime (when it can be redeemed by the user) is at least bondExpiry in the future. That is, the bond remains locked for a period even after it becomes inactive for DEX purposes.

This also introduces an account tier system. Previously, paying the registration fee would mark the account paid, and it could be closed permanently if the user's violations exceed a certain score (binary and permanent). Now, posting a bond increases a user's tier, which offsets violations and in future work will also scale user order limits. When a bond expires, their tier is reduced. To trade, a user must have a tier > 0.

Bond Transaction Structure

There must be at least two outputs: the bond output (P2SH) and an account commitment (nulldata).

See:

Time-locked Bond (output 0)

Output 0 of the fidelity bond transaction must be a P2SH output paying to the bond script.

The redeem script looks similar to the refund path of an atomic swap script:

<locktime[4]> OP_CHECKLOCKTIMEVERIFY OP_DROP <pubkey[33]> OP_CHECKSIG

The above uses P2PK since nothing is gained from P2PKH on account of the need to reveal the script pubkey anyway for validation on the server. (The user must demonstrate ownership of the locked funds by signing a message with the private key corresponding to the pubkey in the TL redeemscript.)

Server validates that this P2SH output actually pays to the provided redeem script.

The pubkey referenced by the script need not be controlled by the user's wallet, but in this implementation it comes from their wallet. The client could even use their account pubkey in the script, but for security reasons it is best not to reuse pubkeys in this bond outputs, which are unspent on the blockchain for long periods. We may consider using a key pair from the client's HD keychain, but I'm leaning toward this being a wallet job.

The lockTime must be after a bond expiry time, the lockTimeThreshold. A bond becomes expired for a DEX account when duration until lockTime is less than a bondExpiry duration, which is on the order of a month. This, a user should create a bond with a lockTime at least bondExpiry in the future, but a practical lockTime would be more like 2x bondExpiry in the future so the account is active on the DEX for bondExpiry.

DEX Account commitment (output 1)

The account commitment OP_RETURN output should reference the account ID that corresponds to the account pubkey in the postbond request.

Having it in the raw like this allows the txn alone to identify the account, and without requiring the bond output pay to the account's private keys.

Protocol

New postbond route replaces register, but with no fee address and instead a txn with time-locked output (P2SH) and a DEX account commitment output (OP_RETURN).

postbond need not be used only for new accounts. Existing accounts may top-up / supplement their bond.

The config response should be consulted for bond requirements, including: expiry time, supported bond assets, amounts, and required confirmations.

The user may submit an initial postbond prior to broadcasting the bond transaction request containing the raw unsigned transaction for validation. The bond script to which the P2SH bond output pays must also be provided in the postbond request for validation by the DEX (correct script structure and lock time). The payload:

// PostBond should include the unsigned bond transaction for validation prior to
// broadcasting the signed transaction so the client hasn't needlessly locked
// funds if the transaction is rejected.
type PostBond struct {
	Signature
	AcctPubKey Bytes  `json:"acctPubKey"` // acctID = blake256(blake256(acctPubKey))
	AssetID    uint32 `json:"assetID"`
	BondTx     Bytes  `json:"bondTx"`
	BondScript Bytes  `json:"bondScript"`
	BondSig    Bytes  `json:"bondSig"` // account id signed by key from bond script's pubkey

	// LegacyFeeRefundAddr is an optional field for the client to specify a
	// return address so they may *request* a refund of their legacy
	// registration fee, if they had paid it.
	LegacyFeeRefundAddr string `json:"legacyFeeRefundAddress,omitempty"`
}

Server must use provided account pubkey to verify signed postbond request, otherwise the user could be spamming or putting up a bond for a bogus account or someone else's account.

Further validation is described in subsequent sections of this PR description.

The response payload:

// PostBondResult is the response to the client's PostBond request. If Confs is
// -1, the bond tx was not found on the network, but was otherwise validated.
type PostBondResult struct {
	Signature        // message is BondID | AccountID
	AccountID Bytes  `json:"accountid"`
	AssetID   uint32 `json:"assetID"`
	Amt       uint64 `json:"amt"`
	Expiry    int64  `json:"expiry"` // not locktime, but time when bond expires for dex
	BondID    Bytes  `json:"bondID"`
	Confs     int64  `json:"confs"`
	Tier      int64  `json:"tier"`
}

After validation of the provided data, the DEX attempts to locate the bond transaction on the asset network:

  • If it is not found, a PostBondResult is sent immediately with Confs=-1 set. The bond is NOT stored in the DB. This was a courtesy pre-validation. The user should then broadcast the transaction, wait for the required number of confirmations, and send postbond again.
  • If the located transaction has the required number of confirmations, the bond is stored in the DB as active, and a response is sent.
  • Otherwise, a response is sent with the observed confirmation count, and a coin waiter is started to watch for the txn to reach the required number of confirmations. The bond is stored in DB as pending. When the transaction reaches the required confirmations, it is flagged active in the DB and a bondconfirmed notification is sent.

When the client receives the initial successful response prior to broadcast, they should then broadcast the bond txn, wait for the required confirmations, and resubmit postbond.

Server must record all known bond txns and their amounts and expiry times. There is no explicit invalidation of bonds due to conduct, and instead a user's tier is a balance between bond "strength" (a function of the active bond total amounts) and their conduct score.

TODOs

See the project board at https://github.com/orgs/decred/projects/2.

Main items:

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).

Refining the bond amount. It's pretty clear the bond amount has to be at least an order of magnitude more than the previous registration fee since you get it back, and it's only an opportunity cost having it locked up. I'm thinking like 5 DCR per tier (the bond increment).

Related to bond amount, the violation weights can use rebalancing. Specifically, increase weight for "no swap as taker" (currently 11, increase to 18 or 20) and "no redeem as maker" (currently 7, increase to 12 to 14), the violations that lock counterparty funds (20hr / 8 hr, respectively). Maybe even higher.

Make the tier system scale up order size limits. This was planned outside of fidelity bond work, in terms of conduct score, but it's more intuitive in terms of tier, which is a function of both account bond level and score.

Scale penalty (score change) with amount locked due to violation (e.g. lots/10 * violation score). This was planned regardless of fidelity bonds, but it dovetails nicely with the tier system.

Open Questions

What change can we make to facilitate recovery/discovery of bonds if the client's DB is lost? What might be gained from deriving the private keys from the client's HD key chain instead of using keys from the wallet itself? Can the bond scripts be deterministic?

When and if accounts go away, would output 1 commit to a magic DEX network key instead of account ID?

Is this transaction structure and account commitment output sufficient to prevent txns from being dual purposed for other services with similar TL output structure?

What architectural and protocol changes might facilitate adding ETH support for time locking funds in some basic contract that server could recognize as a bond?

@buck54321
Copy link
Member

What change can we make to facilitate recovery/discovery of bonds if the client's DB is lost? What might be gained from deriving the private keys from the client's HD key chain instead of using keys from the wallet itself?

Even if we have the keys, we don't have the lock time, so we can't generate the scripts. We could maybe ask the server for their records, but that's not great.

Can the bond scripts be deterministic?

Could we base the script on a time offset or a block height offset, rather than an absolute time? Isn't that what DCP0003 gave us?

@chappjc
Copy link
Member Author

chappjc commented Aug 24, 2022

Broken up in to three pieces now:

@chappjc chappjc closed this Aug 24, 2022
@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.

2 participants