From a5c472e815e1e945ffdb216adac2dfe86207edd5 Mon Sep 17 00:00:00 2001 From: George Tankersley Date: Wed, 22 May 2019 16:22:23 -0400 Subject: [PATCH] swag/server: implement z_sendmany call --- swag/server.go | 104 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 96 insertions(+), 8 deletions(-) diff --git a/swag/server.go b/swag/server.go index 0b9377e..e55c68f 100644 --- a/swag/server.go +++ b/swag/server.go @@ -2,23 +2,111 @@ package swag import ( "context" + "encoding/json" + "errors" + "strconv" + "strings" "github.com/btcsuite/btcd/rpcclient" "github.com/zcash-hackworks/zcon1-swag-api/api" ) +const ( + swagFromAddress = "TODO HARDCODED FROM ADDRESS" + swagDefaultAmount = 1 +) + type Server struct { + rpc *rpcclient.Client } -func NewServer(dbPath string, zc *rpcclient.Client) (api.SwagAPIServer, error) { - return &Server{}, nil -} - -func (s *Server) Redeem(ctx context.Context, req *api.Request) (*api.Response, error) { - return &api.Response{ - Success: true, - Msg: "hello", +func NewServer(dbPath string, rpc *rpcclient.Client) (api.SwagAPIServer, error) { + // TODO: CREATE AND POPULATE THE DATABASE + return &Server{ + rpc: rpc, }, nil } +func (s *Server) Redeem(ctx context.Context, req *api.Request) (*api.Response, error) { + + // TODO: QUERY THE DATABASE + + return s.TriggerSend(ctx, req) +} + func (s *Server) GracefulStop() {} + +type zSendManyDest struct { + Address string `json:"address"` + Amount int64 `json:"amount"` +} + +func (s *Server) TriggerSend(ctx context.Context, req *api.Request) (*api.Response, error) { + // z_sendmany "fromaddress" [{"address":... ,"amount":...},...] ( minconf ) ( fee ) + // + // Send multiple times. Amounts are decimal numbers with at most 8 digits of precision. + // Change generated from a taddr flows to a new taddr address, while change generated from a zaddr returns to itself. + // When sending coinbase UTXOs to a zaddr, change is not allowed. The entire value of the UTXO(s) must be consumed. + // Before Sapling activates, the maximum number of zaddr outputs is 54 due to transaction size limits. + // + // Arguments: + // 1. "fromaddress" (string, required) The taddr or zaddr to send the funds from. + // 2. "amounts" (array, required) An array of json objects representing the amounts to send. + // [{ + // "address":address (string, required) The address is a taddr or zaddr + // "amount":amount (numeric, required) The numeric amount in ZEC is the value + // "memo":memo (string, optional) If the address is a zaddr, raw data represented in hexadecimal string format + // }, ... ] + // 3. minconf (numeric, optional, default=1) Only use funds confirmed at least this many times. + // 4. fee (numeric, optional, default=0.0001) The fee amount to attach to this transaction. + // + // Result: + // "operationid" (string) An operationid to pass to z_getoperationstatus to get the result of the operation. + // + // Examples: + // > zcash-cli z_sendmany "t1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd" '[{"address": "ztfaW34Gj9FrnGUEf833ywDVL62NWXBM81u6EQnM6VR45eYnXhwztecW1SjxA7JrmAXKJhxhj3vDNEpVCQoSvVoSpmbhtjf" ,"amount": 5.0}]' + // > curl --user myusername --data-binary '{"jsonrpc": "1.0", "id":"curltest", "method": "z_sendmany", "params": ["t1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd", [{"address": "ztfaW34Gj9FrnGUEf833ywDVL62NWXBM81u6EQnM6VR45eYnXhwztecW1SjxA7JrmAXKJhxhj3vDNEpVCQoSvVoSpmbhtjf" ,"amount": 5.0}]] }' -H 'content-type: text/plain;' http://127.0.0.1:8232/ + + // Construct raw JSON-RPC params. This is VERY UGLY because the btcd library lacks direct support for our RPC calls. + + zDests := make([]zSendManyDest, 1) + zDests[0] = zSendManyDest{ + Address: req.Address, + Amount: swagDefaultAmount, + } + + encodedDests, err := json.Marshal(zDests) + if err != nil { + return &api.Response{ + Success: false, + Msg: "Couldn't initiate transaction successfully.", + }, err + } + + params := make([]json.RawMessage, 2) + params[0] = json.RawMessage("\"" + swagFromAddress + "\"") + params[1] = json.RawMessage(encodedDests) + result, rpcErr := s.rpc.RawRequest("z_sendmany", params) + + // For some reason, the error responses are not JSON + if rpcErr != nil { + errParts := strings.SplitN(rpcErr.Error(), ":", 2) + errMsg := strings.TrimSpace(errParts[1]) + _, err = strconv.ParseInt(errParts[0], 10, 32) + if err != nil { + // This should never happen. We can't panic here, but it's that class of error. + // This is why we need integration testing to work better than regtest currently does. TODO. + return nil, errors.New("SendTransaction couldn't parse error code") + } + + return &api.Response{ + Success: false, + Msg: errMsg, + }, rpcErr + } + + return &api.Response{ + Success: true, + Msg: string(result), + }, nil +}