-
Notifications
You must be signed in to change notification settings - Fork 271
/
payforblob.go
291 lines (246 loc) · 8.39 KB
/
payforblob.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
package cli
import (
"bufio"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/spf13/cobra"
"github.com/celestiaorg/celestia-app/v2/pkg/appconsts"
"github.com/celestiaorg/celestia-app/v2/x/blob/types"
"github.com/celestiaorg/go-square/blob"
appns "github.com/celestiaorg/go-square/namespace"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/client/input"
sdktx "github.com/cosmos/cosmos-sdk/client/tx"
sdk "github.com/cosmos/cosmos-sdk/types"
)
const (
// FlagShareVersion allows the user to override the share version when
// submitting a PayForBlob.
FlagShareVersion = "share-version"
// FlagNamespaceVersion allows the user to override the namespace version when
// submitting a PayForBlob.
FlagNamespaceVersion = "namespace-version"
// FlagFileInput allows the user to provide the path to a JSON file for
// submitting multiple blobs.
FlagFileInput = "input-file"
// FileInputExtension is the only file extension supported for
// FlagFileInput.
FileInputExtension = ".json"
)
func CmdPayForBlob() *cobra.Command {
cmd := &cobra.Command{
Use: "pay-for-blob [namespaceID blob]",
Example: "celestia-appd tx blob pay-for-blob 0x00010203040506070809 0x48656c6c6f2c20576f726c6421 \\\n" +
"\t--chain-id private \\\n" +
"\t--from validator \\\n" +
"\t--keyring-backend test \\\n" +
"\t--fees 21000utia \\\n" +
"\t--yes \n\n" +
"celestia-appd tx blob pay-for-blob --input-file path/to/blobs.json \\\n" +
"\t--chain-id private \\\n" +
"\t--from validator \\\n" +
"\t--keyring-backend test \\\n" +
"\t--fees 21000utia \\\n" +
"\t--yes \n",
Short: "Pay for data blob(s) to be published to Celestia.",
Long: `Pay for data blob(s) to be published to Celestia.
To publish a single blob, specify the namespaceID and blob via CLI arguments.
To publish multiple blobs, use the --input-file flag with the path to a JSON file.
The JSON should look like:
{
"Blobs": [
{
"namespaceID": "0x00010203040506070809",
"blob": "0x48656c6c6f2c20576f726c6421"
},
{
"namespaceID": "0x00010203040506070809",
"blob": "0x48656c6c6f2c20576f726c6421"
}
]
}
The namespaceID is the user-specifiable portion of a version 0 namespace.
The namespaceID must be a hex encoded string of 10 bytes.
The blob must be a hex encoded string of non-zero length.
`,
Aliases: []string{"pay-for-blobs", "PayForBlobs", "PayForBlob"},
Args: func(cmd *cobra.Command, args []string) error {
path, err := cmd.Flags().GetString(FlagFileInput)
if err != nil {
return err
}
if path != "" {
if filepath.Ext(path) != FileInputExtension {
return fmt.Errorf("invalid file extension %v. The only supported extension is %s", filepath.Ext(path), FileInputExtension)
}
return nil
}
if len(args) < 2 {
return fmt.Errorf("pay-for-blob requires two arguments if %s isn't provided: namespaceID and blob", FlagFileInput)
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
namespaceVersion, err := cmd.Flags().GetUint8(FlagNamespaceVersion)
if err != nil {
return err
}
shareVersion, err := cmd.Flags().GetUint8(FlagShareVersion)
if err != nil {
return err
}
path, err := cmd.Flags().GetString(FlagFileInput)
if err != nil {
return err
}
// In case of no file input, get the namespaceID and blob from the arguments
if path == "" {
blob, err := getBlobFromArguments(args[0], args[1], namespaceVersion, shareVersion)
if err != nil {
return err
}
return broadcastPFB(cmd, blob)
}
paresdBlobs, err := parseSubmitBlobs(path)
if err != nil {
return err
}
var blobs []*blob.Blob
for _, paresdBlob := range paresdBlobs {
blob, err := getBlobFromArguments(paresdBlob.NamespaceID, paresdBlob.Blob, namespaceVersion, shareVersion)
if err != nil {
return err
}
blobs = append(blobs, blob)
}
return broadcastPFB(cmd, blobs...)
},
}
flags.AddTxFlagsToCmd(cmd)
cmd.PersistentFlags().Uint8(FlagNamespaceVersion, 0, "Specify the namespace version (default 0)")
cmd.PersistentFlags().Uint8(FlagShareVersion, 0, "Specify the share version (default 0)")
cmd.PersistentFlags().String(FlagFileInput, "", "Specify the file input")
_ = cmd.MarkFlagRequired(flags.FlagFrom)
return cmd
}
func getBlobFromArguments(namespaceIDArg, blobArg string, namespaceVersion, shareVersion uint8) (*blob.Blob, error) {
namespaceID, err := hex.DecodeString(strings.TrimPrefix(namespaceIDArg, "0x"))
if err != nil {
return nil, fmt.Errorf("failed to decode hex namespace ID: %w", err)
}
namespace, err := getNamespace(namespaceID, namespaceVersion)
if err != nil {
return nil, err
}
hexStr := strings.TrimPrefix(blobArg, "0x")
rawblob, err := hex.DecodeString(hexStr)
if err != nil {
return nil, fmt.Errorf("failure to decode hex blob value %s: %s", hexStr, err.Error())
}
blob, err := types.NewBlob(namespace, rawblob, shareVersion)
if err != nil {
return nil, fmt.Errorf("failure to create blob with hex blob value %s: %s", hexStr, err.Error())
}
return blob, nil
}
func getNamespace(namespaceID []byte, namespaceVersion uint8) (appns.Namespace, error) {
switch namespaceVersion {
case appns.NamespaceVersionZero:
if len(namespaceID) != appns.NamespaceVersionZeroIDSize {
return appns.Namespace{}, fmt.Errorf("the user specifiable portion of the namespace ID must be %d bytes for namespace version 0", appns.NamespaceVersionZeroIDSize)
}
id := make([]byte, 0, appns.NamespaceIDSize)
id = append(id, appns.NamespaceVersionZeroPrefix...)
id = append(id, namespaceID...)
return appns.New(namespaceVersion, id)
default:
return appns.Namespace{}, fmt.Errorf("namespace version %d is not supported", namespaceVersion)
}
}
// broadcastPFB creates the new PFB message type that will later be broadcast to tendermint nodes
// this private func is used in CmdPayForBlob
func broadcastPFB(cmd *cobra.Command, b ...*blob.Blob) error {
clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}
// TODO: allow the user to override the share version via a new flag
// See https://github.com/celestiaorg/celestia-app/issues/1041
pfbMsg, err := types.NewMsgPayForBlobs(clientCtx.FromAddress.String(), appconsts.LatestVersion, b...)
if err != nil {
return err
}
// run message checks
if err = pfbMsg.ValidateBasic(); err != nil {
return err
}
txBytes, err := writeTx(clientCtx, sdktx.NewFactoryCLI(clientCtx, cmd.Flags()), pfbMsg)
if err != nil {
return err
}
blobTx, err := blob.MarshalBlobTx(txBytes, b...)
if err != nil {
return err
}
// broadcast to a Tendermint node
res, err := clientCtx.BroadcastTx(blobTx)
if err != nil {
return err
}
return clientCtx.PrintProto(res)
}
// writeTx attempts to generate and sign a transaction using the normal
// cosmos-sdk cli argument parsing code with the given set of messages. It will also simulate gas
// requirements if necessary. It will return an error upon failure.
//
// NOTE: Copy paste forked from the cosmos-sdk so that we can wrap the PFB with
// a blob while still using all of the normal cli parsing code
func writeTx(clientCtx client.Context, txf sdktx.Factory, msgs ...sdk.Msg) ([]byte, error) {
if clientCtx.GenerateOnly {
return nil, txf.PrintUnsignedTx(clientCtx, msgs...)
}
txf, err := txf.Prepare(clientCtx)
if err != nil {
return nil, err
}
if txf.SimulateAndExecute() || clientCtx.Simulate {
_, adjusted, err := sdktx.CalculateGas(clientCtx, txf, msgs...)
if err != nil {
return nil, err
}
txf = txf.WithGas(adjusted)
_, _ = fmt.Fprintf(os.Stderr, "%s\n", sdktx.GasEstimateResponse{GasEstimate: txf.Gas()})
}
if clientCtx.Simulate {
return nil, nil
}
tx, err := txf.BuildUnsignedTx(msgs...)
if err != nil {
return nil, err
}
if !clientCtx.SkipConfirm {
txBytes, err := clientCtx.TxConfig.TxJSONEncoder()(tx.GetTx())
if err != nil {
return nil, err
}
if err := clientCtx.PrintRaw(json.RawMessage(txBytes)); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "%s\n", txBytes)
}
buf := bufio.NewReader(os.Stdin)
ok, err := input.GetConfirmation("confirm transaction before signing and broadcasting", buf, os.Stderr)
if err != nil || !ok {
_, _ = fmt.Fprintf(os.Stderr, "%s\n", "cancelled transaction")
return nil, err
}
}
err = sdktx.Sign(txf, clientCtx.GetFromName(), tx, true)
if err != nil {
return nil, err
}
return clientCtx.TxConfig.TxEncoder()(tx.GetTx())
}