github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/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  	"SiaPrime/encoding"
    12  	"SiaPrime/modules"
    13  	"SiaPrime/persist"
    14  	siasync "SiaPrime/sync"
    15  	"SiaPrime/types"
    16  
    17  	"gitlab.com/NebulousLabs/bolt"
    18  	"gitlab.com/NebulousLabs/demotemutex"
    19  )
    20  
    21  var (
    22  	errNilGateway = errors.New("cannot have a nil gateway as input")
    23  )
    24  
    25  // marshaler marshals objects into byte slices and unmarshals byte
    26  // slices into objects.
    27  type marshaler interface {
    28  	Marshal(interface{}) []byte
    29  	Unmarshal([]byte, interface{}) error
    30  }
    31  type stdMarshaler struct{}
    32  
    33  func (stdMarshaler) Marshal(v interface{}) []byte            { return encoding.Marshal(v) }
    34  func (stdMarshaler) Unmarshal(b []byte, v interface{}) error { return encoding.Unmarshal(b, v) }
    35  
    36  // The ConsensusSet is the object responsible for tracking the current status
    37  // of the blockchain. Broadly speaking, it is responsible for maintaining
    38  // consensus.  It accepts blocks and constructs a blockchain, forking when
    39  // necessary.
    40  type ConsensusSet struct {
    41  	// The gateway manages peer connections and keeps the consensus set
    42  	// synchronized to the rest of the network.
    43  	gateway modules.Gateway
    44  
    45  	// The block root contains the genesis block.
    46  	blockRoot processedBlock
    47  
    48  	// Subscribers to the consensus set will receive a changelog every time
    49  	// there is an update to the consensus set. At initialization, they receive
    50  	// all changes that they are missing.
    51  	//
    52  	// Memory: A consensus set typically has fewer than 10 subscribers, and
    53  	// subscription typically happens entirely at startup. This slice is
    54  	// unlikely to grow beyond 1kb, and cannot by manipulated by an attacker as
    55  	// the function of adding a subscriber should not be exposed.
    56  	subscribers []modules.ConsensusSetSubscriber
    57  
    58  	// dosBlocks are blocks that are invalid, but the invalidity is only
    59  	// discoverable during an expensive step of validation. These blocks are
    60  	// recorded to eliminate a DoS vector where an expensive-to-validate block
    61  	// is submitted to the consensus set repeatedly.
    62  	//
    63  	// TODO: dosBlocks needs to be moved into the database, and if there's some
    64  	// reason it can't be in THE database, it should be in a separate database.
    65  	// dosBlocks is an unbounded map that an attacker can manipulate, though
    66  	// iirc manipulations are expensive, to the tune of creating a blockchain
    67  	// PoW per DoS block (though the attacker could conceivably build off of
    68  	// the genesis block, meaning the PoW is not very expensive.
    69  	dosBlocks map[types.BlockID]struct{}
    70  
    71  	// checkingConsistency is a bool indicating whether or not a consistency
    72  	// check is in progress. The consistency check logic call itself, resulting
    73  	// in infinite loops. This bool prevents that while still allowing for full
    74  	// granularity consistency checks. Previously, consistency checks were only
    75  	// performed after a full reorg, but now they are performed after every
    76  	// block.
    77  	checkingConsistency bool
    78  
    79  	// synced is true if initial blockchain download has finished. It indicates
    80  	// whether the consensus set is synced with the network.
    81  	synced bool
    82  
    83  	// Interfaces to abstract the dependencies of the ConsensusSet.
    84  	marshaler       marshaler
    85  	blockRuleHelper blockRuleHelper
    86  	blockValidator  blockValidator
    87  
    88  	// Utilities
    89  	db         *persist.BoltDatabase
    90  	staticDeps modules.Dependencies
    91  	log        *persist.Logger
    92  	mu         demotemutex.DemoteMutex
    93  	persistDir string
    94  	tg         siasync.ThreadGroup
    95  }
    96  
    97  // New returns a new ConsensusSet, containing at least the genesis block. If
    98  // there is an existing block database present in the persist directory, it
    99  // will be loaded.
   100  func New(gateway modules.Gateway, bootstrap bool, persistDir string) (*ConsensusSet, error) {
   101  	return NewCustomConsensusSet(gateway, bootstrap, persistDir, modules.ProdDependencies)
   102  }
   103  
   104  // NewCustomConsensusSet returns a new ConsensusSet, containing at least the genesis block. If
   105  // there is an existing block database present in the persist directory, it
   106  // will be loaded.
   107  func NewCustomConsensusSet(gateway modules.Gateway, bootstrap bool, persistDir string, deps modules.Dependencies) (*ConsensusSet, error) {
   108  	// Check for nil dependencies.
   109  	if gateway == nil {
   110  		return nil, errNilGateway
   111  	}
   112  
   113  	// Create the ConsensusSet object.
   114  	cs := &ConsensusSet{
   115  		gateway: gateway,
   116  
   117  		blockRoot: processedBlock{
   118  			Block:       types.GenesisBlock,
   119  			ChildTarget: types.RootTarget,
   120  			Depth:       types.RootDepth,
   121  
   122  			DiffsGenerated: true,
   123  		},
   124  
   125  		dosBlocks: make(map[types.BlockID]struct{}),
   126  
   127  		marshaler:       stdMarshaler{},
   128  		blockRuleHelper: stdBlockRuleHelper{},
   129  		blockValidator:  NewBlockValidator(),
   130  
   131  		staticDeps: deps,
   132  		persistDir: persistDir,
   133  	}
   134  
   135  	// Create the diffs for the genesis siacoin outputs.
   136  	for i, siacoinOutput := range types.GenesisBlock.Transactions[0].SiacoinOutputs {
   137  		scid := types.GenesisBlock.Transactions[0].SiacoinOutputID(uint64(i))
   138  		scod := modules.SiacoinOutputDiff{
   139  			Direction:     modules.DiffApply,
   140  			ID:            scid,
   141  			SiacoinOutput: siacoinOutput,
   142  		}
   143  		cs.blockRoot.SiacoinOutputDiffs = append(cs.blockRoot.SiacoinOutputDiffs, scod)
   144  	}
   145  
   146  	// Create the diffs for the genesis siafund outputs.
   147  	for i, siafundOutput := range types.GenesisBlock.Transactions[1].SiafundOutputs {
   148  		sfid := types.GenesisBlock.Transactions[1].SiafundOutputID(uint64(i))
   149  		sfod := modules.SiafundOutputDiff{
   150  			Direction:     modules.DiffApply,
   151  			ID:            sfid,
   152  			SiafundOutput: siafundOutput,
   153  		}
   154  		cs.blockRoot.SiafundOutputDiffs = append(cs.blockRoot.SiafundOutputDiffs, sfod)
   155  	}
   156  
   157  	// Initialize the consensus persistence structures.
   158  	err := cs.initPersist()
   159  	if err != nil {
   160  		return nil, err
   161  	}
   162  
   163  	go func() {
   164  		// Sync with the network. Don't sync if we are testing because
   165  		// typically we don't have any mock peers to synchronize with in
   166  		// testing.
   167  		if bootstrap {
   168  			// We are in a virgin goroutine right now, so calling the threaded
   169  			// function without a goroutine is okay.
   170  			err = cs.threadedInitialBlockchainDownload()
   171  			if err != nil {
   172  				return
   173  			}
   174  		}
   175  
   176  		// threadedInitialBlockchainDownload will release the threadgroup 'Add'
   177  		// it was holding, so another needs to be grabbed to finish off this
   178  		// goroutine.
   179  		err = cs.tg.Add()
   180  		if err != nil {
   181  			return
   182  		}
   183  		defer cs.tg.Done()
   184  
   185  		// Register RPCs
   186  		gateway.RegisterRPC("SendBlocks", cs.rpcSendBlocks)
   187  		gateway.RegisterRPC("RelayHeader", cs.threadedRPCRelayHeader)
   188  		gateway.RegisterRPC("SendBlk", cs.rpcSendBlk)
   189  		gateway.RegisterConnectCall("SendBlocks", cs.threadedReceiveBlocks)
   190  		cs.tg.OnStop(func() {
   191  			cs.gateway.UnregisterRPC("SendBlocks")
   192  			cs.gateway.UnregisterRPC("RelayHeader")
   193  			cs.gateway.UnregisterRPC("SendBlk")
   194  			cs.gateway.UnregisterConnectCall("SendBlocks")
   195  		})
   196  
   197  		// Mark that we are synced with the network.
   198  		cs.mu.Lock()
   199  		cs.synced = true
   200  		cs.mu.Unlock()
   201  	}()
   202  
   203  	return cs, nil
   204  }
   205  
   206  // BlockAtHeight returns the block at a given height.
   207  func (cs *ConsensusSet) BlockAtHeight(height types.BlockHeight) (block types.Block, exists bool) {
   208  	_ = cs.db.View(func(tx *bolt.Tx) error {
   209  		id, err := getPath(tx, height)
   210  		if err != nil {
   211  			return err
   212  		}
   213  		pb, err := getBlockMap(tx, id)
   214  		if err != nil {
   215  			return err
   216  		}
   217  		block = pb.Block
   218  		exists = true
   219  		return nil
   220  	})
   221  	return block, exists
   222  }
   223  
   224  // BlockByID returns the block for a given BlockID.
   225  func (cs *ConsensusSet) BlockByID(id types.BlockID) (block types.Block, height types.BlockHeight, exists bool) {
   226  	_ = cs.db.View(func(tx *bolt.Tx) error {
   227  		pb, err := getBlockMap(tx, id)
   228  		if err != nil {
   229  			return err
   230  		}
   231  		block = pb.Block
   232  		height = pb.Height
   233  		exists = true
   234  		return nil
   235  	})
   236  	return block, height, exists
   237  }
   238  
   239  // ChildTarget returns the target for the child of a block.
   240  func (cs *ConsensusSet) ChildTarget(id types.BlockID) (target types.Target, exists bool) {
   241  	// A call to a closed database can cause undefined behavior.
   242  	err := cs.tg.Add()
   243  	if err != nil {
   244  		return types.Target{}, false
   245  	}
   246  	defer cs.tg.Done()
   247  
   248  	_ = cs.db.View(func(tx *bolt.Tx) error {
   249  		pb, err := getBlockMap(tx, id)
   250  		if err != nil {
   251  			return err
   252  		}
   253  		target = pb.ChildTarget
   254  		exists = true
   255  		return nil
   256  	})
   257  	return target, exists
   258  }
   259  
   260  // Close safely closes the block database.
   261  func (cs *ConsensusSet) Close() error {
   262  	return cs.tg.Stop()
   263  }
   264  
   265  // managedCurrentBlock returns the latest block in the heaviest known blockchain.
   266  func (cs *ConsensusSet) managedCurrentBlock() (block types.Block) {
   267  	cs.mu.RLock()
   268  	defer cs.mu.RUnlock()
   269  
   270  	_ = cs.db.View(func(tx *bolt.Tx) error {
   271  		pb := currentProcessedBlock(tx)
   272  		block = pb.Block
   273  		return nil
   274  	})
   275  	return block
   276  }
   277  
   278  // CurrentBlock returns the latest block in the heaviest known blockchain.
   279  func (cs *ConsensusSet) CurrentBlock() (block types.Block) {
   280  	// A call to a closed database can cause undefined behavior.
   281  	err := cs.tg.Add()
   282  	if err != nil {
   283  		return types.Block{}
   284  	}
   285  	defer cs.tg.Done()
   286  
   287  	// Block until a lock can be grabbed on the consensus set, indicating that
   288  	// all modules have received the most recent block. The lock is held so that
   289  	// there are no race conditions when trying to synchronize nodes.
   290  	cs.mu.Lock()
   291  	defer cs.mu.Unlock()
   292  
   293  	_ = cs.db.View(func(tx *bolt.Tx) error {
   294  		pb := currentProcessedBlock(tx)
   295  		block = pb.Block
   296  		return nil
   297  	})
   298  	return block
   299  }
   300  
   301  // Flush will block until the consensus set has finished all in-progress
   302  // routines.
   303  func (cs *ConsensusSet) Flush() error {
   304  	return cs.tg.Flush()
   305  }
   306  
   307  // Height returns the height of the consensus set.
   308  func (cs *ConsensusSet) Height() (height types.BlockHeight) {
   309  	// A call to a closed database can cause undefined behavior.
   310  	err := cs.tg.Add()
   311  	if err != nil {
   312  		return 0
   313  	}
   314  	defer cs.tg.Done()
   315  
   316  	// Block until a lock can be grabbed on the consensus set, indicating that
   317  	// all modules have received the most recent block. The lock is held so that
   318  	// there are no race conditions when trying to synchronize nodes.
   319  	cs.mu.Lock()
   320  	defer cs.mu.Unlock()
   321  
   322  	_ = cs.db.View(func(tx *bolt.Tx) error {
   323  		height = blockHeight(tx)
   324  		return nil
   325  	})
   326  	return height
   327  }
   328  
   329  // InCurrentPath returns true if the block presented is in the current path,
   330  // false otherwise.
   331  func (cs *ConsensusSet) InCurrentPath(id types.BlockID) (inPath bool) {
   332  	// A call to a closed database can cause undefined behavior.
   333  	err := cs.tg.Add()
   334  	if err != nil {
   335  		return false
   336  	}
   337  	defer cs.tg.Done()
   338  
   339  	_ = cs.db.View(func(tx *bolt.Tx) error {
   340  		pb, err := getBlockMap(tx, id)
   341  		if err != nil {
   342  			inPath = false
   343  			return nil
   344  		}
   345  		pathID, err := getPath(tx, pb.Height)
   346  		if err != nil {
   347  			inPath = false
   348  			return nil
   349  		}
   350  		inPath = pathID == id
   351  		return nil
   352  	})
   353  	return inPath
   354  }
   355  
   356  // MinimumValidChildTimestamp returns the earliest timestamp that the next block
   357  // can have in order for it to be considered valid.
   358  func (cs *ConsensusSet) MinimumValidChildTimestamp(id types.BlockID) (timestamp types.Timestamp, exists bool) {
   359  	// A call to a closed database can cause undefined behavior.
   360  	err := cs.tg.Add()
   361  	if err != nil {
   362  		return 0, false
   363  	}
   364  	defer cs.tg.Done()
   365  
   366  	// Error is not checked because it does not matter.
   367  	_ = cs.db.View(func(tx *bolt.Tx) error {
   368  		pb, err := getBlockMap(tx, id)
   369  		if err != nil {
   370  			return err
   371  		}
   372  		timestamp = cs.blockRuleHelper.minimumValidChildTimestamp(tx.Bucket(BlockMap), pb)
   373  		exists = true
   374  		return nil
   375  	})
   376  	return timestamp, exists
   377  }
   378  
   379  // StorageProofSegment returns the segment to be used in the storage proof for
   380  // a given file contract.
   381  func (cs *ConsensusSet) StorageProofSegment(fcid types.FileContractID) (index uint64, err error) {
   382  	// A call to a closed database can cause undefined behavior.
   383  	err = cs.tg.Add()
   384  	if err != nil {
   385  		return 0, err
   386  	}
   387  	defer cs.tg.Done()
   388  
   389  	_ = cs.db.View(func(tx *bolt.Tx) error {
   390  		index, err = storageProofSegment(tx, fcid)
   391  		return nil
   392  	})
   393  	return index, err
   394  }
   395  
   396  // Db returns the database associated with the ConsensusSet
   397  func (cs *ConsensusSet) Db() *persist.BoltDatabase {
   398  	return cs.db
   399  }