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

Commit

Permalink
feat(BUX-158): README docs + comments for godocs (#8)
Browse files Browse the repository at this point in the history
* docs: adds comment docs for go docs

* docs: creates docs for implemented methods

* fix: moves line to correct paragraph

* fix: simplifies docs by removing implementation details

* Update broadcast/broadcast-client/client_builder.go

Co-authored-by: Damian Orzepowski <damian.orzepowski@softwarelab.com.pl>

* Update broadcast/broadcast-client/client_builder.go

Co-authored-by: Damian Orzepowski <damian.orzepowski@softwarelab.com.pl>

* fix: adds example to the errundefinedclient

* fix: adds example for ErrStrategyUnkown comments

* fix: adds description for transactionquerier

* fix: adds specific description for TransactionSubmitters

* Update broadcast/interface.go

Co-authored-by: Damian Orzepowski <damian.orzepowski@softwarelab.com.pl>

* feat: removes unnecessary comments inside internal package

* feat: handles decoding error instead of ignoring it

* docs: added description for OneByOneStrategy

* docs: adds suggested description for strategy struct.

* Update broadcast/broadcast-client/arc_config.go

Co-authored-by: Damian Orzepowski <damian.orzepowski@softwarelab.com.pl>

* docs: removes code samples and documented it in "words" instead

* fix: removes unnecessary comments from config interface

* fix: removes queryTransaction implementation comment

* fix: removes SubmitBatchTransaction implementation commit

---------

Co-authored-by: Damian Orzepowski <damian.orzepowski@softwarelab.com.pl>
  • Loading branch information
wregulski and dorzepowski authored Aug 14, 2023
1 parent f8e769f commit 7596179
Show file tree
Hide file tree
Showing 21 changed files with 323 additions and 73 deletions.
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.
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.
// 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.
// 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.
// 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.
// 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.
// 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

0 comments on commit 7596179

Please sign in to comment.