github.com/MetalBlockchain/metalgo@v1.11.9/tests/antithesis/xsvm/main.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package main 5 6 import ( 7 "context" 8 "crypto/rand" 9 "log" 10 "math/big" 11 "os" 12 "time" 13 14 "github.com/antithesishq/antithesis-sdk-go/assert" 15 "github.com/antithesishq/antithesis-sdk-go/lifecycle" 16 17 "github.com/MetalBlockchain/metalgo/genesis" 18 "github.com/MetalBlockchain/metalgo/ids" 19 "github.com/MetalBlockchain/metalgo/tests/antithesis" 20 "github.com/MetalBlockchain/metalgo/utils/crypto/secp256k1" 21 "github.com/MetalBlockchain/metalgo/utils/set" 22 "github.com/MetalBlockchain/metalgo/utils/units" 23 "github.com/MetalBlockchain/metalgo/vms/example/xsvm/api" 24 "github.com/MetalBlockchain/metalgo/vms/example/xsvm/cmd/issue/status" 25 "github.com/MetalBlockchain/metalgo/vms/example/xsvm/cmd/issue/transfer" 26 ) 27 28 const ( 29 NumKeys = 5 30 PollingInterval = 50 * time.Millisecond 31 ) 32 33 func main() { 34 c, err := antithesis.NewConfig(os.Args) 35 if err != nil { 36 log.Fatalf("invalid config: %s", err) 37 } 38 39 ctx := context.Background() 40 if err := antithesis.AwaitHealthyNodes(ctx, c.URIs); err != nil { 41 log.Fatalf("failed to await healthy nodes: %s", err) 42 } 43 44 if len(c.ChainIDs) != 1 { 45 log.Fatalf("expected 1 chainID, saw %d", len(c.ChainIDs)) 46 } 47 chainID, err := ids.FromString(c.ChainIDs[0]) 48 if err != nil { 49 log.Fatalf("failed to parse chainID: %s", err) 50 } 51 52 genesisWorkload := &workload{ 53 id: 0, 54 chainID: chainID, 55 key: genesis.VMRQKey, 56 addrs: set.Of(genesis.VMRQKey.Address()), 57 uris: c.URIs, 58 } 59 60 workloads := make([]*workload, NumKeys) 61 workloads[0] = genesisWorkload 62 63 initialAmount := 100 * units.KiloAvax 64 for i := 1; i < NumKeys; i++ { 65 key, err := secp256k1.NewPrivateKey() 66 if err != nil { 67 log.Fatalf("failed to generate key: %s", err) 68 } 69 70 var ( 71 addr = key.Address() 72 baseStartTime = time.Now() 73 ) 74 transferTxStatus, err := transfer.Transfer( 75 ctx, 76 &transfer.Config{ 77 URI: c.URIs[0], 78 ChainID: chainID, 79 AssetID: chainID, 80 Amount: initialAmount, 81 To: addr, 82 PrivateKey: genesisWorkload.key, 83 }, 84 ) 85 if err != nil { 86 log.Fatalf("failed to issue initial funding transfer: %s", err) 87 } 88 log.Printf("issued initial funding transfer %s in %s", transferTxStatus.TxID, time.Since(baseStartTime)) 89 90 genesisWorkload.confirmTransferTx(ctx, transferTxStatus) 91 92 workloads[i] = &workload{ 93 id: i, 94 chainID: chainID, 95 key: key, 96 addrs: set.Of(addr), 97 uris: c.URIs, 98 } 99 } 100 101 lifecycle.SetupComplete(map[string]any{ 102 "msg": "initialized workers", 103 "numWorkers": NumKeys, 104 }) 105 106 for _, w := range workloads[1:] { 107 go w.run(ctx) 108 } 109 genesisWorkload.run(ctx) 110 } 111 112 type workload struct { 113 id int 114 chainID ids.ID 115 key *secp256k1.PrivateKey 116 addrs set.Set[ids.ShortID] 117 uris []string 118 } 119 120 func (w *workload) run(ctx context.Context) { 121 timer := time.NewTimer(0) 122 if !timer.Stop() { 123 <-timer.C 124 } 125 126 uri := w.uris[w.id%len(w.uris)] 127 128 client := api.NewClient(uri, w.chainID.String()) 129 balance, err := client.Balance(ctx, w.key.Address(), w.chainID) 130 if err != nil { 131 log.Fatalf("failed to fetch balance: %s", err) 132 } 133 log.Printf("worker %d starting with a balance of %d", w.id, balance) 134 assert.Reachable("worker starting", map[string]any{ 135 "worker": w.id, 136 "balance": balance, 137 }) 138 139 for { 140 log.Printf("worker %d executing transfer", w.id) 141 destAddress, _ := w.addrs.Peek() 142 txStatus, err := transfer.Transfer( 143 ctx, 144 &transfer.Config{ 145 URI: uri, 146 ChainID: w.chainID, 147 AssetID: w.chainID, 148 Amount: units.Schmeckle, 149 To: destAddress, 150 PrivateKey: w.key, 151 }, 152 ) 153 if err != nil { 154 log.Printf("worker %d failed to issue transfer: %s", w.id, err) 155 } else { 156 log.Printf("worker %d issued transfer %s in %s", w.id, txStatus.TxID, time.Since(txStatus.StartTime)) 157 w.confirmTransferTx(ctx, txStatus) 158 } 159 160 val, err := rand.Int(rand.Reader, big.NewInt(int64(time.Second))) 161 if err != nil { 162 log.Fatalf("failed to read randomness: %s", err) 163 } 164 165 timer.Reset(time.Duration(val.Int64())) 166 select { 167 case <-ctx.Done(): 168 return 169 case <-timer.C: 170 } 171 } 172 } 173 174 func (w *workload) confirmTransferTx(ctx context.Context, tx *status.TxIssuance) { 175 for _, uri := range w.uris { 176 client := api.NewClient(uri, w.chainID.String()) 177 if err := api.AwaitTxAccepted(ctx, client, w.key.Address(), tx.Nonce, PollingInterval); err != nil { 178 log.Printf("worker %d failed to confirm transaction %s on %s: %s", w.id, tx.TxID, uri, err) 179 return 180 } 181 } 182 log.Printf("worker %d confirmed transaction %s on all nodes", w.id, tx.TxID) 183 }