github.com/dashpay/godash@v0.0.0-20160726055534-e038a21e0e3d/database/cmd/dbtool/insecureimport.go (about) 1 // Copyright (c) 2015-2016 The btcsuite developers 2 // Copyright (c) 2016 The Dash developers 3 // Use of this source code is governed by an ISC 4 // license that can be found in the LICENSE file. 5 6 package main 7 8 import ( 9 "encoding/binary" 10 "fmt" 11 "io" 12 "os" 13 "sync" 14 "time" 15 16 "github.com/dashpay/godash/database" 17 "github.com/dashpay/godash/wire" 18 "github.com/dashpay/godashutil" 19 ) 20 21 // importCmd defines the configuration options for the insecureimport command. 22 type importCmd struct { 23 InFile string `short:"i" long:"infile" description:"File containing the block(s)"` 24 Progress int `short:"p" long:"progress" description:"Show a progress message each time this number of seconds have passed -- Use 0 to disable progress announcements"` 25 } 26 27 var ( 28 // importCfg defines the configuration options for the command. 29 importCfg = importCmd{ 30 InFile: "bootstrap.dat", 31 Progress: 10, 32 } 33 34 // zeroHash is a simply a hash with all zeros. It is defined here to 35 // avoid creating it multiple times. 36 zeroHash = wire.ShaHash{} 37 ) 38 39 // importResults houses the stats and result as an import operation. 40 type importResults struct { 41 blocksProcessed int64 42 blocksImported int64 43 err error 44 } 45 46 // blockImporter houses information about an ongoing import from a block data 47 // file to the block database. 48 type blockImporter struct { 49 db database.DB 50 r io.ReadSeeker 51 processQueue chan []byte 52 doneChan chan bool 53 errChan chan error 54 quit chan struct{} 55 wg sync.WaitGroup 56 blocksProcessed int64 57 blocksImported int64 58 receivedLogBlocks int64 59 receivedLogTx int64 60 lastHeight int64 61 lastBlockTime time.Time 62 lastLogTime time.Time 63 } 64 65 // readBlock reads the next block from the input file. 66 func (bi *blockImporter) readBlock() ([]byte, error) { 67 // The block file format is: 68 // <network> <block length> <serialized block> 69 var net uint32 70 err := binary.Read(bi.r, binary.LittleEndian, &net) 71 if err != nil { 72 if err != io.EOF { 73 return nil, err 74 } 75 76 // No block and no error means there are no more blocks to read. 77 return nil, nil 78 } 79 if net != uint32(activeNetParams.Net) { 80 return nil, fmt.Errorf("network mismatch -- got %x, want %x", 81 net, uint32(activeNetParams.Net)) 82 } 83 84 // Read the block length and ensure it is sane. 85 var blockLen uint32 86 if err := binary.Read(bi.r, binary.LittleEndian, &blockLen); err != nil { 87 return nil, err 88 } 89 if blockLen > wire.MaxBlockPayload { 90 return nil, fmt.Errorf("block payload of %d bytes is larger "+ 91 "than the max allowed %d bytes", blockLen, 92 wire.MaxBlockPayload) 93 } 94 95 serializedBlock := make([]byte, blockLen) 96 if _, err := io.ReadFull(bi.r, serializedBlock); err != nil { 97 return nil, err 98 } 99 100 return serializedBlock, nil 101 } 102 103 // processBlock potentially imports the block into the database. It first 104 // deserializes the raw block while checking for errors. Already known blocks 105 // are skipped and orphan blocks are considered errors. Returns whether the 106 // block was imported along with any potential errors. 107 // 108 // NOTE: This is not a safe import as it does not verify chain rules. 109 func (bi *blockImporter) processBlock(serializedBlock []byte) (bool, error) { 110 // Deserialize the block which includes checks for malformed blocks. 111 block, err := godashutil.NewBlockFromBytes(serializedBlock) 112 if err != nil { 113 return false, err 114 } 115 116 // update progress statistics 117 bi.lastBlockTime = block.MsgBlock().Header.Timestamp 118 bi.receivedLogTx += int64(len(block.MsgBlock().Transactions)) 119 120 // Skip blocks that already exist. 121 var exists bool 122 err = bi.db.View(func(tx database.Tx) error { 123 exists, err = tx.HasBlock(block.Sha()) 124 if err != nil { 125 return err 126 } 127 return nil 128 }) 129 if err != nil { 130 return false, err 131 } 132 if exists { 133 return false, nil 134 } 135 136 // Don't bother trying to process orphans. 137 prevHash := &block.MsgBlock().Header.PrevBlock 138 if !prevHash.IsEqual(&zeroHash) { 139 var exists bool 140 err := bi.db.View(func(tx database.Tx) error { 141 exists, err = tx.HasBlock(prevHash) 142 if err != nil { 143 return err 144 } 145 return nil 146 }) 147 if err != nil { 148 return false, err 149 } 150 if !exists { 151 return false, fmt.Errorf("import file contains block "+ 152 "%v which does not link to the available "+ 153 "block chain", prevHash) 154 } 155 } 156 157 // Put the blocks into the database with no checking of chain rules. 158 err = bi.db.Update(func(tx database.Tx) error { 159 return tx.StoreBlock(block) 160 }) 161 if err != nil { 162 return false, err 163 } 164 165 return true, nil 166 } 167 168 // readHandler is the main handler for reading blocks from the import file. 169 // This allows block processing to take place in parallel with block reads. 170 // It must be run as a goroutine. 171 func (bi *blockImporter) readHandler() { 172 out: 173 for { 174 // Read the next block from the file and if anything goes wrong 175 // notify the status handler with the error and bail. 176 serializedBlock, err := bi.readBlock() 177 if err != nil { 178 bi.errChan <- fmt.Errorf("Error reading from input "+ 179 "file: %v", err.Error()) 180 break out 181 } 182 183 // A nil block with no error means we're done. 184 if serializedBlock == nil { 185 break out 186 } 187 188 // Send the block or quit if we've been signalled to exit by 189 // the status handler due to an error elsewhere. 190 select { 191 case bi.processQueue <- serializedBlock: 192 case <-bi.quit: 193 break out 194 } 195 } 196 197 // Close the processing channel to signal no more blocks are coming. 198 close(bi.processQueue) 199 bi.wg.Done() 200 } 201 202 // logProgress logs block progress as an information message. In order to 203 // prevent spam, it limits logging to one message every importCfg.Progress 204 // seconds with duration and totals included. 205 func (bi *blockImporter) logProgress() { 206 bi.receivedLogBlocks++ 207 208 now := time.Now() 209 duration := now.Sub(bi.lastLogTime) 210 if duration < time.Second*time.Duration(importCfg.Progress) { 211 return 212 } 213 214 // Truncate the duration to 10s of milliseconds. 215 durationMillis := int64(duration / time.Millisecond) 216 tDuration := 10 * time.Millisecond * time.Duration(durationMillis/10) 217 218 // Log information about new block height. 219 blockStr := "blocks" 220 if bi.receivedLogBlocks == 1 { 221 blockStr = "block" 222 } 223 txStr := "transactions" 224 if bi.receivedLogTx == 1 { 225 txStr = "transaction" 226 } 227 log.Infof("Processed %d %s in the last %s (%d %s, height %d, %s)", 228 bi.receivedLogBlocks, blockStr, tDuration, bi.receivedLogTx, 229 txStr, bi.lastHeight, bi.lastBlockTime) 230 231 bi.receivedLogBlocks = 0 232 bi.receivedLogTx = 0 233 bi.lastLogTime = now 234 } 235 236 // processHandler is the main handler for processing blocks. This allows block 237 // processing to take place in parallel with block reads from the import file. 238 // It must be run as a goroutine. 239 func (bi *blockImporter) processHandler() { 240 out: 241 for { 242 select { 243 case serializedBlock, ok := <-bi.processQueue: 244 // We're done when the channel is closed. 245 if !ok { 246 break out 247 } 248 249 bi.blocksProcessed++ 250 bi.lastHeight++ 251 imported, err := bi.processBlock(serializedBlock) 252 if err != nil { 253 bi.errChan <- err 254 break out 255 } 256 257 if imported { 258 bi.blocksImported++ 259 } 260 261 bi.logProgress() 262 263 case <-bi.quit: 264 break out 265 } 266 } 267 bi.wg.Done() 268 } 269 270 // statusHandler waits for updates from the import operation and notifies 271 // the passed doneChan with the results of the import. It also causes all 272 // goroutines to exit if an error is reported from any of them. 273 func (bi *blockImporter) statusHandler(resultsChan chan *importResults) { 274 select { 275 // An error from either of the goroutines means we're done so signal 276 // caller with the error and signal all goroutines to quit. 277 case err := <-bi.errChan: 278 resultsChan <- &importResults{ 279 blocksProcessed: bi.blocksProcessed, 280 blocksImported: bi.blocksImported, 281 err: err, 282 } 283 close(bi.quit) 284 285 // The import finished normally. 286 case <-bi.doneChan: 287 resultsChan <- &importResults{ 288 blocksProcessed: bi.blocksProcessed, 289 blocksImported: bi.blocksImported, 290 err: nil, 291 } 292 } 293 } 294 295 // Import is the core function which handles importing the blocks from the file 296 // associated with the block importer to the database. It returns a channel 297 // on which the results will be returned when the operation has completed. 298 func (bi *blockImporter) Import() chan *importResults { 299 // Start up the read and process handling goroutines. This setup allows 300 // blocks to be read from disk in parallel while being processed. 301 bi.wg.Add(2) 302 go bi.readHandler() 303 go bi.processHandler() 304 305 // Wait for the import to finish in a separate goroutine and signal 306 // the status handler when done. 307 go func() { 308 bi.wg.Wait() 309 bi.doneChan <- true 310 }() 311 312 // Start the status handler and return the result channel that it will 313 // send the results on when the import is done. 314 resultChan := make(chan *importResults) 315 go bi.statusHandler(resultChan) 316 return resultChan 317 } 318 319 // newBlockImporter returns a new importer for the provided file reader seeker 320 // and database. 321 func newBlockImporter(db database.DB, r io.ReadSeeker) *blockImporter { 322 return &blockImporter{ 323 db: db, 324 r: r, 325 processQueue: make(chan []byte, 2), 326 doneChan: make(chan bool), 327 errChan: make(chan error), 328 quit: make(chan struct{}), 329 lastLogTime: time.Now(), 330 } 331 } 332 333 // Execute is the main entry point for the command. It's invoked by the parser. 334 func (cmd *importCmd) Execute(args []string) error { 335 // Setup the global config options and ensure they are valid. 336 if err := setupGlobalConfig(); err != nil { 337 return err 338 } 339 340 // Ensure the specified block file exists. 341 if !fileExists(cmd.InFile) { 342 str := "The specified block file [%v] does not exist" 343 return fmt.Errorf(str, cmd.InFile) 344 } 345 346 // Load the block database. 347 db, err := loadBlockDB() 348 if err != nil { 349 return err 350 } 351 defer db.Close() 352 353 // Ensure the database is sync'd and closed on Ctrl+C. 354 addInterruptHandler(func() { 355 log.Infof("Gracefully shutting down the database...") 356 db.Close() 357 }) 358 359 fi, err := os.Open(importCfg.InFile) 360 if err != nil { 361 return err 362 } 363 defer fi.Close() 364 365 // Create a block importer for the database and input file and start it. 366 // The results channel returned from start will contain an error if 367 // anything went wrong. 368 importer := newBlockImporter(db, fi) 369 370 // Perform the import asynchronously and signal the main goroutine when 371 // done. This allows blocks to be processed and read in parallel. The 372 // results channel returned from Import contains the statistics about 373 // the import including an error if something went wrong. This is done 374 // in a separate goroutine rather than waiting directly so the main 375 // goroutine can be signaled for shutdown by either completion, error, 376 // or from the main interrupt handler. This is necessary since the main 377 // goroutine must be kept running long enough for the interrupt handler 378 // goroutine to finish. 379 go func() { 380 log.Info("Starting import") 381 resultsChan := importer.Import() 382 results := <-resultsChan 383 if results.err != nil { 384 dbErr, ok := results.err.(database.Error) 385 if !ok || ok && dbErr.ErrorCode != database.ErrDbNotOpen { 386 shutdownChannel <- results.err 387 return 388 } 389 } 390 391 log.Infof("Processed a total of %d blocks (%d imported, %d "+ 392 "already known)", results.blocksProcessed, 393 results.blocksImported, 394 results.blocksProcessed-results.blocksImported) 395 shutdownChannel <- nil 396 }() 397 398 // Wait for shutdown signal from either a normal completion or from the 399 // interrupt handler. 400 err = <-shutdownChannel 401 return err 402 }