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