github.com/MetalBlockchain/subnet-evm@v0.4.9/plugin/evm/tx_gossiping_test.go (about) 1 // (c) 2019-2021, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package evm 5 6 import ( 7 "context" 8 "crypto/ecdsa" 9 "encoding/json" 10 "fmt" 11 "math/big" 12 "strings" 13 "sync" 14 "testing" 15 "time" 16 17 "github.com/MetalBlockchain/metalgo/ids" 18 "github.com/MetalBlockchain/metalgo/utils/set" 19 20 "github.com/ethereum/go-ethereum/common" 21 "github.com/ethereum/go-ethereum/crypto" 22 "github.com/ethereum/go-ethereum/rlp" 23 24 "github.com/stretchr/testify/assert" 25 26 "github.com/MetalBlockchain/subnet-evm/core" 27 "github.com/MetalBlockchain/subnet-evm/core/types" 28 "github.com/MetalBlockchain/subnet-evm/params" 29 "github.com/MetalBlockchain/subnet-evm/plugin/evm/message" 30 ) 31 32 func fundAddressByGenesis(addrs []common.Address) (string, error) { 33 balance := big.NewInt(0xffffffffffffff) 34 genesis := &core.Genesis{ 35 Difficulty: common.Big0, 36 GasLimit: params.TestChainConfig.FeeConfig.GasLimit.Uint64(), 37 } 38 funds := make(map[common.Address]core.GenesisAccount) 39 for _, addr := range addrs { 40 funds[addr] = core.GenesisAccount{ 41 Balance: balance, 42 } 43 } 44 genesis.Alloc = funds 45 genesis.Config = params.TestChainConfig 46 47 bytes, err := json.Marshal(genesis) 48 return string(bytes), err 49 } 50 51 func getValidTxs(key *ecdsa.PrivateKey, count int, gasPrice *big.Int) []*types.Transaction { 52 res := make([]*types.Transaction, count) 53 54 to := common.Address{} 55 amount := big.NewInt(10000) 56 gasLimit := uint64(100000) 57 58 for i := 0; i < count; i++ { 59 tx, _ := types.SignTx( 60 types.NewTransaction( 61 uint64(i), 62 to, 63 amount, 64 gasLimit, 65 gasPrice, 66 []byte(strings.Repeat("aaaaaaaaaa", 100))), 67 types.HomesteadSigner{}, key) 68 tx.SetFirstSeen(time.Now().Add(-1 * time.Minute)) 69 res[i] = tx 70 } 71 return res 72 } 73 74 // show that locally issued eth txs are gossiped 75 // Note: channel through which subnet-evm mempool push txs to vm is injected here 76 // to ease up UT, which target only VM behaviors in response to subnet-evm mempool 77 // signals 78 func TestMempoolTxsAddedTxsGossipedAfterActivation(t *testing.T) { 79 t.Skip("FLAKY") 80 assert := assert.New(t) 81 82 key, err := crypto.GenerateKey() 83 assert.NoError(err) 84 85 addr := crypto.PubkeyToAddress(key.PublicKey) 86 cfgJson, err := fundAddressByGenesis([]common.Address{addr}) 87 assert.NoError(err) 88 89 _, vm, _, sender := GenesisVM(t, true, cfgJson, "", "") 90 defer func() { 91 err := vm.Shutdown(context.Background()) 92 assert.NoError(err) 93 }() 94 vm.txPool.SetGasPrice(common.Big1) 95 vm.txPool.SetMinFee(common.Big0) 96 97 // create eth txes 98 ethTxs := getValidTxs(key, 3, common.Big1) 99 100 var wg sync.WaitGroup 101 var wg2 sync.WaitGroup 102 wg.Add(2) 103 wg2.Add(1) 104 sender.CantSendAppGossip = false 105 seen := 0 106 sender.SendAppGossipF = func(_ context.Context, gossipedBytes []byte) error { 107 if seen == 0 { 108 notifyMsgIntf, err := message.ParseGossipMessage(vm.networkCodec, gossipedBytes) 109 assert.NoError(err) 110 111 requestMsg, ok := notifyMsgIntf.(message.TxsGossip) 112 assert.True(ok) 113 assert.NotEmpty(requestMsg.Txs) 114 115 txs := make([]*types.Transaction, 0) 116 assert.NoError(rlp.DecodeBytes(requestMsg.Txs, &txs)) 117 assert.Len(txs, 2) 118 assert.ElementsMatch( 119 []common.Hash{ethTxs[0].Hash(), ethTxs[1].Hash()}, 120 []common.Hash{txs[0].Hash(), txs[1].Hash()}, 121 ) 122 seen++ 123 wg2.Done() 124 } else if seen == 1 { 125 notifyMsgIntf, err := message.ParseGossipMessage(vm.networkCodec, gossipedBytes) 126 assert.NoError(err) 127 128 requestMsg, ok := notifyMsgIntf.(message.TxsGossip) 129 assert.True(ok) 130 assert.NotEmpty(requestMsg.Txs) 131 132 txs := make([]*types.Transaction, 0) 133 assert.NoError(rlp.DecodeBytes(requestMsg.Txs, &txs)) 134 assert.Len(txs, 1) 135 assert.Equal(ethTxs[2].Hash(), txs[0].Hash()) 136 137 seen++ 138 } else { 139 t.Fatal("should not be seen 3 times") 140 } 141 wg.Done() 142 return nil 143 } 144 145 // Notify VM about eth txs 146 errs := vm.txPool.AddRemotesSync(ethTxs[:2]) 147 for _, err := range errs { 148 assert.NoError(err, "failed adding subnet-evm tx to mempool") 149 } 150 151 // Gossip txs again (shouldn't gossip hashes) 152 attemptAwait(t, &wg2, 5*time.Second) // wait until reorg processed 153 assert.NoError(vm.gossiper.GossipTxs(ethTxs[:2])) 154 155 errs = vm.txPool.AddRemotesSync(ethTxs) 156 assert.Contains(errs[0].Error(), "already known") 157 assert.Contains(errs[1].Error(), "already known") 158 assert.NoError(errs[2], "failed adding subnet-evm tx to mempool") 159 160 attemptAwait(t, &wg, 5*time.Second) 161 } 162 163 // show that locally issued eth txs are chunked correctly 164 func TestMempoolTxsAddedTxsGossipedAfterActivationChunking(t *testing.T) { 165 t.Skip("FLAKY") 166 assert := assert.New(t) 167 168 key, err := crypto.GenerateKey() 169 assert.NoError(err) 170 171 addr := crypto.PubkeyToAddress(key.PublicKey) 172 173 cfgJson, err := fundAddressByGenesis([]common.Address{addr}) 174 assert.NoError(err) 175 176 _, vm, _, sender := GenesisVM(t, true, cfgJson, "", "") 177 defer func() { 178 err := vm.Shutdown(context.Background()) 179 assert.NoError(err) 180 }() 181 vm.txPool.SetGasPrice(common.Big1) 182 vm.txPool.SetMinFee(common.Big0) 183 184 // create eth txes 185 txs := getValidTxs(key, 100, common.Big1) 186 187 var wg sync.WaitGroup 188 wg.Add(2) 189 sender.CantSendAppGossip = false 190 seen := map[common.Hash]struct{}{} 191 sender.SendAppGossipF = func(_ context.Context, gossipedBytes []byte) error { 192 notifyMsgIntf, err := message.ParseGossipMessage(vm.networkCodec, gossipedBytes) 193 assert.NoError(err) 194 195 requestMsg, ok := notifyMsgIntf.(message.TxsGossip) 196 assert.True(ok) 197 assert.NotEmpty(requestMsg.Txs) 198 199 txs := make([]*types.Transaction, 0) 200 assert.NoError(rlp.DecodeBytes(requestMsg.Txs, &txs)) 201 for _, tx := range txs { 202 seen[tx.Hash()] = struct{}{} 203 } 204 wg.Done() 205 return nil 206 } 207 208 // Notify VM about eth txs 209 errs := vm.txPool.AddRemotesSync(txs) 210 for _, err := range errs { 211 assert.NoError(err, "failed adding subnet-evm tx to mempool") 212 } 213 214 attemptAwait(t, &wg, 5*time.Second) 215 216 for _, tx := range txs { 217 _, ok := seen[tx.Hash()] 218 assert.True(ok, "missing hash: %v", tx.Hash()) 219 } 220 } 221 222 // show that a geth tx discovered from gossip is requested to the same node that 223 // gossiped it 224 func TestMempoolTxsAppGossipHandling(t *testing.T) { 225 t.Skip("FLAKY") 226 assert := assert.New(t) 227 228 key, err := crypto.GenerateKey() 229 assert.NoError(err) 230 231 addr := crypto.PubkeyToAddress(key.PublicKey) 232 233 cfgJson, err := fundAddressByGenesis([]common.Address{addr}) 234 assert.NoError(err) 235 236 _, vm, _, sender := GenesisVM(t, true, cfgJson, "", "") 237 defer func() { 238 err := vm.Shutdown(context.Background()) 239 assert.NoError(err) 240 }() 241 vm.txPool.SetGasPrice(common.Big1) 242 vm.txPool.SetMinFee(common.Big0) 243 244 var ( 245 wg sync.WaitGroup 246 txRequested bool 247 ) 248 sender.CantSendAppGossip = false 249 sender.SendAppRequestF = func(context.Context, set.Set[ids.NodeID], uint32, []byte) error { 250 txRequested = true 251 return nil 252 } 253 wg.Add(1) 254 sender.SendAppGossipF = func(context.Context, []byte) error { 255 wg.Done() 256 return nil 257 } 258 259 // prepare a tx 260 tx := getValidTxs(key, 1, common.Big1)[0] 261 262 // show that unknown subnet-evm hashes is requested 263 txBytes, err := rlp.EncodeToBytes([]*types.Transaction{tx}) 264 assert.NoError(err) 265 msg := message.TxsGossip{ 266 Txs: txBytes, 267 } 268 msgBytes, err := message.BuildGossipMessage(vm.networkCodec, msg) 269 assert.NoError(err) 270 271 nodeID := ids.GenerateTestNodeID() 272 err = vm.AppGossip(context.Background(), nodeID, msgBytes) 273 assert.NoError(err) 274 assert.False(txRequested, "tx should not be requested") 275 276 // wait for transaction to be re-gossiped 277 attemptAwait(t, &wg, 5*time.Second) 278 } 279 280 func TestMempoolTxsRegossipSingleAccount(t *testing.T) { 281 assert := assert.New(t) 282 283 key, err := crypto.GenerateKey() 284 assert.NoError(err) 285 286 addr := crypto.PubkeyToAddress(key.PublicKey) 287 288 cfgJson, err := fundAddressByGenesis([]common.Address{addr}) 289 assert.NoError(err) 290 291 _, vm, _, _ := GenesisVM(t, true, cfgJson, `{"local-txs-enabled":true}`, "") 292 defer func() { 293 err := vm.Shutdown(context.Background()) 294 assert.NoError(err) 295 }() 296 vm.txPool.SetGasPrice(common.Big1) 297 vm.txPool.SetMinFee(common.Big0) 298 299 // create eth txes 300 txs := getValidTxs(key, 10, big.NewInt(226*params.GWei)) 301 302 // Notify VM about eth txs 303 errs := vm.txPool.AddRemotesSync(txs) 304 for _, err := range errs { 305 assert.NoError(err, "failed adding subnet-evm tx to remote mempool") 306 } 307 308 // Only 1 transaction will be regossiped for an address (should be lowest 309 // nonce) 310 pushNetwork := vm.gossiper.(*pushGossiper) 311 queued := pushNetwork.queueRegossipTxs() 312 assert.Len(queued, 1, "unexpected length of queued txs") 313 assert.Equal(txs[0].Hash(), queued[0].Hash()) 314 } 315 316 func TestMempoolTxsRegossip(t *testing.T) { 317 assert := assert.New(t) 318 319 keys := make([]*ecdsa.PrivateKey, 20) 320 addrs := make([]common.Address, 20) 321 for i := 0; i < 20; i++ { 322 key, err := crypto.GenerateKey() 323 assert.NoError(err) 324 keys[i] = key 325 addrs[i] = crypto.PubkeyToAddress(key.PublicKey) 326 } 327 328 cfgJson, err := fundAddressByGenesis(addrs) 329 assert.NoError(err) 330 331 _, vm, _, _ := GenesisVM(t, true, cfgJson, `{"local-txs-enabled":true}`, "") 332 defer func() { 333 err := vm.Shutdown(context.Background()) 334 assert.NoError(err) 335 }() 336 vm.txPool.SetGasPrice(common.Big1) 337 vm.txPool.SetMinFee(common.Big0) 338 339 // create eth txes 340 ethTxs := make([]*types.Transaction, 20) 341 ethTxHashes := make([]common.Hash, 20) 342 for i := 0; i < 20; i++ { 343 txs := getValidTxs(keys[i], 1, big.NewInt(226*params.GWei)) 344 tx := txs[0] 345 ethTxs[i] = tx 346 ethTxHashes[i] = tx.Hash() 347 } 348 349 // Notify VM about eth txs 350 errs := vm.txPool.AddRemotesSync(ethTxs[:10]) 351 for _, err := range errs { 352 assert.NoError(err, "failed adding subnet-evm tx to remote mempool") 353 } 354 errs = vm.txPool.AddLocals(ethTxs[10:]) 355 for _, err := range errs { 356 assert.NoError(err, "failed adding subnet-evm tx to local mempool") 357 } 358 359 // We expect 16 transactions (the default max number of transactions to 360 // regossip) comprised of 10 local txs and 5 remote txs (we prioritize local 361 // txs over remote). 362 pushNetwork := vm.gossiper.(*pushGossiper) 363 queued := pushNetwork.queueRegossipTxs() 364 assert.Len(queued, 16, "unexpected length of queued txs") 365 366 // Confirm queued transactions (should be ordered based on 367 // timestamp submitted, with local priorized over remote) 368 queuedTxHashes := make([]common.Hash, 16) 369 for i, tx := range queued { 370 queuedTxHashes[i] = tx.Hash() 371 } 372 assert.ElementsMatch(queuedTxHashes[:10], ethTxHashes[10:], "missing local transactions") 373 374 // NOTE: We don't care which remote transactions are included in this test 375 // (due to the non-deterministic way pending transactions are surfaced, this can be difficult 376 // to assert as well). 377 } 378 379 func TestMempoolTxsPriorityRegossip(t *testing.T) { 380 assert := assert.New(t) 381 382 key, err := crypto.GenerateKey() 383 assert.NoError(err) 384 addr := crypto.PubkeyToAddress(key.PublicKey) 385 386 key2, err := crypto.GenerateKey() 387 assert.NoError(err) 388 addr2 := crypto.PubkeyToAddress(key2.PublicKey) 389 390 cfgJson, err := fundAddressByGenesis([]common.Address{addr, addr2}) 391 assert.NoError(err) 392 393 cfg := fmt.Sprintf(`{"local-txs-enabled":true,"priority-regossip-addresses":["%s"]}`, addr) 394 _, vm, _, _ := GenesisVM(t, true, cfgJson, cfg, "") 395 defer func() { 396 err := vm.Shutdown(context.Background()) 397 assert.NoError(err) 398 }() 399 vm.txPool.SetGasPrice(common.Big1) 400 vm.txPool.SetMinFee(common.Big0) 401 402 // create eth txes 403 txs := getValidTxs(key, 10, big.NewInt(226*params.GWei)) 404 txs2 := getValidTxs(key2, 10, big.NewInt(226*params.GWei)) 405 406 // Notify VM about eth txs 407 for _, err := range vm.txPool.AddRemotesSync(txs) { 408 assert.NoError(err, "failed adding subnet-evm tx to remote mempool") 409 } 410 for _, err := range vm.txPool.AddRemotesSync(txs2) { 411 assert.NoError(err, "failed adding subnet-evm tx 2 to remote mempool") 412 } 413 414 // 10 transactions will be regossiped for a priority address (others ignored) 415 pushNetwork := vm.gossiper.(*pushGossiper) 416 queued := pushNetwork.queuePriorityRegossipTxs() 417 assert.Len(queued, 10, "unexpected length of queued txs") 418 assert.ElementsMatch(txs, queued) 419 }