github.com/deso-protocol/core@v1.2.9/lib/block_producer.go (about) 1 package lib 2 3 import ( 4 "encoding/hex" 5 "fmt" 6 "github.com/btcsuite/btcd/wire" 7 "github.com/tyler-smith/go-bip39" 8 "math" 9 "sync" 10 "time" 11 12 "github.com/davecgh/go-spew/spew" 13 "github.com/deso-protocol/go-deadlock" 14 15 "github.com/btcsuite/btcd/btcec" 16 "github.com/golang/glog" 17 "github.com/pkg/errors" 18 ) 19 20 type DeSoBlockProducer struct { 21 // The minimum amount of time we wait before trying to produce a new block 22 // template. If this value is set low enough then we will produce a block template 23 // continuously. 24 minBlockUpdateIntervalSeconds uint64 25 // The number of templates to cache so that we can accept headers for blocks 26 // that are a bit stale. 27 maxBlockTemplatesToCache uint64 28 // A private key that is used to sign blocks produced by this block producer. Only 29 // set if a blockProducerSeed is provided when constructing the BlockProducer. 30 blockProducerPrivateKey *btcec.PrivateKey 31 // A lock on the block templates produced to avoid concurrency issues. 32 mtxRecentBlockTemplatesProduced deadlock.RWMutex 33 // The most recent N blocks that we've produced indexed by their hash. 34 // Keeping this list allows us to accept a valid header from a miner without 35 // requiring them to download/send the whole block. 36 recentBlockTemplatesProduced map[BlockHash]*MsgDeSoBlock 37 latestBlockTemplateHash *BlockHash 38 currentDifficultyTarget *BlockHash 39 40 latestBlockTemplateStats *BlockTemplateStats 41 42 mempool *DeSoMempool 43 chain *Blockchain 44 params *DeSoParams 45 46 producerWaitGroup sync.WaitGroup 47 stopProducerChannel chan struct{} 48 49 postgres *Postgres 50 } 51 52 type BlockTemplateStats struct { 53 // The number of txns in the block template. 54 TxnCount uint32 55 // The final txn we attempted to put in the block. 56 FailingTxnHash string 57 // The reason why the final txn failed to add. 58 FailingTxnError string 59 // The "Added" time on a transaction changes every time a block is mined so we record 60 // the first time added val we are aware of for a specific txn hash here. 61 FailingTxnOriginalTimeAdded time.Time 62 // The time since the failing txn was added to the mempool. 63 FailingTxnMinutesSinceAdded float64 64 } 65 66 func NewDeSoBlockProducer( 67 minBlockUpdateIntervalSeconds uint64, 68 maxBlockTemplatesToCache uint64, 69 blockProducerSeed string, 70 mempool *DeSoMempool, 71 chain *Blockchain, 72 params *DeSoParams, 73 postgres *Postgres, 74 ) (*DeSoBlockProducer, error) { 75 76 var privKey *btcec.PrivateKey 77 if blockProducerSeed != "" { 78 seedBytes, err := bip39.NewSeedWithErrorChecking(blockProducerSeed, "") 79 if err != nil { 80 return nil, fmt.Errorf("NewDeSoBlockProducer: Error converting mnemonic: %+v", err) 81 } 82 83 _, privKey, _, err = ComputeKeysFromSeed(seedBytes, 0, params) 84 if err != nil { 85 return nil, fmt.Errorf( 86 "NewDeSoBlockProducer: Error computing keys from seed: %+v", err) 87 } 88 } 89 90 return &DeSoBlockProducer{ 91 minBlockUpdateIntervalSeconds: minBlockUpdateIntervalSeconds, 92 maxBlockTemplatesToCache: maxBlockTemplatesToCache, 93 blockProducerPrivateKey: privKey, 94 recentBlockTemplatesProduced: make(map[BlockHash]*MsgDeSoBlock), 95 96 mempool: mempool, 97 chain: chain, 98 params: params, 99 stopProducerChannel: make(chan struct{}), 100 postgres: postgres, 101 }, nil 102 } 103 104 func (bbp *DeSoBlockProducer) GetLatestBlockTemplateStats() *BlockTemplateStats { 105 return bbp.latestBlockTemplateStats 106 } 107 108 func (desoBlockProducer *DeSoBlockProducer) _updateBlockTimestamp(blk *MsgDeSoBlock, lastNode *BlockNode) { 109 // Set the block's timestamp. If the timesource's time happens to be before 110 // the timestamp set in the last block then set the time based on the last 111 // block's timestamp instead. We do this because consensus rules require a 112 // monotonically increasing timestamp. 113 blockTstamp := uint32(desoBlockProducer.chain.timeSource.AdjustedTime().Unix()) 114 if blockTstamp <= uint32(lastNode.Header.TstampSecs) { 115 blockTstamp = uint32(lastNode.Header.TstampSecs) + 1 116 } 117 blk.Header.TstampSecs = uint64(blockTstamp) 118 } 119 120 func (desoBlockProducer *DeSoBlockProducer) _getBlockTemplate(publicKey []byte) ( 121 _blk *MsgDeSoBlock, _diffTarget *BlockHash, _lastNode *BlockNode, _err error) { 122 123 // Get the current tip of the best block chain. Note that using the tip of the 124 // best block chain as opposed to the best header chain means we'll be mining 125 // stale blocks until we're fully synced. This isn't ideal, but is currently 126 // preferred to mining atop the best header chain because the latter currently results 127 // in the blocks being rejected as unconnectedTxns before the block tip is in-sync. 128 lastNode := desoBlockProducer.chain.blockTip() 129 130 // Compute the public key to contribute the reward to. 131 rewardPk, err := btcec.ParsePubKey(publicKey, btcec.S256()) 132 if err != nil { 133 return nil, nil, nil, errors.Wrapf(err, "DeSoBlockProducer._getBlockTemplate: ") 134 } 135 136 // Construct the next block. 137 blockRewardOutput := &DeSoOutput{} 138 if rewardPk != nil { 139 // This is to account for a really weird edge case where somebody stops the BlockProducer 140 // in the middle of us getting a block. 141 blockRewardOutput.PublicKey = rewardPk.SerializeCompressed() 142 } 143 // Set the block reward output initially to the maximum value for a uint64. 144 // This ensures it will take the maximum amount of space in the block when 145 // encoded as a varint so our size estimates won't get messed up. 146 blockRewardOutput.AmountNanos = math.MaxUint64 147 148 // Block reward txn only needs a single output. No need to specify spending 149 // pk or sigs. 150 blockRewardTxn := NewMessage(MsgTypeTxn).(*MsgDeSoTxn) 151 blockRewardTxn.TxOutputs = append(blockRewardTxn.TxOutputs, blockRewardOutput) 152 // Set the ExtraData to zero. This gives miners something they can 153 // twiddle if they run out of space on their actual nonce. 154 blockRewardTxn.TxnMeta = &BlockRewardMetadataa{ 155 ExtraData: UintToBuf(0), 156 } 157 158 // Create the block and add the BlockReward txn to it. 159 blockRet := NewMessage(MsgTypeBlock).(*MsgDeSoBlock) 160 blockRet.Txns = append(blockRet.Txns, blockRewardTxn) 161 // The version may be swapped out in a call to GetBlockTemplate in order to remain 162 // backwards-compatible with existing miners that use an older version. 163 blockRet.Header.Version = CurrentHeaderVersion 164 blockRet.Header.Height = uint64(lastNode.Height + 1) 165 blockRet.Header.PrevBlockHash = lastNode.Hash 166 desoBlockProducer._updateBlockTimestamp(blockRet, lastNode) 167 // Start the nonce at zero. This is OK because we'll set a random ExtraData for the 168 // miner later. 169 blockRet.Header.Nonce = 0 170 171 // Only add transactions to the block if our chain is done syncing. 172 if desoBlockProducer.chain.chainState() != SyncStateSyncingHeaders && 173 desoBlockProducer.chain.chainState() != SyncStateNeedBlocksss { 174 175 // Fetch a bunch of mempool transactions to add. 176 txnsOrderedByTimeAdded, _, err := desoBlockProducer.mempool.GetTransactionsOrderedByTimeAdded() 177 if err != nil { 178 return nil, nil, nil, errors.Wrapf(err, "DeSoBlockProducer._getBlockTemplate: Problem getting mempool transactions: ") 179 } 180 181 // Now keep 182 // adding transactions to the block until the block is full. 183 // 184 // Compute the size of the header and then add the number of bytes used to encode 185 // the number of transactions in the block. Note that headers have a fixed size. 186 // 187 // TODO: The code below is lazily-written and could be optimized to squeeze a few 188 // more bytes into each block. 189 // 190 // Track the total size of the block as we go. Since the number of transactions 191 // encoded in the block can become larger as we add transactions to it, add the 192 // maximum size for this field to the current size to ensure we don't overfill 193 // the block. 194 blockBytes, err := blockRet.ToBytes(false) 195 if err != nil { 196 return nil, nil, nil, errors.Wrapf(err, "DeSoBlockProducer._getBlockTemplate: Problem serializing block: ") 197 } 198 currentBlockSize := uint64(len(blockBytes) + MaxVarintLen64) 199 200 // Create a new view object. 201 utxoView, err := NewUtxoView(desoBlockProducer.chain.db, desoBlockProducer.params, desoBlockProducer.postgres) 202 if err != nil { 203 return nil, nil, nil, errors.Wrapf(err, 204 "DeSoBlockProducer._getBlockTemplate: Error generating checker UtxoView: ") 205 } 206 207 txnsAddedToBlock := make(map[BlockHash]bool) 208 for ii, mempoolTx := range txnsOrderedByTimeAdded { 209 // If we hit a transaction that's too big to fit into a block then we're done. 210 if mempoolTx.TxSizeBytes+currentBlockSize > desoBlockProducer.params.MinerMaxBlockSizeBytes { 211 break 212 } 213 214 // Try to apply the transaction to the view with the strictest possible checks. 215 _, _, _, _, err := utxoView._connectTransaction( 216 mempoolTx.Tx, mempoolTx.Hash, int64(mempoolTx.TxSizeBytes), uint32(blockRet.Header.Height), true, 217 false /*ignoreUtxos*/) 218 if err != nil { 219 // If we fail to apply this transaction then we're done. Don't mine any of the 220 // other transactions since they could be dependent on this one. 221 txnErrorString := fmt.Sprintf( 222 "DeSoBlockProducer._getBlockTemplate: Stopping at txn %v because it's not ready yet: %v", ii, err) 223 glog.Infof(txnErrorString) 224 if mempoolTx.Tx.TxnMeta.GetTxnType() == TxnTypeBitcoinExchange { 225 // Print the Bitcoin block hash when we break out due to this. 226 btcErrorString := fmt.Sprintf("A bad BitcoinExchange transaction may be holding "+ 227 "up block production: %v", 228 mempoolTx.Tx.TxnMeta.(*BitcoinExchangeMetadata).BitcoinTransaction.TxHash()) 229 glog.Infof(btcErrorString) 230 txnErrorString += (" " + btcErrorString) 231 scs := spew.ConfigState{DisableMethods: true, Indent: " "} 232 glog.V(1).Infof("Spewing Bitcoin txn: %v", scs.Sdump(mempoolTx.Tx)) 233 } 234 235 // Update the block template stats for the admin dashboard. 236 failingTxnHash := mempoolTx.Hash.String() 237 failingTxnOriginalTimeAdded := mempoolTx.Added 238 if desoBlockProducer.latestBlockTemplateStats != nil && 239 desoBlockProducer.latestBlockTemplateStats.FailingTxnHash == failingTxnHash { 240 // If we already have the txn stored, update the error message in case it changed 241 // and set the originalTimeAdded variable to compute an accurate staleness metric. 242 desoBlockProducer.latestBlockTemplateStats.FailingTxnError = txnErrorString 243 failingTxnOriginalTimeAdded = desoBlockProducer.latestBlockTemplateStats.FailingTxnOriginalTimeAdded 244 } else { 245 // If we haven't seen this txn before, build the block template stats from scratch. 246 blockTemplateStats := &BlockTemplateStats{} 247 blockTemplateStats.FailingTxnHash = mempoolTx.Hash.String() 248 blockTemplateStats.TxnCount = uint32(ii) 249 blockTemplateStats.FailingTxnError = txnErrorString 250 blockTemplateStats.FailingTxnOriginalTimeAdded = failingTxnOriginalTimeAdded 251 desoBlockProducer.latestBlockTemplateStats = blockTemplateStats 252 } 253 // Compute the time since this txn started holding up the mempool. 254 currentTime := time.Now() 255 timeElapsed := currentTime.Sub(failingTxnOriginalTimeAdded) 256 desoBlockProducer.latestBlockTemplateStats.FailingTxnMinutesSinceAdded = timeElapsed.Minutes() 257 258 break 259 } else if desoBlockProducer.latestBlockTemplateStats != nil { 260 desoBlockProducer.latestBlockTemplateStats.FailingTxnError = "You good" 261 desoBlockProducer.latestBlockTemplateStats.FailingTxnHash = "Nada" 262 desoBlockProducer.latestBlockTemplateStats.FailingTxnMinutesSinceAdded = 0 263 desoBlockProducer.latestBlockTemplateStats.FailingTxnOriginalTimeAdded = time.Now() 264 } 265 266 // If we get here then it means the txn is ready to be processed *and* we've added 267 // all of its dependencies to the block already. So go ahead and it to the block. 268 currentBlockSize += mempoolTx.TxSizeBytes + MaxVarintLen64 269 blockRet.Txns = append(blockRet.Txns, mempoolTx.Tx) 270 txnsAddedToBlock[*mempoolTx.Hash] = true 271 } 272 273 // Double-check that the final block size is below the limit. 274 blockBytes, err = blockRet.ToBytes(false) 275 if err != nil { 276 return nil, nil, nil, errors.Wrapf(err, "DeSoBlockProducer._getBlockTemplate: Problem serializing block after txns added: ") 277 } 278 if uint64(len(blockBytes)) > desoBlockProducer.params.MinerMaxBlockSizeBytes { 279 return nil, nil, nil, fmt.Errorf("DeSoBlockProducer._getBlockTemplate: Block created with size "+ 280 "(%d) exceeds BlockProducerMaxBlockSizeBytes (%d): ", len(blockBytes), desoBlockProducer.params.MinerMaxBlockSizeBytes) 281 } 282 } 283 284 // Compute the total fee the BlockProducer should get. 285 totalFeeNanos := uint64(0) 286 feesUtxoView, err := NewUtxoView(desoBlockProducer.chain.db, desoBlockProducer.params, desoBlockProducer.postgres) 287 if err != nil { 288 return nil, nil, nil, fmt.Errorf( 289 "DeSoBlockProducer._getBlockTemplate: Error generating UtxoView to compute txn fees: %v", err) 290 } 291 // Skip the block reward, which is the first txn in the block. 292 for _, txnInBlock := range blockRet.Txns[1:] { 293 var feeNanos uint64 294 _, _, _, feeNanos, err = feesUtxoView._connectTransaction( 295 txnInBlock, txnInBlock.Hash(), 0, uint32(blockRet.Header.Height), false, /*verifySignatures*/ 296 false /*ignoreUtxos*/) 297 if err != nil { 298 return nil, nil, nil, fmt.Errorf( 299 "DeSoBlockProducer._getBlockTemplate: Error attaching txn to UtxoView for computed block: %v", err) 300 } 301 302 // Add the fee to the block reward output as we go. Note this has some risk of 303 // increasing the size of the block by one byte, but it seems like this is an 304 // extreme edge case that goes away as soon as the function is called again. 305 totalFeeNanos += feeNanos 306 } 307 308 // Now that the total fees have been computed, set the value of the block reward 309 // output. 310 blockRewardOutput.AmountNanos = CalcBlockRewardNanos(uint32(blockRet.Header.Height)) + totalFeeNanos 311 312 // Compute the merkle root for the block now that all of the transactions have 313 // been added. 314 merkleRoot, _, err := ComputeMerkleRoot(blockRet.Txns) 315 if err != nil { 316 return nil, nil, nil, errors.Wrapf(err, "DeSoBlockProducer._getBlockTemplate: Problem computing merkle root: ") 317 } 318 blockRet.Header.TransactionMerkleRoot = merkleRoot 319 320 // Compute the next difficulty target given the current tip. 321 diffTarget, err := CalcNextDifficultyTarget( 322 lastNode, CurrentHeaderVersion, desoBlockProducer.params) 323 if err != nil { 324 return nil, nil, nil, errors.Wrapf(err, "DeSoBlockProducer._getBlockTemplate: Problem computing next difficulty: ") 325 } 326 327 glog.Infof("Produced block with %v txns with approx %v total txns in mempool", 328 len(blockRet.Txns), len(desoBlockProducer.mempool.readOnlyUniversalTransactionList)) 329 return blockRet, diffTarget, lastNode, nil 330 } 331 332 func (desoBlockProducer *DeSoBlockProducer) Stop() { 333 desoBlockProducer.stopProducerChannel <- struct{}{} 334 desoBlockProducer.producerWaitGroup.Wait() 335 } 336 337 func (desoBlockProducer *DeSoBlockProducer) GetRecentBlock(blockHash *BlockHash) *MsgDeSoBlock { 338 // Find the block and quickly lock/unlock for reading. 339 desoBlockProducer.mtxRecentBlockTemplatesProduced.RLock() 340 defer desoBlockProducer.mtxRecentBlockTemplatesProduced.RUnlock() 341 342 blockFound, exists := desoBlockProducer.recentBlockTemplatesProduced[*blockHash] 343 if !exists { 344 return nil 345 } 346 347 return blockFound 348 } 349 350 func (desoBlockProducer *DeSoBlockProducer) GetCopyOfRecentBlock(blockID string) (*MsgDeSoBlock, error) { 351 blockHashBytes, err := hex.DecodeString(blockID) 352 if err != nil { 353 return nil, errors.Wrap(err, "") 354 } 355 if len(blockHashBytes) != HashSizeBytes { 356 return nil, fmt.Errorf("Invalid blockID. Length was %v but must "+ 357 "be %v", len(blockHashBytes), HashSizeBytes) 358 } 359 360 blockHash := &BlockHash{} 361 copy(blockHash[:], blockHashBytes) 362 363 blockFound := desoBlockProducer.GetRecentBlock(blockHash) 364 if blockFound == nil { 365 return nil, fmt.Errorf("Block with blockID %v not found "+ 366 "in BlockProducer", blockID) 367 } 368 369 blockFoundBytes, err := blockFound.ToBytes(false /*preSignature*/) 370 if err != nil { 371 return nil, fmt.Errorf("Error serializing block: %v", err) 372 } 373 374 newBlock := &MsgDeSoBlock{} 375 err = newBlock.FromBytes(blockFoundBytes) 376 if err != nil { 377 return nil, fmt.Errorf("Error de-serializing block: %v", err) 378 } 379 380 return newBlock, nil 381 } 382 383 func (desoBlockProducer *DeSoBlockProducer) AddBlockTemplate(block *MsgDeSoBlock, diffTarget *BlockHash) { 384 desoBlockProducer.mtxRecentBlockTemplatesProduced.Lock() 385 defer desoBlockProducer.mtxRecentBlockTemplatesProduced.Unlock() 386 387 hash, _ := block.Header.Hash() 388 desoBlockProducer.recentBlockTemplatesProduced[*hash] = block 389 desoBlockProducer.latestBlockTemplateHash = hash 390 desoBlockProducer.currentDifficultyTarget = diffTarget 391 392 // Evict entries if we're at capacity. 393 for uint64(len(desoBlockProducer.recentBlockTemplatesProduced)) > 394 desoBlockProducer.maxBlockTemplatesToCache { 395 396 // TODO: We could be evicting things out of order if they both happen at the same 397 // second. The fix is to use nanos rather than seconds but we're skipping the work 398 // to do this for now since it doesn't really matter. 399 minTstamp := uint32(math.MaxUint32) 400 var oldestBlockHash *BlockHash 401 for _, cachedBlock := range desoBlockProducer.recentBlockTemplatesProduced { 402 if uint32(cachedBlock.Header.TstampSecs) < minTstamp { 403 minTstamp = uint32(cachedBlock.Header.TstampSecs) 404 oldestBlockHash, _ = cachedBlock.Header.Hash() 405 } 406 } 407 408 delete(desoBlockProducer.recentBlockTemplatesProduced, *oldestBlockHash) 409 } 410 } 411 412 func (blockProducer *DeSoBlockProducer) GetHeadersAndExtraDatas( 413 publicKeyBytes []byte, numHeaders int64, headerVersion uint32) ( 414 _blockID string, _headers [][]byte, _extraNonces []uint64, _diffTarget *BlockHash, _err error) { 415 416 // If we haven't computed the latest block template, then compute it now to bootstrap. 417 if blockProducer.latestBlockTemplateHash == nil { 418 // Use a dummy public key. 419 currentBlockTemplate, diffTarget, _, err := 420 blockProducer._getBlockTemplate(MustBase58CheckDecode(ArchitectPubKeyBase58Check)) 421 if err != nil { 422 return "", nil, nil, nil, 423 fmt.Errorf("GetBlockTemplate: Problem computing first block template: %v", err) 424 } 425 426 blockProducer.AddBlockTemplate(currentBlockTemplate, diffTarget) 427 } 428 // BlockProducer.latestBlockTemplateHash should always be set at this point. 429 430 // Get the latest block 431 blockID := hex.EncodeToString(blockProducer.latestBlockTemplateHash[:]) 432 latestBLockCopy, err := blockProducer.GetCopyOfRecentBlock(blockID) 433 if err != nil { 434 return "", nil, nil, nil, errors.Wrap( 435 fmt.Errorf("GetBlockTemplate: Problem getting latest block: %v", err), "") 436 } 437 438 // Swap out the public key in the block 439 latestBLockCopy.Txns[0].TxOutputs[0].PublicKey = publicKeyBytes 440 441 headers := [][]byte{} 442 extraNonces := []uint64{} 443 444 // For each header the caller asked us for, compute an ExtraData nonce and a block header 445 // using that nonced block reward. 446 for ii := int64(0); ii < numHeaders; ii++ { 447 // Set the version of the header 448 latestBLockCopy.Header.Version = headerVersion 449 450 extraNonce, err := wire.RandomUint64() 451 if err != nil { 452 return "", nil, nil, nil, errors.Wrap( 453 fmt.Errorf("GetBlockTemplate: Error computing extraNonce: %v", err), "") 454 } 455 latestBLockCopy.Txns[0].TxnMeta.(*BlockRewardMetadataa).ExtraData = UintToBuf(extraNonce) 456 457 // Compute the merkle root for the block now that all of the transactions have 458 // been added. 459 merkleRoot, _, err := ComputeMerkleRoot(latestBLockCopy.Txns) 460 if err != nil { 461 return "", nil, nil, nil, errors.Wrapf( 462 err, "GetBlockTemplate: Problem computing merkle root: ") 463 } 464 465 // Set the merkle root in the header. 466 latestBLockCopy.Header.TransactionMerkleRoot = merkleRoot 467 468 headerBytes, err := latestBLockCopy.Header.ToBytes(false) 469 if err != nil { 470 return "", nil, nil, nil, errors.Wrapf( 471 err, "GetBlockTemplate: Problem serializing header: ") 472 } 473 474 // If we get here then the header bytes and the ExtraNonce are good to go. 475 headers = append(headers, headerBytes) 476 extraNonces = append(extraNonces, extraNonce) 477 } 478 // At this point we have everything the miner needs so we should be good to go. 479 480 return blockID, headers, extraNonces, blockProducer.currentDifficultyTarget, nil 481 } 482 483 func (desoBlockProducer *DeSoBlockProducer) UpdateLatestBlockTemplate() error { 484 // Use a dummy public key. 485 currentBlockTemplate, diffTarget, lastNode, err := 486 desoBlockProducer._getBlockTemplate(MustBase58CheckDecode(ArchitectPubKeyBase58Check)) 487 if err != nil { 488 return err 489 } 490 491 // Log the results. 492 glog.V(1).Infof("Produced block template with difficulty target %v "+ 493 "and lastNode %v", diffTarget, lastNode) 494 495 desoBlockProducer.AddBlockTemplate(currentBlockTemplate, diffTarget) 496 return nil 497 } 498 499 func (desoBlockProducer *DeSoBlockProducer) SignBlock(blockFound *MsgDeSoBlock) error { 500 // If there's no private key on this BlockProducer then there's nothing to do. 501 if desoBlockProducer.blockProducerPrivateKey == nil { 502 return nil 503 } 504 505 // If there is a private key on this block producer then sign the block hash with it 506 // and include the signature in the block. 507 blockHash, err := blockFound.Header.Hash() 508 if err != nil { 509 return errors.Wrap( 510 fmt.Errorf("Error computing block hash from header submitted: %v", err), "") 511 } 512 513 signature, err := desoBlockProducer.blockProducerPrivateKey.Sign(blockHash[:]) 514 if err != nil { 515 return errors.Wrap( 516 fmt.Errorf("Error signing block: %v", err), "") 517 } 518 // If we get here, we now have a valid signature for the block. 519 520 // Embed the signature into the block. 521 blockFound.BlockProducerInfo = &BlockProducerInfo{ 522 PublicKey: desoBlockProducer.blockProducerPrivateKey.PubKey().SerializeCompressed(), 523 Signature: signature, 524 } 525 526 return nil 527 } 528 529 func (desoBlockProducer *DeSoBlockProducer) Start() { 530 // Set the time to a nil value so we run on the first iteration of the loop. 531 var lastBlockUpdate time.Time 532 desoBlockProducer.producerWaitGroup.Add(1) 533 534 for { 535 select { 536 case <-desoBlockProducer.stopProducerChannel: 537 desoBlockProducer.producerWaitGroup.Done() 538 return 539 default: 540 secondsLeft := float64(desoBlockProducer.minBlockUpdateIntervalSeconds) - time.Since(lastBlockUpdate).Seconds() 541 if !lastBlockUpdate.IsZero() && secondsLeft > 0 { 542 glog.V(1).Infof("Sleeping for %v seconds before producing next block template...", secondsLeft) 543 time.Sleep(time.Duration(math.Ceil(secondsLeft)) * time.Second) 544 continue 545 } 546 547 // Update the time so start the clock for the next iteration. 548 lastBlockUpdate = time.Now() 549 550 glog.V(1).Infof("Producing block template...") 551 err := desoBlockProducer.UpdateLatestBlockTemplate() 552 if err != nil { 553 // If we hit an error, log it and sleep for a second. This could happen due to us 554 // being in the middle of processing a block or something. 555 glog.Errorf("Error producing block template: %v", err) 556 time.Sleep(time.Second) 557 continue 558 } 559 560 } 561 } 562 }