Skip to content

Commit

Permalink
loadtest: add send test
Browse files Browse the repository at this point in the history
  • Loading branch information
guggero committed Oct 10, 2023
1 parent 49ffe7b commit d7f826d
Show file tree
Hide file tree
Showing 4 changed files with 214 additions and 3 deletions.
22 changes: 19 additions & 3 deletions itest/loadtest/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"time"

"github.com/jessevdk/go-flags"
"github.com/lightninglabs/taproot-assets/taprpc"
)

const (
Expand Down Expand Up @@ -58,9 +59,21 @@ type Config struct {
// Bitcoin is the configuration for the bitcoin backend.
Bitcoin *BitcoinConfig `group:"bitcoin" namespace:"bitcoin" long:"bitcoin" description:"bitcoin client configuration"`

// BatchSize is the number of assets to mint in a single batch. This is only
// relevant for some test cases.
BatchSize int `long:"batch-size" description:"the number of assets to mint in a single batch"`
// BatchSize is the number of assets to mint in a single batch. This is
// only relevant for the mint test.
BatchSize int `long:"mint-test-batch-size" description:"the number of assets to mint in a single batch; only relevant for the mint test"`

// NumSends is the number of asset sends to perform. This is only
// relevant for the send test.
NumSends int `long:"send-test-num-sends" description:"the number of send operations to perform; only relevant for the send test"`

// NumAssets is the number of assets to send in each send operation.
// This is only relevant for the send test.
NumAssets uint64 `long:"send-test-num-assets" description:"the number of assets to send in each send operation; only relevant for the send test"`

// SendType is the type of asset to attempt to send. This is only
// relevant for the send test.
SendType taprpc.AssetType `long:"send-test-send-type" description:"the type of asset to attempt to send; only relevant for the send test"`

// TestSuiteTimeout is the timeout for the entire test suite.
TestSuiteTimeout time.Duration `long:"test-suite-timeout" description:"the timeout for the entire test suite"`
Expand All @@ -84,6 +97,9 @@ func DefaultConfig() Config {
},
},
BatchSize: 100,
NumSends: 50,
NumAssets: 1, // We only mint collectibles.
SendType: taprpc.AssetType_COLLECTIBLE,
TestSuiteTimeout: defaultSuiteTimeout,
TestTimeout: defaultTestTimeout,
}
Expand Down
4 changes: 4 additions & 0 deletions itest/loadtest/load_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ var loadTestCases = []testCase{
name: "mint",
fn: mintTest,
},
{
name: "send",
fn: sendTest,
},
}

// TestPerformance executes the configured performance tests.
Expand Down
129 changes: 129 additions & 0 deletions itest/loadtest/send_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package loadtest

import (
"context"
prand "math/rand"
"testing"

"github.com/btcsuite/btcd/rpcclient"
"github.com/lightninglabs/taproot-assets/itest"
"github.com/lightninglabs/taproot-assets/taprpc"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/stretchr/testify/require"
)

var (
statusDetected = taprpc.AddrEventStatus_ADDR_EVENT_STATUS_TRANSACTION_DETECTED
statusCompleted = taprpc.AddrEventStatus_ADDR_EVENT_STATUS_COMPLETED
)

// sendTest checks that we are able to send assets between the two nodes.
func sendTest(t *testing.T, ctx context.Context, cfg *Config) {
// Start by initializing all our client connections.
alice, bob, bitcoinClient := initClients(t, ctx, cfg)

ctxb := context.Background()
ctxt, cancel := context.WithTimeout(ctxb, cfg.TestTimeout)
defer cancel()

t.Logf("Running send test, sending %d asset(s) of type %v %d times",
cfg.NumAssets, cfg.SendType, cfg.NumSends)
for i := 1; i <= cfg.NumSends; i++ {
send, receive, ok := pickSendNode(
t, ctx, cfg.NumAssets, cfg.SendType, alice, bob,
)
if !ok {
t.Fatalf("Aborting send test at attempt %d of %d as "+
"no node has enough balance to send %d "+
"assets of type %v", i, cfg.NumSends,
cfg.NumAssets, cfg.SendType)
return
}

sendAssets(
t, ctxt, cfg.NumAssets, cfg.SendType, send, receive,
bitcoinClient,
)

t.Logf("Finished %d of %d send operations", i, cfg.NumSends)
}
}

