Skip to content

Instantly share code, notes, and snippets.

@gz-c

gz-c/txt Secret

Last active January 28, 2018 09:18
Show Gist options
  • Save gz-c/87cbc2a534ca58e8b89d4926216a2cb4 to your computer and use it in GitHub Desktop.
Save gz-c/87cbc2a534ca58e8b89d4926216a2cb4 to your computer and use it in GitHub Desktop.
======Overview======
Passthrough is an alternate sending mechanism in teller.
At present, exchange.Exchange service sends coins from a skycoin wallet.
Passthrough will be added as a new service, exchange.Passthrough
Only one of these can be active at a time.
Use a configuration parameter to choose between standard exchange and passthrough.
The passthrough service will use the same database as the exchange service.
This is necessary because the scanner may send the same deposit to the exchanger twice,
so the exchanger keeps a record of deposits that it has seen to prevent resending coins.
If they had separate databases, then each service could potentially process the same deposit.
Only BTC will be accepted, but the design should leave room for supporting any currency.
All purchases will passthrough to C2CX, we will not buy from Cryptopia.
Based upon a deposit, sky will be bought from C2CX. The amount purchased will be
sent from a local wallet.
C2CX
======Architectural implementation======
exchange.Exchange does 3 general things, that may need to be refactored for reuse by exchange.Passthrough:
1. Provide an API for access to the database. This including binding an address, getting deposit status and metrics
2. Read deposits from the scanner and record a receipt of the deposit
3. Process recorded deposits sequentially: for each recorded deposit, send then confirm coins
For these 3 points, exchange.Passthrough would:
1. Need to implement a similar, if not the same, DB interface
2. Same mechanism of recording deposits
3. New logic for sending and confirming coins
TODO - It might make more sense to have one master state machine,
and implement exchange.Passthrough as a simpler object.
This is because exchange.Passthrough would perform the same send logic and only interjects a new state.
Regardless, some attention must be made to sharing the core logic and the send logic, because this code
is very specific in terms of processing order and error handling.
======Database changes======
This relates to points (1) and (2) in "Architectural implementation".
DepositInfo records information about a deposit in the exchange db.
A new field is required, tentatively "SendMethod", which indicates if the DepositInfo
was associated with a wallet OTC send or a passthrough send.
TODO: New metadata fields specific to passthrough deposits.
exchange.Storer's BindAddress should also save the SendMethod, but this value will not be used for anything.
It is only to keep a record of which send method was active at the time of binding.
Database changes require a migration tool. This should add the new fields to the struct and backfill the data.
======Send mechanism======
This describes point (3) in "Architectural implementation".
exchange.Passthrough will have a goroutine that performs sends, with the same structure as exchange.Exchange.
The send routine processes 1 deposit at a time. A simple state machine is used to advance the deposit through
its stages.
An incoming deposit will have a status of "status_wait_send".
In exchange.Exchange, the coins would be sent from the wallet, then the deposit moves to "status_wait_confirm",
then once the send transaction confirms, the deposit moves to "status_done" and the next deposit is processed.
TODO: The recorder creates new deposits with "status_wait_send", but the initial state for passthrough is
"status_wait_buy". The initial state should be "status_wait_decide".
The first thing the exchange and passthrough send loop will do is advance "status_wait_decide" to "status_wait_send"
and "status_wait_buy" respectively.
In exchange.Passthrough,
1. status_wait_decide -> status_wait_buy
2. status_wait_buy -> status_wait_send
3. status_wait_send -> status_wait_confirm
1. Only update DepositInfo.Status in the db
2. Buy the coins from the exchange
3. Send the coins from our wallet, the same as exchange.Exchange sends.
======Passthrough purchase implementation======
First, fix all open issues on exchange-api repository. It needs to be brought up to standard.
Step (1) from "Send mechanism" is a new step that also needs to be added to exchange.Exchange.
This step only updates the DepositInfo status. It effectively "claims" the deposit for either
the exchange.Exchange or exchange.Passthrough, whichever begins processing it.
Step (3) sends coins in the same way as exchange.Exchange. The amount to send is the amount
that was bought from the exchange.
TODO: This suggests breaking the "send" loop out into its own object:
- The "receiver" accepts deposits from the scanner and records them with "status_wait_decide". Deposits are written to another channel
- The "processer" accepts deposits from the "receiver"
- If in passthrough mode, update status to "status_wait_buy" and perform the purchase, deposits are written to another channel
- If in direct buy mode, update status to "status_wait_send", deposits are written to another channel
- The "sender" sends coins based upon the processor's output channel
Step (2) is the complex step.
PROBLEM: How to get the BTC onto the exchange
Either:
1. The user directly deposits to our exchange's deposit address
- The exchange's API does not expose info about individual deposits.
- We could scan for deposits to this address, but we'd have to change the logic of how we detect deposits.
- Normally a deposit BTC address is bound to a SKY receive address
- Instead, the user would have to bind their *originating* BTC address with the SKY receive address
- Users would not be able to send from an exchange and would have to use specific bitcoin wallets to make the purchase (wallets that let you specify the origin address)
2. The user deposits to their assigned BTC address (like we do currently) then we move the BTC to the exchange
- Our BTC deposit addresses are pregenerated from a seed
- There is no mechanism for sending BTC from teller and it is a significant amount of work to implement and test.
- We would need to treat the BTC deposits as a hot wallet, which has a high risk (although we would be resending the coins immediately)
- This introduces an extra delay in purchasing the coins (due to transaction and confirmation times)
- This adds overhead in transaction fees
3. The user deposits to their assigned BTC address (like we do currently) and we maintain a BTC balance on the exchange to buy with
- We must refill the exchange balance from time to time
- This requires sufficient BTC on hand to begin with (we have it)
- We need to access the deposited BTC easily enough to move it to the exchange from time to time
- Does not require implementing any large new components or have changes in the security architecture
- Logically corresponds with the OTC sales wallet, which must be refilled
(1) is untenable, users must be able to send from coinbase
(2) requires a significant amount of work, and increases security and stability risks
(3) requires some active management of the wallet balances, but this is tractable. Aligns with the existing implementation
(3) seems to be a better option than (2)
The implementation must be resumable from any state.
======status_wait_buy======
C2CX API Docs: https://api.c2cx.com/
Procedure:
A BTC deposit has been received.
Check our balance on the exchange:
https://api.c2cx.com/v1/getbalance
Note: This API returns "balance" and "frozen". "frozen" must be subtracted from "balance" to get available funds to spend.
Note: The values are returned as floats. Use https://godoc.org/github.com/shopspring/decimal to avoid floating point precision errors.
While our adjusted BTC balance is less than the deposited BTC amount, log a warning and sleep.
If the adjusted BTC balance is more than the deposited BTC amount, make the purchase.
Making the purchase:
Orders will be placed to match existing orders on the orderbook.
First, check the orderbook to get a list of sell orders on the orderbook.
Then, take the lowest sell order and place an order matching that amount.
If the sell order amount is higher than the remaining BTC deposit, cap it by the remaining BTC deposit amount.
Order placement API:
https://api.c2cx.com/v1/createOrder
After placing the order, wait for it to complete. If it does not complete with N duration (1 second?) issue a cancellation.
Wait for the cancellation if the order was cancelled, and try again.
Order status API:
https://api.c2cx.com/v1/getorderinfo
Repeat until the BTC deposit amount has been purchased from the exchange.
Upon completed purchase:
The status becomes "status_wait_send" and the deposit is sent to the "sender" component.
=======Frontend changes=======
Add an input box for the user to enter the amount they want to buy.
The backend should return a price quote for this amount, based upon the exchange order book.
If teller is in "direct buy" mode, the quote is the fixed SKY/BTC rate from the configuration.
TODO:
Textual changes to instructions on the page.
=======Exchange order book / price quote======
The C2CX order book must be monitored and queried in order to return a quote for a given amount of BTC to purchase.
This should be exposed as an API in teller.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment