github.com/johnathanhowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/consensus/synchronize.go (about) 1 package consensus 2 3 import ( 4 "errors" 5 "net" 6 "time" 7 8 "github.com/NebulousLabs/Sia/build" 9 "github.com/NebulousLabs/Sia/crypto" 10 "github.com/NebulousLabs/Sia/encoding" 11 "github.com/NebulousLabs/Sia/modules" 12 "github.com/NebulousLabs/Sia/types" 13 14 "github.com/NebulousLabs/bolt" 15 ) 16 17 const ( 18 // minNumOutbound is the minimum number of outbound peers required before ibd 19 // is confident we are synced. 20 minNumOutbound = 5 21 ) 22 23 var ( 24 // MaxCatchUpBlocks is the maxiumum number of blocks that can be given to 25 // the consensus set in a single iteration during the initial blockchain 26 // download. 27 MaxCatchUpBlocks = func() types.BlockHeight { 28 switch build.Release { 29 case "dev": 30 return 50 31 case "standard": 32 return 10 33 case "testing": 34 return 3 35 default: 36 panic("unrecognized build.Release") 37 } 38 }() 39 // sendBlocksTimeout is the timeout for the SendBlocks RPC. 40 sendBlocksTimeout = func() time.Duration { 41 switch build.Release { 42 case "dev": 43 return 40 * time.Second 44 case "standard": 45 return 5 * time.Minute 46 case "testing": 47 return 5 * time.Second 48 default: 49 panic("unrecognized build.Release") 50 } 51 }() 52 // minIBDWaitTime is the time threadedInitialBlockchainDownload waits before 53 // exiting if there are >= 1 and <= minNumOutbound peers synced. This timeout 54 // will primarily affect miners who have multiple nodes daisy chained off each 55 // other. Those nodes will likely have to wait minIBDWaitTime on every startup 56 // before IBD is done. 57 minIBDWaitTime = func() time.Duration { 58 switch build.Release { 59 case "dev": 60 return 80 * time.Second 61 case "standard": 62 return 90 * time.Minute 63 case "testing": 64 return 10 * time.Second 65 default: 66 panic("unrecognized build.Release") 67 } 68 }() 69 // ibdLoopDelay is the time that threadedInitialBlockchainDownload waits 70 // between attempts to synchronize with the network if the last attempt 71 // failed. 72 ibdLoopDelay = func() time.Duration { 73 switch build.Release { 74 case "dev": 75 return 1 * time.Second 76 case "standard": 77 return 10 * time.Second 78 case "testing": 79 return 100 * time.Millisecond 80 default: 81 panic("unrecognized build.Release") 82 } 83 }() 84 85 errSendBlocksStalled = errors.New("SendBlocks RPC timed and never received any blocks") 86 ) 87 88 // blockHistory returns up to 32 block ids, starting with recent blocks and 89 // then proving exponentially increasingly less recent blocks. The genesis 90 // block is always included as the last block. This block history can be used 91 // to find a common parent that is reasonably recent, usually the most recent 92 // common parent is found, but always a common parent within a factor of 2 is 93 // found. 94 func blockHistory(tx *bolt.Tx) (blockIDs [32]types.BlockID) { 95 height := blockHeight(tx) 96 step := types.BlockHeight(1) 97 // The final step is to include the genesis block, which is why the final 98 // element is skipped during iteration. 99 for i := 0; i < 31; i++ { 100 // Include the next block. 101 blockID, err := getPath(tx, height) 102 if build.DEBUG && err != nil { 103 panic(err) 104 } 105 blockIDs[i] = blockID 106 107 // Determine the height of the next block to include and then increase 108 // the step size. The height must be decreased first to prevent 109 // underflow. 110 // 111 // `i >= 9` means that the first 10 blocks will be included, and then 112 // skipping will start. 113 if i >= 9 { 114 step *= 2 115 } 116 if height <= step { 117 break 118 } 119 height -= step 120 } 121 // Include the genesis block as the last element 122 blockID, err := getPath(tx, 0) 123 if build.DEBUG && err != nil { 124 panic(err) 125 } 126 blockIDs[31] = blockID 127 return blockIDs 128 } 129 130 // threadedReceiveBlocks is the calling end of the SendBlocks RPC. 131 func (cs *ConsensusSet) threadedReceiveBlocks(conn modules.PeerConn) (returnErr error) { 132 // Set a deadline after which SendBlocks will timeout. During IBD, esepcially, 133 // SendBlocks will timeout. This is by design so that IBD switches peers to 134 // prevent any one peer from stalling IBD. 135 err := conn.SetDeadline(time.Now().Add(sendBlocksTimeout)) 136 // Ignore errors returned by SetDeadline if the conn is a pipe in testing. 137 // Pipes do not support Set{,Read,Write}Deadline and should only be used in 138 // testing. 139 if opErr, ok := err.(*net.OpError); ok && opErr.Op == "set" && opErr.Net == "pipe" && build.Release == "testing" { 140 err = nil 141 } 142 if err != nil { 143 return err 144 } 145 stalled := true 146 defer func() { 147 // TODO: Timeout errors returned by muxado do not conform to the net.Error 148 // interface and therefore we cannot check if the error is a timeout using 149 // the Timeout() method. Once muxado issue #14 is resolved change the below 150 // condition to: 151 // if netErr, ok := returnErr.(net.Error); ok && netErr.Timeout() && stalled { ... } 152 if stalled && returnErr != nil && (returnErr.Error() == "Read timeout" || returnErr.Error() == "Write timeout") { 153 returnErr = errSendBlocksStalled 154 } 155 }() 156 157 // Get blockIDs to send. 158 var history [32]types.BlockID 159 cs.mu.RLock() 160 err = cs.db.View(func(tx *bolt.Tx) error { 161 history = blockHistory(tx) 162 return nil 163 }) 164 cs.mu.RUnlock() 165 if err != nil { 166 return err 167 } 168 169 // Send the block ids. 170 if err := encoding.WriteObject(conn, history); err != nil { 171 return err 172 } 173 174 // Broadcast the last block accepted. This functionality is in a defer to 175 // ensure that a block is always broadcast if any blocks are accepted. This 176 // is to stop an attacker from preventing block broadcasts. 177 chainExtended := false 178 defer func() { 179 if chainExtended && cs.Synced() { 180 // The last block received will be the current block since 181 // managedAcceptBlock only returns nil if a block extends the longest chain. 182 currentBlock := cs.CurrentBlock() 183 // COMPATv0.5.1 - broadcast the block to all peers <= v0.5.1 and block header to all peers > v0.5.1 184 var relayBlockPeers, relayHeaderPeers []modules.Peer 185 for _, p := range cs.gateway.Peers() { 186 if build.VersionCmp(p.Version, "0.5.1") <= 0 { 187 relayBlockPeers = append(relayBlockPeers, p) 188 } else { 189 relayHeaderPeers = append(relayHeaderPeers, p) 190 } 191 } 192 go cs.gateway.Broadcast("RelayBlock", currentBlock, relayBlockPeers) 193 go cs.gateway.Broadcast("RelayHeader", currentBlock.Header(), relayHeaderPeers) 194 } 195 }() 196 197 // Read blocks off of the wire and add them to the consensus set until 198 // there are no more blocks available. 199 moreAvailable := true 200 for moreAvailable { 201 // Read a slice of blocks from the wire. 202 var newBlocks []types.Block 203 if err := encoding.ReadObject(conn, &newBlocks, uint64(MaxCatchUpBlocks)*types.BlockSizeLimit); err != nil { 204 return err 205 } 206 if err := encoding.ReadObject(conn, &moreAvailable, 1); err != nil { 207 return err 208 } 209 210 // Integrate the blocks into the consensus set. 211 for _, block := range newBlocks { 212 stalled = false 213 // Call managedAcceptBlock instead of AcceptBlock so as not to broadcast 214 // every block. 215 acceptErr := cs.managedAcceptBlock(block) 216 // Set a flag to indicate that we should broadcast the last block received. 217 if acceptErr == nil { 218 chainExtended = true 219 } 220 // ErrNonExtendingBlock must be ignored until headers-first block 221 // sharing is implemented, block already in database should also be 222 // ignored. 223 if acceptErr == modules.ErrNonExtendingBlock || acceptErr == modules.ErrBlockKnown { 224 acceptErr = nil 225 } 226 if acceptErr != nil { 227 return acceptErr 228 } 229 } 230 } 231 return nil 232 } 233 234 // rpcSendBlocks is the receiving end of the SendBlocks RPC. It returns a 235 // sequential set of blocks based on the 32 input block IDs. The most recent 236 // known ID is used as the starting point, and up to 'MaxCatchUpBlocks' from 237 // that BlockHeight onwards are returned. It also sends a boolean indicating 238 // whether more blocks are available. 239 func (cs *ConsensusSet) rpcSendBlocks(conn modules.PeerConn) error { 240 // Read a list of blocks known to the requester and find the most recent 241 // block from the current path. 242 var knownBlocks [32]types.BlockID 243 err := encoding.ReadObject(conn, &knownBlocks, 32*crypto.HashSize) 244 if err != nil { 245 return err 246 } 247 248 // Find the most recent block from knownBlocks in the current path. 249 found := false 250 var start types.BlockHeight 251 var csHeight types.BlockHeight 252 cs.mu.RLock() 253 err = cs.db.View(func(tx *bolt.Tx) error { 254 csHeight = blockHeight(tx) 255 for _, id := range knownBlocks { 256 pb, err := getBlockMap(tx, id) 257 if err != nil { 258 continue 259 } 260 pathID, err := getPath(tx, pb.Height) 261 if err != nil { 262 continue 263 } 264 if pathID != pb.Block.ID() { 265 continue 266 } 267 if pb.Height == csHeight { 268 break 269 } 270 found = true 271 // Start from the child of the common block. 272 start = pb.Height + 1 273 break 274 } 275 return nil 276 }) 277 cs.mu.RUnlock() 278 if err != nil { 279 return err 280 } 281 282 // If no matching blocks are found, or if the caller has all known blocks, 283 // don't send any blocks. 284 if !found { 285 // Send 0 blocks. 286 err = encoding.WriteObject(conn, []types.Block{}) 287 if err != nil { 288 return err 289 } 290 // Indicate that no more blocks are available. 291 return encoding.WriteObject(conn, false) 292 } 293 294 // Send the caller all of the blocks that they are missing. 295 moreAvailable := true 296 for moreAvailable { 297 // Get the set of blocks to send. 298 var blocks []types.Block 299 cs.mu.RLock() 300 err = cs.db.View(func(tx *bolt.Tx) error { 301 height := blockHeight(tx) 302 for i := start; i <= height && i < start+MaxCatchUpBlocks; i++ { 303 id, err := getPath(tx, i) 304 if build.DEBUG && err != nil { 305 panic(err) 306 } 307 pb, err := getBlockMap(tx, id) 308 if build.DEBUG && err != nil { 309 panic(err) 310 } 311 blocks = append(blocks, pb.Block) 312 } 313 moreAvailable = start+MaxCatchUpBlocks <= height 314 start += MaxCatchUpBlocks 315 return nil 316 }) 317 cs.mu.RUnlock() 318 if err != nil { 319 return err 320 } 321 322 // Send a set of blocks to the caller + a flag indicating whether more 323 // are available. 324 if err = encoding.WriteObject(conn, blocks); err != nil { 325 return err 326 } 327 if err = encoding.WriteObject(conn, moreAvailable); err != nil { 328 return err 329 } 330 } 331 332 return nil 333 } 334 335 // rpcRelayBlock is an RPC that accepts a block from a peer. 336 // COMPATv0.5.1 337 func (cs *ConsensusSet) rpcRelayBlock(conn modules.PeerConn) error { 338 // Decode the block from the connection. 339 var b types.Block 340 err := encoding.ReadObject(conn, &b, types.BlockSizeLimit) 341 if err != nil { 342 return err 343 } 344 345 // Submit the block to the consensus set and broadcast it. 346 err = cs.AcceptBlock(b) 347 if err == errOrphan { 348 // If the block is an orphan, try to find the parents. The block 349 // received from the peer is discarded and will be downloaded again if 350 // the parent is found. 351 go func() { 352 err := cs.gateway.RPC(modules.NetAddress(conn.RemoteAddr().String()), "SendBlocks", cs.threadedReceiveBlocks) 353 if err != nil { 354 cs.log.Debugln("WARN: failed to get parents of orphan block:", err) 355 } 356 }() 357 } 358 if err != nil { 359 return err 360 } 361 return nil 362 } 363 364 // rpcRelayHeader is an RPC that accepts a block header from a peer. 365 func (cs *ConsensusSet) rpcRelayHeader(conn modules.PeerConn) error { 366 // Decode the block header from the connection. 367 var h types.BlockHeader 368 err := encoding.ReadObject(conn, &h, types.BlockHeaderSize) 369 if err != nil { 370 return err 371 } 372 373 // Start verification inside of a bolt View tx. 374 cs.mu.RLock() 375 err = cs.db.View(func(tx *bolt.Tx) error { 376 // Do some relatively inexpensive checks to validate the header 377 return cs.validateHeader(boltTxWrapper{tx}, h) 378 }) 379 cs.mu.RUnlock() 380 if err == errOrphan { 381 // If the header is an orphan, try to find the parents. 382 go func() { 383 err := cs.gateway.RPC(modules.NetAddress(conn.RemoteAddr().String()), "SendBlocks", cs.threadedReceiveBlocks) 384 if err != nil { 385 cs.log.Debugln("WARN: failed to get parents of orphan header:", err) 386 } 387 }() 388 return nil 389 } else if err != nil { 390 return err 391 } 392 // If the header is valid and extends the heaviest chain, fetch, accept it, 393 // and broadcast it. 394 go func() { 395 err := cs.gateway.RPC(modules.NetAddress(conn.RemoteAddr().String()), "SendBlk", cs.threadedReceiveBlock(h.ID())) 396 if err != nil { 397 cs.log.Debugln("WARN: failed to get header's corresponding block:", err) 398 } 399 }() 400 return nil 401 } 402 403 // rpcSendBlk is an RPC that sends the requested block to the requesting peer. 404 func (cs *ConsensusSet) rpcSendBlk(conn modules.PeerConn) error { 405 // Decode the block id from the conneciton. 406 var id types.BlockID 407 err := encoding.ReadObject(conn, &id, crypto.HashSize) 408 if err != nil { 409 return err 410 } 411 // Lookup the corresponding block. 412 var b types.Block 413 cs.mu.RLock() 414 err = cs.db.View(func(tx *bolt.Tx) error { 415 pb, err := getBlockMap(tx, id) 416 if err != nil { 417 return err 418 } 419 b = pb.Block 420 return nil 421 }) 422 cs.mu.RUnlock() 423 if err != nil { 424 return err 425 } 426 // Encode and send the block to the caller. 427 err = encoding.WriteObject(conn, b) 428 if err != nil { 429 return err 430 } 431 return nil 432 } 433 434 // threadedReceiveBlock takes a block id and returns an RPCFunc that requests 435 // that block and then calls AcceptBlock on it. The returned function should be 436 // used as the calling end of the SendBlk RPC. Note that although the function 437 // itself does not do any locking, it is still prefixed with "threaded" because 438 // the function it returns calls the exported method AcceptBlock. 439 func (cs *ConsensusSet) threadedReceiveBlock(id types.BlockID) modules.RPCFunc { 440 managedFN := func(conn modules.PeerConn) error { 441 if err := encoding.WriteObject(conn, id); err != nil { 442 return err 443 } 444 var block types.Block 445 if err := encoding.ReadObject(conn, &block, types.BlockSizeLimit); err != nil { 446 return err 447 } 448 if err := cs.AcceptBlock(block); err != nil { 449 return err 450 } 451 return nil 452 } 453 return managedFN 454 } 455 456 // threadedInitialBlockchainDownload performs the IBD on outbound peers. Blocks 457 // are downloaded from one peer at a time in 5 minute intervals, so as to 458 // prevent any one peer from significantly slowing down IBD. 459 // 460 // NOTE: IBD will succeed right now when each peer has a different blockchain. 461 // The height and the block id of the remote peers' current blocks are not 462 // checked to be the same. This can cause issues if you are connected to 463 // outbound peers <= v0.5.1 that are stalled in IBD. 464 func (cs *ConsensusSet) threadedInitialBlockchainDownload() { 465 // Set the deadline 10 minutes in the future. After this deadline, we will say 466 // IBD is done as long as there is at least one outbound peer synced. 467 deadline := time.Now().Add(minIBDWaitTime) 468 numOutboundSynced := 0 469 for { 470 numOutboundSynced = 0 471 for _, p := range cs.gateway.Peers() { 472 // We only sync on outbound peers at first to make IBD less susceptible to 473 // fast-mining and other attacks, as outbound peers are more difficult to 474 // manipulate. 475 if p.Inbound { 476 continue 477 } 478 479 err := cs.gateway.RPC(p.NetAddress, "SendBlocks", cs.threadedReceiveBlocks) 480 if err == nil { 481 numOutboundSynced++ 482 continue 483 } 484 // TODO: Timeout errors returned by muxado do not conform to the net.Error 485 // interface and therefore we cannot check if the error is a timeout using 486 // the Timeout() method. Once muxado issue #14 is resolved change the below 487 // condition to: 488 // if netErr, ok := returnErr.(net.Error); !ok || !netErr.Timeout() { ... } 489 if err.Error() != "Read timeout" && err.Error() != "Write timeout" { 490 cs.log.Printf("WARN: disconnecting from peer %v because IBD failed: %v", p.NetAddress, err) 491 // Disconnect if there is an unexpected error (not a timeout). This 492 // includes errSendBlocksStalled. 493 // 494 // We disconnect so that these peers are removed from gateway.Peers() and 495 // do not prevent us from marking ourselves as fully synced. 496 err := cs.gateway.Disconnect(p.NetAddress) 497 if err != nil { 498 cs.log.Printf("WARN: disconnecting from peer %v failed: %v", p.NetAddress, err) 499 } 500 } 501 } 502 503 // If we have minNumOutbound peers synced, we are done. Otherwise, don't say 504 // we are synced until we've been doing ibd for 10 minutes and we are synced 505 // with at least one peer. 506 if numOutboundSynced >= minNumOutbound || (numOutboundSynced > 0 && time.Now().After(deadline)) { 507 break 508 } else { 509 // Sleep so we don't hammer the network with SendBlock requests. 510 time.Sleep(ibdLoopDelay) 511 } 512 } 513 514 cs.log.Printf("INFO: IBD done, synced with %v peers", numOutboundSynced) 515 } 516 517 // Synced returns true if the consensus set is synced with the network. 518 func (cs *ConsensusSet) Synced() bool { 519 cs.mu.RLock() 520 defer cs.mu.RUnlock() 521 return cs.synced 522 }