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 }