github.com/ava-labs/subnet-evm@v0.6.4/tests/warp/warp_test.go (about) 1 // Copyright (C) 2023, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 // Implements solidity tests. 5 package warp 6 7 import ( 8 "context" 9 "crypto/ecdsa" 10 "encoding/hex" 11 "fmt" 12 "math/big" 13 "os" 14 "path/filepath" 15 "strings" 16 "testing" 17 "time" 18 19 ginkgo "github.com/onsi/ginkgo/v2" 20 21 "github.com/onsi/gomega" 22 23 "github.com/stretchr/testify/require" 24 25 "github.com/ethereum/go-ethereum/common" 26 "github.com/ethereum/go-ethereum/crypto" 27 "github.com/ethereum/go-ethereum/log" 28 29 "github.com/ava-labs/avalanchego/api/info" 30 "github.com/ava-labs/avalanchego/ids" 31 "github.com/ava-labs/avalanchego/snow/validators" 32 "github.com/ava-labs/avalanchego/tests/fixture/e2e" 33 "github.com/ava-labs/avalanchego/tests/fixture/tmpnet" 34 "github.com/ava-labs/avalanchego/utils/constants" 35 "github.com/ava-labs/avalanchego/vms/platformvm" 36 avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" 37 "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" 38 39 "github.com/ava-labs/subnet-evm/cmd/simulator/key" 40 "github.com/ava-labs/subnet-evm/cmd/simulator/load" 41 "github.com/ava-labs/subnet-evm/cmd/simulator/metrics" 42 "github.com/ava-labs/subnet-evm/cmd/simulator/txs" 43 "github.com/ava-labs/subnet-evm/core/types" 44 "github.com/ava-labs/subnet-evm/ethclient" 45 "github.com/ava-labs/subnet-evm/interfaces" 46 "github.com/ava-labs/subnet-evm/params" 47 "github.com/ava-labs/subnet-evm/precompile/contracts/warp" 48 "github.com/ava-labs/subnet-evm/predicate" 49 "github.com/ava-labs/subnet-evm/tests" 50 "github.com/ava-labs/subnet-evm/tests/utils" 51 warpBackend "github.com/ava-labs/subnet-evm/warp" 52 "github.com/ava-labs/subnet-evm/warp/aggregator" 53 ) 54 55 const ( 56 subnetAName = "warp-subnet-a" 57 subnetBName = "warp-subnet-b" 58 ) 59 60 var ( 61 flagVars *e2e.FlagVars 62 63 repoRootPath = tests.GetRepoRootPath("tests/warp") 64 65 genesisPath = filepath.Join(repoRootPath, "tests/precompile/genesis/warp.json") 66 67 subnetA, subnetB, cChainSubnetDetails *Subnet 68 69 testPayload = []byte{1, 2, 3} 70 ) 71 72 func init() { 73 // Configures flags used to configure tmpnet (via SynchronizedBeforeSuite) 74 flagVars = e2e.RegisterFlags() 75 } 76 77 // Subnet provides the basic details of a created subnet 78 type Subnet struct { 79 // SubnetID is the txID of the transaction that created the subnet 80 SubnetID ids.ID 81 // For simplicity assume a single blockchain per subnet 82 BlockchainID ids.ID 83 // Key funded in the genesis of the blockchain 84 PreFundedKey *ecdsa.PrivateKey 85 // ValidatorURIs are the base URIs for each participant of the Subnet 86 ValidatorURIs []string 87 } 88 89 func TestE2E(t *testing.T) { 90 gomega.RegisterFailHandler(ginkgo.Fail) 91 ginkgo.RunSpecs(t, "subnet-evm warp e2e test") 92 } 93 94 var _ = ginkgo.SynchronizedBeforeSuite(func() []byte { 95 // Run only once in the first ginkgo process 96 97 nodes := utils.NewTmpnetNodes(tmpnet.DefaultNodeCount) 98 99 env := e2e.NewTestEnvironment( 100 flagVars, 101 utils.NewTmpnetNetwork( 102 "subnet-evm-warp-e2e", 103 nodes, 104 tmpnet.FlagsMap{}, 105 utils.NewTmpnetSubnet(subnetAName, genesisPath, utils.DefaultChainConfig, nodes...), 106 utils.NewTmpnetSubnet(subnetBName, genesisPath, utils.DefaultChainConfig, nodes...), 107 ), 108 ) 109 110 return env.Marshal() 111 }, func(envBytes []byte) { 112 // Run in every ginkgo process 113 114 require := require.New(ginkgo.GinkgoT()) 115 116 // Initialize the local test environment from the global state 117 if len(envBytes) > 0 { 118 e2e.InitSharedTestEnvironment(envBytes) 119 } 120 121 network := e2e.Env.GetNetwork() 122 123 // By default all nodes are validating all subnets 124 validatorURIs := make([]string, len(network.Nodes)) 125 for i, node := range network.Nodes { 126 validatorURIs[i] = node.URI 127 } 128 129 tmpnetSubnetA := network.GetSubnet(subnetAName) 130 require.NotNil(tmpnetSubnetA) 131 subnetA = &Subnet{ 132 SubnetID: tmpnetSubnetA.SubnetID, 133 BlockchainID: tmpnetSubnetA.Chains[0].ChainID, 134 PreFundedKey: tmpnetSubnetA.Chains[0].PreFundedKey.ToECDSA(), 135 ValidatorURIs: validatorURIs, 136 } 137 138 tmpnetSubnetB := network.GetSubnet(subnetBName) 139 require.NotNil(tmpnetSubnetB) 140 subnetB = &Subnet{ 141 SubnetID: tmpnetSubnetB.SubnetID, 142 BlockchainID: tmpnetSubnetB.Chains[0].ChainID, 143 PreFundedKey: tmpnetSubnetB.Chains[0].PreFundedKey.ToECDSA(), 144 ValidatorURIs: validatorURIs, 145 } 146 147 infoClient := info.NewClient(network.Nodes[0].URI) 148 cChainBlockchainID, err := infoClient.GetBlockchainID(e2e.DefaultContext(), "C") 149 require.NoError(err) 150 151 cChainSubnetDetails = &Subnet{ 152 SubnetID: constants.PrimaryNetworkID, 153 BlockchainID: cChainBlockchainID, 154 PreFundedKey: tmpnet.HardhatKey.ToECDSA(), 155 ValidatorURIs: validatorURIs, 156 } 157 }) 158 159 var _ = ginkgo.Describe("[Warp]", func() { 160 testFunc := func(sendingSubnet *Subnet, receivingSubnet *Subnet) { 161 w := newWarpTest(e2e.DefaultContext(), sendingSubnet, receivingSubnet) 162 163 log.Info("Sending message from A to B") 164 w.sendMessageFromSendingSubnet() 165 166 log.Info("Aggregating signatures via API") 167 w.aggregateSignaturesViaAPI() 168 169 log.Info("Aggregating signatures via p2p aggregator") 170 w.aggregateSignatures() 171 172 log.Info("Delivering addressed call payload to receiving subnet") 173 w.deliverAddressedCallToReceivingSubnet() 174 175 log.Info("Delivering block hash payload to receiving subnet") 176 w.deliverBlockHashPayload() 177 178 log.Info("Executing HardHat test") 179 w.executeHardHatTest() 180 181 log.Info("Executing warp load test") 182 w.warpLoad() 183 } 184 ginkgo.It("SubnetA -> SubnetB", func() { testFunc(subnetA, subnetB) }) 185 ginkgo.It("SubnetA -> SubnetA", func() { testFunc(subnetA, subnetA) }) 186 ginkgo.It("SubnetA -> C-Chain", func() { testFunc(subnetA, cChainSubnetDetails) }) 187 ginkgo.It("C-Chain -> SubnetA", func() { testFunc(cChainSubnetDetails, subnetA) }) 188 ginkgo.It("C-Chain -> C-Chain", func() { testFunc(cChainSubnetDetails, cChainSubnetDetails) }) 189 }) 190 191 type warpTest struct { 192 // network-wide fields set in the constructor 193 networkID uint32 194 195 // sendingSubnet fields set in the constructor 196 sendingSubnet *Subnet 197 sendingSubnetURIs []string 198 sendingSubnetClients []ethclient.Client 199 sendingSubnetFundedKey *ecdsa.PrivateKey 200 sendingSubnetFundedAddress common.Address 201 sendingSubnetChainID *big.Int 202 sendingSubnetSigner types.Signer 203 204 // receivingSubnet fields set in the constructor 205 receivingSubnet *Subnet 206 receivingSubnetURIs []string 207 receivingSubnetClients []ethclient.Client 208 receivingSubnetFundedKey *ecdsa.PrivateKey 209 receivingSubnetFundedAddress common.Address 210 receivingSubnetChainID *big.Int 211 receivingSubnetSigner types.Signer 212 213 // Fields set throughout test execution 214 blockID ids.ID 215 blockPayload *payload.Hash 216 blockPayloadUnsignedMessage *avalancheWarp.UnsignedMessage 217 blockPayloadSignedMessage *avalancheWarp.Message 218 219 addressedCallUnsignedMessage *avalancheWarp.UnsignedMessage 220 addressedCallSignedMessage *avalancheWarp.Message 221 } 222 223 func newWarpTest(ctx context.Context, sendingSubnet *Subnet, receivingSubnet *Subnet) *warpTest { 224 require := require.New(ginkgo.GinkgoT()) 225 226 sendingSubnetFundedKey := sendingSubnet.PreFundedKey 227 receivingSubnetFundedKey := receivingSubnet.PreFundedKey 228 229 warpTest := &warpTest{ 230 sendingSubnet: sendingSubnet, 231 sendingSubnetURIs: sendingSubnet.ValidatorURIs, 232 receivingSubnet: receivingSubnet, 233 receivingSubnetURIs: receivingSubnet.ValidatorURIs, 234 sendingSubnetFundedKey: sendingSubnetFundedKey, 235 sendingSubnetFundedAddress: crypto.PubkeyToAddress(sendingSubnetFundedKey.PublicKey), 236 receivingSubnetFundedKey: receivingSubnetFundedKey, 237 receivingSubnetFundedAddress: crypto.PubkeyToAddress(receivingSubnetFundedKey.PublicKey), 238 } 239 infoClient := info.NewClient(sendingSubnet.ValidatorURIs[0]) 240 networkID, err := infoClient.GetNetworkID(ctx) 241 require.NoError(err) 242 warpTest.networkID = networkID 243 244 warpTest.initClients() 245 246 sendingClient := warpTest.sendingSubnetClients[0] 247 sendingSubnetChainID, err := sendingClient.ChainID(ctx) 248 require.NoError(err) 249 warpTest.sendingSubnetChainID = sendingSubnetChainID 250 warpTest.sendingSubnetSigner = types.LatestSignerForChainID(sendingSubnetChainID) 251 252 receivingClient := warpTest.receivingSubnetClients[0] 253 receivingChainID, err := receivingClient.ChainID(ctx) 254 require.NoError(err) 255 // Issue transactions to activate ProposerVM on the receiving chain 256 require.NoError(utils.IssueTxsToActivateProposerVMFork(ctx, receivingChainID, receivingSubnetFundedKey, receivingClient)) 257 warpTest.receivingSubnetChainID = receivingChainID 258 warpTest.receivingSubnetSigner = types.LatestSignerForChainID(receivingChainID) 259 260 return warpTest 261 } 262 263 func (w *warpTest) initClients() { 264 require := require.New(ginkgo.GinkgoT()) 265 266 w.sendingSubnetClients = make([]ethclient.Client, 0, len(w.sendingSubnetClients)) 267 for _, uri := range w.sendingSubnet.ValidatorURIs { 268 wsURI := toWebsocketURI(uri, w.sendingSubnet.BlockchainID.String()) 269 log.Info("Creating ethclient for blockchain A", "blockchainID", w.sendingSubnet.BlockchainID) 270 client, err := ethclient.Dial(wsURI) 271 require.NoError(err) 272 w.sendingSubnetClients = append(w.sendingSubnetClients, client) 273 } 274 275 w.receivingSubnetClients = make([]ethclient.Client, 0, len(w.receivingSubnetClients)) 276 for _, uri := range w.receivingSubnet.ValidatorURIs { 277 wsURI := toWebsocketURI(uri, w.receivingSubnet.BlockchainID.String()) 278 log.Info("Creating ethclient for blockchain B", "blockchainID", w.receivingSubnet.BlockchainID) 279 client, err := ethclient.Dial(wsURI) 280 require.NoError(err) 281 w.receivingSubnetClients = append(w.receivingSubnetClients, client) 282 } 283 } 284 285 func (w *warpTest) getBlockHashAndNumberFromTxReceipt(ctx context.Context, client ethclient.Client, tx *types.Transaction) (common.Hash, uint64) { 286 // This uses the Subnet-EVM client to fetch a block from Coreth (when testing the C-Chain), so we use this 287 // workaround to get the correct block hash. Note the client recalculates the block hash locally, which results 288 // in a different block hash due to small differences in the block format. 289 require := require.New(ginkgo.GinkgoT()) 290 for { 291 require.NoError(ctx.Err()) 292 receipt, err := client.TransactionReceipt(ctx, tx.Hash()) 293 if err == nil { 294 return receipt.BlockHash, receipt.BlockNumber.Uint64() 295 } 296 } 297 } 298 299 func (w *warpTest) sendMessageFromSendingSubnet() { 300 ctx := e2e.DefaultContext() 301 require := require.New(ginkgo.GinkgoT()) 302 303 client := w.sendingSubnetClients[0] 304 log.Info("Subscribing to new heads") 305 newHeads := make(chan *types.Header, 10) 306 sub, err := client.SubscribeNewHead(ctx, newHeads) 307 require.NoError(err) 308 defer sub.Unsubscribe() 309 310 startingNonce, err := client.NonceAt(ctx, w.sendingSubnetFundedAddress, nil) 311 require.NoError(err) 312 313 packedInput, err := warp.PackSendWarpMessage(testPayload) 314 require.NoError(err) 315 tx := types.NewTx(&types.DynamicFeeTx{ 316 ChainID: w.sendingSubnetChainID, 317 Nonce: startingNonce, 318 To: &warp.Module.Address, 319 Gas: 200_000, 320 GasFeeCap: big.NewInt(225 * params.GWei), 321 GasTipCap: big.NewInt(params.GWei), 322 Value: common.Big0, 323 Data: packedInput, 324 }) 325 signedTx, err := types.SignTx(tx, w.sendingSubnetSigner, w.sendingSubnetFundedKey) 326 require.NoError(err) 327 log.Info("Sending sendWarpMessage transaction", "txHash", signedTx.Hash()) 328 err = client.SendTransaction(ctx, signedTx) 329 require.NoError(err) 330 331 log.Info("Waiting for new block confirmation") 332 <-newHeads 333 receiptCtx, cancel := context.WithTimeout(ctx, 10*time.Second) 334 defer cancel() 335 blockHash, blockNumber := w.getBlockHashAndNumberFromTxReceipt(receiptCtx, client, signedTx) 336 337 log.Info("Constructing warp block hash unsigned message", "blockHash", blockHash) 338 w.blockID = ids.ID(blockHash) // Set blockID to construct a warp message containing a block hash payload later 339 w.blockPayload, err = payload.NewHash(w.blockID) 340 require.NoError(err) 341 w.blockPayloadUnsignedMessage, err = avalancheWarp.NewUnsignedMessage(w.networkID, w.sendingSubnet.BlockchainID, w.blockPayload.Bytes()) 342 require.NoError(err) 343 344 log.Info("Fetching relevant warp logs from the newly produced block") 345 logs, err := client.FilterLogs(ctx, interfaces.FilterQuery{ 346 BlockHash: &blockHash, 347 Addresses: []common.Address{warp.Module.Address}, 348 }) 349 require.NoError(err) 350 require.Len(logs, 1) 351 352 // Check for relevant warp log from subscription and ensure that it matches 353 // the log extracted from the last block. 354 txLog := logs[0] 355 log.Info("Parsing logData as unsigned warp message") 356 unsignedMsg, err := warp.UnpackSendWarpEventDataToMessage(txLog.Data) 357 require.NoError(err) 358 359 // Set local variables for the duration of the test 360 w.addressedCallUnsignedMessage = unsignedMsg 361 log.Info("Parsed unsignedWarpMsg", "unsignedWarpMessageID", w.addressedCallUnsignedMessage.ID(), "unsignedWarpMessage", w.addressedCallUnsignedMessage) 362 363 // Loop over each client on chain A to ensure they all have time to accept the block. 364 // Note: if we did not confirm this here, the next stage could be racy since it assumes every node 365 // has accepted the block. 366 for i, client := range w.sendingSubnetClients { 367 // Loop until each node has advanced to >= the height of the block that emitted the warp log 368 for { 369 block, err := client.BlockByNumber(ctx, nil) 370 require.NoError(err) 371 if block.NumberU64() >= blockNumber { 372 log.Info("client accepted the block containing SendWarpMessage", "client", i, "height", block.NumberU64()) 373 break 374 } 375 } 376 } 377 } 378 379 func (w *warpTest) aggregateSignaturesViaAPI() { 380 require := require.New(ginkgo.GinkgoT()) 381 ctx := e2e.DefaultContext() 382 383 warpAPIs := make(map[ids.NodeID]warpBackend.Client, len(w.sendingSubnetURIs)) 384 for _, uri := range w.sendingSubnetURIs { 385 client, err := warpBackend.NewClient(uri, w.sendingSubnet.BlockchainID.String()) 386 require.NoError(err) 387 388 infoClient := info.NewClient(uri) 389 nodeID, _, err := infoClient.GetNodeID(ctx) 390 require.NoError(err) 391 warpAPIs[nodeID] = client 392 } 393 394 pChainClient := platformvm.NewClient(w.sendingSubnetURIs[0]) 395 pChainHeight, err := pChainClient.GetHeight(ctx) 396 require.NoError(err) 397 // If the source subnet is the Primary Network, then we only need to aggregate signatures from the receiving 398 // subnet's validator set instead of the entire Primary Network. 399 // If the destination turns out to be the Primary Network as well, then this is a no-op. 400 var validators map[ids.NodeID]*validators.GetValidatorOutput 401 if w.sendingSubnet.SubnetID == constants.PrimaryNetworkID { 402 validators, err = pChainClient.GetValidatorsAt(ctx, w.receivingSubnet.SubnetID, pChainHeight) 403 } else { 404 validators, err = pChainClient.GetValidatorsAt(ctx, w.sendingSubnet.SubnetID, pChainHeight) 405 } 406 require.NoError(err) 407 require.NotZero(len(validators)) 408 409 totalWeight := uint64(0) 410 warpValidators := make([]*avalancheWarp.Validator, 0, len(validators)) 411 for nodeID, validator := range validators { 412 warpValidators = append(warpValidators, &avalancheWarp.Validator{ 413 PublicKey: validator.PublicKey, 414 Weight: validator.Weight, 415 NodeIDs: []ids.NodeID{nodeID}, 416 }) 417 totalWeight += validator.Weight 418 } 419 420 log.Info("Aggregating signatures from validator set", "numValidators", len(warpValidators), "totalWeight", totalWeight) 421 apiSignatureGetter := warpBackend.NewAPIFetcher(warpAPIs) 422 signatureResult, err := aggregator.New(apiSignatureGetter, warpValidators, totalWeight).AggregateSignatures(ctx, w.addressedCallUnsignedMessage, 100) 423 require.NoError(err) 424 require.Equal(signatureResult.SignatureWeight, signatureResult.TotalWeight) 425 require.Equal(signatureResult.SignatureWeight, totalWeight) 426 427 w.addressedCallSignedMessage = signatureResult.Message 428 429 signatureResult, err = aggregator.New(apiSignatureGetter, warpValidators, totalWeight).AggregateSignatures(ctx, w.blockPayloadUnsignedMessage, 100) 430 require.NoError(err) 431 require.Equal(signatureResult.SignatureWeight, signatureResult.TotalWeight) 432 require.Equal(signatureResult.SignatureWeight, totalWeight) 433 w.blockPayloadSignedMessage = signatureResult.Message 434 435 log.Info("Aggregated signatures for warp messages", "addressedCallMessage", common.Bytes2Hex(w.addressedCallSignedMessage.Bytes()), "blockPayloadMessage", common.Bytes2Hex(w.blockPayloadSignedMessage.Bytes())) 436 } 437 438 func (w *warpTest) aggregateSignatures() { 439 require := require.New(ginkgo.GinkgoT()) 440 ctx := e2e.DefaultContext() 441 442 // Verify that the signature aggregation matches the results of manually constructing the warp message 443 client, err := warpBackend.NewClient(w.sendingSubnetURIs[0], w.sendingSubnet.BlockchainID.String()) 444 require.NoError(err) 445 446 log.Info("Fetching addressed call aggregate signature via p2p API") 447 subnetIDStr := "" 448 if w.sendingSubnet.SubnetID == constants.PrimaryNetworkID { 449 subnetIDStr = w.receivingSubnet.SubnetID.String() 450 } 451 signedWarpMessageBytes, err := client.GetMessageAggregateSignature(ctx, w.addressedCallSignedMessage.ID(), warp.WarpQuorumDenominator, subnetIDStr) 452 require.NoError(err) 453 require.Equal(w.addressedCallSignedMessage.Bytes(), signedWarpMessageBytes) 454 455 log.Info("Fetching block payload aggregate signature via p2p API") 456 signedWarpBlockBytes, err := client.GetBlockAggregateSignature(ctx, w.blockID, warp.WarpQuorumDenominator, subnetIDStr) 457 require.NoError(err) 458 require.Equal(w.blockPayloadSignedMessage.Bytes(), signedWarpBlockBytes) 459 } 460 461 func (w *warpTest) deliverAddressedCallToReceivingSubnet() { 462 require := require.New(ginkgo.GinkgoT()) 463 ctx := e2e.DefaultContext() 464 465 client := w.receivingSubnetClients[0] 466 log.Info("Subscribing to new heads") 467 newHeads := make(chan *types.Header, 10) 468 sub, err := client.SubscribeNewHead(ctx, newHeads) 469 require.NoError(err) 470 defer sub.Unsubscribe() 471 472 nonce, err := client.NonceAt(ctx, w.receivingSubnetFundedAddress, nil) 473 require.NoError(err) 474 475 packedInput, err := warp.PackGetVerifiedWarpMessage(0) 476 require.NoError(err) 477 tx := predicate.NewPredicateTx( 478 w.receivingSubnetChainID, 479 nonce, 480 &warp.Module.Address, 481 5_000_000, 482 big.NewInt(225*params.GWei), 483 big.NewInt(params.GWei), 484 common.Big0, 485 packedInput, 486 types.AccessList{}, 487 warp.ContractAddress, 488 w.addressedCallSignedMessage.Bytes(), 489 ) 490 signedTx, err := types.SignTx(tx, w.receivingSubnetSigner, w.receivingSubnetFundedKey) 491 require.NoError(err) 492 txBytes, err := signedTx.MarshalBinary() 493 require.NoError(err) 494 log.Info("Sending getVerifiedWarpMessage transaction", "txHash", signedTx.Hash(), "txBytes", common.Bytes2Hex(txBytes)) 495 require.NoError(client.SendTransaction(ctx, signedTx)) 496 497 log.Info("Waiting for new block confirmation") 498 <-newHeads 499 receiptCtx, cancel := context.WithTimeout(ctx, 10*time.Second) 500 defer cancel() 501 blockHash, _ := w.getBlockHashAndNumberFromTxReceipt(receiptCtx, client, signedTx) 502 503 log.Info("Fetching relevant warp logs and receipts from new block") 504 logs, err := client.FilterLogs(ctx, interfaces.FilterQuery{ 505 BlockHash: &blockHash, 506 Addresses: []common.Address{warp.Module.Address}, 507 }) 508 require.NoError(err) 509 require.Len(logs, 0) 510 receipt, err := client.TransactionReceipt(ctx, signedTx.Hash()) 511 require.NoError(err) 512 require.Equal(receipt.Status, types.ReceiptStatusSuccessful) 513 } 514 515 func (w *warpTest) deliverBlockHashPayload() { 516 require := require.New(ginkgo.GinkgoT()) 517 ctx := e2e.DefaultContext() 518 519 client := w.receivingSubnetClients[0] 520 log.Info("Subscribing to new heads") 521 newHeads := make(chan *types.Header, 10) 522 sub, err := client.SubscribeNewHead(ctx, newHeads) 523 require.NoError(err) 524 defer sub.Unsubscribe() 525 526 nonce, err := client.NonceAt(ctx, w.receivingSubnetFundedAddress, nil) 527 require.NoError(err) 528 529 packedInput, err := warp.PackGetVerifiedWarpBlockHash(0) 530 require.NoError(err) 531 tx := predicate.NewPredicateTx( 532 w.receivingSubnetChainID, 533 nonce, 534 &warp.Module.Address, 535 5_000_000, 536 big.NewInt(225*params.GWei), 537 big.NewInt(params.GWei), 538 common.Big0, 539 packedInput, 540 types.AccessList{}, 541 warp.ContractAddress, 542 w.blockPayloadSignedMessage.Bytes(), 543 ) 544 signedTx, err := types.SignTx(tx, w.receivingSubnetSigner, w.receivingSubnetFundedKey) 545 require.NoError(err) 546 txBytes, err := signedTx.MarshalBinary() 547 require.NoError(err) 548 log.Info("Sending getVerifiedWarpBlockHash transaction", "txHash", signedTx.Hash(), "txBytes", common.Bytes2Hex(txBytes)) 549 err = client.SendTransaction(ctx, signedTx) 550 require.NoError(err) 551 552 log.Info("Waiting for new block confirmation") 553 <-newHeads 554 receiptCtx, cancel := context.WithTimeout(ctx, 10*time.Second) 555 defer cancel() 556 blockHash, _ := w.getBlockHashAndNumberFromTxReceipt(receiptCtx, client, signedTx) 557 log.Info("Fetching relevant warp logs and receipts from new block") 558 logs, err := client.FilterLogs(ctx, interfaces.FilterQuery{ 559 BlockHash: &blockHash, 560 Addresses: []common.Address{warp.Module.Address}, 561 }) 562 require.NoError(err) 563 require.Len(logs, 0) 564 receipt, err := client.TransactionReceipt(ctx, signedTx.Hash()) 565 require.NoError(err) 566 require.Equal(receipt.Status, types.ReceiptStatusSuccessful) 567 } 568 569 func (w *warpTest) executeHardHatTest() { 570 require := require.New(ginkgo.GinkgoT()) 571 ctx := e2e.DefaultContext() 572 573 client := w.sendingSubnetClients[0] 574 log.Info("Subscribing to new heads") 575 newHeads := make(chan *types.Header, 10) 576 sub, err := client.SubscribeNewHead(ctx, newHeads) 577 require.NoError(err) 578 defer sub.Unsubscribe() 579 580 chainID, err := client.ChainID(ctx) 581 require.NoError(err) 582 583 rpcURI := toRPCURI(w.sendingSubnetURIs[0], w.sendingSubnet.BlockchainID.String()) 584 585 os.Setenv("SENDER_ADDRESS", crypto.PubkeyToAddress(w.sendingSubnetFundedKey.PublicKey).Hex()) 586 os.Setenv("SOURCE_CHAIN_ID", "0x"+w.sendingSubnet.BlockchainID.Hex()) 587 os.Setenv("PAYLOAD", "0x"+common.Bytes2Hex(testPayload)) 588 os.Setenv("EXPECTED_UNSIGNED_MESSAGE", "0x"+hex.EncodeToString(w.addressedCallUnsignedMessage.Bytes())) 589 os.Setenv("CHAIN_ID", fmt.Sprintf("%d", chainID.Uint64())) 590 591 cmdPath := filepath.Join(repoRootPath, "contracts") 592 // test path is relative to the cmd path 593 testPath := "./test/warp.ts" 594 utils.RunHardhatTestsCustomURI(ctx, rpcURI, cmdPath, testPath) 595 } 596 597 func (w *warpTest) warpLoad() { 598 require := require.New(ginkgo.GinkgoT()) 599 ctx := e2e.DefaultContext() 600 601 var ( 602 numWorkers = len(w.sendingSubnetClients) 603 txsPerWorker uint64 = 10 604 batchSize uint64 = 10 605 sendingClient = w.sendingSubnetClients[0] 606 ) 607 608 chainAKeys, chainAPrivateKeys := generateKeys(w.sendingSubnetFundedKey, numWorkers) 609 chainBKeys, chainBPrivateKeys := generateKeys(w.receivingSubnetFundedKey, numWorkers) 610 611 loadMetrics := metrics.NewDefaultMetrics() 612 613 log.Info("Distributing funds on sending subnet", "numKeys", len(chainAKeys)) 614 chainAKeys, err := load.DistributeFunds(ctx, sendingClient, chainAKeys, len(chainAKeys), new(big.Int).Mul(big.NewInt(100), big.NewInt(params.Ether)), loadMetrics) 615 require.NoError(err) 616 617 log.Info("Distributing funds on receiving subnet", "numKeys", len(chainBKeys)) 618 _, err = load.DistributeFunds(ctx, w.receivingSubnetClients[0], chainBKeys, len(chainBKeys), new(big.Int).Mul(big.NewInt(100), big.NewInt(params.Ether)), loadMetrics) 619 require.NoError(err) 620 621 log.Info("Creating workers for each subnet...") 622 chainAWorkers := make([]txs.Worker[*types.Transaction], 0, len(chainAKeys)) 623 for i := range chainAKeys { 624 chainAWorkers = append(chainAWorkers, load.NewTxReceiptWorker(ctx, w.sendingSubnetClients[i])) 625 } 626 chainBWorkers := make([]txs.Worker[*types.Transaction], 0, len(chainBKeys)) 627 for i := range chainBKeys { 628 chainBWorkers = append(chainBWorkers, load.NewTxReceiptWorker(ctx, w.receivingSubnetClients[i])) 629 } 630 631 log.Info("Subscribing to warp send events on sending subnet") 632 logs := make(chan types.Log, numWorkers*int(txsPerWorker)) 633 sub, err := sendingClient.SubscribeFilterLogs(ctx, interfaces.FilterQuery{ 634 Addresses: []common.Address{warp.Module.Address}, 635 }, logs) 636 require.NoError(err) 637 defer func() { 638 sub.Unsubscribe() 639 err := <-sub.Err() 640 require.NoError(err) 641 }() 642 643 log.Info("Generating tx sequence to send warp messages...") 644 warpSendSequences, err := txs.GenerateTxSequences(ctx, func(key *ecdsa.PrivateKey, nonce uint64) (*types.Transaction, error) { 645 data, err := warp.PackSendWarpMessage([]byte(fmt.Sprintf("Jets %d-%d Dolphins", key.X.Int64(), nonce))) 646 if err != nil { 647 return nil, err 648 } 649 tx := types.NewTx(&types.DynamicFeeTx{ 650 ChainID: w.sendingSubnetChainID, 651 Nonce: nonce, 652 To: &warp.Module.Address, 653 Gas: 200_000, 654 GasFeeCap: big.NewInt(225 * params.GWei), 655 GasTipCap: big.NewInt(params.GWei), 656 Value: common.Big0, 657 Data: data, 658 }) 659 return types.SignTx(tx, w.sendingSubnetSigner, key) 660 }, w.sendingSubnetClients[0], chainAPrivateKeys, txsPerWorker, false) 661 require.NoError(err) 662 log.Info("Executing warp send loader...") 663 warpSendLoader := load.New(chainAWorkers, warpSendSequences, batchSize, loadMetrics) 664 // TODO: execute send and receive loaders concurrently. 665 require.NoError(warpSendLoader.Execute(ctx)) 666 require.NoError(warpSendLoader.ConfirmReachedTip(ctx)) 667 668 warpClient, err := warpBackend.NewClient(w.sendingSubnetURIs[0], w.sendingSubnet.BlockchainID.String()) 669 require.NoError(err) 670 subnetIDStr := "" 671 if w.sendingSubnet.SubnetID == constants.PrimaryNetworkID { 672 subnetIDStr = w.receivingSubnet.SubnetID.String() 673 } 674 675 log.Info("Executing warp delivery sequences...") 676 warpDeliverSequences, err := txs.GenerateTxSequences(ctx, func(key *ecdsa.PrivateKey, nonce uint64) (*types.Transaction, error) { 677 // Wait for the next warp send log 678 warpLog := <-logs 679 680 unsignedMessage, err := warp.UnpackSendWarpEventDataToMessage(warpLog.Data) 681 if err != nil { 682 return nil, err 683 } 684 log.Info("Fetching addressed call aggregate signature via p2p API") 685 686 signedWarpMessageBytes, err := warpClient.GetMessageAggregateSignature(ctx, unsignedMessage.ID(), warp.WarpDefaultQuorumNumerator, subnetIDStr) 687 if err != nil { 688 return nil, err 689 } 690 691 packedInput, err := warp.PackGetVerifiedWarpMessage(0) 692 if err != nil { 693 return nil, err 694 } 695 tx := predicate.NewPredicateTx( 696 w.receivingSubnetChainID, 697 nonce, 698 &warp.Module.Address, 699 5_000_000, 700 big.NewInt(225*params.GWei), 701 big.NewInt(params.GWei), 702 common.Big0, 703 packedInput, 704 types.AccessList{}, 705 warp.ContractAddress, 706 signedWarpMessageBytes, 707 ) 708 return types.SignTx(tx, w.receivingSubnetSigner, key) 709 }, w.receivingSubnetClients[0], chainBPrivateKeys, txsPerWorker, true) 710 require.NoError(err) 711 712 log.Info("Executing warp delivery...") 713 warpDeliverLoader := load.New(chainBWorkers, warpDeliverSequences, batchSize, loadMetrics) 714 require.NoError(warpDeliverLoader.Execute(ctx)) 715 require.NoError(warpSendLoader.ConfirmReachedTip(ctx)) 716 log.Info("Completed warp delivery successfully.") 717 } 718 719 func generateKeys(preFundedKey *ecdsa.PrivateKey, numWorkers int) ([]*key.Key, []*ecdsa.PrivateKey) { 720 keys := []*key.Key{ 721 key.CreateKey(preFundedKey), 722 } 723 privateKeys := []*ecdsa.PrivateKey{ 724 preFundedKey, 725 } 726 for i := 1; i < numWorkers; i++ { 727 newKey, err := key.Generate() 728 require.NoError(ginkgo.GinkgoT(), err) 729 keys = append(keys, newKey) 730 privateKeys = append(privateKeys, newKey.PrivKey) 731 } 732 return keys, privateKeys 733 } 734 735 func toWebsocketURI(uri string, blockchainID string) string { 736 return fmt.Sprintf("ws://%s/ext/bc/%s/ws", strings.TrimPrefix(uri, "http://"), blockchainID) 737 } 738 739 func toRPCURI(uri string, blockchainID string) string { 740 return fmt.Sprintf("%s/ext/bc/%s/rpc", uri, blockchainID) 741 }