lbcd/accept.go
2013-07-19 08:50:13 -05:00

159 lines
5.3 KiB
Go

// Copyright (c) 2013 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package btcchain
import (
"fmt"
"github.com/conformal/btcutil"
"github.com/conformal/btcwire"
)
// maybeAcceptBlock potentially accepts a block into the memory block chain.
// It performs several validation checks which depend on its position within
// the block chain before adding it. The block is expected to have already gone
// through ProcessBlock before calling this function with it.
func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block) error {
// Get a block node for the block previous to this one. Will be nil
// if this is the genesis block.
prevNode, err := b.getPrevNodeFromBlock(block)
if err != nil {
return err
}
// The height of this block one more than the referenced previous block.
blockHeight := int64(0)
if prevNode != nil {
blockHeight = prevNode.height + 1
}
// Ensure the difficulty specified in the block header matches the
// calculated difficulty based on the previous block and difficulty
// retarget rules.
blockHeader := block.MsgBlock().Header
expectedDifficulty, err := b.calcNextRequiredDifficulty(prevNode)
if err != nil {
return err
}
blockDifficulty := blockHeader.Bits
if blockDifficulty != expectedDifficulty {
str := "block difficulty of %d is not the expected value of %d"
str = fmt.Sprintf(str, blockDifficulty, expectedDifficulty)
return RuleError(str)
}
// Ensure the timestamp for the block header is after the median time of
// the last several blocks (medianTimeBlocks).
medianTime, err := b.calcPastMedianTime(prevNode)
if err != nil {
return err
}
if !blockHeader.Timestamp.After(medianTime) {
str := "block timestamp of %v is not after expected %v"
str = fmt.Sprintf(str, blockHeader.Timestamp, medianTime)
return RuleError(str)
}
// Ensure all transactions in the block are finalized.
for i, tx := range block.MsgBlock().Transactions {
if !isFinalizedTransaction(tx, blockHeight, blockHeader.Timestamp) {
// Use the TxSha function from the block rather
// than the transaction itself since the block version
// is cached. Also, it's safe to ignore the error here
// since the only reason TxSha can fail is if the index
// is out of range which is impossible here.
txSha, _ := block.TxSha(i)
str := fmt.Sprintf("block contains unfinalized "+
"transaction %v", txSha)
return RuleError(str)
}
}
// Ensure chain matches up to predetermined checkpoints.
// It's safe to ignore the error on Sha since it's already cached.
blockHash, _ := block.Sha()
if !b.verifyCheckpoint(blockHeight, blockHash) {
// TODO(davec): This should probably be a distinct error type
// (maybe CheckpointError). Since this error shouldn't happen
// unless the peer is connected to a rogue network serving up an
// alternate chain, the caller would likely need to react by
// disconnecting peers and rolling back the chain to the last
// known good point.
str := fmt.Sprintf("block at height %d does not match "+
"checkpoint hash", blockHeight)
return RuleError(str)
}
// Reject version 1 blocks once a majority of the network has upgraded.
// Rules:
// 95% (950 / 1000) for main network
// 75% (75 / 100) for the test network
// This is part of BIP_0034.
if blockHeader.Version == 1 {
minRequired := uint64(950)
numToCheck := uint64(1000)
if b.btcnet == btcwire.TestNet3 || b.btcnet == btcwire.TestNet {
minRequired = 75
numToCheck = 100
}
if b.isMajorityVersion(2, prevNode, minRequired, numToCheck) {
str := "new blocks with version %d are no longer valid"
str = fmt.Sprintf(str, blockHeader.Version)
return RuleError(str)
}
}
// Ensure coinbase starts with serialized block heights for blocks
// whose version is the serializedHeightVersion or newer once a majority
// of the network has upgraded.
// Rules:
// 75% (750 / 1000) for main network
// 51% (51 / 100) for the test network
// This is part of BIP_0034.
if blockHeader.Version >= serializedHeightVersion {
minRequired := uint64(750)
numToCheck := uint64(1000)
if b.btcnet == btcwire.TestNet3 || b.btcnet == btcwire.TestNet {
minRequired = 51
numToCheck = 100
}
if b.isMajorityVersion(serializedHeightVersion, prevNode,
minRequired, numToCheck) {
expectedHeight := int64(0)
if prevNode != nil {
expectedHeight = prevNode.height + 1
}
coinbaseTx := block.MsgBlock().Transactions[0]
err := checkSerializedHeight(coinbaseTx, expectedHeight)
if err != nil {
return err
}
}
}
// Create a new block node for the block and add it to the in-memory
// block chain (could be either a side chain or the main chain).
newNode := newBlockNode(block)
if prevNode != nil {
newNode.parent = prevNode
newNode.height = blockHeight
newNode.workSum.Add(prevNode.workSum, newNode.workSum)
}
// Connect the passed block to the chain while respecting proper chain
// selection according to the chain with the most proof of work. This
// also handles validation of the transaction scripts.
err = b.connectBestChain(newNode, block)
if err != nil {
return err
}
// Notify the caller that the new block was accepted into the block
// chain. The caller would typically want to react by relaying the
// inventory to other peers.
b.sendNotification(NTBlockAccepted, block)
return nil
}