Skip to content

Commit

Permalink
refactor(ARCO-212): Move test utils for bitcoin node to node client (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
boecklim authored Nov 13, 2024
1 parent 120be56 commit fe15fda
Show file tree
Hide file tree
Showing 13 changed files with 553 additions and 534 deletions.
2 changes: 2 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ run:
# Timeout for analysis, e.g. 30s, 5m.
# Default: 1m
timeout: 3m
build-tags:
- e2e


# This file contains only configs which differ from defaults.
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/getkin/kin-openapi v0.127.0
github.com/go-redis/redis/v8 v8.11.5
github.com/go-testfixtures/testfixtures/v3 v3.9.0
github.com/go-zeromq/zmq4 v0.17.0
github.com/golang-migrate/migrate/v4 v4.16.2
github.com/google/uuid v1.6.0
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1
Expand All @@ -32,7 +33,6 @@ require (
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.19.1
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.8.1
github.com/spf13/viper v1.19.0
github.com/stretchr/testify v1.9.0
Expand Down Expand Up @@ -82,7 +82,6 @@ require (
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-zeromq/goczmq/v4 v4.2.2 // indirect
github.com/go-zeromq/zmq4 v0.17.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang/protobuf v1.5.4 // indirect
Expand Down Expand Up @@ -134,6 +133,7 @@ require (
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/segmentio/asm v1.2.0 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
Expand Down
3 changes: 3 additions & 0 deletions internal/node_client/node_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package node_client

// Todo: Implement node client
289 changes: 289 additions & 0 deletions internal/node_client/test_utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
package node_client

import (
"fmt"
"testing"
"time"

ec "github.com/bitcoin-sv/go-sdk/primitives/ec"
sdkTx "github.com/bitcoin-sv/go-sdk/transaction"
"github.com/bitcoin-sv/go-sdk/transaction/template/p2pkh"
"github.com/bitcoinsv/bsvutil"
"github.com/ordishs/go-bitcoin"
"github.com/stretchr/testify/require"
)

type UnspentOutput struct {
Txid string `json:"txid"`
Vout uint32 `json:"vout"`
Address string `json:"address"`
Account string `json:"account"`
ScriptPubKey string `json:"scriptPubKey"`
Amount float64 `json:"amount"`
Confirmations int `json:"confirmations"`
Spendable bool `json:"spendable"`
Solvable bool `json:"solvable"`
Safe bool `json:"safe"`
}

type RawTransaction struct {
Hex string `json:"hex"`
BlockHash string `json:"blockhash,omitempty"`
}

type BlockData struct {
Height uint64 `json:"height"`
Txs []string `json:"txs"`
MerkleRoot string `json:"merkleroot"`
}

func GetNewWalletAddress(t *testing.T, bitcoind *bitcoin.Bitcoind) (address, privateKey string) {
address, err := bitcoind.GetNewAddress()
require.NoError(t, err)
t.Logf("new address: %s", address)

privateKey, err = bitcoind.DumpPrivKey(address)
require.NoError(t, err)
t.Logf("new private key: %s", privateKey)

accountName := "test-account"
err = bitcoind.SetAccount(address, accountName)
require.NoError(t, err)

t.Logf("account %s created", accountName)

return
}

func SendToAddress(t *testing.T, bitcoind *bitcoin.Bitcoind, address string, bsv float64) (txID string) {
t.Helper()

txID, err := bitcoind.SendToAddress(address, bsv)
require.NoError(t, err)

t.Logf("sent %f to %s: %s", bsv, address, txID)

return
}

func Generate(t *testing.T, bitcoind *bitcoin.Bitcoind, amount uint64) string {
t.Helper()

// run command instead
blockHash := ExecCommandGenerate(t, bitcoind, amount)
time.Sleep(5 * time.Second)

t.Logf(
"generated %d block(s): block hash: %s",
amount,
blockHash,
)

return blockHash
}

func ExecCommandGenerate(t *testing.T, bitcoind *bitcoin.Bitcoind, amount uint64) string {
t.Helper()
t.Logf("Amount to generate: %d", amount)

hashes, err := bitcoind.Generate(float64(amount))
require.NoError(t, err)

return hashes[len(hashes)-1]
}

func GetUtxos(t *testing.T, bitcoind *bitcoin.Bitcoind, address string) []UnspentOutput {
t.Helper()

data, err := bitcoind.ListUnspent([]string{address})
require.NoError(t, err)

result := make([]UnspentOutput, len(data))

for index, utxo := range data {
t.Logf("UTXO Txid: %s, Amount: %f, Address: %s\n", utxo.TXID, utxo.Amount, utxo.Address)
result[index] = UnspentOutput{
Txid: utxo.TXID,
Vout: utxo.Vout,
Address: utxo.Address,
ScriptPubKey: utxo.ScriptPubKey,
Amount: utxo.Amount,
Confirmations: int(utxo.Confirmations),
}
}

return result
}

func CreateTxChain(privateKey string, utxo0 UnspentOutput, length int) ([]*sdkTx.Transaction, error) {
batch := make([]*sdkTx.Transaction, length)
const feeSat = 10
utxoTxID := utxo0.Txid
utxoVout := uint32(utxo0.Vout)
utxoSatoshis := uint64(utxo0.Amount * 1e8)
utxoScript := utxo0.ScriptPubKey
utxoAddress := utxo0.Address

for i := 0; i < length; i++ {
tx := sdkTx.NewTransaction()

utxo, err := sdkTx.NewUTXO(utxoTxID, utxoVout, utxoScript, utxoSatoshis)
if err != nil {
return nil, fmt.Errorf("failed creating UTXO: %v", err)
}

err = tx.AddInputsFromUTXOs(utxo)
if err != nil {
return nil, fmt.Errorf("failed adding input: %v", err)
}

amountToSend := utxoSatoshis - feeSat

err = tx.PayToAddress(utxoAddress, amountToSend)
if err != nil {
return nil, fmt.Errorf("failed to pay to address: %v", err)
}

// Sign the input
wif, err := bsvutil.DecodeWIF(privateKey)
if err != nil {
return nil, err
}

privateKeyDecoded := wif.PrivKey.Serialize()
pk, _ := ec.PrivateKeyFromBytes(privateKeyDecoded)

unlockingScriptTemplate, err := p2pkh.Unlock(pk, nil)
if err != nil {
return nil, err
}

for _, input := range tx.Inputs {
input.UnlockingScriptTemplate = unlockingScriptTemplate
}

err = tx.Sign()
if err != nil {
return nil, err
}

batch[i] = tx

utxoTxID = tx.TxID()
utxoVout = 0
utxoSatoshis = amountToSend
utxoScript = utxo0.ScriptPubKey
}

return batch, nil
}

func FundNewWallet(t *testing.T, bitcoind *bitcoin.Bitcoind) (addr, privKey string) {
t.Helper()

addr, privKey = GetNewWalletAddress(t, bitcoind)
SendToAddress(t, bitcoind, addr, 0.001)
// mine a block with the transaction from above
Generate(t, bitcoind, 1)

return
}

func GetBlockRootByHeight(t *testing.T, bitcoind *bitcoin.Bitcoind, blockHeight int) string {
t.Helper()
block, err := bitcoind.GetBlockByHeight(blockHeight)
require.NoError(t, err)

return block.MerkleRoot
}

func GetRawTx(t *testing.T, bitcoind *bitcoin.Bitcoind, txID string) RawTransaction {
t.Helper()

rawTx, err := bitcoind.GetRawTransaction(txID)
require.NoError(t, err)

return RawTransaction{
Hex: rawTx.Hex,
BlockHash: rawTx.BlockHash,
}
}

func GetBlockDataByBlockHash(t *testing.T, bitcoind *bitcoin.Bitcoind, blockHash string) BlockData {
t.Helper()

block, err := bitcoind.GetBlock(blockHash)
require.NoError(t, err)

return BlockData{
Height: block.Height,
Txs: block.Tx,
MerkleRoot: block.MerkleRoot,
}
}

func CreateTx(privateKey string, address string, utxo UnspentOutput, fee ...uint64) (*sdkTx.Transaction, error) {
return CreateTxFrom(privateKey, address, []UnspentOutput{utxo}, fee...)
}

func CreateTxFrom(privateKey string, address string, utxos []UnspentOutput, fee ...uint64) (*sdkTx.Transaction, error) {
tx := sdkTx.NewTransaction()

// Add an input using the UTXOs
for _, utxo := range utxos {
utxoTxID := utxo.Txid
utxoVout := utxo.Vout
utxoSatoshis := uint64(utxo.Amount * 1e8) // Convert BTC to satoshis
utxoScript := utxo.ScriptPubKey

u, err := sdkTx.NewUTXO(utxoTxID, utxoVout, utxoScript, utxoSatoshis)
if err != nil {
return nil, fmt.Errorf("failed creating UTXO: %v", err)
}
err = tx.AddInputsFromUTXOs(u)
if err != nil {
return nil, fmt.Errorf("failed adding input: %v", err)
}
}
// Add an output to the address you've previously created
recipientAddress := address

var feeValue uint64
if len(fee) > 0 {
feeValue = fee[0]
} else {
feeValue = 20 // Set your default fee value here
}
amountToSend := tx.TotalInputSatoshis() - feeValue

err := tx.PayToAddress(recipientAddress, amountToSend)
if err != nil {
return nil, fmt.Errorf("failed to pay to address: %v", err)
}

// Sign the input
wif, err := bsvutil.DecodeWIF(privateKey)
if err != nil {
return nil, fmt.Errorf("failed to decode WIF: %v", err)
}

// Extract raw private key bytes directly from the WIF structure
privateKeyDecoded := wif.PrivKey.Serialize()
pk, _ := ec.PrivateKeyFromBytes(privateKeyDecoded)

unlockingScriptTemplate, err := p2pkh.Unlock(pk, nil)
if err != nil {
return nil, err
}

for _, input := range tx.Inputs {
input.UnlockingScriptTemplate = unlockingScriptTemplate
}

err = tx.Sign()
if err != nil {
return nil, err
}

return tx, nil
}
31 changes: 19 additions & 12 deletions test/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM golang:1.22.5-alpine3.20
FROM golang:1.22.5-alpine3.20 AS build-stage

# Set the Current Working Directory inside the container
WORKDIR /app
Expand All @@ -7,18 +7,25 @@ WORKDIR /app
COPY go.mod go.sum ./

# Download all dependencies. Dependencies will be cached if the go.mod and the go.sum files are not changed
COPY go.mod go.sum ./
RUN go mod download
RUN go mod verify

COPY cmd/ cmd/
COPY internal/ internal/
COPY pkg/ pkg/
COPY config/ config/
COPY test/ test/

# Build tests
RUN go test --tags=e2e -v -failfast github.com/bitcoin-sv/arc/test -c -o /e2e.test

# Deploy the application binary into a lean image
FROM scratch

COPY ./test/e2e_globals.go ./e2e_globals.go
COPY ./test/init_test.go ./init_test.go
COPY ./test/utils.go ./utils.go
COPY ./test/fixtures ./fixtures
WORKDIR /test

# Copy tests to run
COPY ./test/submit_single_test.go ./submit_01_single_test.go
COPY ./test/submit_batch_test.go ./submit_02_batch_test.go
COPY ./test/submit_double_spending_test.go ./submit_03_double_spending_test.go
COPY ./test/submit_beef_test.go ./submit_04_beef_test.go
COPY --from=build-stage /e2e.test /test/e2e.test

# This will compile and run the tests
CMD [ "go", "test", "--tags=e2e", "-v", "-failfast", "./..."]
# Run tests
CMD ["/test/e2e.test"]
Loading

0 comments on commit fe15fda

Please sign in to comment.