Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

loadtest: prepare for multiple consecutive runs #550

Merged
merged 5 commits into from
Oct 7, 2023
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ cmd/tapd/tapd
/itest/.minerlogs/*
/itest/tapd-itest

# Load test binaries and config
/loadtest
/loadtest.conf

# Test binary, built with `go test -c`
*.test

Expand Down
54 changes: 27 additions & 27 deletions itest/assertions.go
Original file line number Diff line number Diff line change
Expand Up @@ -932,12 +932,17 @@ func AssertSplitTombstoneTransfer(t *testing.T,
func AssertNumGroups(t *testing.T, client taprpc.TaprootAssetsClient,
num int) {

require.Equal(t, num, NumGroups(t, client))
}

// NumGroups returns the current number of asset groups present.
func NumGroups(t *testing.T, client taprpc.TaprootAssetsClient) int {
ctxb := context.Background()
groupResp, err := client.ListGroups(
ctxb, &taprpc.ListGroupsRequest{},
)
require.NoError(t, err)
require.Equal(t, num, len(groupResp.Groups))
return len(groupResp.Groups)
}

// AssertGroupSizes asserts that a set of groups the daemon is aware of contain
Expand Down Expand Up @@ -1039,40 +1044,37 @@ func AssertUniverseRootEquality(t *testing.T,
))
}

// AssertUniverseRoot makes sure the given universe root exists with the given
// sum, either identified by the asset ID or group key.
func AssertUniverseRoot(t *testing.T, client unirpc.UniverseClient,
sum int, assetID []byte, groupKey []byte) error {
sum int, assetID []byte, groupKey []byte) {

bothSet := assetID != nil && groupKey != nil
neitherSet := assetID == nil && groupKey == nil
if bothSet || neitherSet {
return fmt.Errorf("only set one of assetID or groupKey")
}

// Re-parse and serialize the keys to account for the different
// formats returned in RPC responses.
matchingGroupKey := func(root *unirpc.UniverseRoot) bool {
rootGroupKeyBytes := root.Id.GetGroupKey()
require.NotNil(t, rootGroupKeyBytes)

expectedGroupKey, err := btcec.ParsePubKey(groupKey)
require.NoError(t, err)
require.Equal(
t, rootGroupKeyBytes,
schnorr.SerializePubKey(expectedGroupKey),
)

return true
}
require.False(
t, bothSet || neitherSet, "only set one of assetID or groupKey",
)

// Comparing the asset ID is always safe, even if nil.
matchingRoot := func(root *unirpc.UniverseRoot) bool {
require.Equal(t, root.MssmtRoot.RootSum, int64(sum))
require.Equal(t, root.Id.GetAssetId(), assetID)
sumEqual := root.MssmtRoot.RootSum == int64(sum)
idEqual := bytes.Equal(root.Id.GetAssetId(), assetID)
groupKeyEqual := true
if groupKey != nil {
return matchingGroupKey(root)
parsedGroupKey, err := btcec.ParsePubKey(groupKey)
require.NoError(t, err)

rootGroupKey := root.Id.GetGroupKey()
if rootGroupKey != nil {
groupKeyEqual = bytes.Equal(
rootGroupKey, schnorr.SerializePubKey(
parsedGroupKey,
),
)
}
}

return true
return sumEqual && idEqual && groupKeyEqual
}

ctx := context.Background()
Expand All @@ -1082,8 +1084,6 @@ func AssertUniverseRoot(t *testing.T, client unirpc.UniverseClient,

correctRoot := fn.Any(maps.Values(uniRoots.UniverseRoots), matchingRoot)
require.True(t, correctRoot)

return nil
}

func AssertUniverseRootEqual(a, b *unirpc.UniverseRoot) bool {
Expand Down
56 changes: 40 additions & 16 deletions itest/loadtest/mint_batch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,22 @@ package loadtest

import (
"context"
_ "embed"
"encoding/binary"
"encoding/hex"
"fmt"
"strconv"
"math/rand"
"strings"
"testing"
"time"

_ "embed"

"github.com/btcsuite/btcd/rpcclient"
"github.com/lightninglabs/taproot-assets/fn"
"github.com/lightninglabs/taproot-assets/itest"
"github.com/lightninglabs/taproot-assets/taprpc"
"github.com/lightninglabs/taproot-assets/taprpc/mintrpc"
unirpc "github.com/lightninglabs/taproot-assets/taprpc/universerpc"
"github.com/lightninglabs/taproot-assets/universe"
"github.com/stretchr/testify/require"
)

Expand All @@ -44,6 +44,15 @@ func execMintBatchStressTest(t *testing.T, ctx context.Context, cfg *Config) {
// Create bitcoin client.
bitcoinClient := getBitcoinConn(t, cfg.Bitcoin)

itest.MineBlocks(t, bitcoinClient, 1, 0)

// If we fail from this point onward, we might have created a
// transaction that isn't mined yet. To make sure we can run the test
// again, we'll make sure to clean up the mempool by mining a block.
t.Cleanup(func() {
itest.MineBlocks(t, bitcoinClient, 1, 0)
})

imageMetadataBytes, err := hex.DecodeString(
strings.Trim(string(imageMetadataHex), "\n"),
)
Expand All @@ -66,11 +75,15 @@ func mintBatchStressTest(t *testing.T, ctx context.Context,

var (
batchReqs = make([]*mintrpc.MintAssetRequest, batchSize)
baseName = "jpeg"
baseName = fmt.Sprintf("jpeg-%d", rand.Int31())
metaPrefixSize = binary.MaxVarintLen16
metadataPrefix = make([]byte, metaPrefixSize)
)

// Before we mint a new group, let's first find out how many there
// already are.
initialGroups := itest.NumGroups(t, alice)

// Each asset in the batch will share a name and metdata preimage, that
// will be updated based on the asset's index in the batch.
collectibleRequestTemplate := mintrpc.MintAssetRequest{
Expand All @@ -87,9 +100,9 @@ func mintBatchStressTest(t *testing.T, ctx context.Context,
}

// Update the asset name and metadata to match an index.
incrementMintAsset := func(asset *mintrpc.MintAsset, ind int) {
asset.Name = asset.Name + strconv.Itoa(ind)
binary.PutUvarint(metadataPrefix, uint64(ind))
incrementMintAsset := func(asset *mintrpc.MintAsset, idx int) {
asset.Name = fmt.Sprintf("%s-%d", asset.Name, idx)
binary.PutUvarint(metadataPrefix, uint64(idx))
copy(asset.AssetMeta.Data[0:metaPrefixSize], metadataPrefix)
}

Expand Down Expand Up @@ -131,7 +144,7 @@ func mintBatchStressTest(t *testing.T, ctx context.Context,

// We should have one group, with the specified number of assets and an
// equivalent balance, since the group is made of collectibles.
groupCount := 1
groupCount := initialGroups + 1
groupBalance := batchSize

itest.AssertNumGroups(t, alice, groupCount)
Expand All @@ -146,16 +159,11 @@ func mintBatchStressTest(t *testing.T, ctx context.Context,
// The universe tree should reflect the same properties about the batch;
// there should be one root with a group key and balance matching what
// we asserted previously.
uniRoots, err := alice.AssetRoots(
ctx, &unirpc.AssetRootRequest{},
)
uniRoots, err := alice.AssetRoots(ctx, &unirpc.AssetRootRequest{})
require.NoError(t, err)
require.Len(t, uniRoots.UniverseRoots, groupCount)

err = itest.AssertUniverseRoot(
t, alice, groupBalance, nil, collectGroupKey,
)
require.NoError(t, err)
itest.AssertUniverseRoot(t, alice, groupBalance, nil, collectGroupKey)

// The universe tree should also have a leaf for each asset minted.
// TODO(jhb): Resolve issue of 33-byte group key handling.
Expand Down Expand Up @@ -189,7 +197,23 @@ func mintBatchStressTest(t *testing.T, ctx context.Context,
},
},
)
require.NoError(t, err)
if err != nil {
// Only fail the test for other errors than duplicate universe
// errors, as we might have already added the server in a
// previous run.
require.ErrorContains(
t, err, universe.ErrDuplicateUniverse.Error(),
)

// 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).
_, err := bob.SyncUniverse(ctx, &unirpc.SyncRequest{
UniverseHost: aliceHost,
SyncMode: unirpc.UniverseSyncMode_SYNC_ISSUANCE_ONLY,
})
require.NoError(t, err)
}

require.Eventually(t, func() bool {
return itest.AssertUniverseStateEqual(
Expand Down
5 changes: 1 addition & 4 deletions itest/mint_batch_stress_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,10 +183,7 @@ func mintBatchStressTest(
require.NoError(t, err)
require.Len(t, uniRoots.UniverseRoots, groupCount)

err = AssertUniverseRoot(
t, alice, groupBalance, nil, collectGroupKey,
)
require.NoError(t, err)
AssertUniverseRoot(t, alice, groupBalance, nil, collectGroupKey)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this just fail the test either way?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, but before it was a mixture of require and returning an error. And now we just do t.Fatalf() directly instead of returning the error and then doing require.NoError() outside.


// The universe tree should also have a leaf for each asset minted.
// TODO(jhb): Resolve issue of 33-byte group key handling.
Expand Down
32 changes: 28 additions & 4 deletions itest/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"testing"
"time"

"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/rpcclient"
"github.com/btcsuite/btcd/wire"
Expand All @@ -20,7 +22,9 @@ import (
)

var (
zeroHash chainhash.Hash
zeroHash chainhash.Hash
regtestMiningAddr = "n1VgRjYDzJT2TV72PnungWgWu18SWorXZS"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is seed generation by the nodes deterministic?

Do we need to also move to simnet, so bitcoind won't wipe the chain on start up?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just to send to bitcoind to specify where to mine the the coins to. We don't really ever use that address for anything else.

No, it's btcd that drops the chain on startup, not bitcoind. So we can switch batch to bitcoind and regtest.

regtestParams = &chaincfg.RegressionNetParams
)

// CopyRequest is a helper function to copy a request so that we can modify it.
Expand Down Expand Up @@ -101,9 +105,29 @@ func MineBlocks(t *testing.T, client *rpcclient.Client,

blocks := make([]*wire.MsgBlock, num)

blockHashes, err := client.Generate(num)
if err != nil {
t.Fatalf("unable to generate blocks: %v", err)
backend, err := client.BackendVersion()
require.NoError(t, err)

var blockHashes []*chainhash.Hash

switch backend {
case rpcclient.BitcoindPost19:
addr, err := btcutil.DecodeAddress(
regtestMiningAddr, regtestParams,
)
require.NoError(t, err)

blockHashes, err = client.GenerateToAddress(
int64(num), addr, nil,
)
require.NoError(t, err)

case rpcclient.Btcd:
blockHashes, err = client.Generate(num)
require.NoError(t, err)

default:
require.Fail(t, "unknown chain backend: %v", backend)
}

for i, blockHash := range blockHashes {
Expand Down