-
Notifications
You must be signed in to change notification settings - Fork 26
/
Copy pathnear.go
301 lines (263 loc) · 8.84 KB
/
near.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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
// Package sidecar provides a client for interacting with the Near Protocol Sidecar service.
//
// The sidecar service is responsible for submitting and retrieving data blobs to and from the Near blockchain.
// It acts as an intermediary between the application and the Near blockchain, abstracting away the complexities
// of interacting with the blockchain directly.
//
// Security Considerations:
// - The sidecar service should be running on a trusted host and port.
// - The host and port should be configurable and not hardcoded.
// - The client should verify the identity of the sidecar service using TLS certificates.
// - The client should validate and sanitize all input parameters to prevent injection attacks.
// - The client should handle errors gracefully and not leak sensitive information in error messages.
// - The client should use secure communication channels (e.g., HTTPS) to prevent eavesdropping and tampering.
// - The client should have proper authentication and authorization mechanisms to prevent unauthorized access.
//
// Usage:
//
// 1. Create a new client instance using the `NewClient` function, providing the host and configuration.
//
// client, err := sidecar.NewClient("http://localhost:5888", &sidecar.ConfigureClientRequest{...})
// if err != nil {
// // Handle error
// }
//
// 2. Use the client to interact with the sidecar service.
//
// // Submit a blob
// blob := sidecar.Blob{Data: []byte("test_data")}
// blobRef, err := client.SubmitBlob(blob)
// if err != nil {
// // Handle error
// }
//
// // Get a blob
// retrievedBlob, err := client.GetBlob(*blobRef)
// if err != nil {
// // Handle error
// }
//
// 3. Close the client when done.
//
// client.Close()
package sidecar
import (
"bytes"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"net/http"
log "github.com/sirupsen/logrus"
)
// Client represents a client for interacting with the Near Protocol Sidecar service.
type Client struct {
client *http.Client
host string
config *ConfigureClientRequest
}
// NewClient creates a new instance of the Near Protocol Sidecar client.
// It takes the host and configuration as parameters and returns a pointer to the client.
// If the host is empty, it defaults to "http://localhost:5888".
// The configuration can be nil, assuming the sidecar is set up outside of this package.
func NewClient(host string, config *ConfigureClientRequest) (*Client, error) {
if host == "" {
host = "http://localhost:5888"
}
client := &Client{
client: &http.Client{},
host: host,
config: config,
}
return client, client.Health()
}
func (c *Client) GetHost() string {
return c.host
}
// ConfigureClient configures the Near Protocol Sidecar client with the provided configuration.
// It sends a PUT request to the "/configure" endpoint with the configuration as JSON payload.
func (c *Client) ConfigureClient(req *ConfigureClientRequest) error {
if req == nil {
req = c.config
}
jsonData, err := json.Marshal(req)
if err != nil {
return fmt.Errorf("failed to marshal configure client request: %v", err)
}
httpReq, err := http.NewRequest(http.MethodPut, c.host+"/configure", bytes.NewBuffer(jsonData))
if err != nil {
return fmt.Errorf("failed to create configure client request: %v", err)
}
httpReq.Header.Set("Content-Type", "application/json")
resp, err := c.client.Do(httpReq)
if err != nil {
return fmt.Errorf("failed to send configure client request: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("failed to configure client, status code: %d", resp.StatusCode)
}
return nil
}
// GetBlob retrieves a blob from the Near blockchain using the provided BlobRef.
// It sends a GET request to the "/blob" endpoint with the transaction ID as a query parameter.
func (c *Client) GetBlob(b BlobRef) (*Blob, error) {
resp, err := c.client.Get(c.host + "/blob?transaction_id=" + b.ID())
if err != nil {
return nil, fmt.Errorf("failed to send get blob request: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to get blob, status code: %d", resp.StatusCode)
}
var blob Blob
err = json.NewDecoder(resp.Body).Decode(&blob)
if err != nil {
return nil, fmt.Errorf("failed to decode blob response: %v", err)
}
return &blob, nil
}
// SubmitBlob submits a blob to the Near blockchain.
// It sends a POST request to the "/blob" endpoint with the blob data as JSON payload.
// The response contains the transaction ID of the submitted blob.
func (c *Client) SubmitBlob(b Blob) (*BlobRef, error) {
if b.Data == nil {
return nil, errors.New("blob data cannot be nil")
}
jsonData, err := b.MarshalJSON()
if err != nil {
return nil, fmt.Errorf("failed to marshal blob: %v", err)
}
log.Debug("near-sidecar: SubmitBlob json: ", jsonData)
resp, err := c.client.Post(c.host+"/blob", "application/json", bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("failed to send submit blob request: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to submit blob, status code: %d", resp.StatusCode)
}
var blobRef BlobRef
err = json.NewDecoder(resp.Body).Decode(&blobRef)
if err != nil {
return nil, fmt.Errorf("failed to decode transaction ID: %v", err)
}
return &blobRef, nil
}
// Health checks the health of the Near Protocol Sidecar service.
// It sends a GET request to the "/health" endpoint and expects a successful response.
func (c *Client) Health() error {
resp, err := c.client.Get(c.host + "/health")
if err != nil {
return fmt.Errorf("failed to send health check request: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("health check failed, status code: %d", resp.StatusCode)
}
return nil
}
// Close closes the Near Protocol Sidecar client.
// It should be called when the client is no longer needed.
func (c *Client) Close() {
// Perform any necessary cleanup or resource release
}
// BlobRef represents a reference to a blob on the Near blockchain.
type BlobRef struct {
transactionID [EncodedBlobRefSize]byte
}
// EncodedBlobRefSize is the size of an encoded BlobRef in bytes.
const EncodedBlobRefSize = 32
// NewBlobRef creates a new BlobRef from the provided transaction ID.
// It returns an error if the transaction ID is not exactly 32 bytes.
func NewBlobRef(transactionID []byte) (*BlobRef, error) {
if len(transactionID) != EncodedBlobRefSize {
return nil, errors.New("invalid transaction ID length")
}
var ref BlobRef
copy(ref.transactionID[:], transactionID)
return &ref, nil
}
// Deref returns the transaction ID of the BlobRef.
func (r *BlobRef) Deref() []byte {
return r.transactionID[:]
}
// ID returns the transaction ID of the BlobRef as a hex-encoded string.
func (r *BlobRef) ID() string {
return hex.EncodeToString(r.transactionID[:])
}
// MarshalJSON marshals the BlobRef to JSON format.
// It encodes the transaction ID as a hex string.
func (r *BlobRef) MarshalJSON() ([]byte, error) {
json, err := json.Marshal(struct {
TransactionID string `json:"transaction_id"`
}{
TransactionID: r.ID(),
})
return json, err
}
// UnmarshalJSON unmarshals the BlobRef from JSON format.
// It decodes the transaction ID from a hex string.
func (r *BlobRef) UnmarshalJSON(data []byte) error {
var aux struct {
TransactionID string `json:"transaction_id"`
}
if err := json.Unmarshal(data, &aux); err != nil {
return err
}
transactionID, err := hex.DecodeString(aux.TransactionID)
if err != nil {
return err
}
copy(r.transactionID[:], transactionID)
return nil
}
// Blob represents a blob of data stored on the Near blockchain.
type Blob struct {
Data []byte `json:"data"`
}
// MarshalJSON marshals the Blob to JSON format.
// It encodes the data as a hex string.
func (b *Blob) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
Data string `json:"data"`
}{
Data: hex.EncodeToString(b.Data),
})
}
// UnmarshalJSON unmarshals the Blob from JSON format.
// It decodes the data from a hex string.
func (b *Blob) UnmarshalJSON(data []byte) error {
var aux struct {
Data string `json:"data"`
}
if err := json.Unmarshal(data, &aux); err != nil {
return err
}
decodedData, err := hex.DecodeString(aux.Data)
if err != nil {
return err
}
b.Data = decodedData
return nil
}
// Network represents a Near network.
type Network string
const (
Mainnet Network = "mainnet"
Testnet Network = "testnet"
Localnet Network = "localnet"
)
// ConfigureClientRequest represents a request to configure the Near Protocol Sidecar client.
type ConfigureClientRequest struct {
AccountID string `json:"account_id"`
SecretKey string `json:"secret_key"`
ContractID string `json:"contract_id"`
Network Network `json:"network"`
Namespace *Namespace `json:"namespace"`
}
// Namespace represents a namespace on the Near blockchain.
type Namespace struct {
ID int `json:"id"`
Version int `json:"version"`
}