diff --git a/README.md b/README.md index 006c721..7af0811 100644 --- a/README.md +++ b/README.md @@ -158,7 +158,7 @@ Having your client created, you can use the method `SubmitTx` to submit a single ```go // ... tx := broadcast.Transaction{ - RawTx: "xyz", + Hex: "xyz", } result, err := client.SubmitTransaction(context.Background(), tx) @@ -167,14 +167,46 @@ Having your client created, you can use the method `SubmitTx` to submit a single You need to pass the [transaction](#transaction) as a parameter to the method `SubmitTransaction`. -Setting tx.MerkleProof to true will add the header `X-MerkleProof` to the request. -MerkleProof while broadcasting will handle the merkle proof capability of the node. +You may add options to this method: + +##### WithCallback -Setting tx.CallBackURL and tx.CallBackToken will add the headers `X-CallbackUrl` and `X-CallbackToken` to the request. +```go + result, err := client.SubmitTransaction(context.Background(), tx, broadcast.WithCallback(callBackUrl, callbackToken)) +``` +Setting `CallbackURL` and `CallBackToken` will add the headers `X-CallbackUrl` and `X-CallbackToken` to the request. It will allow you to get the callback from the node when the transaction is mined, and receive the transaction details and status. -Setting tx.WaitForStatus will add the header `X-WaitForStatus` to the request. -It will allow you to wait for the transaction to be mined and return the result only when the transaction reaches the status you set. +##### WithMerkleProof + +```go + result, err := client.SubmitTransaction(context.Background(), tx, broadcast.WithMerkleProof()) +``` +Setting `MerkleProof` to true will add the header `X-MerkleProof` to the request. +MerkleProof while broadcasting will handle the merkle proof capability of the node. + +##### WithWaitForstatus + +```go + result, err := client.SubmitTransaction(context.Background(), tx, broadcast.WithWaitForstatus(broadcast.AnnouncedToNetwork)) +``` +Setting `WaitForStatus` will add the header `X-WaitForStatus` to the request. +It will allow you to return the result only when the transaction reaches the status you set. + +##### WithBeefFormat + +```go + result, err := client.SubmitTransaction(context.Background(), tx, broadcast.WithBeefFormat()) +``` +Setting `BeefFormat` will accept your transaction in BEEF format and decode it for a proper format acceptable by Arc. + +##### WithRawFormat (**DEPRECATED!**) + +```go + result, err := client.SubmitTransaction(context.Background(), tx, broadcast.WithRawFormat()) +``` +Setting `RawFormat` will accept your transaction in RawTx format and encode it for a proper format acceptable by Arc. +This option will become deprecated soon. ### SubmitBatchTx Method @@ -183,11 +215,11 @@ Having your client created, you can use the method `SubmitBatchTx` to submit a b ```go // ... txs := []*broadcast.Transaction{ - {RawTx: "xyz1"}, - {RawTx: "xyz2"}, - {RawTx: "xyz3"}, - {RawTx: "xyz4"}, - {RawTx: "xyz5"}, + {Hex: "xyz1"}, + {Hex: "xyz2"}, + {Hex: "xyz3"}, + {Hex: "xyz4"}, + {Hex: "xyz5"}, } result, err := client.SubmitBatchTransaction(context.Background(), txs) @@ -204,11 +236,11 @@ The method works the same as the `SubmitTx` method, but it is sending a batch of ```go type QueryTxResponse struct { - BlockHash string `json:"blockHash,omitempty"` - BlockHeight int64 `json:"blockHeight,omitempty"` - Timestamp string `json:"timestamp,omitempty"` - TxID string `json:"txid,omitempty"` - TxStatus TxStatus `json:"txStatus,omitempty"` + BlockHash string `json:"blockHash,omitempty"` + BlockHeight int64 `json:"blockHeight,omitempty"` + Timestamp string `json:"timestamp,omitempty"` + TxID string `json:"txid,omitempty"` + TxStatus TxStatus `json:"txStatus,omitempty"` } ``` @@ -262,12 +294,12 @@ type FeeQuote struct { ```go type SubmitTxResponse struct { - BlockHash string `json:"blockHash,omitempty"` - BlockHeight int64 `json:"blockHeight,omitempty"` - ExtraInfo string `json:"extraInfo,omitempty"` - Status int `json:"status,omitempty"` - Title string `json:"title,omitempty"` - TxStatus TxStatus `json:"txStatus,omitempty"` + BlockHash string `json:"blockHash,omitempty"` + BlockHeight int64 `json:"blockHeight,omitempty"` + ExtraInfo string `json:"extraInfo,omitempty"` + Status int `json:"status,omitempty"` + Title string `json:"title,omitempty"` + TxStatus TxStatus `json:"txStatus,omitempty"` } ``` @@ -275,14 +307,14 @@ type SubmitTxResponse struct { ```go type Transaction struct { - CallBackEncryption string `json:"callBackEncryption,omitempty"` - CallBackToken string `json:"callBackToken,omitempty"` - CallBackURL string `json:"callBackUrl,omitempty"` - DsCheck bool `json:"dsCheck,omitempty"` - MerkleFormat string `json:"merkleFormat,omitempty"` - MerkleProof bool `json:"merkleProof,omitempty"` - RawTx string `json:"rawtx"` - WaitForStatus TxStatus `json:"waitForStatus,omitempty"` + CallBackEncryption string `json:"callBackEncryption,omitempty"` + CallBackToken string `json:"callBackToken,omitempty"` + CallBackURL string `json:"callBackUrl,omitempty"` + DsCheck bool `json:"dsCheck,omitempty"` + MerkleFormat string `json:"merkleFormat,omitempty"` + MerkleProof bool `json:"merkleProof,omitempty"` + Hex string `json:"hex"` + WaitForStatus TxStatus `json:"waitForStatus,omitempty"` } ``` diff --git a/broadcast/broadcast-client-mock/mock_client_test.go b/broadcast/broadcast-client-mock/mock_client_test.go index 565b845..628b711 100644 --- a/broadcast/broadcast-client-mock/mock_client_test.go +++ b/broadcast/broadcast-client-mock/mock_client_test.go @@ -71,7 +71,7 @@ func TestMockClientSuccess(t *testing.T) { Build() // when - result, err := broadcaster.SubmitTransaction(context.Background(), &broadcast.Transaction{RawTx: "test-rawtx"}) + result, err := broadcaster.SubmitTransaction(context.Background(), &broadcast.Transaction{Hex: "test-rawtx"}) // then assert.NoError(t, err) @@ -98,7 +98,7 @@ func TestMockClientSuccess(t *testing.T) { } // when - result, err := broadcaster.SubmitBatchTransactions(context.Background(), []*broadcast.Transaction{{RawTx: "test-rawtx"}, {RawTx: "test2-rawtx"}}) + result, err := broadcaster.SubmitBatchTransactions(context.Background(), []*broadcast.Transaction{{Hex: "test-rawtx"}, {Hex: "test2-rawtx"}}) // then assert.NoError(t, err) @@ -160,7 +160,7 @@ func TestMockClientFailure(t *testing.T) { Build() // when - result, err := broadcaster.SubmitTransaction(context.Background(), &broadcast.Transaction{RawTx: "test-rawtx"}) + result, err := broadcaster.SubmitTransaction(context.Background(), &broadcast.Transaction{Hex: "test-rawtx"}) // then assert.Error(t, err) @@ -175,7 +175,7 @@ func TestMockClientFailure(t *testing.T) { Build() // when - result, err := broadcaster.SubmitBatchTransactions(context.Background(), []*broadcast.Transaction{{RawTx: "test-rawtx"}, {RawTx: "test2-rawtx"}}) + result, err := broadcaster.SubmitBatchTransactions(context.Background(), []*broadcast.Transaction{{Hex: "test-rawtx"}, {Hex: "test2-rawtx"}}) // then assert.Error(t, err) @@ -260,7 +260,7 @@ func TestMockClientTimeout(t *testing.T) { startTime := time.Now() // when - result, err := broadcaster.SubmitTransaction(ctx, &broadcast.Transaction{RawTx: "test-rawtx"}) + result, err := broadcaster.SubmitTransaction(ctx, &broadcast.Transaction{Hex: "test-rawtx"}) // then assert.NoError(t, err) @@ -291,7 +291,7 @@ func TestMockClientTimeout(t *testing.T) { } // when - result, err := broadcaster.SubmitBatchTransactions(ctx, []*broadcast.Transaction{{RawTx: "test-rawtx"}, {RawTx: "test2-rawtx"}}) + result, err := broadcaster.SubmitBatchTransactions(ctx, []*broadcast.Transaction{{Hex: "test-rawtx"}, {Hex: "test2-rawtx"}}) // then assert.NoError(t, err) diff --git a/broadcast/internal/acceptance_tests/arc_submit_tx_test.go b/broadcast/internal/acceptance_tests/arc_submit_tx_test.go index b07a8b7..c9bd8d8 100644 --- a/broadcast/internal/acceptance_tests/arc_submit_tx_test.go +++ b/broadcast/internal/acceptance_tests/arc_submit_tx_test.go @@ -51,7 +51,7 @@ func TestSubmitTransaction(t *testing.T) { httpClientMock.On("DoRequest", mock.Anything, mock.Anything).Return(httpResponse2, nil).Times(0) // when - result, err := broadcaster.SubmitTransaction(context.Background(), &broadcast.Transaction{RawTx: "transaction-data"}) + result, err := broadcaster.SubmitTransaction(context.Background(), &broadcast.Transaction{Hex: "transaction-data"}) // then httpClientMock.AssertExpectations(t) @@ -74,7 +74,7 @@ func TestSubmitTransaction(t *testing.T) { httpClientMock.On("DoRequest", mock.Anything, mock.Anything).Return(httpResponse, errors.New("http error")).Once() // when - result, err := broadcaster.SubmitTransaction(context.Background(), &broadcast.Transaction{RawTx: "transaction-data"}) + result, err := broadcaster.SubmitTransaction(context.Background(), &broadcast.Transaction{Hex: "transaction-data"}) // then httpClientMock.AssertExpectations(t) @@ -98,7 +98,7 @@ func TestSubmitTransaction(t *testing.T) { httpClientMock.On("DoRequest", mock.Anything, mock.Anything).Return(httpResponse, errors.New("http error")).Once() // when - result, err := broadcaster.SubmitTransaction(context.Background(), &broadcast.Transaction{RawTx: "transaction-data"}) + result, err := broadcaster.SubmitTransaction(context.Background(), &broadcast.Transaction{Hex: "transaction-data"}) // then httpClientMock.AssertExpectations(t) @@ -122,7 +122,7 @@ func TestSubmitTransaction(t *testing.T) { httpClientMock.On("DoRequest", mock.Anything, mock.Anything).Return(httpResponse2, nil).Once() // when - result, err := broadcaster.SubmitTransaction(context.Background(), &broadcast.Transaction{RawTx: "transaction-data"}) + result, err := broadcaster.SubmitTransaction(context.Background(), &broadcast.Transaction{Hex: "transaction-data"}) // then httpClientMock.AssertExpectations(t) @@ -146,8 +146,8 @@ func TestSubmitBatchTransactions(t *testing.T) { httpClientMock.On("DoRequest", mock.Anything, mock.Anything).Return(httpResponse2, nil).Times(0) batch := []*broadcast.Transaction{ - {RawTx: "transaction-0-data"}, - {RawTx: "transaction-1-data"}, + {Hex: "transaction-0-data"}, + {Hex: "transaction-1-data"}, } // when @@ -174,8 +174,8 @@ func TestSubmitBatchTransactions(t *testing.T) { httpClientMock.On("DoRequest", mock.Anything, mock.Anything).Return(httpResponse2, nil).Once() batch := []*broadcast.Transaction{ - {RawTx: "transaction-0-data"}, - {RawTx: "transaction-1-data"}, + {Hex: "transaction-0-data"}, + {Hex: "transaction-1-data"}, } // when @@ -198,8 +198,8 @@ func TestSubmitBatchTransactions(t *testing.T) { httpClientMock.On("DoRequest", mock.Anything, mock.Anything).Return(httpResponse, errors.New("http error")).Once() batch := []*broadcast.Transaction{ - {RawTx: "transaction-0-data"}, - {RawTx: "transaction-1-data"}, + {Hex: "transaction-0-data"}, + {Hex: "transaction-1-data"}, } // when @@ -223,8 +223,8 @@ func TestSubmitBatchTransactions(t *testing.T) { httpClientMock.On("DoRequest", mock.Anything, mock.Anything).Return(httpResponse, errors.New("http error")).Once() batch := []*broadcast.Transaction{ - {RawTx: "transaction-0-data"}, - {RawTx: "transaction-1-data"}, + {Hex: "transaction-0-data"}, + {Hex: "transaction-1-data"}, } // when diff --git a/broadcast/internal/arc/arc_submit_tx.go b/broadcast/internal/arc/arc_submit_tx.go index c5aa4d9..a65d36b 100644 --- a/broadcast/internal/arc/arc_submit_tx.go +++ b/broadcast/internal/arc/arc_submit_tx.go @@ -2,10 +2,15 @@ package arc import ( "context" + "encoding/hex" "encoding/json" "errors" + "fmt" "strconv" + "github.com/GorillaPool/go-junglebus" + "github.com/libsv/go-bt/v2" + "github.com/bitcoin-sv/go-broadcast-client/broadcast" arc_utils "github.com/bitcoin-sv/go-broadcast-client/broadcast/internal/arc/utils" "github.com/bitcoin-sv/go-broadcast-client/httpclient" @@ -38,7 +43,7 @@ func (a *ArcClient) SubmitTransaction(ctx context.Context, tx *broadcast.Transac response := &broadcast.SubmitTxResponse{ BaseResponse: broadcast.BaseResponse{Miner: a.apiURL}, - SubmittedTx: result, + SubmittedTx: result, } return response, nil @@ -77,7 +82,7 @@ func (a *ArcClient) SubmitBatchTransactions(ctx context.Context, txs []*broadcas func submitTransaction(ctx context.Context, arc *ArcClient, tx *broadcast.Transaction, opts *broadcast.TransactionOpts) (*broadcast.SubmittedTx, error) { url := arc.apiURL + arcSubmitTxRoute - data, err := createSubmitTxBody(tx) + data, err := createSubmitTxBody(tx, opts.TransactionFormat) if err != nil { return nil, err } @@ -109,7 +114,7 @@ func submitTransaction(ctx context.Context, arc *ArcClient, tx *broadcast.Transa func submitBatchTransactions(ctx context.Context, arc *ArcClient, txs []*broadcast.Transaction, opts *broadcast.TransactionOpts) ([]*broadcast.SubmittedTx, error) { url := arc.apiURL + arcSubmitBatchTxsRoute - data, err := createSubmitBatchTxsBody(txs) + data, err := createSubmitBatchTxsBody(txs, opts.TransactionFormat) if err != nil { return nil, err } @@ -139,10 +144,19 @@ func submitBatchTransactions(ctx context.Context, arc *ArcClient, txs []*broadca return model, nil } -func createSubmitTxBody(tx *broadcast.Transaction) ([]byte, error) { - body := &SubmitTxRequest{tx.RawTx} - data, err := json.Marshal(body) +func createSubmitTxBody(tx *broadcast.Transaction, txFormat broadcast.TransactionFormat) ([]byte, error) { + body := &SubmitTxRequest{tx.Hex} + + if txFormat == broadcast.RawTxFormat { + if err := convertToEfTransaction(body); err != nil { + return nil, fmt.Errorf("Conversion to EF format failed: %s", err.Error()) + } + } else if txFormat == broadcast.BeefFormat { + // To be implemented + return nil, fmt.Errorf("Submitting transactions in BEEF format is unimplemented yet...") + } + data, err := json.Marshal(body) if err != nil { return nil, ErrSubmitTxMarshal } @@ -150,10 +164,19 @@ func createSubmitTxBody(tx *broadcast.Transaction) ([]byte, error) { return data, nil } -func createSubmitBatchTxsBody(txs []*broadcast.Transaction) ([]byte, error) { +func createSubmitBatchTxsBody(txs []*broadcast.Transaction, txFormat broadcast.TransactionFormat) ([]byte, error) { rawTxs := make([]*SubmitTxRequest, 0, len(txs)) for _, tx := range txs { - rawTxs = append(rawTxs, &SubmitTxRequest{RawTx: tx.RawTx}) + rawTxs = append(rawTxs, &SubmitTxRequest{RawTx: tx.Hex}) + } + + if txFormat == broadcast.RawTxFormat { + if err := convertBatchToEfTransaction(rawTxs); err != nil { + return nil, fmt.Errorf("Conversion to EF format failed for one or more transactions with error: %s", err.Error()) + } + } else if txFormat == broadcast.BeefFormat { + // To be implemented + return nil, fmt.Errorf("Submitting transactions in BEEF format is unimplemented yet...") } data, err := json.Marshal(rawTxs) @@ -203,3 +226,54 @@ func validateSubmitTxResponse(model *broadcast.SubmittedTx) error { return nil } + +func convertToEfTransaction(tx *SubmitTxRequest) error { + junglebusClient, err := junglebus.New( + junglebus.WithHTTP("https://junglebus.gorillapool.io"), + ) + if err != nil { + return err + } + + transaction, err := bt.NewTxFromString(tx.RawTx) + if err != nil { + return err + } + + for _, input := range transaction.Inputs { + if err = updateUtxoWithMissingData(junglebusClient, input); err != nil { + return err + } + } + + tx.RawTx = hex.EncodeToString(transaction.ExtendedBytes()) + return nil +} + +func updateUtxoWithMissingData(jbc *junglebus.Client, input *bt.Input) error { + txid := input.PreviousTxIDStr() + + tx, err := jbc.GetTransaction(context.Background(), txid) + if err != nil { + return err + } + + actualTx, err := bt.NewTxFromBytes(tx.Transaction) + if err != nil { + return err + } + + o := actualTx.Outputs[input.PreviousTxOutIndex] + input.PreviousTxScript = o.LockingScript + input.PreviousTxSatoshis = o.Satoshis + return nil +} + +func convertBatchToEfTransaction(rawTxs []*SubmitTxRequest) error { + for _, rawTx := range rawTxs { + if err := convertToEfTransaction(rawTx); err != nil { + return err + } + } + return nil +} diff --git a/broadcast/internal/arc/arc_submit_tx_test.go b/broadcast/internal/arc/arc_submit_tx_test.go index 7fb54e0..72eb925 100644 --- a/broadcast/internal/arc/arc_submit_tx_test.go +++ b/broadcast/internal/arc/arc_submit_tx_test.go @@ -26,7 +26,7 @@ func TestSubmitTransaction(t *testing.T) { { name: "successful request", transaction: &broadcast.Transaction{ - RawTx: "abc123", + Hex: "abc123", }, httpResponse: &http.Response{ StatusCode: http.StatusOK, @@ -44,7 +44,7 @@ func TestSubmitTransaction(t *testing.T) { { name: "error in HTTP request", transaction: &broadcast.Transaction{ - RawTx: "abc123", + Hex: "abc123", }, httpError: errors.New("some error"), expectedError: errors.New("some error"), @@ -52,7 +52,7 @@ func TestSubmitTransaction(t *testing.T) { { name: "missing txStatus in response", transaction: &broadcast.Transaction{ - RawTx: "abc123", + Hex: "abc123", }, httpResponse: &http.Response{ StatusCode: http.StatusOK, @@ -70,7 +70,7 @@ func TestSubmitTransaction(t *testing.T) { // given mockHttpClient := new(MockHttpClient) - body, _ := createSubmitTxBody(tc.transaction) + body, _ := createSubmitTxBody(tc.transaction, broadcast.EfFormat) expectedPayload := httpclient.NewPayload( httpclient.POST, "http://example.com"+arcSubmitTxRoute, @@ -101,6 +101,78 @@ func TestSubmitTransaction(t *testing.T) { } } +func TestConvertTransaction(t *testing.T) { + testCases := []struct { + name string + transaction *SubmitTxRequest + expectedResult *SubmitTxRequest + }{ + { + name: "successful conversion from RawTx to EF", + transaction: &SubmitTxRequest{ + RawTx: "0100000001a96fe5db0c2108e70abfb2b98ffbf4b7f66ca2a341c97484f1b1ebf967a2f51b000000006a47304402201846783d9e0e7abcaf3554b130f2e336865d67cbf18c5ad55580164a0b2a23590220614af1d8de08ffcbe3705de1fc48bd54031449cf8dba653da1af463922a6618d412102f9b7ecb5a0393e91aed5d27e35e723cf08c02979a8b0b1777c231a80b3d78d60ffffffff0232000000000000001976a914c63808bda42320a5a2425b3247e85cfc29f5e6f688ac31000000000000001976a9141d873677dfe9f3ae987c64fa3cb351194c68599988ac00000000", + }, + expectedResult: &SubmitTxRequest{ + RawTx: "010000000000000000ef01a96fe5db0c2108e70abfb2b98ffbf4b7f66ca2a341c97484f1b1ebf967a2f51b000000006a47304402201846783d9e0e7abcaf3554b130f2e336865d67cbf18c5ad55580164a0b2a23590220614af1d8de08ffcbe3705de1fc48bd54031449cf8dba653da1af463922a6618d412102f9b7ecb5a0393e91aed5d27e35e723cf08c02979a8b0b1777c231a80b3d78d60ffffffff64000000000000001976a91421c463658fc4457b937a7bb6aabd9c09fc70fcbb88ac0232000000000000001976a914c63808bda42320a5a2425b3247e85cfc29f5e6f688ac31000000000000001976a9141d873677dfe9f3ae987c64fa3cb351194c68599988ac00000000", + }, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // given + test_tx := *tc.transaction + + // when + err := convertToEfTransaction(&test_tx) + + // then + assert.NoError(t, err) + assert.Equal(t, *tc.expectedResult, test_tx) + }) + } +} + +func TestConvertBatchTransactions(t *testing.T) { + testCases := []struct { + name string + transactions []*SubmitTxRequest + expectedResults []*SubmitTxRequest + }{ + { + name: "successful batch conversion from RawTx to EF", + transactions: []*SubmitTxRequest{ + { + RawTx: "0100000001a96fe5db0c2108e70abfb2b98ffbf4b7f66ca2a341c97484f1b1ebf967a2f51b000000006a47304402201846783d9e0e7abcaf3554b130f2e336865d67cbf18c5ad55580164a0b2a23590220614af1d8de08ffcbe3705de1fc48bd54031449cf8dba653da1af463922a6618d412102f9b7ecb5a0393e91aed5d27e35e723cf08c02979a8b0b1777c231a80b3d78d60ffffffff0232000000000000001976a914c63808bda42320a5a2425b3247e85cfc29f5e6f688ac31000000000000001976a9141d873677dfe9f3ae987c64fa3cb351194c68599988ac00000000", + }, + { + RawTx: "0100000001a96fe5db0c2108e70abfb2b98ffbf4b7f66ca2a341c97484f1b1ebf967a2f51b000000006a47304402201846783d9e0e7abcaf3554b130f2e336865d67cbf18c5ad55580164a0b2a23590220614af1d8de08ffcbe3705de1fc48bd54031449cf8dba653da1af463922a6618d412102f9b7ecb5a0393e91aed5d27e35e723cf08c02979a8b0b1777c231a80b3d78d60ffffffff0232000000000000001976a914c63808bda42320a5a2425b3247e85cfc29f5e6f688ac31000000000000001976a9141d873677dfe9f3ae987c64fa3cb351194c68599988ac00000000", + }, + }, + expectedResults: []*SubmitTxRequest{ + { + RawTx: "010000000000000000ef01a96fe5db0c2108e70abfb2b98ffbf4b7f66ca2a341c97484f1b1ebf967a2f51b000000006a47304402201846783d9e0e7abcaf3554b130f2e336865d67cbf18c5ad55580164a0b2a23590220614af1d8de08ffcbe3705de1fc48bd54031449cf8dba653da1af463922a6618d412102f9b7ecb5a0393e91aed5d27e35e723cf08c02979a8b0b1777c231a80b3d78d60ffffffff64000000000000001976a91421c463658fc4457b937a7bb6aabd9c09fc70fcbb88ac0232000000000000001976a914c63808bda42320a5a2425b3247e85cfc29f5e6f688ac31000000000000001976a9141d873677dfe9f3ae987c64fa3cb351194c68599988ac00000000", + }, + { + RawTx: "010000000000000000ef01a96fe5db0c2108e70abfb2b98ffbf4b7f66ca2a341c97484f1b1ebf967a2f51b000000006a47304402201846783d9e0e7abcaf3554b130f2e336865d67cbf18c5ad55580164a0b2a23590220614af1d8de08ffcbe3705de1fc48bd54031449cf8dba653da1af463922a6618d412102f9b7ecb5a0393e91aed5d27e35e723cf08c02979a8b0b1777c231a80b3d78d60ffffffff64000000000000001976a91421c463658fc4457b937a7bb6aabd9c09fc70fcbb88ac0232000000000000001976a914c63808bda42320a5a2425b3247e85cfc29f5e6f688ac31000000000000001976a9141d873677dfe9f3ae987c64fa3cb351194c68599988ac00000000", + }, + }, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // given + test_txs := tc.transactions + + // when + err := convertBatchToEfTransaction(test_txs) + + // then + assert.NoError(t, err) + assert.Equal(t, tc.expectedResults, test_txs) + }) + } +} + func TestSubmitBatchTransactions(t *testing.T) { testCases := []struct { name string @@ -113,8 +185,8 @@ func TestSubmitBatchTransactions(t *testing.T) { { name: "successful request", transactions: []*broadcast.Transaction{ - {RawTx: "abc123"}, - {RawTx: "cba321"}, + {Hex: "abc123"}, + {Hex: "cba321"}, }, httpResponse: &http.Response{ StatusCode: http.StatusOK, @@ -139,8 +211,8 @@ func TestSubmitBatchTransactions(t *testing.T) { { name: "error in HTTP request", transactions: []*broadcast.Transaction{ - {RawTx: "abc123"}, - {RawTx: "cba321"}, + {Hex: "abc123"}, + {Hex: "cba321"}, }, httpError: errors.New("some error"), expectedError: errors.New("some error"), @@ -148,8 +220,8 @@ func TestSubmitBatchTransactions(t *testing.T) { { name: "missing txStatus in response", transactions: []*broadcast.Transaction{ - {RawTx: "abc123"}, - {RawTx: "cba321"}, + {Hex: "abc123"}, + {Hex: "cba321"}, }, httpResponse: &http.Response{ StatusCode: http.StatusOK, @@ -171,7 +243,7 @@ func TestSubmitBatchTransactions(t *testing.T) { // given mockHttpClient := new(MockHttpClient) - body, _ := createSubmitBatchTxsBody(tc.transactions) + body, _ := createSubmitBatchTxsBody(tc.transactions, broadcast.EfFormat) expectedPayload := httpclient.NewPayload( httpclient.POST, "http://example.com"+arcSubmitBatchTxsRoute, diff --git a/broadcast/transaction.go b/broadcast/transaction.go index f74804b..d47c8e0 100644 --- a/broadcast/transaction.go +++ b/broadcast/transaction.go @@ -2,13 +2,22 @@ package broadcast // Transaction is the body contents in the "submit transaction" request type Transaction struct { - // RawTx is the raw transaction hex string. - RawTx string `json:"rawtx"` + // Hex is the transaction hex string. + Hex string `json:"hex"` } // TransactionOptFunc defines an optional arguments that can be passed to the SubmitTransaction method. type TransactionOptFunc func(o *TransactionOpts) +// TransactionFormat is the format of transaction being submitted. +type TransactionFormat int + +const ( + EfFormat TransactionFormat = iota + BeefFormat + RawTxFormat +) + // TransactionOpts is a struct that holds optional arguments that can be passed to the SubmitTransaction method. type TransactionOpts struct { // CallbackURL is the URL that will be called when the transaction status changes. @@ -19,8 +28,13 @@ type TransactionOpts struct { MerkleProof bool // WaitForStatus is the status that the callback request will wait for. WaitForStatus TxStatus + // TransactionFormat is the format of transaction being submitted. Acceptable + // formats are: RawFormat (deprecated soon), BeefFormat and EfFormat (default). + TransactionFormat TransactionFormat } +// WithCallback allow you to get the callback from the node when the transaction is mined, +// and receive the transaction details and status. func WithCallback(callbackURL string, callbackToken ...string) TransactionOptFunc { return func(o *TransactionOpts) { o.CallbackToken = callbackURL @@ -30,14 +44,42 @@ func WithCallback(callbackURL string, callbackToken ...string) TransactionOptFun } } +// WithMerkleProof will return merkle proof from Arc. func WithMerkleProof() TransactionOptFunc { return func(o *TransactionOpts) { o.MerkleProof = true } } +// WithWaitForStatus will allow you to return the result only +// when the transaction reaches the status you set. func WithWaitForStatus(status TxStatus) TransactionOptFunc { return func(o *TransactionOpts) { o.WaitForStatus = status } } + +// WithBeefFormat will accept your transaction in BEEF format +// and decode it for a proper format acceptable by Arc. +func WithBeefFormat() TransactionOptFunc { + return func(o *TransactionOpts) { + o.TransactionFormat = BeefFormat + } +} + +// WithEfFormat will submit your transaction in EF format. +func WithEfFormat() TransactionOptFunc { + return func(o *TransactionOpts) { + o.TransactionFormat = EfFormat + } +} + +// WithRawFormat will accept your transaction in RawTx format +// and encode it for a proper format acceptable by Arc. +// Deprecated: This function will be depreacted soon. +// Only EF and BEEF format will be acceptable. +func WithRawFormat() TransactionOptFunc { + return func(o *TransactionOpts) { + o.TransactionFormat = RawTxFormat + } +} diff --git a/examples/submit_batch_transactions/submit_batch_txs.go b/examples/submit_batch_transactions/submit_batch_txs.go index 1c75ca1..13afae4 100644 --- a/examples/submit_batch_transactions/submit_batch_txs.go +++ b/examples/submit_batch_transactions/submit_batch_txs.go @@ -13,11 +13,11 @@ func main() { token := "{token}" apiURL := "https://tapi.taal.com/arc" txs := []*broadcast.Transaction{ - {RawTx: "0100000001d6d1607b208b30c0a3fe21d563569c4d2a0f913604b4c5054fe267da6be324ab220000006b4830450221009a965dcd5d42983090a63cfd761038ff8adcea621c46a68a205f326292a95383022061b8d858f366c69f3ebd30a60ccafe36faca4e242ac3d2edd3bf63b669bcf23b4121034e871e147aa4a3e2f1665eaf76cf9264d089b6a91702af92bd6ce33bac84a765ffffffff0123020000000000001976a914d8819a7197d3e221e15f4348203fdecfd29fa2b888ac00000000"}, - {RawTx: "0100000001bbf4d68a935126df1eea3e14714c5a6438bbda3d18b7e754bc90895cf2190196060000006a47304402206a0c3923a9ae253ac5ea22d36e667f067c51f88a8bfb2861d9124bc5402fecd00220745731fa951076c63df2de6528616e0ef97ef8ade5b17d8e1d51494cf153a64b412103af3ead8a3ab792225bf22262f0b81a72e5070788d363ee717c5868421b75a62dffffffff01000000000000000040006a0a6d793263656e74732c201c4759384d656c46634d326449674d75557356644d53776f6a66467431152c20302e323135353133313536343634343531313600000000"}, - {RawTx: "0100000001bbf4d68a935126df1eea3e14714c5a6438bbda3d18b7e754bc90895cf21901962b0000006a47304402202eb4a5a437da4f2d17f0301e8f7fe1364a3655069797284501b5295d707238e802202ff312652a492ff9bd63c700283abe321b637b34b41794e47b37d589503132c1412103af3ead8a3ab792225bf22262f0b81a72e5070788d363ee717c5868421b75a62dffffffff01000000000000000042006a0a6d793263656e74732c201c625a753465447130333856726a75736e6f4651746573455355344c32172c20302e3030313834343339383431333331333235393800000000"}, - {RawTx: "010000000141dcf2f3e9146db096eaab29576546567c81ec43b181f61012606d0bd6e2ad5c010000006b483045022100bda2f44e5c27f1c10ca908996125d4debbc8bfdd9e23c8140d3006390d71ffd50220486c0725a03e798295270b20b1163f525f211a4be4cd9855225e4a3865ab3ae841210262142850483b6728b8ecd299e4d0c8cf30ea0636f66205166814e52d73b64b4bffffffff0200000000000000000a006a075354554b2e434f8ea2e60c000000001976a91454cba8da8701174e34aac2bb31d42a88e2c302d088ac00000000"}, - {RawTx: "01000000018e5cbbfdf1c4e1c168037bbb305be7018de2d19f6de919c75678daad07c375af010000006b483045022100a0b73eca88a56b30e7f2ea7388aac64506cbdccdc04452b9150344054fc969ae022071dc9e99e42825495ab4f274066a519f0ecaf671d84c156b37458fcd888159ba41210262142850483b6728b8ecd299e4d0c8cf30ea0636f66205166814e52d73b64b4bffffffff0200000000000000000a006a075354554b2e434f90a2e60c000000001976a91454cba8da8701174e34aac2bb31d42a88e2c302d088ac00000000"}, + {Hex: "0100000001d6d1607b208b30c0a3fe21d563569c4d2a0f913604b4c5054fe267da6be324ab220000006b4830450221009a965dcd5d42983090a63cfd761038ff8adcea621c46a68a205f326292a95383022061b8d858f366c69f3ebd30a60ccafe36faca4e242ac3d2edd3bf63b669bcf23b4121034e871e147aa4a3e2f1665eaf76cf9264d089b6a91702af92bd6ce33bac84a765ffffffff0123020000000000001976a914d8819a7197d3e221e15f4348203fdecfd29fa2b888ac00000000"}, + {Hex: "0100000001bbf4d68a935126df1eea3e14714c5a6438bbda3d18b7e754bc90895cf2190196060000006a47304402206a0c3923a9ae253ac5ea22d36e667f067c51f88a8bfb2861d9124bc5402fecd00220745731fa951076c63df2de6528616e0ef97ef8ade5b17d8e1d51494cf153a64b412103af3ead8a3ab792225bf22262f0b81a72e5070788d363ee717c5868421b75a62dffffffff01000000000000000040006a0a6d793263656e74732c201c4759384d656c46634d326449674d75557356644d53776f6a66467431152c20302e323135353133313536343634343531313600000000"}, + {Hex: "0100000001bbf4d68a935126df1eea3e14714c5a6438bbda3d18b7e754bc90895cf21901962b0000006a47304402202eb4a5a437da4f2d17f0301e8f7fe1364a3655069797284501b5295d707238e802202ff312652a492ff9bd63c700283abe321b637b34b41794e47b37d589503132c1412103af3ead8a3ab792225bf22262f0b81a72e5070788d363ee717c5868421b75a62dffffffff01000000000000000042006a0a6d793263656e74732c201c625a753465447130333856726a75736e6f4651746573455355344c32172c20302e3030313834343339383431333331333235393800000000"}, + {Hex: "010000000141dcf2f3e9146db096eaab29576546567c81ec43b181f61012606d0bd6e2ad5c010000006b483045022100bda2f44e5c27f1c10ca908996125d4debbc8bfdd9e23c8140d3006390d71ffd50220486c0725a03e798295270b20b1163f525f211a4be4cd9855225e4a3865ab3ae841210262142850483b6728b8ecd299e4d0c8cf30ea0636f66205166814e52d73b64b4bffffffff0200000000000000000a006a075354554b2e434f8ea2e60c000000001976a91454cba8da8701174e34aac2bb31d42a88e2c302d088ac00000000"}, + {Hex: "01000000018e5cbbfdf1c4e1c168037bbb305be7018de2d19f6de919c75678daad07c375af010000006b483045022100a0b73eca88a56b30e7f2ea7388aac64506cbdccdc04452b9150344054fc969ae022071dc9e99e42825495ab4f274066a519f0ecaf671d84c156b37458fcd888159ba41210262142850483b6728b8ecd299e4d0c8cf30ea0636f66205166814e52d73b64b4bffffffff0200000000000000000a006a075354554b2e434f90a2e60c000000001976a91454cba8da8701174e34aac2bb31d42a88e2c302d088ac00000000"}, } cfg := broadcast_client.ArcClientConfig{ @@ -29,7 +29,7 @@ func main() { WithArc(cfg). Build() - result, err := client.SubmitBatchTransactions(context.Background(), txs) + result, err := client.SubmitBatchTransactions(context.Background(), txs, broadcast.WithRawFormat()) if err != nil { log.Fatalf("error: %s", err.Error()) } diff --git a/examples/submit_transaction/submit_tx.go b/examples/submit_transaction/submit_tx.go index 0fbb6d4..ff4125e 100644 --- a/examples/submit_transaction/submit_tx.go +++ b/examples/submit_transaction/submit_tx.go @@ -12,7 +12,7 @@ import ( func main() { token := "" apiURL := "https://tapi.taal.com/arc" - tx := broadcast.Transaction{RawTx: "0100000001d6d1607b208b30c0a3fe21d563569c4d2a0f913604b4c5054fe267da6be324ab220000006b4830450221009a965dcd5d42983090a63cfd761038ff8adcea621c46a68a205f326292a95383022061b8d858f366c69f3ebd30a60ccafe36faca4e242ac3d2edd3bf63b669bcf23b4121034e871e147aa4a3e2f1665eaf76cf9264d089b6a91702af92bd6ce33bac84a765ffffffff0123020000000000001976a914d8819a7197d3e221e15f4348203fdecfd29fa2b888ac00000000"} + tx := broadcast.Transaction{Hex: "0100000001d6d1607b208b30c0a3fe21d563569c4d2a0f913604b4c5054fe267da6be324ab220000006b4830450221009a965dcd5d42983090a63cfd761038ff8adcea621c46a68a205f326292a95383022061b8d858f366c69f3ebd30a60ccafe36faca4e242ac3d2edd3bf63b669bcf23b4121034e871e147aa4a3e2f1665eaf76cf9264d089b6a91702af92bd6ce33bac84a765ffffffff0123020000000000001976a914d8819a7197d3e221e15f4348203fdecfd29fa2b888ac00000000"} cfg := broadcast_client.ArcClientConfig{ Token: token, @@ -23,7 +23,7 @@ func main() { WithArc(cfg). Build() - result, err := client.SubmitTransaction(context.Background(), &tx) + result, err := client.SubmitTransaction(context.Background(), &tx, broadcast.WithRawFormat()) if err != nil { log.Fatalf("error: %s", err.Error()) } diff --git a/go.mod b/go.mod index a98ccb4..0ed2051 100644 --- a/go.mod +++ b/go.mod @@ -2,11 +2,30 @@ module github.com/bitcoin-sv/go-broadcast-client go 1.20 -require github.com/stretchr/testify v1.8.4 +require ( + github.com/GorillaPool/go-junglebus v0.2.0 + github.com/libsv/go-bt/v2 v2.2.5 + github.com/stretchr/testify v1.8.4 +) require ( + github.com/centrifugal/centrifuge-go v0.9.4 // indirect + github.com/centrifugal/protocol v0.8.11 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/gorilla/websocket v1.5.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/jpillora/backoff v1.0.0 // indirect + github.com/libsv/go-bk v0.1.6 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rogpeppe/go-internal v1.11.0 // indirect + github.com/segmentio/asm v1.2.0 // indirect + github.com/segmentio/encoding v0.3.5 // indirect github.com/stretchr/objx v0.5.0 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + golang.org/x/crypto v0.13.0 // indirect + golang.org/x/sys v0.12.0 // indirect + google.golang.org/protobuf v1.28.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 673e918..413c716 100644 --- a/go.sum +++ b/go.sum @@ -1,18 +1,68 @@ +github.com/GorillaPool/go-junglebus v0.2.0 h1:ZePeZgQLEyYrJbBaHB6GKeMt+QOyw4EOtXxAyZvpkAk= +github.com/GorillaPool/go-junglebus v0.2.0/go.mod h1:LigiSImLy9G/Tg9RKbn+pNYnuPbs5oLUprQZkxEr/2k= +github.com/centrifugal/centrifuge-go v0.9.4 h1:YNuh3B3iiKj/6AVseiiGDG3NBW3ykHwVSz1kk+9YhuI= +github.com/centrifugal/centrifuge-go v0.9.4/go.mod h1:L0aQ0u5ixvTJ/pz7ZyXvwOXuRARzXW32fVRvCOUbu5I= +github.com/centrifugal/protocol v0.8.11 h1:LP3fi5h8mApGmQ3iYb6PWrfID0PvGHS+zXZIjB82qhM= +github.com/centrifugal/protocol v0.8.11/go.mod h1:qpYrxz4cDj+rlgC6giSADkf7XDN1K7aFmkkFwt/bayQ= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/libsv/go-bk v0.1.6 h1:c9CiT5+64HRDbzxPl1v/oiFmbvWZTuUYqywCf+MBs/c= +github.com/libsv/go-bk v0.1.6/go.mod h1:khJboDoH18FPUaZlzRFKzlVN84d4YfdmlDtdX4LAjQA= +github.com/libsv/go-bt/v2 v2.2.5 h1:VoggBLMRW9NYoFujqe5bSYKqnw5y+fYfufgERSoubog= +github.com/libsv/go-bt/v2 v2.2.5/go.mod h1:cV45+jDlPOLfhJLfpLmpQoWzrIvVth9Ao2ZO1f6CcqU= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg= +github.com/segmentio/asm v1.1.4/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg= +github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= +github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= +github.com/segmentio/encoding v0.3.5 h1:UZEiaZ55nlXGDL92scoVuw00RmiRCazIEmvPSbSvt8Y= +github.com/segmentio/encoding v0.3.5/go.mod h1:n0JeuIqEQrQoPDGsjo8UNd1iA0U8d8+oHAA4E3G3OxM= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=