/
textile.go
134 lines (114 loc) · 3.1 KB
/
textile.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
package output
import (
"bytes"
"crypto/ecdsa"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"log/slog"
"net/http"
"strconv"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/pkg/errors"
"github.com/tablelandnetwork/basin-cli/pkg/signing"
"github.com/tidwall/gjson"
"github.com/machinefi/sprout/types"
)
type textileDB struct {
endpoint string
secretKey *ecdsa.PrivateKey
}
func (t *textileDB) Output(task *types.Task, proof []byte) (string, error) {
slog.Debug("outputing to textileDB", "chain endpoint", t.endpoint)
encodedData, err := t.packData(proof)
if err != nil {
return "", err
}
txHash, err := t.write(encodedData)
if err != nil {
return "", err
}
return txHash, nil
}
func (t *textileDB) packData(proof []byte) ([]byte, error) {
proof, err := hex.DecodeString(string(proof))
if err != nil {
return nil, errors.Wrap(err, "failed to decode proof")
}
var (
result string
values []gjson.Result
)
if valueJournal := gjson.GetBytes(proof, "Stark.journal.bytes"); valueJournal.Exists() {
values = valueJournal.Array()
} else if valueJournal = gjson.GetBytes(proof, "Snark.journal"); valueJournal.Exists() {
values = valueJournal.Array()
} else {
return nil, errors.New("proof does not contain journal")
}
// get result from proof
for _, value := range values {
result += fmt.Sprint(value.Int())
}
data := map[string]string{
"result": result,
"proof": string(proof),
}
jsonData, err := json.Marshal(data)
if err != nil {
return nil, errors.Wrap(err, "marshal pack data error")
}
return jsonData, nil
}
func (t *textileDB) write(data []byte) (string, error) {
signatureBytes, err := signing.NewSigner(t.secretKey).SignBytes(data)
if err != nil {
return "", errors.Wrap(err, "failed to sign data")
}
txHash := hex.EncodeToString(signatureBytes)
url := fmt.Sprintf("%s?timestamp=%s&signature=%s",
t.endpoint,
strconv.FormatInt(time.Now().Unix(), 10),
txHash)
err = writeTextileEvent(url, data)
if err != nil {
return "", err
}
return txHash, err
}
// writeTextileEvent writes a file to a vault via the Basin API.
func writeTextileEvent(url string, fileData []byte) error {
req, err := http.NewRequest("POST", url, bytes.NewReader(fileData))
if err != nil {
return errors.Wrap(err, "failed to create request")
}
hn := sha256.New()
hr := hn.Sum(fileData)
req.Header.Set("filename", string(hr)[0:7])
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return errors.Wrap(err, "failed to send request")
}
defer resp.Body.Close()
responseBody, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
slog.Debug("Write event", "response", string(responseBody))
return nil
}
// TODO: refactor textile with a KV database adapter
func newTextileDBAdapter(conf TextileConfig, secretKey string) (*textileDB, error) {
if secretKey == "" {
return nil, errors.New("secret key is empty")
}
return &textileDB{
endpoint: fmt.Sprintf("https://basin.tableland.xyz/vaults/%s/events", conf.VaultID),
secretKey: crypto.ToECDSAUnsafe(common.FromHex(secretKey)),
}, nil
}