forked from stellar/go
-
Notifications
You must be signed in to change notification settings - Fork 0
/
client.go
178 lines (142 loc) · 3.84 KB
/
client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
package stellarcore
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"path"
"strings"
"time"
proto "github.com/leevlad/go/protocols/stellarcore"
"github.com/leevlad/go/support/errors"
)
// Client represents a client that is capable of communicating with a
// stellar-core server using HTTP
type Client struct {
// HTTP is the client to use when communicating with stellar-core. If nil,
// http.DefaultClient will be used.
HTTP HTTP
// URL of Stellar Core server to connect.
URL string
}
// Info calls the `info` command on the connected stellar core and returns the
// provided response
func (c *Client) Info(ctx context.Context) (resp *proto.InfoResponse, err error) {
req, err := c.simpleGet(ctx, "info", nil)
if err != nil {
err = errors.Wrap(err, "failed to create request")
return
}
hresp, err := c.http().Do(req)
if err != nil {
err = errors.Wrap(err, "http request errored")
return
}
defer hresp.Body.Close()
if !(hresp.StatusCode >= 200 && hresp.StatusCode < 300) {
err = errors.New("http request failed with non-200 status code")
return
}
err = json.NewDecoder(hresp.Body).Decode(&resp)
if err != nil {
err = errors.Wrap(err, "json decode failed")
return
}
return
}
// SetCursor calls the `setcursor` command on the connected stellar core
func (c *Client) SetCursor(ctx context.Context, id string, cursor int32) error {
req, err := c.simpleGet(ctx, "setcursor", url.Values{
"id": []string{id},
"cursor": []string{fmt.Sprintf("%d", cursor)},
})
if err != nil {
return errors.Wrap(err, "failed to create request")
}
hresp, err := c.http().Do(req)
if err != nil {
return errors.Wrap(err, "http request errored")
}
defer hresp.Body.Close()
raw, err := ioutil.ReadAll(hresp.Body)
if err != nil {
return err
}
body := strings.TrimSpace(string(raw))
if body != SetCursorDone {
return errors.Errorf("failed to set cursor on stellar-core: %s", body)
}
return nil
}
// SubmitTransaction calls the `tx` command on the connected stellar core with the provided envelope
func (c *Client) SubmitTransaction(ctx context.Context, envelope string) (resp *proto.TXResponse, err error) {
q := url.Values{}
q.Set("blob", envelope)
req, err := c.simpleGet(ctx, "tx", q)
if err != nil {
err = errors.Wrap(err, "failed to create request")
return
}
hresp, err := c.http().Do(req)
if err != nil {
err = errors.Wrap(err, "http request errored")
return
}
defer hresp.Body.Close()
err = json.NewDecoder(hresp.Body).Decode(&resp)
if err != nil {
err = errors.Wrap(err, "json decode failed")
return
}
return
}
// WaitForNetworkSync continually polls the connected stellar-core until it
// receives a response that indicated the node has synced with the network
func (c *Client) WaitForNetworkSync(ctx context.Context) error {
for {
info, err := c.Info(ctx)
if err != nil {
return errors.Wrap(err, "info request failed")
}
if info.IsSynced() {
return nil
}
// wait for next attempt or error if canceled while waiting
select {
case <-ctx.Done():
return errors.New("canceled")
case <-time.After(5 * time.Second):
continue
}
}
}
func (c *Client) http() HTTP {
if c.HTTP == nil {
return http.DefaultClient
}
return c.HTTP
}
// simpleGet returns a new GET request to the connected stellar-core using the
// provided path and query values to construct the result.
func (c *Client) simpleGet(
ctx context.Context,
newPath string,
query url.Values,
) (*http.Request, error) {
u, err := url.Parse(c.URL)
if err != nil {
return nil, errors.Wrap(err, "unparseable url")
}
u.Path = path.Join(u.Path, newPath)
if query != nil {
u.RawQuery = query.Encode()
}
newURL := u.String()
req, err := http.NewRequest(http.MethodGet, newURL, nil)
if err != nil {
return nil, errors.Wrap(err, "failed to create request")
}
return req.WithContext(ctx), nil
}