Skip to content
This repository has been archived by the owner on Oct 21, 2024. It is now read-only.

feat(BUX-158): README docs + comments for godocs #8

Merged
merged 20 commits into from
Aug 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
3fda614
docs: adds comment docs for go docs
wregulski Aug 10, 2023
a9f1763
docs: creates docs for implemented methods
wregulski Aug 10, 2023
f885e1f
fix: moves line to correct paragraph
wregulski Aug 11, 2023
50038c1
fix: simplifies docs by removing implementation details
wregulski Aug 11, 2023
bea2fd5
Update broadcast/broadcast-client/client_builder.go
wregulski Aug 12, 2023
c08cb37
Update broadcast/broadcast-client/client_builder.go
wregulski Aug 12, 2023
288d2ef
fix: adds example to the errundefinedclient
wregulski Aug 12, 2023
24c2ed9
fix: adds example for ErrStrategyUnkown comments
wregulski Aug 12, 2023
4f07673
fix: adds description for transactionquerier
wregulski Aug 12, 2023
a2bd972
fix: adds specific description for TransactionSubmitters
wregulski Aug 12, 2023
49de171
Update broadcast/interface.go
wregulski Aug 12, 2023
7eddb09
feat: removes unnecessary comments inside internal package
wregulski Aug 12, 2023
5fa1e0b
feat: handles decoding error instead of ignoring it
wregulski Aug 12, 2023
6dc9319
docs: added description for OneByOneStrategy
wregulski Aug 12, 2023
023aafc
docs: adds suggested description for strategy struct.
wregulski Aug 12, 2023
8d86fbc
Update broadcast/broadcast-client/arc_config.go
wregulski Aug 12, 2023
42b280b
docs: removes code samples and documented it in "words" instead
wregulski Aug 12, 2023
321b992
fix: removes unnecessary comments from config interface
wregulski Aug 14, 2023
3968650
fix: removes queryTransaction implementation comment
wregulski Aug 14, 2023
908c02c
fix: removes SubmitBatchTransaction implementation commit
wregulski Aug 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
run:
skip-dirs:
- internal/
219 changes: 194 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,46 +1,53 @@
# WORK IN PROGRESS

# go-broadcast-client