// sendAsset sends the given number of assets of the given type from the given
// node to the other node.
func sendAssets(t *testing.T, ctx context.Context, numAssets uint64,
assetType taprpc.AssetType, send, receive *rpcClient,
bitcoinClient *rpcclient.Client) {

// Query the asset we'll be sending, so we can assert some things about
// it later.
sendAsset := send.assetIDWithBalance(t, ctx, numAssets, assetType)
t.Logf("Sending %d asset(s) with ID %x from %v to %v", numAssets,
sendAsset.AssetGenesis.AssetId, send.cfg.Name, receive.cfg.Name)

// Let's create an address on the receiving node and make sure it's
// created correctly.
addr, err := receive.NewAddr(ctx, &taprpc.NewAddrRequest{
AssetId: sendAsset.AssetGenesis.AssetId,
Amt: numAssets,
})
require.NoError(t, err)
itest.AssertAddrCreated(t, receive, sendAsset, addr)

// Before we send the asset, we record the existing transfers on the
// sending node, so we can easily select the new transfer once it
// appears.
transfersBefore := send.listTransfersSince(t, ctx, nil)

// Initiate the send now.
_, err = send.SendAsset(ctx, &taprpc.SendAssetRequest{
TapAddrs: []string{addr.Encoded},
})
require.NoError(t, err)

// Wait for the transfer to appear on the sending node.
require.Eventually(t, func() bool {
newTransfers := send.listTransfersSince(t, ctx, transfersBefore)
return len(newTransfers) == 1
}, defaultTimeout, wait.PollInterval)

// And for it to be detected on the receiving node.
itest.AssertAddrEvent(t, receive, addr, 1, statusDetected)

// Mine a block to confirm the transfer.
itest.MineBlocks(t, bitcoinClient, 1, 1)

// Now the transfer should go to completed eventually.
itest.AssertAddrEvent(t, receive, addr, 1, statusCompleted)
}

// pickSendNode picks a node at random, checks whether it has enough assets of
// the given type, and returns it. The second return value is the other node,
// which will be the receiving node. The boolean argument returns true if there
// is a node with sufficient balance. If that is false, the test should be
// skipped.
func pickSendNode(t *testing.T, ctx context.Context, minBalance uint64,
assetType taprpc.AssetType, a, b *rpcClient) (*rpcClient, *rpcClient,
bool) {

send, receive := a, b
if prand.Intn(1) == 0 {
send, receive = b, a
}

// Check if the randomly picked send node has enough balance.
if send.assetIDWithBalance(t, ctx, minBalance, assetType) != nil {
return send, receive, true
}

// If we get here, the send node doesn't have enough balance. We'll try
// the other one.
send, receive = receive, send
if send.assetIDWithBalance(t, ctx, minBalance, assetType) != nil {
return send, receive, true
}

// None of the nodes have enough balance. We can't run the send test
// currently.
return nil, nil, false
}
62 changes: 62 additions & 0 deletions itest/loadtest/utils.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package loadtest

import (
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"encoding/hex"
"fmt"
"os"
"testing"
"time"

"github.com/btcsuite/btcd/rpcclient"
"github.com/lightninglabs/taproot-assets/itest"
Expand All @@ -26,6 +29,10 @@ var (
// maxMsgRecvSize is the largest message our client will receive. We
// set this to 200MiB atm.
maxMsgRecvSize = grpc.MaxCallRecvMsgSize(lnrpc.MaxGrpcMsgSize)

// defaultTimeout is a timeout that will be used for various wait
// scenarios where no custom timeout value is defined.
defaultTimeout = time.Second * 10
)

type rpcClient struct {
Expand All @@ -36,6 +43,61 @@ type rpcClient struct {
assetwalletrpc.AssetWalletClient
}

// assetIDWithBalance returns the asset ID of an asset that has at least the
// given balance. If no such asset is found, nil is returned.
func (r *rpcClient) assetIDWithBalance(t *testing.T, ctx context.Context,
minBalance uint64, assetType taprpc.AssetType) *taprpc.Asset {

balances, err := r.ListBalances(ctx, &taprpc.ListBalancesRequest{
GroupBy: &taprpc.ListBalancesRequest_AssetId{
AssetId: true,
},
})
require.NoError(t, err)

for assetIDHex, balance := range balances.AssetBalances {
if balance.Balance >= minBalance &&
balance.AssetType == assetType {

assetIDBytes, err := hex.DecodeString(assetIDHex)
require.NoError(t, err)

assets, err := r.ListAssets(
ctx, &taprpc.ListAssetRequest{},
)
require.NoError(t, err)

for _, asset := range assets.Assets {
if bytes.Equal(
asset.AssetGenesis.AssetId,
assetIDBytes,
) {

return asset
}
}
}
}

return nil
}

// listTransfersSince returns all transfers that have been made since the last
// transfer in the given list. If the list is empty, all transfers are returned.
func (r *rpcClient) listTransfersSince(t *testing.T, ctx context.Context,
existingTransfers []*taprpc.AssetTransfer) []*taprpc.AssetTransfer {

resp, err := r.ListTransfers(ctx, &taprpc.ListTransfersRequest{})
require.NoError(t, err)

if len(existingTransfers) == 0 {
return resp.Transfers
}

newIndex := len(existingTransfers)
return resp.Transfers[newIndex:]
}

func initClients(t *testing.T, ctx context.Context,
cfg *Config) (*rpcClient, *rpcClient, *rpcclient.Client) {

Expand Down

0 comments on commit d7f826d

Please sign in to comment.