Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 6 additions & 6 deletions itest/loadtest/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (

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

const (
Expand Down Expand Up @@ -111,9 +110,10 @@ type Config struct {
// 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"`
// SendAssetType is the type of asset to attempt to send. This is only
// relevant for the send test. Acceptable values are "normal" and
// "collectible".
SendAssetType string `long:"send-asset-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 Down Expand Up @@ -143,8 +143,8 @@ func DefaultConfig() Config {
Network: "regtest",
BatchSize: 100,
NumSends: 50,
NumAssets: 1, // We only mint collectibles.
SendType: taprpc.AssetType_COLLECTIBLE,
NumAssets: 1,
SendAssetType: "normal",
TestSuiteTimeout: defaultSuiteTimeout,
TestTimeout: defaultTestTimeout,
PrometheusGateway: &PrometheusGatewayConfig{
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 @@ -47,6 +47,10 @@ var loadTestCases = []testCase{
name: "send",
fn: sendTest,
},
{
name: "sendV2",
fn: sendTestV2,
},
{
name: "multisig",
fn: multisigTest,
Expand Down
4 changes: 4 additions & 0 deletions itest/loadtest/loadtest-sample.conf
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ send-test-num-sends=5
# Number of assets to send in each send operation for send test
send-test-num-assets=1

# Type of asset to use in the test case. For V2 test cases, a "normal" type is
# required.
send-asset-type="normal"

# Timeout for the entire test suite
test-suite-timeout=120m

Expand Down
161 changes: 156 additions & 5 deletions itest/loadtest/send_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
prand "math/rand"
"math/rand/v2"
"testing"
"time"

Expand All @@ -29,30 +30,180 @@ func sendTest(t *testing.T, ctx context.Context, cfg *Config) {
ctxt, cancel := context.WithTimeout(ctxb, cfg.TestTimeout)
defer cancel()

sendType := stringToAssetType(cfg.SendAssetType)

t.Logf("Running send test, sending %d asset(s) of type %v %d times",
cfg.NumAssets, cfg.SendType, cfg.NumSends)
cfg.NumAssets, sendType, cfg.NumSends)
for i := 1; i <= cfg.NumSends; i++ {
send, receive, ok := pickSendNode(
t, ctx, cfg.NumAssets, cfg.SendType, alice, bob,
t, ctx, cfg.NumAssets, 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)
cfg.NumAssets, sendType)
return
}

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

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

// sendAssets sends the given number of assets of the given type from the given
// sendTestV2 checks that we are able to send assets between the two nodes. It
// is a more performant and lightweight version of sendTest, as it uses less
// assertions and RPC calls.
func sendTestV2(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()

sendType := stringToAssetType(cfg.SendAssetType)

// Alice is set to be the minter in mintV2, so we use Alice's universe.
uniHost := fmt.Sprintf("%s:%d", alice.cfg.Host, alice.cfg.Port)

// Let's make sure Bob is aware of all the assets that Alice may have
// minted.
itest.SyncUniverses(
ctx, t, bob, alice, uniHost, cfg.TestTimeout,
itest.WithSyncMode(itest.SyncModeFull),
)

// We now retrieve Alice and Bob's balances just once, and will re-use
// them in future function calls. Any update to the balances will be
// directly applied to these response objects, to skip future calls to
// ListBalances.
resAlice, err := alice.ListBalances(ctx, &taprpc.ListBalancesRequest{
GroupBy: &taprpc.ListBalancesRequest_AssetId{
AssetId: true,
},
})
require.NoError(t, err)

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

for i := 1; i <= cfg.NumSends; i++ {
var (
sender, receiver *rpcClient
senderAssets map[string]*taprpc.AssetBalance
)

// Assets may be sent in both directions, so we make a random
// draw to conclude who the sender is.
draw := rand.IntN(2)

switch draw {
case 0:
sender = alice
senderAssets = resAlice.AssetBalances
receiver = bob

case 1:
sender = bob
senderAssets = resBob.AssetBalances
receiver = alice
}

sendAssetV2(
t, ctxt, cfg.NumAssets, sendType, senderAssets,
sender, receiver, bitcoinClient, cfg.TestTimeout,
)
}
}

// sendAssetV2 sends a certain amount of assets of a specific type from a sender
// to a receiver. It will scan the balance of the sender and find a suitable
// asset to carry out the send, then will dispatch the send and assert its
// completion.
func sendAssetV2(t *testing.T, ctx context.Context, numAssets uint64,
assetType taprpc.AssetType, assets map[string]*taprpc.AssetBalance,
sender, receiver *rpcClient, bitcoinClient *rpcclient.Client,
timeout time.Duration) {

// Look over the sender's balances to see if any asset balance qualifies
// for this send.
var (
assetID []byte
balance *taprpc.AssetBalance
)
for _, v := range assets {
if v.Balance >= numAssets &&
v.AssetGenesis.AssetType == assetType {

assetID = v.AssetGenesis.AssetId
balance = v

break
}
}

// No balance satisfies the amount of this send, we can skip this round.
if assetID == nil {
t.Logf("%s could not send %v assets, no available balance",
sender.cfg.Name, numAssets)

return
}

t.Logf("%s sending %v assets to %s", sender.cfg.Name, numAssets,
receiver.cfg.Name)

// Receiver creates the address to receive the assets.
addr, err := receiver.NewAddr(ctx, &taprpc.NewAddrRequest{
AssetId: assetID,
Amt: numAssets,
ProofCourierAddr: fmt.Sprintf(
"%s://%s:%d", proof.UniverseRpcCourierType,
sender.cfg.Host, sender.cfg.Port,
),
})
require.NoError(t, err)

t.Logf("%s created address %v", receiver.cfg.Name, addr.String())

// Sender initiates the send.
_, err = sender.SendAsset(ctx, &taprpc.SendAssetRequest{
TapAddrs: []string{addr.Encoded},
})
require.NoError(t, err)
t.Logf("%s sent assets to address %v", sender.cfg.Name, addr.String())

// We assert the receiver detects the spend.
itest.AssertAddrEventCustomTimeout(
t, receiver, addr, 1, statusDetected, timeout,
)
t.Logf("%s detected send", receiver.cfg.Name)

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

// Assert that the transfer is now completed
itest.AssertAddrEventCustomTimeout(
t, receiver, addr, 1, statusCompleted, timeout,
)
t.Logf("%s completed send of %v assets", sender.cfg.Name, numAssets)

// If everything completed correctly, subtract the asset amount from the
// sender's asset balance.
balance.Balance -= numAssets
}

// 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,
Expand Down
12 changes: 12 additions & 0 deletions itest/loadtest/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,3 +289,15 @@ func getBitcoinConn(t *testing.T, cfg *BitcoinConfig) *rpcclient.Client {

return client
}

// stringToAssetType converts a string of an asset type to its respective taprpc
// type enum value.
func stringToAssetType(t string) taprpc.AssetType {
switch t {
case "collectible":
return taprpc.AssetType_COLLECTIBLE

default:
return taprpc.AssetType_NORMAL
}
}
40 changes: 37 additions & 3 deletions itest/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ var (
regtestParams = &chaincfg.RegressionNetParams
)

const (
SyncModeIssuance = universerpc.UniverseSyncMode_SYNC_ISSUANCE_ONLY
SyncModeFull = universerpc.UniverseSyncMode_SYNC_FULL
)

// ClientEventStream is a generic interface for a client stream that allows us
// to receive events from a server.
type ClientEventStream[T any] interface {
Expand Down Expand Up @@ -834,15 +839,45 @@ func MintAssetExternalSigner(t *harnessTest, tapNode *tapdHarness,
return batchAssets
}

// syncOptions is a struct that is used to customize the way we perform a
// universe sync.
type syncOptions struct {
syncMode universerpc.UniverseSyncMode
}

// defaultSyncOptions returns the default syncOptions.
func defaultSyncOptions() *syncOptions {
return &syncOptions{
syncMode: SyncModeIssuance,
}
}

// SyncUniverseOpt is used to modify the parameters of a universe sync.
type SyncUniverseOpt func(*syncOptions)

// WithSyncMode can be used to define which sync mode to be used when performing
// a universe sync.
func WithSyncMode(mode universerpc.UniverseSyncMode) SyncUniverseOpt {
return func(so *syncOptions) {
so.syncMode = mode
}
}

// SyncUniverses syncs the universes of two tapd instances and waits until they
// are in sync.
func SyncUniverses(ctx context.Context, t *testing.T, clientTapd,
universeTapd commands.RpcClientsBundle, universeHost string,
timeout time.Duration) {
timeout time.Duration, opts ...SyncUniverseOpt) {

ctxt, cancel := context.WithTimeout(ctx, timeout)
defer cancel()

options := defaultSyncOptions()

for _, opt := range opts {
opt(options)
}

_, err := clientTapd.AddFederationServer(
ctxt, &universerpc.AddFederationServerRequest{
Servers: []*universerpc.UniverseFederationServer{
Expand All @@ -863,10 +898,9 @@ func SyncUniverses(ctx context.Context, t *testing.T, clientTapd,
// If we've already added the server in a previous run, we'll
// just need to kick off a sync (as that would otherwise be done
// by adding the server request already).
mode := universerpc.UniverseSyncMode_SYNC_ISSUANCE_ONLY
_, err := clientTapd.SyncUniverse(ctxt, &universerpc.SyncRequest{
UniverseHost: universeHost,
SyncMode: mode,
SyncMode: options.syncMode,
})
require.NoError(t, err)
}
Expand Down