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

Commit

Permalink
feat: adds tests and basic docs (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
wregulski authored Aug 4, 2023
1 parent ebb8aea commit 0063bb2
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 17 deletions.
72 changes: 71 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,71 @@
**Work In Progress**
# 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
- 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)
- [ ] Quote Services -> WORK IN PROGRESS
- [ ] Submit Batch Transactions -> WORK IN PROGRESS

# 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)

# How to use it?

## Create client
```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()
```

## Use the method exposed by the interface
```go
// ...
hex := "9c5f5244ee45e8c3213521c1d1d5df265d6c74fb108961a876917073d65fef14"

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

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

| Code | Status | Description |
|-----|------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 0 | `UNKNOWN` | The transaction has been sent to metamorph, but no processing has taken place. This should never be the case, unless something goes wrong. |
| 1 | `QUEUED` | The transaction has been queued for processing. |
| 2 | `RECEIVED` | The transaction has been properly received by the metamorph processor. |
| 3 | `STORED` | The transaction has been stored in the metamorph store. This should ensure the transaction will be processed and retried if not picked up immediately by a mining node. |
| 4 | `ANNOUNCED_TO_NETWORK` | The transaction has been announced (INV message) to the Bitcoin network. |
| 5 | `REQUESTED_BY_NETWORK` | The transaction has been requested from metamorph by a Bitcoin node. |
| 6 | `SENT_TO_NETWORK` | The transaction has been sent to at least 1 Bitcoin node. |
| 7 | `ACCEPTED_BY_NETWORK` | The transaction has been accepted by a connected Bitcoin node on the ZMQ interface. If metamorph is not connected to ZQM, this status will never by set. |
| 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.

*Source* [Arc API](https://github.com/bitcoin-sv/arc/blob/main/README.md)
2 changes: 2 additions & 0 deletions broadcast/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@ var ErrBroadcasterFailed = errors.New("broadcaster failed")
var ErrUnableToDecodeResponse = errors.New("unable to decode response")

var ErrMissingStatus = errors.New("missing tx status")

var ErrStrategyUnkown = errors.New("unknown strategy")
47 changes: 47 additions & 0 deletions broadcast/internal/composite/broadcaster_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package composite

import (
"testing"

"github.com/bitcoin-sv/go-broadcast-client/broadcast"
"github.com/stretchr/testify/assert"
)

// MockClient to mock broadcast client
type MockClient struct {
broadcast.Client
}

// MockBroadcastFactory to mock broadcast factory
type MockBroadcastFactory struct{}

// Create mock client
func (m *MockBroadcastFactory) Create() broadcast.Client {
return &MockClient{}
}

func TestNewBroadcasterWithDefaultStrategy(t *testing.T) {
// Given
mockFactory := &MockBroadcastFactory{}

// When
broadcaster := NewBroadcasterWithDefaultStrategy(mockFactory.Create)

// Then
assert.NotNil(t, broadcaster)
_, ok := broadcaster.(*compositeBroadcaster)
assert.True(t, ok, "Expected broadcaster to be of type *compositeBroadcaster")
}

func TestNewBroadcaster(t *testing.T) {
// Given
mockFactory := &MockBroadcastFactory{}

// When
broadcaster := NewBroadcaster(*OneByOne, mockFactory.Create)

// Then
assert.NotNil(t, broadcaster)
_, ok := broadcaster.(*compositeBroadcaster)
assert.True(t, ok, "Expected broadcaster to be of type *compositeBroadcaster")
}
13 changes: 9 additions & 4 deletions broadcast/internal/composite/strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,21 @@ type Strategy struct {
executionFunc StrategyExecutionFunc
}

func New(name StrategyName, executionFunc StrategyExecutionFunc) *Strategy {
return &Strategy{name: name, executionFunc: executionFunc}
func New(name StrategyName) (*Strategy, error) {
switch name {
case OneByOneStrategy:
return &Strategy{name: name, executionFunc: OneByOne.executionFunc}, nil
default:
return nil, broadcast.ErrStrategyUnkown
}
}

func (s *Strategy) Execute(ctx context.Context, executionFuncs []executionFunc) (Result, error) {
return s.executionFunc(ctx, executionFuncs)
}

var (
OneByOne = New(OneByOneStrategy, func(ctx context.Context, executionFuncs []executionFunc) (Result, error) {
OneByOne = &Strategy{name: OneByOneStrategy, executionFunc: func(ctx context.Context, executionFuncs []executionFunc) (Result, error) {
for _, executionFunc := range executionFuncs {
result, err := executionFunc(ctx)
if err != nil {
Expand All @@ -40,5 +45,5 @@ var (
return result, nil
}
return nil, broadcast.ErrAllBroadcastersFailed
})
}}
)
97 changes: 97 additions & 0 deletions broadcast/internal/composite/strategy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package composite

import (
"context"
"errors"
"testing"

"github.com/bitcoin-sv/go-broadcast-client/broadcast"
"github.com/stretchr/testify/assert"
)

type mockExecutionFunc struct {
result Result
err error
}

func (m mockExecutionFunc) Execute(_ context.Context) (Result, error) {
return m.result, m.err
}

func TestStrategy_Execute(t *testing.T) {
// given
errFunc := mockExecutionFunc{
err: errors.New("failed"),
}
successFunc := mockExecutionFunc{
result: "success",
}

testCases := []struct {
name string
funcs []executionFunc
expected Result
err error
}{
{
name: "first execution func should return success",
funcs: []executionFunc{successFunc.Execute, errFunc.Execute},
expected: "success",
err: nil,
},
{
name: "last execution func should return success",
funcs: []executionFunc{errFunc.Execute, successFunc.Execute},
expected: "success",
err: nil,
},
{
name: "all execution funcs should fail",
funcs: []executionFunc{errFunc.Execute, errFunc.Execute},
expected: nil,
err: broadcast.ErrAllBroadcastersFailed,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// given
strategy, err := New(OneByOneStrategy)
assert.NoError(t, err)

// when
result, err := strategy.Execute(context.Background(), tc.funcs)

// then
assert.Equal(t, tc.err, err)
assert.Equal(t, tc.expected, result)
})
}
}

func TestNew(t *testing.T) {
// Test case: Default OneByOne strategy
t.Run("should return correct strategy for OneByOneStrategy", func(t *testing.T) {
// given
expectedStrategyName := OneByOneStrategy

// when
actualStrategy, err := New(OneByOneStrategy)

// then
assert.NoError(t, err)
assert.Equal(t, expectedStrategyName, actualStrategy.name)
})

// Test case: Unknown strategy
t.Run("should return error for unknown strategy name", func(t *testing.T) {
// given
unknownStrategyName := StrategyName("Unknown")

// when
_, err := New(unknownStrategyName)

// then
assert.Error(t, err)
})
}
24 changes: 12 additions & 12 deletions broadcast/tx_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,29 @@ type TxStatus string

// List of statuses available here: https://github.com/bitcoin-sv/arc
const (
// Unknown contains value for unknown status
// The transaction has been sent to metamorph, but no processing has taken place. This should never be the case, unless something goes wrong.
Unknown TxStatus = "UNKNOWN" // 0
// Queued contains value for queued status
// The transaction has been queued for processing.
Queued TxStatus = "QUEUED" // 1
// Received contains value for received status
// The transaction has been properly received by the metamorph processor.
Received TxStatus = "RECEIVED" // 2
// Stored contains value for stored status
// The transaction has been stored in the metamorph store. This should ensure the transaction will be processed and retried if not picked up immediately by a mining node.
Stored TxStatus = "STORED" // 3
// AnnouncedToNetwork contains value for announced to network status
// The transaction has been announced (INV message) to the Bitcoin network.
AnnouncedToNetwork TxStatus = "ANNOUNCED_TO_NETWORK" // 4
// RequestedByNetwork contains value for requested by network status
// The transaction has been requested from metamorph by a Bitcoin node.
RequestedByNetwork TxStatus = "REQUESTED_BY_NETWORK" // 5
// SentToNetwork contains value for sent to network status
// The transaction has been sent to at least 1 Bitcoin node.
SentToNetwork TxStatus = "SENT_TO_NETWORK" // 6
// AcceptedByNetwork contains value for accepted by network status
// The transaction has been accepted by a connected Bitcoin node on the ZMQ interface. If metamorph is not connected to ZQM, this status will never by set.
AcceptedByNetwork TxStatus = "ACCEPTED_BY_NETWORK" // 7
// SeenOnNetwork contains value for seen on network status
// 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.
SeenOnNetwork TxStatus = "SEEN_ON_NETWORK" // 8
// Mined contains value for mined status
// The transaction has been mined into a block by a mining node.
Mined TxStatus = "MINED" // 9
// Confirmed contains value for confirmed status
// The transaction is marked as confirmed when it is in a block with 100 blocks built on top of that block.
Confirmed TxStatus = "CONFIRMED" // 108
// Rejected contains value for rejected status
// The transaction has been rejected by the Bitcoin network.
Rejected TxStatus = "REJECTED" // 109
)

Expand Down

0 comments on commit 0063bb2

Please sign in to comment.