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 }