github.com/ava-labs/avalanchego@v1.11.11/vms/avm/index_test.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package avm
     5  
     6  import (
     7  	"testing"
     8  
     9  	"github.com/prometheus/client_golang/prometheus"
    10  	"github.com/stretchr/testify/require"
    11  
    12  	"github.com/ava-labs/avalanchego/database"
    13  	"github.com/ava-labs/avalanchego/database/memdb"
    14  	"github.com/ava-labs/avalanchego/database/prefixdb"
    15  	"github.com/ava-labs/avalanchego/database/versiondb"
    16  	"github.com/ava-labs/avalanchego/ids"
    17  	"github.com/ava-labs/avalanchego/upgrade/upgradetest"
    18  	"github.com/ava-labs/avalanchego/utils"
    19  	"github.com/ava-labs/avalanchego/utils/constants"
    20  	"github.com/ava-labs/avalanchego/utils/crypto/secp256k1"
    21  	"github.com/ava-labs/avalanchego/utils/logging"
    22  	"github.com/ava-labs/avalanchego/vms/avm/txs"
    23  	"github.com/ava-labs/avalanchego/vms/components/avax"
    24  	"github.com/ava-labs/avalanchego/vms/components/index"
    25  	"github.com/ava-labs/avalanchego/vms/secp256k1fx"
    26  )
    27  
    28  func TestIndexTransaction_Ordered(t *testing.T) {
    29  	require := require.New(t)
    30  
    31  	env := setup(t, &envConfig{fork: upgradetest.Durango})
    32  	defer env.vm.ctx.Lock.Unlock()
    33  
    34  	key := keys[0]
    35  	addr := key.PublicKey().Address()
    36  	txAssetID := avax.Asset{ID: env.genesisTx.ID()}
    37  
    38  	var txs []*txs.Tx
    39  	for i := 0; i < 5; i++ {
    40  		// make utxo
    41  		utxoID := avax.UTXOID{
    42  			TxID: ids.GenerateTestID(),
    43  		}
    44  		utxo := buildUTXO(utxoID, txAssetID, addr)
    45  		env.vm.state.AddUTXO(utxo)
    46  
    47  		// make transaction
    48  		tx := buildTX(env.vm.ctx.XChainID, utxoID, txAssetID, addr)
    49  		require.NoError(tx.SignSECP256K1Fx(env.vm.parser.Codec(), [][]*secp256k1.PrivateKey{{key}}))
    50  
    51  		env.vm.ctx.Lock.Unlock()
    52  
    53  		issueAndAccept(require, env.vm, env.issuer, tx)
    54  
    55  		env.vm.ctx.Lock.Lock()
    56  
    57  		txs = append(txs, tx)
    58  	}
    59  
    60  	// for each tx check its indexed at right index
    61  	for i, tx := range txs {
    62  		assertIndexedTX(t, env.vm.db, uint64(i), addr, txAssetID.ID, tx.ID())
    63  	}
    64  	assertLatestIdx(t, env.vm.db, addr, txAssetID.ID, 5)
    65  }
    66  
    67  func TestIndexTransaction_MultipleTransactions(t *testing.T) {
    68  	require := require.New(t)
    69  
    70  	env := setup(t, &envConfig{fork: upgradetest.Durango})
    71  	defer env.vm.ctx.Lock.Unlock()
    72  
    73  	addressTxMap := map[ids.ShortID]*txs.Tx{}
    74  	txAssetID := avax.Asset{ID: env.genesisTx.ID()}
    75  
    76  	for _, key := range keys {
    77  		addr := key.PublicKey().Address()
    78  
    79  		// make utxo
    80  		utxoID := avax.UTXOID{
    81  			TxID: ids.GenerateTestID(),
    82  		}
    83  		utxo := buildUTXO(utxoID, txAssetID, addr)
    84  		env.vm.state.AddUTXO(utxo)
    85  
    86  		// make transaction
    87  		tx := buildTX(env.vm.ctx.XChainID, utxoID, txAssetID, addr)
    88  		require.NoError(tx.SignSECP256K1Fx(env.vm.parser.Codec(), [][]*secp256k1.PrivateKey{{key}}))
    89  
    90  		env.vm.ctx.Lock.Unlock()
    91  
    92  		// issue transaction
    93  		issueAndAccept(require, env.vm, env.issuer, tx)
    94  
    95  		env.vm.ctx.Lock.Lock()
    96  
    97  		addressTxMap[addr] = tx
    98  	}
    99  
   100  	// ensure length is same as keys length
   101  	require.Len(addressTxMap, len(keys))
   102  
   103  	// for each *UniqueTx check its indexed at right index for the right address
   104  	for addr, tx := range addressTxMap {
   105  		assertIndexedTX(t, env.vm.db, 0, addr, txAssetID.ID, tx.ID())
   106  		assertLatestIdx(t, env.vm.db, addr, txAssetID.ID, 1)
   107  	}
   108  }
   109  
   110  func TestIndexTransaction_MultipleAddresses(t *testing.T) {
   111  	require := require.New(t)
   112  
   113  	env := setup(t, &envConfig{fork: upgradetest.Durango})
   114  	defer env.vm.ctx.Lock.Unlock()
   115  
   116  	addrs := make([]ids.ShortID, len(keys))
   117  	for i, key := range keys {
   118  		addrs[i] = key.PublicKey().Address()
   119  	}
   120  	utils.Sort(addrs)
   121  
   122  	txAssetID := avax.Asset{ID: env.genesisTx.ID()}
   123  
   124  	key := keys[0]
   125  	addr := key.PublicKey().Address()
   126  
   127  	// make utxo
   128  	utxoID := avax.UTXOID{
   129  		TxID: ids.GenerateTestID(),
   130  	}
   131  	utxo := buildUTXO(utxoID, txAssetID, addr)
   132  	env.vm.state.AddUTXO(utxo)
   133  
   134  	// make transaction
   135  	tx := buildTX(env.vm.ctx.XChainID, utxoID, txAssetID, addrs...)
   136  	require.NoError(tx.SignSECP256K1Fx(env.vm.parser.Codec(), [][]*secp256k1.PrivateKey{{key}}))
   137  
   138  	env.vm.ctx.Lock.Unlock()
   139  
   140  	issueAndAccept(require, env.vm, env.issuer, tx)
   141  
   142  	env.vm.ctx.Lock.Lock()
   143  
   144  	assertIndexedTX(t, env.vm.db, 0, addr, txAssetID.ID, tx.ID())
   145  	assertLatestIdx(t, env.vm.db, addr, txAssetID.ID, 1)
   146  }
   147  
   148  func TestIndexer_Read(t *testing.T) {
   149  	require := require.New(t)
   150  
   151  	env := setup(t, &envConfig{fork: upgradetest.Durango})
   152  	defer env.vm.ctx.Lock.Unlock()
   153  
   154  	// generate test address and asset IDs
   155  	assetID := ids.GenerateTestID()
   156  	addr := ids.GenerateTestShortID()
   157  
   158  	// setup some fake txs under the above generated address and asset IDs
   159  	testTxs := initTestTxIndex(t, env.vm.db, addr, assetID, 25)
   160  	require.Len(testTxs, 25)
   161  
   162  	// read the pages, 5 items at a time
   163  	var (
   164  		cursor   uint64
   165  		pageSize uint64 = 5
   166  	)
   167  	for cursor < 25 {
   168  		txIDs, err := env.vm.addressTxsIndexer.Read(addr[:], assetID, cursor, pageSize)
   169  		require.NoError(err)
   170  		require.Len(txIDs, 5)
   171  		require.Equal(txIDs, testTxs[cursor:cursor+pageSize])
   172  		cursor += pageSize
   173  	}
   174  }
   175  
   176  func TestIndexingNewInitWithIndexingEnabled(t *testing.T) {
   177  	require := require.New(t)
   178  
   179  	db := memdb.New()
   180  
   181  	// start with indexing enabled
   182  	_, err := index.NewIndexer(db, logging.NoWarn{}, "", prometheus.NewRegistry(), true)
   183  	require.NoError(err)
   184  
   185  	// now disable indexing with allow-incomplete set to false
   186  	_, err = index.NewNoIndexer(db, false)
   187  	require.ErrorIs(err, index.ErrCausesIncompleteIndex)
   188  
   189  	// now disable indexing with allow-incomplete set to true
   190  	_, err = index.NewNoIndexer(db, true)
   191  	require.NoError(err)
   192  }
   193  
   194  func TestIndexingNewInitWithIndexingDisabled(t *testing.T) {
   195  	require := require.New(t)
   196  
   197  	db := memdb.New()
   198  
   199  	// disable indexing with allow-incomplete set to false
   200  	_, err := index.NewNoIndexer(db, false)
   201  	require.NoError(err)
   202  
   203  	// It's not OK to have an incomplete index when allowIncompleteIndices is false
   204  	_, err = index.NewIndexer(db, logging.NoWarn{}, "", prometheus.NewRegistry(), false)
   205  	require.ErrorIs(err, index.ErrIndexingRequiredFromGenesis)
   206  
   207  	// It's OK to have an incomplete index when allowIncompleteIndices is true
   208  	_, err = index.NewIndexer(db, logging.NoWarn{}, "", prometheus.NewRegistry(), true)
   209  	require.NoError(err)
   210  
   211  	// It's OK to have an incomplete index when indexing currently disabled
   212  	_, err = index.NewNoIndexer(db, false)
   213  	require.NoError(err)
   214  
   215  	// It's OK to have an incomplete index when allowIncompleteIndices is true
   216  	_, err = index.NewNoIndexer(db, true)
   217  	require.NoError(err)
   218  }
   219  
   220  func TestIndexingAllowIncomplete(t *testing.T) {
   221  	require := require.New(t)
   222  
   223  	baseDB := memdb.New()
   224  	db := versiondb.New(baseDB)
   225  	// disabled indexer will persist idxEnabled as false
   226  	_, err := index.NewNoIndexer(db, false)
   227  	require.NoError(err)
   228  
   229  	// we initialize with indexing enabled now and allow incomplete indexing as false
   230  	_, err = index.NewIndexer(db, logging.NoWarn{}, "", prometheus.NewRegistry(), false)
   231  	// we should get error because:
   232  	// - indexing was disabled previously
   233  	// - node now is asked to enable indexing with allow incomplete set to false
   234  	require.ErrorIs(err, index.ErrIndexingRequiredFromGenesis)
   235  }
   236  
   237  func buildUTXO(utxoID avax.UTXOID, txAssetID avax.Asset, addr ids.ShortID) *avax.UTXO {
   238  	return &avax.UTXO{
   239  		UTXOID: utxoID,
   240  		Asset:  txAssetID,
   241  		Out: &secp256k1fx.TransferOutput{
   242  			Amt: startBalance,
   243  			OutputOwners: secp256k1fx.OutputOwners{
   244  				Threshold: 1,
   245  				Addrs:     []ids.ShortID{addr},
   246  			},
   247  		},
   248  	}
   249  }
   250  
   251  func buildTX(chainID ids.ID, utxoID avax.UTXOID, txAssetID avax.Asset, address ...ids.ShortID) *txs.Tx {
   252  	return &txs.Tx{Unsigned: &txs.BaseTx{
   253  		BaseTx: avax.BaseTx{
   254  			NetworkID:    constants.UnitTestID,
   255  			BlockchainID: chainID,
   256  			Ins: []*avax.TransferableInput{{
   257  				UTXOID: utxoID,
   258  				Asset:  txAssetID,
   259  				In: &secp256k1fx.TransferInput{
   260  					Amt:   startBalance,
   261  					Input: secp256k1fx.Input{SigIndices: []uint32{0}},
   262  				},
   263  			}},
   264  			Outs: []*avax.TransferableOutput{{
   265  				Asset: txAssetID,
   266  				Out: &secp256k1fx.TransferOutput{
   267  					Amt: startBalance - testTxFee,
   268  					OutputOwners: secp256k1fx.OutputOwners{
   269  						Threshold: 1,
   270  						Addrs:     address,
   271  					},
   272  				},
   273  			}},
   274  		},
   275  	}}
   276  }
   277  
   278  func assertLatestIdx(t *testing.T, db database.Database, sourceAddress ids.ShortID, assetID ids.ID, expectedIdx uint64) {
   279  	require := require.New(t)
   280  
   281  	addressDB := prefixdb.New(sourceAddress[:], db)
   282  	assetDB := prefixdb.New(assetID[:], addressDB)
   283  
   284  	expectedIdxBytes := database.PackUInt64(expectedIdx)
   285  	idxBytes, err := assetDB.Get([]byte("idx"))
   286  	require.NoError(err)
   287  	require.Equal(expectedIdxBytes, idxBytes)
   288  }
   289  
   290  func assertIndexedTX(t *testing.T, db database.Database, index uint64, sourceAddress ids.ShortID, assetID ids.ID, transactionID ids.ID) {
   291  	require := require.New(t)
   292  
   293  	addressDB := prefixdb.New(sourceAddress[:], db)
   294  	assetDB := prefixdb.New(assetID[:], addressDB)
   295  
   296  	idxBytes := database.PackUInt64(index)
   297  	txID, err := database.GetID(assetDB, idxBytes)
   298  	require.NoError(err)
   299  	require.Equal(transactionID, txID)
   300  }
   301  
   302  // Sets up test tx IDs in DB in the following structure for the indexer to pick
   303  // them up:
   304  //
   305  //	[address] prefix DB
   306  //	  [assetID] prefix DB
   307  //	    - "idx": 2
   308  //	    - 0: txID1
   309  //	    - 1: txID1
   310  func initTestTxIndex(t *testing.T, db *versiondb.Database, address ids.ShortID, assetID ids.ID, txCount int) []ids.ID {
   311  	require := require.New(t)
   312  
   313  	testTxs := make([]ids.ID, txCount)
   314  	for i := 0; i < txCount; i++ {
   315  		testTxs[i] = ids.GenerateTestID()
   316  	}
   317  
   318  	addressPrefixDB := prefixdb.New(address[:], db)
   319  	assetPrefixDB := prefixdb.New(assetID[:], addressPrefixDB)
   320  
   321  	for i, txID := range testTxs {
   322  		idxBytes := database.PackUInt64(uint64(i))
   323  		txID := txID
   324  		require.NoError(assetPrefixDB.Put(idxBytes, txID[:]))
   325  	}
   326  	_, err := db.CommitBatch()
   327  	require.NoError(err)
   328  
   329  	idxBytes := database.PackUInt64(uint64(len(testTxs)))
   330  	require.NoError(assetPrefixDB.Put([]byte("idx"), idxBytes))
   331  	require.NoError(db.Commit())
   332  	return testTxs
   333  }