github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/consensus/consensusset.go (about)

     1  package consensus
     2  
     3  // All changes to the consenuss set are made via diffs, specifically by calling
     4  // a commitDiff function. This means that future modifications (such as
     5  // replacing in-memory versions of the utxo set with on-disk versions of the
     6  // utxo set) should be relatively easy to verify for correctness. Modifying the
     7  // commitDiff functions will be sufficient.
     8  
     9  import (
    10  	"errors"
    11  	"fmt"
    12  
    13  	"github.com/NebulousLabs/Sia/build"
    14  	"github.com/NebulousLabs/Sia/encoding"
    15  	"github.com/NebulousLabs/Sia/modules"
    16  	"github.com/NebulousLabs/Sia/persist"
    17  	"github.com/NebulousLabs/Sia/types"
    18  
    19  	"github.com/NebulousLabs/bolt"
    20  	"github.com/NebulousLabs/demotemutex"
    21  )
    22  
    23  var (
    24  	errNilGateway = errors.New("cannot have a nil gateway as input")
    25  )
    26  
    27  // The ConsensusSet is the object responsible for tracking the current status
    28  // of the blockchain. Broadly speaking, it is responsible for maintaining
    29  // consensus.  It accepts blocks and constructs a blockchain, forking when
    30  // necessary.
    31  type ConsensusSet struct {
    32  	// The gateway manages peer connections and keeps the consensus set
    33  	// synchronized to the rest of the network.
    34  	gateway modules.Gateway
    35  
    36  	// The block root contains the genesis block.
    37  	blockRoot processedBlock
    38  
    39  	// Subscribers to the consensus set will receive a changelog every time
    40  	// there is an update to the consensus set. At initialization, they receive
    41  	// all changes that they are missing.
    42  	//
    43  	// Memory: A consensus set typically has fewer than 10 subscribers, and
    44  	// subscription typically happens entirely at startup. This slice is
    45  	// unlikely to grow beyond 1kb, and cannot by manipulated by an attacker as
    46  	// the function of adding a subscriber should not be exposed.
    47  	subscribers []modules.ConsensusSetSubscriber
    48  
    49  	// dosBlocks are blocks that are invalid, but the invalidity is only
    50  	// discoverable during an expensive step of validation. These blocks are
    51  	// recorded to eliminate a DoS vector where an expensive-to-validate block
    52  	// is submitted to the consensus set repeatedly.
    53  	//
    54  	// TODO: dosBlocks needs to be moved into the database, and if there's some
    55  	// reason it can't be in THE database, it should be in a separate database.
    56  	// dosBlocks is an unbounded map that an attacker can manipulate, though
    57  	// iirc manipulations are expensive, to the tune of creating a blockchain
    58  	// PoW per DoS block (though the attacker could conceivably build off of
    59  	// the genesis block, meaning the PoW is not very expensive.
    60  	dosBlocks map[types.BlockID]struct{}
    61  
    62  	// checkingConsistency is a bool indicating whether or not a consistency
    63  	// check is in progress. The consistency check logic call itself, resulting
    64  	// in infinite loops. This bool prevents that while still allowing for full
    65  	// granularity consistency checks. Previously, consistency checks were only
    66  	// performed after a full reorg, but now they are performed after every
    67  	// block.
    68  	checkingConsistency bool
    69  
    70  	// synced is true if initial blockchain download has finished. It indicates
    71  	// whether the consensus set is synced with the network.
    72  	synced bool
    73  
    74  	// Interfaces to abstract the dependencies of the ConsensusSet.
    75  	marshaler       encoding.GenericMarshaler
    76  	blockRuleHelper blockRuleHelper
    77  	blockValidator  blockValidator
    78  
    79  	// Utilities
    80  	db         *persist.BoltDatabase
    81  	log        *persist.Logger
    82  	mu         demotemutex.DemoteMutex
    83  	persistDir string
    84  }
    85  
    86  // New returns a new ConsensusSet, containing at least the genesis block. If
    87  // there is an existing block database present in the persist directory, it
    88  // will be loaded.
    89  func New(gateway modules.Gateway, persistDir string) (*ConsensusSet, error) {
    90  	// Check for nil dependencies.
    91  	if gateway == nil {
    92  		return nil, errNilGateway
    93  	}
    94  
    95  	// Create the ConsensusSet object.
    96  	cs := &ConsensusSet{
    97  		gateway: gateway,
    98  
    99  		blockRoot: processedBlock{
   100  			Block:       types.GenesisBlock,
   101  			ChildTarget: types.RootTarget,
   102  			Depth:       types.RootDepth,
   103  
   104  			DiffsGenerated: true,
   105  		},
   106  
   107  		dosBlocks: make(map[types.BlockID]struct{}),
   108  
   109  		marshaler:       encoding.StdGenericMarshaler{},
   110  		blockRuleHelper: stdBlockRuleHelper{},
   111  		blockValidator:  NewBlockValidator(),
   112  
   113  		persistDir: persistDir,
   114  	}
   115  
   116  	// Create the diffs for the genesis siafund outputs.
   117  	for i, siafundOutput := range types.GenesisBlock.Transactions[0].SiafundOutputs {
   118  		sfid := types.GenesisBlock.Transactions[0].SiafundOutputID(uint64(i))
   119  		sfod := modules.SiafundOutputDiff{
   120  			Direction:     modules.DiffApply,
   121  			ID:            sfid,
   122  			SiafundOutput: siafundOutput,
   123  		}
   124  		cs.blockRoot.SiafundOutputDiffs = append(cs.blockRoot.SiafundOutputDiffs, sfod)
   125  	}
   126  
   127  	// Initialize the consensus persistence structures.
   128  	err := cs.initPersist()
   129  	if err != nil {
   130  		return nil, err
   131  	}
   132  
   133  	go func() {
   134  		// Sync with the network. Don't sync if we are testing because typically we
   135  		// don't have any mock peers to synchronize with in testing.
   136  		// TODO: figure out a better way to conditionally do IBD. Otherwise this block will never be tested.
   137  		if build.Release != "testing" {
   138  			cs.threadedInitialBlockchainDownload()
   139  		}
   140  
   141  		// Register RPCs
   142  		gateway.RegisterRPC("SendBlocks", cs.rpcSendBlocks)
   143  		gateway.RegisterRPC("RelayBlock", cs.rpcRelayBlock) // COMPATv0.5.1
   144  		gateway.RegisterRPC("RelayHeader", cs.rpcRelayHeader)
   145  		gateway.RegisterRPC("SendBlk", cs.rpcSendBlk)
   146  		gateway.RegisterConnectCall("SendBlocks", cs.threadedReceiveBlocks)
   147  
   148  		// Mark that we are synced with the network.
   149  		cs.mu.Lock()
   150  		cs.synced = true
   151  		cs.mu.Unlock()
   152  	}()
   153  
   154  	return cs, nil
   155  }
   156  
   157  // BlockAtHeight returns the block at a given height.
   158  func (cs *ConsensusSet) BlockAtHeight(height types.BlockHeight) (block types.Block, exists bool) {
   159  	_ = cs.db.View(func(tx *bolt.Tx) error {
   160  		id, err := getPath(tx, height)
   161  		if err != nil {
   162  			return err
   163  		}
   164  		pb, err := getBlockMap(tx, id)
   165  		if err != nil {
   166  			return err
   167  		}
   168  		block = pb.Block
   169  		exists = true
   170  		return nil
   171  	})
   172  	return block, exists
   173  }
   174  
   175  // ChildTarget returns the target for the child of a block.
   176  func (cs *ConsensusSet) ChildTarget(id types.BlockID) (target types.Target, exists bool) {
   177  	_ = cs.db.View(func(tx *bolt.Tx) error {
   178  		pb, err := getBlockMap(tx, id)
   179  		if err != nil {
   180  			return err
   181  		}
   182  		target = pb.ChildTarget
   183  		exists = true
   184  		return nil
   185  	})
   186  	return target, exists
   187  }
   188  
   189  // Close safely closes the block database.
   190  func (cs *ConsensusSet) Close() error {
   191  	cs.mu.Lock()
   192  	defer cs.mu.Unlock()
   193  
   194  	var errs []error
   195  	if err := cs.db.Close(); err != nil {
   196  		errs = append(errs, fmt.Errorf("db.Close failed: %v", err))
   197  	}
   198  	if err := cs.log.Close(); err != nil {
   199  		errs = append(errs, fmt.Errorf("log.Close failed: %v", err))
   200  	}
   201  	return build.JoinErrors(errs, "; ")
   202  }
   203  
   204  // CurrentBlock returns the latest block in the heaviest known blockchain.
   205  func (cs *ConsensusSet) CurrentBlock() (block types.Block) {
   206  	_ = cs.db.View(func(tx *bolt.Tx) error {
   207  		pb := currentProcessedBlock(tx)
   208  		block = pb.Block
   209  		return nil
   210  	})
   211  	return block
   212  }
   213  
   214  // Height returns the height of the consensus set.
   215  func (cs *ConsensusSet) Height() (height types.BlockHeight) {
   216  	_ = cs.db.View(func(tx *bolt.Tx) error {
   217  		height = blockHeight(tx)
   218  		return nil
   219  	})
   220  	return height
   221  }
   222  
   223  // InCurrentPath returns true if the block presented is in the current path,
   224  // false otherwise.
   225  func (cs *ConsensusSet) InCurrentPath(id types.BlockID) (inPath bool) {
   226  	_ = cs.db.View(func(tx *bolt.Tx) error {
   227  		pb, err := getBlockMap(tx, id)
   228  		if err != nil {
   229  			inPath = false
   230  			return nil
   231  		}
   232  		pathID, err := getPath(tx, pb.Height)
   233  		if err != nil {
   234  			inPath = false
   235  			return nil
   236  		}
   237  		inPath = pathID == id
   238  		return nil
   239  	})
   240  	return inPath
   241  }
   242  
   243  // MinimumValidChildTimestamp returns the earliest timestamp that the next block
   244  // can have in order for it to be considered valid.
   245  func (cs *ConsensusSet) MinimumValidChildTimestamp(id types.BlockID) (timestamp types.Timestamp, exists bool) {
   246  	// Error is not checked because it does not matter.
   247  	_ = cs.db.View(func(tx *bolt.Tx) error {
   248  		pb, err := getBlockMap(tx, id)
   249  		if err != nil {
   250  			return err
   251  		}
   252  		timestamp = cs.blockRuleHelper.minimumValidChildTimestamp(tx.Bucket(BlockMap), pb)
   253  		exists = true
   254  		return nil
   255  	})
   256  	return timestamp, exists
   257  }
   258  
   259  // StorageProofSegment returns the segment to be used in the storage proof for
   260  // a given file contract.
   261  func (cs *ConsensusSet) StorageProofSegment(fcid types.FileContractID) (index uint64, err error) {
   262  	_ = cs.db.View(func(tx *bolt.Tx) error {
   263  		index, err = storageProofSegment(tx, fcid)
   264  		return nil
   265  	})
   266  	return index, err
   267  }