diff --git a/.gitignore b/.gitignore index 8a269e0..8cc509d 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,17 @@ go.work #asdf .tool-versions + +## Visual Studio Code +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix diff --git a/broadcast/internal/arc/arc_submit_tx.go b/broadcast/internal/arc/arc_submit_tx.go index 1cd462b..8689603 100644 --- a/broadcast/internal/arc/arc_submit_tx.go +++ b/broadcast/internal/arc/arc_submit_tx.go @@ -2,14 +2,17 @@ package arc import ( "context" + "encoding/hex" "encoding/json" "errors" + "fmt" "net/http" "strconv" "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" + "github.com/libsv/go-bt/v2" ) type SubmitTxRequest struct { @@ -150,8 +153,9 @@ func submitBatchTransactions(ctx context.Context, arc *ArcClient, txs []*broadca } func createSubmitTxBody(arc *ArcClient, tx *broadcast.Transaction, txFormat broadcast.TransactionFormat) ([]byte, error) { - body := &SubmitTxRequest{ - RawTx: tx.Hex, + body, err := formatTxRequest(arc, tx, txFormat) + if err != nil { + return nil, err } data, err := json.Marshal(body) @@ -165,8 +169,9 @@ func createSubmitTxBody(arc *ArcClient, tx *broadcast.Transaction, txFormat broa func createSubmitBatchTxsBody(arc *ArcClient, txs []*broadcast.Transaction, txFormat broadcast.TransactionFormat) ([]byte, error) { rawTxs := make([]*SubmitTxRequest, 0, len(txs)) for _, tx := range txs { - requestTx := &SubmitTxRequest{ - RawTx: tx.Hex, + requestTx, err := formatTxRequest(arc, tx, txFormat) + if err != nil { + return nil, err } rawTxs = append(rawTxs, requestTx) } @@ -249,3 +254,110 @@ func validateSubmitTxResponse(model *broadcast.SubmittedTx) error { return nil } + +func formatTxRequest(arc *ArcClient, tx *broadcast.Transaction, txFormat broadcast.TransactionFormat) (*SubmitTxRequest, error) { + var ( + body *SubmitTxRequest + err error + ) + + switch txFormat { + case broadcast.RawTxFormat: + body, err = rawTxRequest(arc, tx.Hex) + case broadcast.EfFormat: + body, err = efTxRequest(tx.Hex) + case broadcast.BeefFormat: + body, err = beefTxRequest(tx.Hex) + default: + err = fmt.Errorf("unknown transaction format") + } + + if err != nil { + return nil, err + } + + return body, nil +} + +func efTxRequest(rawTx string) (*SubmitTxRequest, error) { + request := &SubmitTxRequest{RawTx: rawTx} + + return request, nil +} + +func beefTxRequest(rawTx string) (*SubmitTxRequest, error) { + return nil, fmt.Errorf("submitting transactions in BEEF format is unimplemented yet...") +} + +func rawTxRequest(arc *ArcClient, rawTx string) (*SubmitTxRequest, error) { + transaction, err := bt.NewTxFromString(rawTx) + if err != nil { + return nil, err + } + + for _, input := range transaction.Inputs { + if err = updateUtxoWithMissingData(arc, input); err != nil { + return nil, err + } + } + + request := &SubmitTxRequest{ + RawTx: hex.EncodeToString(transaction.ExtendedBytes()), + } + return request, nil +} + +func updateUtxoWithMissingData(arc *ArcClient, input *bt.Input) error { + txid := input.PreviousTxIDStr() + + pld := httpclient.NewPayload( + httpclient.GET, + fmt.Sprintf("https://junglebus.gorillapool.io/v1/transaction/get/%s", txid), + "", + nil, + ) + resp, err := arc.HTTPClient.DoRequest(context.Background(), pld) + if err != nil { + return err + } + + var tx *junglebusTransaction + err = arc_utils.DecodeResponseBody(resp.Body, &tx) + 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 +} + +type junglebusTransaction struct { + ID string `json:"id"` + Transaction []byte `json:"transaction"` + BlockHash string `json:"block_hash"` + BlockHeight uint32 `json:"block_height"` + BlockTime uint32 `json:"block_time"` + BlockIndex uint64 `json:"block_index"` + + // index data + // input/output types are + // p2pkh, p2sh, token-stas, opreturn, tokenized, metanet, bitcom, run, map, bap, non-standard etc. + Addresses []string `json:"addresses"` + Inputs []string `json:"inputs"` + Outputs []string `json:"outputs"` + InputTypes []string `json:"input_types"` + OutputTypes []string `json:"output_types"` + Contexts []string `json:"contexts"` // optional contexts of output types, only for known protocols + SubContexts []string `json:"sub_contexts"` // optional sub-contexts of output types, only for known protocols + Data []string `json:"data"` // optional data of output types, only for known protocols + + // the merkle proof in binary + MerkleProof []byte `json:"merkle_proof"` +} diff --git a/broadcast/transaction_options.go b/broadcast/transaction_options.go index 73af50a..49bfeaa 100644 --- a/broadcast/transaction_options.go +++ b/broadcast/transaction_options.go @@ -52,3 +52,28 @@ func WithWaitForStatus(status TxStatus) TransactionOptFunc { 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 e3356b0..fbd807e 100644 --- a/examples/submit_batch_transactions/submit_batch_txs.go +++ b/examples/submit_batch_transactions/submit_batch_txs.go @@ -32,7 +32,7 @@ func main() { WithArc(cfg, &logger). 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 47d72ff..a3540de 100644 --- a/examples/submit_transaction/submit_tx.go +++ b/examples/submit_transaction/submit_tx.go @@ -26,7 +26,7 @@ func main() { WithArc(cfg, &logger). 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 f1b0949..924d105 100644 --- a/go.mod +++ b/go.mod @@ -3,19 +3,22 @@ module github.com/bitcoin-sv/go-broadcast-client go 1.21.5 require ( + github.com/libsv/go-bt/v2 v2.2.5 github.com/rs/zerolog v1.31.0 github.com/stretchr/testify v1.8.4 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect - github.com/kr/pretty v0.3.1 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/libsv/go-bk v0.1.6 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // 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/stretchr/objx v0.5.1 // indirect + golang.org/x/crypto v0.13.0 // indirect golang.org/x/sys v0.15.0 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 3bb6d40..4678972 100644 --- a/go.sum +++ b/go.sum @@ -4,24 +4,24 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs 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/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +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/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +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.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 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/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= @@ -37,6 +37,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=