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 }