// removeFileContract removes a file contract from the database. func removeFileContract(tx *bolt.Tx, id types.FileContractID) { // Delete the file contract entry. fcBucket := tx.Bucket(FileContracts) fcBytes := fcBucket.Get(id[:]) // Sanity check - should not be removing a file contract not in the db. if build.DEBUG && fcBytes == nil { panic("nil file contract") } err := fcBucket.Delete(id[:]) if build.DEBUG && err != nil { panic(err) } // Delete the entry for the file contract's expiration. The portion of // 'fcBytes' used to determine the expiration bucket id is the // byte-representation of the file contract window end, which always // appears at bytes 48-56. expirationBucketID := append(prefixFCEX, fcBytes[48:56]...) expirationBucket := tx.Bucket(expirationBucketID) expirationBytes := expirationBucket.Get(id[:]) if expirationBytes == nil { panic(errNilItem) } err = expirationBucket.Delete(id[:]) if build.DEBUG && err != nil { panic(err) } }
func dbRemoveFileContractRevision(tx *bolt.Tx, fcid types.FileContractID) { var history fileContractHistory assertNil(dbGetAndDecode(bucketFileContractHistories, fcid, &history)(tx)) // TODO: could be more rigorous history.Revisions = history.Revisions[:len(history.Revisions)-1] mustPut(tx.Bucket(bucketFileContractHistories), fcid, history) }
// transactionConfirmed returns true if the transaction has been confirmed on // the blockchain and false if the transaction has not been confirmed on the // blockchain. func (tp *TransactionPool) transactionConfirmed(tx *bolt.Tx, id types.TransactionID) bool { confirmedBytes := tx.Bucket(bucketConfirmedTransactions).Get(id[:]) if confirmedBytes == nil { return false } return true }
// addFileContract adds a file contract to the database. An error is returned // if the file contract is already in the database. func addFileContract(tx *bolt.Tx, id types.FileContractID, fc types.FileContract) { // Add the file contract to the database. fcBucket := tx.Bucket(FileContracts) // Sanity check - should not be adding a zero-payout file contract. if build.DEBUG && fc.Payout.IsZero() { panic("adding zero-payout file contract") } // Sanity check - should not be adding a file contract already in the db. if build.DEBUG && fcBucket.Get(id[:]) != nil { panic("repeat file contract") } err := fcBucket.Put(id[:], encoding.Marshal(fc)) if build.DEBUG && err != nil { panic(err) } // Add an entry for when the file contract expires. expirationBucketID := append(prefixFCEX, encoding.Marshal(fc.WindowEnd)...) expirationBucket, err := tx.CreateBucketIfNotExists(expirationBucketID) if build.DEBUG && err != nil { panic(err) } err = expirationBucket.Put(id[:], []byte{}) if build.DEBUG && err != nil { panic(err) } }
// addBlockMap adds a processed block to the block map. func addBlockMap(tx *bolt.Tx, pb *processedBlock) { id := pb.Block.ID() err := tx.Bucket(BlockMap).Put(id[:], encoding.Marshal(*pb)) if build.DEBUG && err != nil { panic(err) } }
// applyFileContractMaintenance looks for all of the file contracts that have // expired without an appropriate storage proof, and calls 'applyMissedProof' // for the file contract. func applyFileContractMaintenance(tx *bolt.Tx, pb *processedBlock) { // Get the bucket pointing to all of the expiring file contracts. fceBucketID := append(prefixFCEX, encoding.Marshal(pb.Height)...) fceBucket := tx.Bucket(fceBucketID) // Finish if there are no expiring file contracts. if fceBucket == nil { return } var dscods []modules.DelayedSiacoinOutputDiff var fcds []modules.FileContractDiff err := fceBucket.ForEach(func(keyBytes, valBytes []byte) error { var id types.FileContractID copy(id[:], keyBytes) amspDSCODS, fcd := applyMissedStorageProof(tx, pb, id) fcds = append(fcds, fcd) dscods = append(dscods, amspDSCODS...) return nil }) if build.DEBUG && err != nil { panic(err) } for _, dscod := range dscods { pb.DelayedSiacoinOutputDiffs = append(pb.DelayedSiacoinOutputDiffs, dscod) commitDelayedSiacoinOutputDiff(tx, dscod, modules.DiffApply) } for _, fcd := range fcds { pb.FileContractDiffs = append(pb.FileContractDiffs, fcd) commitFileContractDiff(tx, fcd, modules.DiffApply) } err = tx.DeleteBucket(fceBucketID) if build.DEBUG && err != nil { panic(err) } }
// validSiacoins checks that the siacoin inputs and outputs are valid in the // context of the current consensus set. func validSiacoins(tx *bolt.Tx, t types.Transaction) error { scoBucket := tx.Bucket(SiacoinOutputs) var inputSum types.Currency for _, sci := range t.SiacoinInputs { // Check that the input spends an existing output. scoBytes := scoBucket.Get(sci.ParentID[:]) if scoBytes == nil { return errMissingSiacoinOutput } // Check that the unlock conditions match the required unlock hash. var sco types.SiacoinOutput err := encoding.Unmarshal(scoBytes, &sco) if build.DEBUG && err != nil { panic(err) } if sci.UnlockConditions.UnlockHash() != sco.UnlockHash { return errWrongUnlockConditions } inputSum = inputSum.Add(sco.Value) } if inputSum.Cmp(t.SiacoinOutputSum()) != 0 { return errSiacoinInputOutputMismatch } return nil }
// putStorageObligation places a storage obligation into the database, // overwriting the existing storage obligation if there is one. func putStorageObligation(tx *bolt.Tx, so storageObligation) error { soBytes, err := json.Marshal(so) if err != nil { return err } soid := so.id() return tx.Bucket(bucketStorageObligations).Put(soid[:], soBytes) }
// inconsistencyDetected indicates whether inconsistency has been detected // within the database. func inconsistencyDetected(tx *bolt.Tx) (detected bool) { inconsistencyBytes := tx.Bucket(Consistency).Get(Consistency) err := encoding.Unmarshal(inconsistencyBytes, &detected) if build.DEBUG && err != nil { panic(err) } return detected }
// getRecentConsensusChange returns the most recent consensus change from the // database. func (tp *TransactionPool) getRecentConsensusChange(tx *bolt.Tx) (cc modules.ConsensusChangeID, err error) { ccBytes := tx.Bucket(bucketRecentConsensusChange).Get(fieldRecentConsensusChange) if ccBytes == nil { return modules.ConsensusChangeID{}, errNilConsensusChange } copy(cc[:], ccBytes) return cc, nil }
// markInconsistency flags the database to indicate that inconsistency has been // detected. func markInconsistency(tx *bolt.Tx) { // Place a 'true' in the consistency bucket to indicate that // inconsistencies have been found. err := tx.Bucket(Consistency).Put(Consistency, encoding.Marshal(true)) if build.DEBUG && err != nil { panic(err) } }
// blockHeight returns the height of the blockchain. func blockHeight(tx *bolt.Tx) types.BlockHeight { var height types.BlockHeight bh := tx.Bucket(BlockHeight) err := encoding.Unmarshal(bh.Get(BlockHeight), &height) if build.DEBUG && err != nil { panic(err) } return height }
// removeSiafundOutput removes a siafund output from the database. An error is // returned if the siafund output is not in the database prior to removal. func removeSiafundOutput(tx *bolt.Tx, id types.SiafundOutputID) { sfoBucket := tx.Bucket(SiafundOutputs) if build.DEBUG && sfoBucket.Get(id[:]) == nil { panic("nil siafund output") } err := sfoBucket.Delete(id[:]) if build.DEBUG && err != nil { panic(err) } }
// getFileContract fetches a file contract from the database, returning an // error if it is not there. func getFileContract(tx *bolt.Tx, id types.FileContractID) (fc types.FileContract, err error) { fcBytes := tx.Bucket(FileContracts).Get(id[:]) if fcBytes == nil { return types.FileContract{}, errNilItem } err = encoding.Unmarshal(fcBytes, &fc) if err != nil { return types.FileContract{}, err } return fc, nil }
// removeSiacoinOutput removes a siacoin output from the database. An error is // returned if the siacoin output is not in the database prior to removal. func removeSiacoinOutput(tx *bolt.Tx, id types.SiacoinOutputID) { scoBucket := tx.Bucket(SiacoinOutputs) // Sanity check - should not be removing an item that is not in the db. if build.DEBUG && scoBucket.Get(id[:]) == nil { panic("nil siacoin output") } err := scoBucket.Delete(id[:]) if build.DEBUG && err != nil { panic(err) } }
// createConsensusObjects initialzes the consensus portions of the database. func (cs *ConsensusSet) createConsensusDB(tx *bolt.Tx) error { // Enumerate and create the database buckets. buckets := [][]byte{ BlockHeight, BlockMap, BlockPath, Consistency, SiacoinOutputs, FileContracts, SiafundOutputs, SiafundPool, } for _, bucket := range buckets { _, err := tx.CreateBucket(bucket) if err != nil { return err } } // Set the block height to -1, so the genesis block is at height 0. blockHeight := tx.Bucket(BlockHeight) underflow := types.BlockHeight(0) err := blockHeight.Put(BlockHeight, encoding.Marshal(underflow-1)) if err != nil { return err } // Set the siafund pool to 0. setSiafundPool(tx, types.NewCurrency64(0)) // Update the siafund output diffs map for the genesis block on disk. This // needs to happen between the database being opened/initilized and the // consensus set hash being calculated for _, sfod := range cs.blockRoot.SiafundOutputDiffs { commitSiafundOutputDiff(tx, sfod, modules.DiffApply) } // Add the miner payout from the genesis block to the delayed siacoin // outputs - unspendable, as the unlock hash is blank. createDSCOBucket(tx, types.MaturityDelay) addDSCO(tx, types.MaturityDelay, cs.blockRoot.Block.MinerPayoutID(0), types.SiacoinOutput{ Value: types.CalculateCoinbase(0), UnlockHash: types.UnlockHash{}, }) // Add the genesis block to the block strucutres - checksum must be taken // after pushing the genesis block into the path. pushPath(tx, cs.blockRoot.Block.ID()) if build.DEBUG { cs.blockRoot.ConsensusChecksum = consensusChecksum(tx) } addBlockMap(tx, &cs.blockRoot) return nil }
// getStorageObligation fetches a storage obligation from the database tx. func getStorageObligation(tx *bolt.Tx, soid types.FileContractID) (so storageObligation, err error) { soBytes := tx.Bucket(bucketStorageObligations).Get(soid[:]) if soBytes == nil { return storageObligation{}, errNoStorageObligation } err = json.Unmarshal(soBytes, &so) if err != nil { return storageObligation{}, err } return so, nil }
// getPath returns the block id at 'height' in the block path. func getPath(tx *bolt.Tx, height types.BlockHeight) (id types.BlockID, err error) { idBytes := tx.Bucket(BlockPath).Get(encoding.Marshal(height)) if idBytes == nil { return types.BlockID{}, errNilItem } err = encoding.Unmarshal(idBytes, &id) if build.DEBUG && err != nil { panic(err) } return id, nil }
// generateAndApplyDiff will verify the block and then integrate it into the // consensus state. These two actions must happen at the same time because // transactions are allowed to depend on each other. We can't be sure that a // transaction is valid unless we have applied all of the previous transactions // in the block, which means we need to apply while we verify. func generateAndApplyDiff(tx *bolt.Tx, pb *processedBlock) error { // Sanity check - the block being applied should have the current block as // a parent. if build.DEBUG && pb.Block.ParentID != currentBlockID(tx) { panic(errInvalidSuccessor) } // Create the bucket to hold all of the delayed siacoin outputs created by // transactions this block. Needs to happen before any transactions are // applied. createDSCOBucket(tx, pb.Height+types.MaturityDelay) // Validate and apply each transaction in the block. They cannot be // validated all at once because some transactions may not be valid until // previous transactions have been applied. for _, txn := range pb.Block.Transactions { err := validTransaction(tx, txn) if err != nil { return err } applyTransaction(tx, pb, txn) } // After all of the transactions have been applied, 'maintenance' is // applied on the block. This includes adding any outputs that have reached // maturity, applying any contracts with missed storage proofs, and adding // the miner payouts to the list of delayed outputs. applyMaintenance(tx, pb) // DiffsGenerated are only set to true after the block has been fully // validated and integrated. This is required to prevent later blocks from // being accepted on top of an invalid block - if the consensus set ever // forks over an invalid block, 'DiffsGenerated' will be set to 'false', // requiring validation to occur again. when 'DiffsGenerated' is set to // true, validation is skipped, therefore the flag should only be set to // true on fully validated blocks. pb.DiffsGenerated = true // Add the block to the current path and block map. bid := pb.Block.ID() blockMap := tx.Bucket(BlockMap) updateCurrentPath(tx, pb, modules.DiffApply) // Sanity check preparation - set the consensus hash at this height so that // during reverting a check can be performed to assure consistency when // adding and removing blocks. Must happen after the block is added to the // path. if build.DEBUG { pb.ConsensusChecksum = consensusChecksum(tx) } return blockMap.Put(bid[:], encoding.Marshal(*pb)) }
// getSiafundOutput fetches a siafund output from the database. An error is // returned if the siafund output does not exist. func getSiafundOutput(tx *bolt.Tx, id types.SiafundOutputID) (types.SiafundOutput, error) { sfoBytes := tx.Bucket(SiafundOutputs).Get(id[:]) if sfoBytes == nil { return types.SiafundOutput{}, errNilItem } var sfo types.SiafundOutput err := encoding.Unmarshal(sfoBytes, &sfo) if err != nil { return types.SiafundOutput{}, err } return sfo, nil }
// removeDSCO removes a delayed siacoin output from the consensus set. func removeDSCO(tx *bolt.Tx, bh types.BlockHeight, id types.SiacoinOutputID) { bucketID := append(prefixDSCO, encoding.Marshal(bh)...) // Sanity check - should not remove an item not in the db. dscoBucket := tx.Bucket(bucketID) if build.DEBUG && dscoBucket.Get(id[:]) == nil { panic("nil dsco") } err := dscoBucket.Delete(id[:]) if build.DEBUG && err != nil { panic(err) } }
// getSiafundPool returns the current value of the siafund pool. No error is // returned as the siafund pool should always be available. func getSiafundPool(tx *bolt.Tx) (pool types.Currency) { bucket := tx.Bucket(SiafundPool) poolBytes := bucket.Get(SiafundPool) // An error should only be returned if the object stored in the siafund // pool bucket is either unavailable or otherwise malformed. As this is a // developer error, a panic is appropriate. err := encoding.Unmarshal(poolBytes, &pool) if build.DEBUG && err != nil { panic(err) } return pool }
// getEntry returns the change entry with a given id, using a bool to indicate // existence. func getEntry(tx *bolt.Tx, id modules.ConsensusChangeID) (ce changeEntry, exists bool) { var cn changeNode cl := tx.Bucket(ChangeLog) changeNodeBytes := cl.Get(id[:]) if changeNodeBytes == nil { return changeEntry{}, false } err := encoding.Unmarshal(changeNodeBytes, &cn) if build.DEBUG && err != nil { panic(err) } return cn.Entry, true }
// NextEntry returns the entry after the current entry. func (ce *changeEntry) NextEntry(tx *bolt.Tx) (nextEntry changeEntry, exists bool) { // Get the change node associated with the provided change entry. ceid := ce.ID() var cn changeNode cl := tx.Bucket(ChangeLog) changeNodeBytes := cl.Get(ceid[:]) err := encoding.Unmarshal(changeNodeBytes, &cn) if build.DEBUG && err != nil { panic(err) } return getEntry(tx, cn.Next) }
// blockHeight returns the height of the blockchain. func blockHeight(tx *bolt.Tx) types.BlockHeight { var height types.BlockHeight bh := tx.Bucket(BlockHeight) err := encoding.Unmarshal(bh.Get(BlockHeight), &height) if build.DEBUG && err != nil { panic(err) } // Check that there was not an underflow on the height. zeroHeight := types.BlockHeight(0) if height > zeroHeight-1e9 { panic(height) } return types.BlockHeight(height) }
// getBlockMap returns a processed block with the input id. func getBlockMap(tx *bolt.Tx, id types.BlockID) (*processedBlock, error) { // Look up the encoded block. pbBytes := tx.Bucket(BlockMap).Get(id[:]) if pbBytes == nil { return nil, errNilItem } // Decode the block - should never fail. var pb processedBlock err := encoding.Unmarshal(pbBytes, &pb) if build.DEBUG && err != nil { panic(err) } return &pb, nil }
// addSiafundOutput adds a siafund output to the database. An error is returned // if the siafund output is already in the database. func addSiafundOutput(tx *bolt.Tx, id types.SiafundOutputID, sfo types.SiafundOutput) { siafundOutputs := tx.Bucket(SiafundOutputs) // Sanity check - should not be adding a siafund output with a value of // zero. if build.DEBUG && sfo.Value.IsZero() { panic("zero value siafund being added") } // Sanity check - should not be adding an item already in the db. if build.DEBUG && siafundOutputs.Get(id[:]) != nil { panic("repeat siafund output") } err := siafundOutputs.Put(id[:], encoding.Marshal(sfo)) if build.DEBUG && err != nil { panic(err) } }
// newChild creates a blockNode from a block and adds it to the parent's set of // children. The new node is also returned. It necessairly modifies the database func (cs *ConsensusSet) newChild(tx *bolt.Tx, pb *processedBlock, b types.Block) *processedBlock { // Create the child node. childID := b.ID() child := &processedBlock{ Block: b, Height: pb.Height + 1, Depth: pb.childDepth(), } blockMap := tx.Bucket(BlockMap) cs.setChildTarget(blockMap, child) err := blockMap.Put(childID[:], encoding.Marshal(*child)) if build.DEBUG && err != nil { panic(err) } return child }
// deleteDSCOBucket deletes the bucket that held a set of delayed siacoin // outputs. func deleteDSCOBucket(tx *bolt.Tx, bh types.BlockHeight) { // Delete the bucket. bucketID := append(prefixDSCO, encoding.Marshal(bh)...) bucket := tx.Bucket(bucketID) if build.DEBUG && bucket == nil { panic(errNilBucket) } // TODO: Check that the bucket is empty. Using Stats() does not work at the // moment, as there is an error in the boltdb code. err := tx.DeleteBucket(bucketID) if build.DEBUG && err != nil { panic(err) } }
// consensusChecksum grabs a checksum of the consensus set by pushing all of // the elements in sorted order into a merkle tree and taking the root. All // consensus sets with the same current block should have identical consensus // checksums. func consensusChecksum(tx *bolt.Tx) crypto.Hash { // Create a checksum tree. tree := crypto.NewTree() // For all of the constant buckets, push every key and every value. Buckets // are sorted in byte-order, therefore this operation is deterministic. consensusSetBuckets := []*bolt.Bucket{ tx.Bucket(BlockPath), tx.Bucket(SiacoinOutputs), tx.Bucket(FileContracts), tx.Bucket(SiafundOutputs), tx.Bucket(SiafundPool), } for i := range consensusSetBuckets { err := consensusSetBuckets[i].ForEach(func(k, v []byte) error { tree.Push(k) tree.Push(v) return nil }) if err != nil { manageErr(tx, err) } } // Iterate through all the buckets looking for buckets prefixed with // prefixDSCO or prefixFCEX. Buckets are presented in byte-sorted order by // name. err := tx.ForEach(func(name []byte, b *bolt.Bucket) error { // If the bucket is not a delayed siacoin output bucket or a file // contract expiration bucket, skip. if !bytes.HasPrefix(name, prefixDSCO) && !bytes.HasPrefix(name, prefixFCEX) { return nil } // The bucket is a prefixed bucket - add all elements to the tree. return b.ForEach(func(k, v []byte) error { tree.Push(k) tree.Push(v) return nil }) }) if err != nil { manageErr(tx, err) } return tree.Root() }