github.com/core-coin/go-core/v2@v2.1.9/cmd/devp2p/internal/xcbtest/suite.go (about) 1 // Copyright 2020 by the Authors 2 // This file is part of go-core. 3 // 4 // go-core is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // go-core is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with go-core. If not, see <http://www.gnu.org/licenses/>. 16 17 package xcbtest 18 19 import ( 20 crand "crypto/rand" 21 "fmt" 22 "net" 23 "time" 24 25 "github.com/davecgh/go-spew/spew" 26 "github.com/stretchr/testify/assert" 27 28 "github.com/core-coin/go-core/v2/core/types" 29 "github.com/core-coin/go-core/v2/crypto" 30 "github.com/core-coin/go-core/v2/internal/utesting" 31 "github.com/core-coin/go-core/v2/p2p" 32 "github.com/core-coin/go-core/v2/p2p/enode" 33 "github.com/core-coin/go-core/v2/p2p/rlpx" 34 ) 35 36 var pretty = spew.ConfigState{ 37 Indent: " ", 38 DisableCapacities: true, 39 DisablePointerAddresses: true, 40 SortKeys: true, 41 } 42 43 var timeout = 20 * time.Second 44 45 // Suite represents a structure used to test the xcb 46 // protocol of a node(s). 47 type Suite struct { 48 Dest *enode.Node 49 50 chain *Chain 51 fullChain *Chain 52 } 53 54 // NewSuite creates and returns a new xcb-test suite that can 55 // be used to test the given node against the given blockchain 56 // data. 57 func NewSuite(dest *enode.Node, chainfile string, genesisfile string) *Suite { 58 chain, err := loadChain(chainfile, genesisfile) 59 if err != nil { 60 panic(err) 61 } 62 return &Suite{ 63 Dest: dest, 64 chain: chain.Shorten(10), 65 fullChain: chain, 66 } 67 } 68 69 func (s *Suite) AllTests() []utesting.Test { 70 return []utesting.Test{ 71 {Name: "Status", Fn: s.TestStatus}, 72 {Name: "GetBlockHeaders", Fn: s.TestGetBlockHeaders}, 73 {Name: "Broadcast", Fn: s.TestBroadcast}, 74 {Name: "GetBlockBodies", Fn: s.TestGetBlockBodies}, 75 {Name: "TestLargeAnnounce", Fn: s.TestLargeAnnounce}, 76 {Name: "TestMaliciousHandshake", Fn: s.TestMaliciousHandshake}, 77 {Name: "TestMaliciousStatus", Fn: s.TestMaliciousStatus}, 78 {Name: "TestTransactions", Fn: s.TestTransaction}, 79 {Name: "TestMaliciousTransactions", Fn: s.TestMaliciousTx}, 80 } 81 } 82 83 // TestStatus attempts to connect to the given node and exchange 84 // a status message with it, and then check to make sure 85 // the chain head is correct. 86 func (s *Suite) TestStatus(t *utesting.T) { 87 conn, err := s.dial() 88 if err != nil { 89 t.Fatalf("could not dial: %v", err) 90 } 91 // get protoHandshake 92 conn.handshake(t) 93 // get status 94 switch msg := conn.statusExchange(t, s.chain, nil).(type) { 95 case *Status: 96 t.Logf("got status message: %s", pretty.Sdump(msg)) 97 default: 98 t.Fatalf("unexpected: %s", pretty.Sdump(msg)) 99 } 100 } 101 102 // TestMaliciousStatus sends a status package with a large total difficulty. 103 func (s *Suite) TestMaliciousStatus(t *utesting.T) { 104 conn, err := s.dial() 105 if err != nil { 106 t.Fatalf("could not dial: %v", err) 107 } 108 // get protoHandshake 109 conn.handshake(t) 110 status := &Status{ 111 ProtocolVersion: uint32(conn.xcbProtocolVersion), 112 NetworkID: s.chain.chainConfig.NetworkID.Uint64(), 113 TD: largeNumber(2), 114 Head: s.chain.blocks[s.chain.Len()-1].Hash(), 115 Genesis: s.chain.blocks[0].Hash(), 116 ForkID: s.chain.ForkID(), 117 } 118 // get status 119 switch msg := conn.statusExchange(t, s.chain, status).(type) { 120 case *Status: 121 t.Logf("%+v\n", msg) 122 default: 123 t.Fatalf("expected status, got: %#v ", msg) 124 } 125 // wait for disconnect 126 switch msg := conn.ReadAndServe(s.chain, timeout).(type) { 127 case *Disconnect: 128 case *Error: 129 return 130 default: 131 t.Fatalf("expected disconnect, got: %s", pretty.Sdump(msg)) 132 } 133 } 134 135 // TestGetBlockHeaders tests whether the given node can respond to 136 // a `GetBlockHeaders` request and that the response is accurate. 137 func (s *Suite) TestGetBlockHeaders(t *utesting.T) { 138 conn, err := s.dial() 139 if err != nil { 140 t.Fatalf("could not dial: %v", err) 141 } 142 143 conn.handshake(t) 144 conn.statusExchange(t, s.chain, nil) 145 146 // get block headers 147 req := &GetBlockHeaders{ 148 Origin: hashOrNumber{ 149 Hash: s.chain.blocks[1].Hash(), 150 }, 151 Amount: 2, 152 Skip: 1, 153 Reverse: false, 154 } 155 156 if err := conn.Write(req); err != nil { 157 t.Fatalf("could not write to connection: %v", err) 158 } 159 160 switch msg := conn.ReadAndServe(s.chain, timeout).(type) { 161 case *BlockHeaders: 162 headers := msg 163 for _, header := range *headers { 164 num := header.Number.Uint64() 165 t.Logf("received header (%d): %s", num, pretty.Sdump(header)) 166 assert.Equal(t, s.chain.blocks[int(num)].Header(), header) 167 } 168 default: 169 t.Fatalf("unexpected: %s", pretty.Sdump(msg)) 170 } 171 } 172 173 // TestGetBlockBodies tests whether the given node can respond to 174 // a `GetBlockBodies` request and that the response is accurate. 175 func (s *Suite) TestGetBlockBodies(t *utesting.T) { 176 conn, err := s.dial() 177 if err != nil { 178 t.Fatalf("could not dial: %v", err) 179 } 180 181 conn.handshake(t) 182 conn.statusExchange(t, s.chain, nil) 183 // create block bodies request 184 req := &GetBlockBodies{s.chain.blocks[4].Hash(), s.chain.blocks[8].Hash()} 185 if err := conn.Write(req); err != nil { 186 t.Fatalf("could not write to connection: %v", err) 187 } 188 189 switch msg := conn.ReadAndServe(s.chain, timeout).(type) { 190 case *BlockBodies: 191 t.Logf("received %d block bodies", len(*msg)) 192 default: 193 t.Fatalf("unexpected: %s", pretty.Sdump(msg)) 194 } 195 } 196 197 // TestBroadcast tests whether a block announcement is correctly 198 // propagated to the given node's peer(s). 199 func (s *Suite) TestBroadcast(t *utesting.T) { 200 sendConn, receiveConn := s.setupConnection(t), s.setupConnection(t) 201 nextBlock := len(s.chain.blocks) 202 blockAnnouncement := &NewBlock{ 203 Block: s.fullChain.blocks[nextBlock], 204 TD: s.fullChain.TD(nextBlock + 1), 205 } 206 s.testAnnounce(t, sendConn, receiveConn, blockAnnouncement) 207 // update test suite chain 208 s.chain.blocks = append(s.chain.blocks, s.fullChain.blocks[nextBlock]) 209 // wait for client to update its chain 210 if err := receiveConn.waitForBlock(s.chain.Head()); err != nil { 211 t.Fatal(err) 212 } 213 } 214 215 // TestMaliciousHandshake tries to send malicious data during the handshake. 216 func (s *Suite) TestMaliciousHandshake(t *utesting.T) { 217 conn, err := s.dial() 218 if err != nil { 219 t.Fatalf("could not dial: %v", err) 220 } 221 // write hello to client 222 pub0 := conn.ourKey.PublicKey()[:] 223 handshakes := []*Hello{ 224 { 225 Version: 5, 226 Caps: []p2p.Cap{ 227 {Name: largeString(2), Version: 64}, 228 }, 229 ID: pub0, 230 }, 231 { 232 Version: 5, 233 Caps: []p2p.Cap{ 234 {Name: "xcb", Version: 64}, 235 {Name: "xcb", Version: 65}, 236 }, 237 ID: append(pub0, byte(0)), 238 }, 239 { 240 Version: 5, 241 Caps: []p2p.Cap{ 242 {Name: "xcb", Version: 64}, 243 {Name: "xcb", Version: 65}, 244 }, 245 ID: append(pub0, pub0...), 246 }, 247 { 248 Version: 5, 249 Caps: []p2p.Cap{ 250 {Name: "xcb", Version: 64}, 251 {Name: "xcb", Version: 65}, 252 }, 253 ID: largeBuffer(2), 254 }, 255 { 256 Version: 5, 257 Caps: []p2p.Cap{ 258 {Name: largeString(2), Version: 64}, 259 }, 260 ID: largeBuffer(2), 261 }, 262 } 263 for i, handshake := range handshakes { 264 t.Logf("Testing malicious handshake %v\n", i) 265 // Init the handshake 266 if err := conn.Write(handshake); err != nil { 267 t.Fatalf("could not write to connection: %v", err) 268 } 269 // check that the peer disconnected 270 timeout := 20 * time.Second 271 // Discard one hello 272 for i := 0; i < 2; i++ { 273 switch msg := conn.ReadAndServe(s.chain, timeout).(type) { 274 case *Disconnect: 275 case *Error: 276 case *Hello: 277 // Hello's are send concurrently, so ignore them 278 continue 279 default: 280 t.Fatalf("unexpected: %s", pretty.Sdump(msg)) 281 } 282 } 283 // Dial for the next round 284 conn, err = s.dial() 285 if err != nil { 286 t.Fatalf("could not dial: %v", err) 287 } 288 } 289 } 290 291 // TestLargeAnnounce tests the announcement mechanism with a large block. 292 func (s *Suite) TestLargeAnnounce(t *utesting.T) { 293 nextBlock := len(s.chain.blocks) 294 blocks := []*NewBlock{ 295 { 296 Block: largeBlock(), 297 TD: s.fullChain.TD(nextBlock + 1), 298 }, 299 { 300 Block: s.fullChain.blocks[nextBlock], 301 TD: largeNumber(2), 302 }, 303 { 304 Block: largeBlock(), 305 TD: largeNumber(2), 306 }, 307 { 308 Block: s.fullChain.blocks[nextBlock], 309 TD: s.fullChain.TD(nextBlock + 1), 310 }, 311 } 312 313 for i, blockAnnouncement := range blocks[0:3] { 314 t.Logf("Testing malicious announcement: %v\n", i) 315 sendConn := s.setupConnection(t) 316 if err := sendConn.Write(blockAnnouncement); err != nil { 317 t.Fatalf("could not write to connection: %v", err) 318 } 319 // Invalid announcement, check that peer disconnected 320 switch msg := sendConn.ReadAndServe(s.chain, timeout).(type) { 321 case *Disconnect: 322 case *Error: 323 break 324 default: 325 t.Fatalf("unexpected: %s wanted disconnect", pretty.Sdump(msg)) 326 } 327 } 328 // Test the last block as a valid block 329 sendConn := s.setupConnection(t) 330 receiveConn := s.setupConnection(t) 331 s.testAnnounce(t, sendConn, receiveConn, blocks[3]) 332 // update test suite chain 333 s.chain.blocks = append(s.chain.blocks, s.fullChain.blocks[nextBlock]) 334 // wait for client to update its chain 335 if err := receiveConn.waitForBlock(s.fullChain.blocks[nextBlock]); err != nil { 336 t.Fatal(err) 337 } 338 } 339 340 func (s *Suite) testAnnounce(t *utesting.T, sendConn, receiveConn *Conn, blockAnnouncement *NewBlock) { 341 // Announce the block. 342 if err := sendConn.Write(blockAnnouncement); err != nil { 343 t.Fatalf("could not write to connection: %v", err) 344 } 345 s.waitAnnounce(t, receiveConn, blockAnnouncement) 346 } 347 348 func (s *Suite) waitAnnounce(t *utesting.T, conn *Conn, blockAnnouncement *NewBlock) { 349 timeout := 20 * time.Second 350 switch msg := conn.ReadAndServe(s.chain, timeout).(type) { 351 case *NewBlock: 352 t.Logf("received NewBlock message: %s", pretty.Sdump(msg.Block)) 353 assert.Equal(t, 354 blockAnnouncement.Block.Header(), msg.Block.Header(), 355 "wrong block header in announcement", 356 ) 357 assert.Equal(t, 358 blockAnnouncement.TD, msg.TD, 359 "wrong TD in announcement", 360 ) 361 case *NewBlockHashes: 362 hashes := *msg 363 t.Logf("received NewBlockHashes message: %s", pretty.Sdump(hashes)) 364 assert.Equal(t, 365 blockAnnouncement.Block.Hash(), hashes[0].Hash, 366 "wrong block hash in announcement", 367 ) 368 default: 369 t.Fatalf("unexpected: %s", pretty.Sdump(msg)) 370 } 371 } 372 373 func (s *Suite) setupConnection(t *utesting.T) *Conn { 374 // create conn 375 sendConn, err := s.dial() 376 if err != nil { 377 t.Fatalf("could not dial: %v", err) 378 } 379 sendConn.handshake(t) 380 sendConn.statusExchange(t, s.chain, nil) 381 return sendConn 382 } 383 384 // dial attempts to dial the given node and perform a handshake, 385 // returning the created Conn if successful. 386 func (s *Suite) dial() (*Conn, error) { 387 var conn Conn 388 389 fd, err := net.Dial("tcp", fmt.Sprintf("%v:%d", s.Dest.IP(), s.Dest.TCP())) 390 if err != nil { 391 return nil, err 392 } 393 conn.Conn = rlpx.NewConn(fd, s.Dest.Pubkey()) 394 395 // do encHandshake 396 conn.ourKey, _ = crypto.GenerateKey(crand.Reader) 397 _, err = conn.Handshake(conn.ourKey) 398 if err != nil { 399 return nil, err 400 } 401 402 return &conn, nil 403 } 404 405 func (s *Suite) TestTransaction(t *utesting.T) { 406 tests := []*types.Transaction{ 407 getNextTxFromChain(t, s), 408 } 409 for i, tx := range tests { 410 t.Logf("Testing tx propagation: %v\n", i) 411 sendSuccessfulTx(t, s, tx) 412 } 413 } 414 415 func (s *Suite) TestMaliciousTx(t *utesting.T) { 416 tests := []*types.Transaction{ 417 getOldTxFromChain(t, s), 418 invalidNonceTx(t, s), 419 hugeAmount(t, s), 420 hugeEnergyPrice(t, s), 421 hugeData(t, s), 422 } 423 for i, tx := range tests { 424 t.Logf("Testing malicious tx propagation: %v\n", i) 425 sendFailingTx(t, s, tx) 426 } 427 }