> Interact with Bitcoin SV Overlay Nodes exposing the interface [interface.go](https://github.com/bitcoin-sv/go-broadcast-client/blob/main/broadcast/interface.go)

## Features

# Features
- Arc API Support [details](https://github.com/bitcoin-sv/arc):
- [x] [Query Transaction Status](https://bitcoin-sv.github.io/arc/api.html#/Arc/GET%20transaction%20status)
- [x] [Submit Transaction](https://bitcoin-sv.github.io/arc/api.html#/Arc/POST%20transaction)
- [x] [Submit Batch Transactions](https://bitcoin-sv.github.io/arc/api.html#/Arc/POST%20transactions)
- [ ] Quote Services -> WORK IN PROGRESS
- [ ] Submit Batch Transactions -> WORK IN PROGRESS

# What is library doing?
## What is library doing?

It is a wrapper around the [Bitcoin SV Overlay API](https://bitcoin-sv.github.io/arc/api.html) that allows you to interact with the API in a more convenient way by providing a set of
custom features to work with multiple nodes and retry logic.

# Custom features
- [x] Possibility to work with multiple nodes [builder pattern](https://github.com/bitcoin-sv/go-broadcast-client/blob/main/broadcast/broadcast-client/client_builder.go)
- [x] Define strategy how to work with multiple nodes [strategy](https://github.com/bitcoin-sv/go-broadcast-client/blob/main/broadcast/internal/composite/strategy.go)
- [x] Gives possibility to handle different client exposing the same interface as Arc [WithArc](https://github.com/bitcoin-sv/go-broadcast-client/blob/main/broadcast/broadcast-client/client_builder.go)
- [x] Possibility to set url and access token for each node independently [Config](https://github.com/bitcoin-sv/go-broadcast-client/blob/main/broadcast/broadcast-client/arc_config.go)
- [x] Possibility to use custom http client [WithHTTPClient](https://github.com/bitcoin-sv/go-broadcast-client/blob/main/broadcast/broadcast-client/client_builder.go#L19)
## Custom features

- [x] Possibility to work with multiple nodes [builder pattern](https://github.com/bitcoin-sv/go-broadcast-client/blob/main/broadcast/broadcast-client/client_builder.go)

- [x] Define strategy how to work with multiple nodes [strategy](https://github.com/bitcoin-sv/go-broadcast-client/blob/main/broadcast/internal/composite/strategy.go)

- [x] Gives possibility to handle different client exposing the same interface as Arc [WithArc](https://github.com/bitcoin-sv/go-broadcast-client/blob/main/broadcast/broadcast-client/client_builder.go)

- [x] Possibility to set url and access token for each node independently [Config](https://github.com/bitcoin-sv/go-broadcast-client/blob/main/broadcast/broadcast-client/arc_config.go)

- [x] Possibility to use custom http client [WithHTTPClient](https://github.com/bitcoin-sv/go-broadcast-client/blob/main/broadcast/broadcast-client/client_builder.go#L19)

## How to use it?

# How to use it?
### Create client

## Create client
```go
// Set the access token and url for the node
token := ""
apiURL := "https://tapi.taal.com/arc"
token := ""
apiURL := "https://tapi.taal.com/arc"

cfg := broadcast_client.ArcClientConfig{
Token: token,
APIUrl: apiURL,
}
cfg := broadcast_client.ArcClientConfig{
Token: token,
APIUrl: apiURL,
}

client := broadcast_client.Builder().
WithArc(cfg).
Build()
client := broadcast_client.Builder().
WithArc(cfg).
Build()
```

## Use the method exposed by the interface
### Use the method exposed by the interface

```go
// ...
hex := "9c5f5244ee45e8c3213521c1d1d5df265d6c74fb108961a876917073d65fef14"
Expand All @@ -51,7 +58,169 @@ custom features to work with multiple nodes and retry logic.

Examples of usage can be found in the [examples](https://github.com/bitcoin-sv/go-broadcast-client/tree/main/examples)

# Transaction Statuses returned by the library
## ClientBuilder Methods

Client allows you to create your own client with setting it with multiple nodes and custom http client.

### WithArc Method

```go
// Set the access token and url for the node
token := ""
apiURL := "https://tapi.taal.com/arc"

cfg := broadcast_client.ArcClientConfig{
Token: token,
APIUrl: apiURL,
}

client := broadcast_client.Builder().
WithArc(cfg).
Build()
```

We can also call multiple times the method `WithArc` to set multiple nodes.

```go
// Set the access token and url for the node
client := broadcast_client.Builder().
WithArc(cfg1).
WithArc(cfg2).
WithArc(cfg3).
Build()
```

What is the call order if we have multiple nodes configured?

We use the **strategy** to define the order of the calls. The default strategy is `OneByOne` in RoundRobin algorith that will call the nodes in the order they were set.
**Only if all of them fail, we will return the error to the user.**

### WithHTTPClient Method

```go
// (...)
clent := broadcast_client.Builder().
WithArc(cfg).
WithHTTPClient(&http.Client{}).
Build()
```

We can use the method `WithHTTPClient` to set the custom http client.
It needs to implement the interface `HTTPInterface` that is defined in the [httpclient.go](/broadcast/internal/httpclient/http_client.go#L35)

```go
type HTTPInterface interface {
DoRequest(ctx context.Context, pld HTTPRequest) (*http.Response, error)
}
```


## ARC Client Methods

### QueryTx Method

When you created your own client, you can use the method `QueryTx` to query the status of a transaction.

```go
// ...
hex := "9c5f5244ee45e8c3213521c1d1d5df265d6c74fb108961a876917073d65fef14"

result, err := client.QueryTransaction(context.Background(), hex)
// ...
```

### SubmitTx Method

Having your client created, you can use the method `SubmitTx` to submit a single transaction to the node.

```go
// ...
tx := broadcast.Transaction{
RawTx: "xyz",
}

result, err := client.SubmitTransaction(context.Background(), tx)
// ...
```

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.

Setting tx.CallBackURL and tx.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.

### SubmitBatchTx Method

Having your client created, you can use the method `SubmitBatchTx` to submit a batch of transactions to the node.

```go
// ...
txs := []*broadcast.Transaction{
{RawTx: "xyz1"},
{RawTx: "xyz2"},
{RawTx: "xyz3"},
{RawTx: "xyz4"},
{RawTx: "xyz5"},
}

result, err := client.SubmitBatchTransaction(context.Background(), txs)
// ...
```

The method works the same as the `SubmitTx` method, but it is sending a batch of transactions instead of a single one. It is also receiving a batch of responses for each of the transactions.

## Models and constants

### QueryTx

#### QueryTxResponse

```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"`
}
```

### SubmitTx

#### SubmitTxResponse

```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"`
}
```

#### Transaction

```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"`
}
```

### Transaction Statuses returned by the library

| Code | Status | Description |
|-----|------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
Expand All @@ -66,6 +235,6 @@ Examples of usage can be found in the [examples](https://github.com/bitcoin-sv/g
| 8 | `SEEN_ON_NETWORK` | The transaction has been seen on the Bitcoin network and propagated to other nodes. This status is set when metamorph receives an INV message for the transaction from another node than it was sent to. |
| 9 | `MINED` | The transaction has been mined into a block by a mining node. |
| 108 | `CONFIRMED` | The transaction is marked as confirmed when it is in a block with 100 blocks built on top of that block. |
| 109 | `REJECTED` | The transaction has been rejected by the Bitcoin network.
| 109 | `REJECTED` | The transaction has been rejected by the Bitcoin network.

*Source* [Arc API](https://github.com/bitcoin-sv/arc/blob/main/README.md)
5 changes: 5 additions & 0 deletions broadcast/broadcast-client/arc_config.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
// Package broadcast_client contains the client for the broadcast service.
package broadcast_client

// ArcClientConfig is used by [WithArc] to set up the connection between the broadcast client and Arc.
// The provided token will be used as the Authorization header.
type ArcClientConfig struct {
APIUrl string
Token string
}

// GetApiUrl returns the API url.
func (c *ArcClientConfig) GetApiUrl() string {
return c.APIUrl
}

// GetToken returns the token.
func (c *ArcClientConfig) GetToken() string {
return c.Token
}
5 changes: 5 additions & 0 deletions broadcast/broadcast-client/client_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,27 @@ type builder struct {
client httpclient.HTTPInterface
}

// Builder is used to prepare the broadcast client. It is recommended to use that builder for creating the broadcast client.
func Builder() *builder {
return &builder{}
}

// WithHttpClient sets the http client to be used by the broadcast client. It requires a httpclient.HTTPInterface to be passed.
dorzepowski marked this conversation as resolved.
Show resolved Hide resolved
func (cb *builder) WithHttpClient(client httpclient.HTTPInterface) *builder {
cb.client = client
return cb
}

// WithArc sets up the connection of the broadcast client to the Arc service using the provided ArcClientConfig.
// This method can be called multiple times with different ArcClientConfigurations to establish connections to multiple Arc instances.
func (cb *builder) WithArc(config ArcClientConfig) *builder {
cb.factories = append(cb.factories, func() broadcast_api.Client {
return arc.NewArcClient(&config, cb.client)
})
return cb
}

// Build builds the broadcast client based on the provided configuration.
func (cb *builder) Build() broadcast_api.Client {
if len(cb.factories) == 1 {
return cb.factories[0]()
Expand Down
23 changes: 23 additions & 0 deletions broadcast/errors.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Package broadcast provides a set of functions to broadcast or query transactions to the Bitcoin SV network using the client provided.
package broadcast

import (
Expand All @@ -6,22 +7,43 @@ import (
"strings"
)

// ErrClientUndefined is returned when the client is undefined.
dorzepowski marked this conversation as resolved.
Show resolved Hide resolved
// Example:
//
// func (a *ArcClient) QueryTransaction(ctx context.Context, txID string) (*broadcast.QueryTxResponse, error) {
// if a == nil {
// return nil, broadcast.ErrClientUndefined
// }
//
// It should be returned for all defined clients in the future.
var ErrClientUndefined = errors.New("client is undefined")

// ErrAllBroadcastersFailed is returned when all configured broadcasters failed to broadcast the transaction.
var ErrAllBroadcastersFailed = errors.New("all broadcasters failed")

// ErrURLEmpty is returned when the API URL is empty.
var ErrURLEmpty = errors.New("url is empty")

// ErrBroadcastFailed is returned when the broadcast failed.
var ErrBroadcasterFailed = errors.New("broadcaster failed")

// ErrUnableToDecodeResponse is returned when the http response cannot be decoded.
var ErrUnableToDecodeResponse = errors.New("unable to decode response")

// ErrMissingStatus is returned when the tx status is missing.
var ErrMissingStatus = errors.New("missing tx status")

// ErrStrategyUnkown is returned when the strategy provided is unknown.
dorzepowski marked this conversation as resolved.
Show resolved Hide resolved
// Example:
//
// func NewBroadcaster(strategy Strategy, factories ...BroadcastFactory) broadcast.Client
// Calling NewBroadcaster we need to provide a strategy, if the strategy is unknown (we don't have an implementation for that) we return ErrStrategyUnkown.
var ErrStrategyUnkown = errors.New("unknown strategy")

// ErrNoMinerResponse is returned when no response is received from any miner.
var ErrNoMinerResponse = errors.New("failed to get reponse from any miner")

// ArcError is general type for the error returned by the ArcClient.
type ArcError struct {
Type string `json:"type"`
Title string `json:"title"`
Expand All @@ -32,6 +54,7 @@ type ArcError struct {
ExtraInfo string `json:"extraInfo,omitempty"`
}

// Error returns the error string it's the implementation of the error interface.
func (err ArcError) Error() string {
sb := strings.Builder{}

Expand Down
11 changes: 11 additions & 0 deletions broadcast/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,29 @@ type PolicyQuoter interface {
GetPolicyQuote(ctx context.Context) (*PolicyQuoteResponse, error)
}

// TransactionQuerier is the interface that wraps the QueryTransaction method.
dorzepowski marked this conversation as resolved.
Show resolved Hide resolved
// It takes a transaction ID and returns the transaction details, like it's status, hash, height etc.
// Everything is wrapped in the QueryTxResponse struct.
type TransactionQuerier interface {
QueryTransaction(ctx context.Context, txID string) (*QueryTxResponse, error)
}

// TransactionSubmitter is the interface that wraps the SubmitTransaction method.
dorzepowski marked this conversation as resolved.
Show resolved Hide resolved
// It takes a transaction and tries to broadcast it to the P2P network.
// Transaction object needs RawTx to be set. All other fields are optional and used to append headers related to status callbacks.
// As a result it returns a SubmitTxResponse object.
type TransactionSubmitter interface {
SubmitTransaction(ctx context.Context, tx *Transaction) (*SubmitTxResponse, error)
}

// TransactionsSubmitter is the interface that wraps the SubmitBatchTransactions method.
dorzepowski marked this conversation as resolved.
Show resolved Hide resolved
// It is the same as TransactionSubmitter but it takes a slice of transactions and tries to broadcast them to the P2P network.
// As a result it returns a slice of SubmitTxResponse objects.
type TransactionsSubmitter interface {
SubmitBatchTransactions(ctx context.Context, tx []*Transaction) ([]*SubmitTxResponse, error)
}

// Client is a grouping interface that represents the entire exposed functionality of the broadcast client.
type Client interface {
BestQuoter
FastestQuoter
Expand Down
Loading