diff --git a/README.md b/README.md index 1811a3c..92edbd9 100644 --- a/README.md +++ b/README.md @@ -6,82 +6,64 @@ ## Overview -User purchases a ticket, doesnt need any special conditions, indistinguishable -from solo ticket. User can then choose to use a VSP on a per-ticket basis. Once -the ticket is mined, and ideally before it has matured, the user sends the -ticket details + fee to a VSP, and the VSP will take the fee and vote in return. +dcrvsp is a from scratch implementation of a Voting Service Provider (VSP) for +the Decred network. -## Advantages +A VSP running dcrvsp can be used to vote on any ticket - tickets do not need to +be purchased with any special conditions such as dedicated outputs for paying +VSP fees. Fees are paid directly to the VSP with an independent on-chain +transaction. -### For Administrators +To use dcrvsp, ticket holders must prove ownership of their ticket with a +cryptographic signature, pay the fee requested by the VSP, and submit a private +key which enables the VSP to vote the ticket. Once this process is complete the +VSP will add the ticket to a pool of always-online voting wallets. -- bbolt db - no database admin required. -- Database is not used outside of dcrvsp server. -- No stakepoold. -- Client accountability. -- No need to use the same wallet seed on each voting wallet. -- Fees can change regularly - previously cached by wallet. +## Features -### For Users +- **API** - Tickets are registered with the VSP using a JSON HTTP API. For more + detail on the API and its usage, read [api.md](./docs/api.md) -- No redeem script to back up. -- No registration required. No email. -- Multiple VSPs on a single ticket. -- Voting preferences per ticket. -- Server accountability. -- No address reuse. -- VSP fees are paid "out of band", rather than being included in the ticket - itself. This makes solo tickets and VSP tickets indistinguishable from - eachother, enabling VSP users to purchase tickets in the same anonymity set - as solo stakers. +- **Web front-end** - A minimal, static, website providing pool stats. -## Design Decisions +- **Two-way accountability** - All dcrvsp requests must be signed with a private + key corresponding to the relevant ticket, and all dcrvsp responses are signed + by with a private key known only by the server. This enables both the client + and the server to prove to outside observers if their counterparty is + misbehaving. For more detail, and examples, read + [two-way-accountability.md](./docs/two-way-accountability.md). -- [gin-gonic](https://github.com/gin-gonic/gin) webserver. - - Success responses use HTTP status 200 and a JSON encoded body. - - Error responses use either HTTP status 500 or 400, and a JSON encoded error - in the body (eg. `{"error":"Description"}') -- [bbolt](https://github.com/etcd-io/bbolt) k/v database. - - Tickets are stored in a single bucket, using ticket hash as the key and a - json encoded representation of the ticket as the value. -- [wsrpc](https://github.com/jrick/wsrpc) for RPC communication between dcrvsp +- **Dynamic fees** - Clients must request a new fee address and amount for every + ticket. When these are given to a client, there is an associated expiry + period. If the fee is not paid in this period, the client must request a new + fee. This enables the VSP admin to change their fee as often as they like. + +## Implementation + +dcrvsp is built and tested on go 1.13 and 1.14, making use of the following +libraries: + +- [gin-gonic/gin](https://github.com/gin-gonic/gin) webserver. + +- [etcd-io/bbolt](https://github.com/etcd-io/bbolt) k/v database. + +- [jrick/wsrpc](https://github.com/jrick/wsrpc) for RPC communication with dcrd and dcrwallet. -## Architecture +## Deployment -- Single server running dcrvsp and dcrd. dcrd requires txindex so - `getrawtransaction` can be used. -- Multiple remote voting servers, each running dcrwallet and dcrd. dcrwallet - on these servers should be constantly unlocked and have voting enabled. +- Single server running dcrvsp and dcrd. dcrd on this server is used for fishing + ticket details out of the chain, and for broadcasting and checking the status + of fee transactions. `--txindex` is required so `getrawtransaction` can be + used. -## MVP Features +- A xpub key is provided to dcrvsp via config. dcrvsp will use this key to + derive a new addresses for each fee payments. It is recommended to export an + xpub from a cold wallet which is not a part of the dcrvsp deployment. -- When dcrvsp is started for the first time, it generates a ed25519 keypair and - stores it in the database. This key is used to sign all API responses, and the - signature is included in the response header `VSP-Server-Signature`. Error responses - are not signed. -- Every client request which references a ticket should include a HTTP header - `VSP-Client-Signature`. The value of this header must be a signature of the - request body, signed with the commitment address of the referenced ticket. -- An xpub key is provided to dcrvsp via config. dcrvsp will use this key to - derive addresses for fee payments. A new address is generated for each fee. -- VSP API as described in [dcrstakepool #574](https://github.com/decred/dcrstakepool/issues/574) - - Request fee amount (`GET /fee`) - - Request fee address (`POST /feeaddress`) - - Pay fee (`POST /payFee`) - - Ticket status (`GET /ticketstatus`) - - Set voting preferences (`POST /setvotechoices`) -- A minimal, static, web front-end providing pool stats and basic connection - instructions. -- Fees have an expiry period. If the fee is not paid within this period, the - client must request a new fee. This enables the VSP to alter its fee rate. - -## Future Features - -- Write database backups to disk periodically. -- Backup over http. -- Status check API call as described in [dcrstakepool #628](https://github.com/decred/dcrstakepool/issues/628). -- Consistency checking across connected wallets. +- Multiple remote voting servers, each running dcrwallet and dcrd. dcrwallet on + these servers should be constantly unlocked and have voting enabled. Three + voting servers in different physical locations are recommended for production. ## Backup diff --git a/docs/ANN.md b/docs/ANN.md new file mode 100644 index 0000000..eec32dd --- /dev/null +++ b/docs/ANN.md @@ -0,0 +1,48 @@ +# Announcement + +## Advantages vs dcrstakepool + +### For VSP Administrators + +- An instance of bbolt db on the front-end server is used as the single source + of truth: + - bbolt does not have the sys admin overhead associated with maintaining a + MySQL database. The database will be automatically created and maintained + by dcrvsp. + - The bbolt database is only accessed by dcrvsp. There is no need to open + additional ports on your front-end server for the voting wallets to access + the database. +- Voting wallet servers require only dcrwallet and dcrd. There is no longer a + VSP binary (ie. stakepoold) running on voting servers. +- Voting servers no longer need dcrd to be running with `--txindex`. +- No need to use the same wallet seed on each voting wallet. +- A new fee address and amount are requested for each ticket: + - Fee addresses are never reused. + - Fee amount can be changed freely. +- No emails or personal information are held. No need to worry about GDPR et al. + +### For VSP Users + +- No redeem script to back up. +- No registration required - no email, no password, no CAPTCHA. +- Voting preferences can be set for each individual ticket. +- No address reuse. +- VSP fees are paid independently of the ticket purchase, rather than being + included in the ticket: + - Multiple VSPs can be used for a single ticket. + - Fees can be paid using funds from a mixed account. + - VSP users can purchase tickets in the same anonymity set at solo stakers. + +### For the Decred Ecosystem + +- Solo tickets and VSP tickets are indistinguishable on-chain. +- Clients and servers can hold eachother accountable for actions. This enables + users to prove if a VSP is misbehaving, and VSPs to defend themselves if they + are falsely accused. + +## Disadvantages + +- Front-end is more important than before. +- Front-end requires dcrd with `--txindex`. +- Failure cases + - fee tx doesnt broadcast diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..9ac2461 --- /dev/null +++ b/docs/api.md @@ -0,0 +1,155 @@ +# API + +## General notes + +- Success responses use HTTP status 200 and a JSON encoded body. + +- Error responses use either HTTP status 500 or 400, and a JSON encoded error + in the body. For example `{"error":"Description"}`. + +- Requests which reference specific tickets need to be properly signed as + described in [two-way-accountability.md](./two-way-accountability.md). + +- Implementation of request and response types can be found in + [webapi/types.go](./webapi/types.go). + +## Expected usage + +### Get VSP public key + +Clients should first retrieve the VSP's public key so they can check the +signature on later API responses. A VSP should never change their public key so +it can be requested once and cached indefinitely. + +- `GET /api/pubkey` + + No request body. + + Response: + + ```json + { + "timestamp":1590509065, + "pubkey":"bLNwVVcda3LqRLv+m0J5sjd60+twjO/fuhcx8RUErDQ=" + } + ``` + +### Register ticket + +**Registering a ticket is a two step process. The VSP will not add a ticket to +its voting wallets unless both of these calls have succeeded.** + +Request fee amount and address for a ticket. The fee amount is only valid until +the expiration time has passed. + +- `POST /feeaddress` + + Request: + + ```json + { + "timestamp":1590509066, + "tickethash":"484a68f7148e55d05f0b64a29fe7b148572cb5272d1ce2438cf15466d347f4f4" + } + + ``` + + Response: + + ```json + { + "timestamp":1590509066, + "feeaddress":"Tsfkn6k9AoYgVZRV6ZzcgmuVSgCdJQt9JY2", + "fee":0.001, + "expiration":1590563759, + "request": {""} + } + ``` + +Provide the voting key for the ticket, voting preference, and a signed +transaction which pays the fee to the specified address. If the fee has expired, +this call will return an error and the client will need to request a new fee by +calling `/feeaddress` again. The VSP will not broadcast the fee transaction +until the ticket purchase has 6 confirmations, and it will not add the ticket to +its voting wallets until the fee transaction has 6 confirmations. + +- `POST /payfee` + + Request: + + ```json + { + "timestamp":1590509066, + "tickethash":"484a68f7148e55d05f0b64a29fe7b148572cb5272d1ce2438cf15466d347f4f4", + "feetx":"010000000125...737b266ffb9a93", + "votingkey":"PtWUJWhSXsM9ztPkdtH8REe91z7uoidX8dsMChJUZ2spagm7YvrNm", + "votechoices":{"headercommitments":"yes"} + } + ``` + + Response: + + ```json + { + "timestamp":1590509066, + "request": {""} + } + ``` + +### Information requests + +Clients can check the status of the server at any time. + +// TODO + +Clients can check the status of a ticket at any time after calling +`/feeaddress`. + +- `GET /ticketstatus` + + Request: + + ```json + { + "timestamp":1590509066, + "tickethash":"484a68f7148e55d05f0b64a29fe7b148572cb5272d1ce2438cf15466d347f4f4" + } + ``` + + Response: + + ```json + { + "timestamp":1590509066, + "status":"active", + "votechoices":{"headercommitments":"no"}, + "request": {""} + } + ``` + +### Update vote choices + +Clients can update the voting preferences of their ticket at any time after +after calling `/payfee`. + +- `POST /setvotechoices` + + Request: + + ```json + { + "timestamp":1590509066, + "tickethash":"484a68f7148e55d05f0b64a29fe7b148572cb5272d1ce2438cf15466d347f4f4", + "votechoices":{"headercommitments":"no"} + } + ``` + + Response: + + ```json + { + "timestamp":1590509066, + "votechoices":{"headercommitments":"no"}, + "request": {""} + } + ``` diff --git a/docs/two-way-accountability.md b/docs/two-way-accountability.md new file mode 100644 index 0000000..d09be4f --- /dev/null +++ b/docs/two-way-accountability.md @@ -0,0 +1,15 @@ +# Two-way Accountability + +- When dcrvsp is started for the first time, it generates a ed25519 keypair and + stores it in the database. This key is used to sign all API responses, and the + signature is included in the response header `VSP-Server-Signature`. + +- Every client request which references a ticket should include a HTTP header + `VSP-Client-Signature`. The value of this header must be a signature of the + request body, signed with the commitment address of the referenced ticket. + +## Examples + +### Server does not vote ticket + +### Client denies changing their voting preferences diff --git a/webapi/setvotechoices.go b/webapi/setvotechoices.go index 3f9dd99..044ade8 100644 --- a/webapi/setvotechoices.go +++ b/webapi/setvotechoices.go @@ -25,6 +25,8 @@ func setVoteChoices(c *gin.Context) { return } + // TODO: Return an error if we dont have a FeeTx for this ticket yet. + var setVoteChoicesRequest SetVoteChoicesRequest if err := binding.JSON.BindBody(rawRequest, &setVoteChoicesRequest); err != nil { log.Warnf("Bad setvotechoices request from %s: %v", c.ClientIP(), err)