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