forked from ethereum-optimism/optimism
-
Notifications
You must be signed in to change notification settings - Fork 0
/
daclient.go
149 lines (129 loc) · 3.91 KB
/
daclient.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
package altda
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"net/http"
"time"
)
// ErrNotFound is returned when the server could not find the input.
var ErrNotFound = errors.New("not found")
// ErrInvalidInput is returned when the input is not valid for posting to the DA storage.
var ErrInvalidInput = errors.New("invalid input")
// DAClient is an HTTP client to communicate with a DA storage service.
// It creates commitments and retrieves input data + verifies if needed.
type DAClient struct {
url string
// verify sets the client to verify a Keccak256 commitment on read.
verify bool
// whether commitment is precomputable (only applicable to keccak256)
precompute bool
getTimeout time.Duration
putTimeout time.Duration
}
func NewDAClient(url string, verify bool, pc bool) *DAClient {
return &DAClient{
url: url,
verify: verify,
precompute: pc,
}
}
// GetInput returns the input data for the given encoded commitment bytes.
func (c *DAClient) GetInput(ctx context.Context, comm CommitmentData) ([]byte, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/get/0x%x", c.url, comm.Encode()), nil)
if err != nil {
return nil, fmt.Errorf("failed to create HTTP request: %w", err)
}
client := &http.Client{Timeout: c.getTimeout}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
if resp.StatusCode == http.StatusNotFound {
return nil, ErrNotFound
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to get preimage: %v", resp.StatusCode)
}
defer resp.Body.Close()
input, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if c.verify {
if err := comm.Verify(input); err != nil {
return nil, err
}
}
return input, nil
}
// SetInput sets the input data and returns the respective commitment.
func (c *DAClient) SetInput(ctx context.Context, img []byte) (CommitmentData, error) {
if len(img) == 0 {
return nil, ErrInvalidInput
}
if c.precompute { // precompute commitment (only applicable to keccak256)
comm := NewKeccak256Commitment(img)
if err := c.setInputWithCommit(ctx, comm, img); err != nil {
return nil, err
}
return comm, nil
}
// let DA server generate commitment
return c.setInput(ctx, img)
}
// setInputWithCommit sets a precomputed commitment for some pre-image data.
func (c *DAClient) setInputWithCommit(ctx context.Context, comm CommitmentData, img []byte) error {
// encode with commitment type prefix
key := comm.Encode()
body := bytes.NewReader(img)
url := fmt.Sprintf("%s/put/0x%x", c.url, key)
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, body)
if err != nil {
return fmt.Errorf("failed to create HTTP request: %w", err)
}
req.Header.Set("Content-Type", "application/octet-stream")
client := &http.Client{Timeout: c.putTimeout}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("failed to store preimage: %v", resp.StatusCode)
}
return nil
}
// setInput sets the input data and reads the respective DA generated commitment.
func (c *DAClient) setInput(ctx context.Context, img []byte) (CommitmentData, error) {
if len(img) == 0 {
return nil, ErrInvalidInput
}
body := bytes.NewReader(img)
url := fmt.Sprintf("%s/put", c.url)
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, body)
if err != nil {
return nil, fmt.Errorf("failed to create HTTP request: %w", err)
}
req.Header.Set("Content-Type", "application/octet-stream")
client := &http.Client{Timeout: c.putTimeout}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to store data: %v", resp.StatusCode)
}
b, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
comm, err := DecodeCommitmentData(b)
if err != nil {
return nil, err
}
return comm, nil
}