github.com/NebulousLabs/Sia@v1.3.7/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 "github.com/NebulousLabs/Sia/encoding" 13 "github.com/NebulousLabs/Sia/modules" 14 "github.com/NebulousLabs/Sia/persist" 15 siasync "github.com/NebulousLabs/Sia/sync" 16 "github.com/NebulousLabs/Sia/types" 17 18 "github.com/NebulousLabs/demotemutex" 19 "github.com/coreos/bbolt" 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 // Create the diffs for the genesis siafund outputs. 137 for i, siafundOutput := range types.GenesisBlock.Transactions[0].SiafundOutputs { 138 sfid := types.GenesisBlock.Transactions[0].SiafundOutputID(uint64(i)) 139 sfod := modules.SiafundOutputDiff{ 140 Direction: modules.DiffApply, 141 ID: sfid, 142 SiafundOutput: siafundOutput, 143 } 144 cs.blockRoot.SiafundOutputDiffs = append(cs.blockRoot.SiafundOutputDiffs, sfod) 145 } 146 147 // Initialize the consensus persistence structures. 148 err := cs.initPersist() 149 if err != nil { 150 return nil, err 151 } 152 153 go func() { 154 // Sync with the network. Don't sync if we are testing because 155 // typically we don't have any mock peers to synchronize with in 156 // testing. 157 if bootstrap { 158 // We are in a virgin goroutine right now, so calling the threaded 159 // function without a goroutine is okay. 160 err = cs.threadedInitialBlockchainDownload() 161 if err != nil { 162 return 163 } 164 } 165 166 // threadedInitialBlockchainDownload will release the threadgroup 'Add' 167 // it was holding, so another needs to be grabbed to finish off this 168 // goroutine. 169 err = cs.tg.Add() 170 if err != nil { 171 return 172 } 173 defer cs.tg.Done() 174 175 // Register RPCs 176 gateway.RegisterRPC("SendBlocks", cs.rpcSendBlocks) 177 gateway.RegisterRPC("RelayHeader", cs.threadedRPCRelayHeader) 178 gateway.RegisterRPC("SendBlk", cs.rpcSendBlk) 179 gateway.RegisterConnectCall("SendBlocks", cs.threadedReceiveBlocks) 180 cs.tg.OnStop(func() { 181 cs.gateway.UnregisterRPC("SendBlocks") 182 cs.gateway.UnregisterRPC("RelayHeader") 183 cs.gateway.UnregisterRPC("SendBlk") 184 cs.gateway.UnregisterConnectCall("SendBlocks") 185 }) 186 187 // Mark that we are synced with the network. 188 cs.mu.Lock() 189 cs.synced = true 190 cs.mu.Unlock() 191 }() 192 193 return cs, nil 194 } 195 196 // BlockAtHeight returns the block at a given height. 197 func (cs *ConsensusSet) BlockAtHeight(height types.BlockHeight) (block types.Block, exists bool) { 198 _ = cs.db.View(func(tx *bolt.Tx) error { 199 id, err := getPath(tx, height) 200 if err != nil { 201 return err 202 } 203 pb, err := getBlockMap(tx, id) 204 if err != nil { 205 return err 206 } 207 block = pb.Block 208 exists = true 209 return nil 210 }) 211 return block, exists 212 } 213 214 // BlockByID returns the block for a given BlockID. 215 func (cs *ConsensusSet) BlockByID(id types.BlockID) (block types.Block, height types.BlockHeight, exists bool) { 216 _ = cs.db.View(func(tx *bolt.Tx) error { 217 pb, err := getBlockMap(tx, id) 218 if err != nil { 219 return err 220 } 221 block = pb.Block 222 height = pb.Height 223 exists = true 224 return nil 225 }) 226 return block, height, exists 227 } 228 229 // ChildTarget returns the target for the child of a block. 230 func (cs *ConsensusSet) ChildTarget(id types.BlockID) (target types.Target, exists bool) { 231 // A call to a closed database can cause undefined behavior. 232 err := cs.tg.Add() 233 if err != nil { 234 return types.Target{}, false 235 } 236 defer cs.tg.Done() 237 238 _ = cs.db.View(func(tx *bolt.Tx) error { 239 pb, err := getBlockMap(tx, id) 240 if err != nil { 241 return err 242 } 243 target = pb.ChildTarget 244 exists = true 245 return nil 246 }) 247 return target, exists 248 } 249 250 // Close safely closes the block database. 251 func (cs *ConsensusSet) Close() error { 252 return cs.tg.Stop() 253 } 254 255 // managedCurrentBlock returns the latest block in the heaviest known blockchain. 256 func (cs *ConsensusSet) managedCurrentBlock() (block types.Block) { 257 cs.mu.RLock() 258 defer cs.mu.RUnlock() 259 260 _ = cs.db.View(func(tx *bolt.Tx) error { 261 pb := currentProcessedBlock(tx) 262 block = pb.Block 263 return nil 264 }) 265 return block 266 } 267 268 // CurrentBlock returns the latest block in the heaviest known blockchain. 269 func (cs *ConsensusSet) CurrentBlock() (block types.Block) { 270 // A call to a closed database can cause undefined behavior. 271 err := cs.tg.Add() 272 if err != nil { 273 return types.Block{} 274 } 275 defer cs.tg.Done() 276 277 // Block until a lock can be grabbed on the consensus set, indicating that 278 // all modules have received the most recent block. The lock is held so that 279 // there are no race conditions when trying to synchronize nodes. 280 cs.mu.Lock() 281 defer cs.mu.Unlock() 282 283 _ = cs.db.View(func(tx *bolt.Tx) error { 284 pb := currentProcessedBlock(tx) 285 block = pb.Block 286 return nil 287 }) 288 return block 289 } 290 291 // Flush will block until the consensus set has finished all in-progress 292 // routines. 293 func (cs *ConsensusSet) Flush() error { 294 return cs.tg.Flush() 295 } 296 297 // Height returns the height of the consensus set. 298 func (cs *ConsensusSet) Height() (height types.BlockHeight) { 299 // A call to a closed database can cause undefined behavior. 300 err := cs.tg.Add() 301 if err != nil { 302 return 0 303 } 304 defer cs.tg.Done() 305 306 // Block until a lock can be grabbed on the consensus set, indicating that 307 // all modules have received the most recent block. The lock is held so that 308 // there are no race conditions when trying to synchronize nodes. 309 cs.mu.Lock() 310 defer cs.mu.Unlock() 311 312 _ = cs.db.View(func(tx *bolt.Tx) error { 313 height = blockHeight(tx) 314 return nil 315 }) 316 return height 317 } 318 319 // InCurrentPath returns true if the block presented is in the current path, 320 // false otherwise. 321 func (cs *ConsensusSet) InCurrentPath(id types.BlockID) (inPath bool) { 322 // A call to a closed database can cause undefined behavior. 323 err := cs.tg.Add() 324 if err != nil { 325 return false 326 } 327 defer cs.tg.Done() 328 329 _ = cs.db.View(func(tx *bolt.Tx) error { 330 pb, err := getBlockMap(tx, id) 331 if err != nil { 332 inPath = false 333 return nil 334 } 335 pathID, err := getPath(tx, pb.Height) 336 if err != nil { 337 inPath = false 338 return nil 339 } 340 inPath = pathID == id 341 return nil 342 }) 343 return inPath 344 } 345 346 // MinimumValidChildTimestamp returns the earliest timestamp that the next block 347 // can have in order for it to be considered valid. 348 func (cs *ConsensusSet) MinimumValidChildTimestamp(id types.BlockID) (timestamp types.Timestamp, exists bool) { 349 // A call to a closed database can cause undefined behavior. 350 err := cs.tg.Add() 351 if err != nil { 352 return 0, false 353 } 354 defer cs.tg.Done() 355 356 // Error is not checked because it does not matter. 357 _ = cs.db.View(func(tx *bolt.Tx) error { 358 pb, err := getBlockMap(tx, id) 359 if err != nil { 360 return err 361 } 362 timestamp = cs.blockRuleHelper.minimumValidChildTimestamp(tx.Bucket(BlockMap), pb) 363 exists = true 364 return nil 365 }) 366 return timestamp, exists 367 } 368 369 // StorageProofSegment returns the segment to be used in the storage proof for 370 // a given file contract. 371 func (cs *ConsensusSet) StorageProofSegment(fcid types.FileContractID) (index uint64, err error) { 372 // A call to a closed database can cause undefined behavior. 373 err = cs.tg.Add() 374 if err != nil { 375 return 0, err 376 } 377 defer cs.tg.Done() 378 379 _ = cs.db.View(func(tx *bolt.Tx) error { 380 index, err = storageProofSegment(tx, fcid) 381 return nil 382 }) 383 return index, err 384 }