// ReceiveUpdatedUnconfirmedTransactions updates the wallet's unconfirmed // transaction set. func (w *Wallet) ReceiveUpdatedUnconfirmedTransactions(txns []types.Transaction, _ modules.ConsensusChange) { // There are two different situations under which a subscribee calls // ProcessConsensusChange. The first is when w.subscribed is set to false // AND the mutex is already locked. The other situation is that subscribed // is set to true and is not going to be changed. Therefore there is no // race condition here. If w.subscribed is set to false, trying to grab the // lock would cause a deadlock. if w.subscribed { lockID := w.mu.Lock() defer w.mu.Unlock(lockID) } w.unconfirmedProcessedTransactions = nil for _, txn := range txns { // To save on code complexity, relveancy is determined while building // up the wallet transaction. relevant := false pt := modules.ProcessedTransaction{ Transaction: txn, TransactionID: txn.ID(), ConfirmationHeight: types.BlockHeight(math.MaxUint64), ConfirmationTimestamp: types.Timestamp(math.MaxUint64), } for _, sci := range txn.SiacoinInputs { _, exists := w.keys[sci.UnlockConditions.UnlockHash()] if exists { relevant = true } pt.Inputs = append(pt.Inputs, modules.ProcessedInput{ FundType: types.SpecifierSiacoinInput, WalletAddress: exists, RelatedAddress: sci.UnlockConditions.UnlockHash(), Value: w.historicOutputs[types.OutputID(sci.ParentID)], }) } for i, sco := range txn.SiacoinOutputs { _, exists := w.keys[sco.UnlockHash] if exists { relevant = true } pt.Outputs = append(pt.Outputs, modules.ProcessedOutput{ FundType: types.SpecifierSiacoinOutput, MaturityHeight: types.BlockHeight(math.MaxUint64), WalletAddress: exists, RelatedAddress: sco.UnlockHash, Value: sco.Value, }) w.historicOutputs[types.OutputID(txn.SiacoinOutputID(i))] = sco.Value } for _, fee := range txn.MinerFees { pt.Outputs = append(pt.Outputs, modules.ProcessedOutput{ FundType: types.SpecifierMinerFee, Value: fee, }) } if relevant { w.unconfirmedProcessedTransactions = append(w.unconfirmedProcessedTransactions, pt) } } }
// ProcessConsensusDigest will update the miner's most recent block. func (m *Miner) ProcessConsensusChange(cc modules.ConsensusChange) { m.mu.Lock() defer m.mu.Unlock() // Adjust the height of the miner. m.persist.Height -= types.BlockHeight(len(cc.RevertedBlocks)) m.persist.Height += types.BlockHeight(len(cc.AppliedBlocks)) // Update the unsolved block. var exists1, exists2 bool m.persist.UnsolvedBlock.ParentID = cc.AppliedBlocks[len(cc.AppliedBlocks)-1].ID() m.persist.Target, exists1 = m.cs.ChildTarget(m.persist.UnsolvedBlock.ParentID) m.persist.UnsolvedBlock.Timestamp, exists2 = m.cs.MinimumValidChildTimestamp(m.persist.UnsolvedBlock.ParentID) if build.DEBUG && !exists1 { panic("could not get child target") } if build.DEBUG && !exists2 { panic("could not get child earliest timestamp") } // There is a new parent block, the source block should be updated to keep // the stale rate as low as possible. m.newSourceBlock() m.persist.RecentChange = cc.ID // Save the new consensus information. err := m.save() if err != nil { m.log.Println("ERROR:", err) } }
// ReceiveConsensusSetUpdate will be called by the consensus set every time // there is a change in the blockchain. Updates will always be called in order. func (r *Renter) ReceiveConsensusSetUpdate(cc modules.ConsensusChange) { lockID := r.mu.Lock() defer r.mu.Unlock(lockID) r.blockHeight -= types.BlockHeight(len(cc.RevertedBlocks)) r.blockHeight += types.BlockHeight(len(cc.AppliedBlocks)) r.updateSubscribers() }
// ProcessConsensusChange will update the miner's most recent block. This is a // part of the ConsensusSetSubscriber interface. func (m *Miner) ProcessConsensusChange(cc modules.ConsensusChange) { lockID := m.mu.Lock() defer m.mu.Unlock(lockID) m.height -= types.BlockHeight(len(cc.RevertedBlocks)) m.height += types.BlockHeight(len(cc.AppliedBlocks)) if len(cc.AppliedBlocks) == 0 { return } m.parent = cc.AppliedBlocks[len(cc.AppliedBlocks)-1].ID() target, exists1 := m.cs.ChildTarget(m.parent) timestamp, exists2 := m.cs.EarliestChildTimestamp(m.parent) if build.DEBUG { if !exists1 { panic("could not get child target") } if !exists2 { panic("could not get child earliest timestamp") } } m.target = target m.earliestTimestamp = timestamp m.prepareNewBlock() }
// walletTransactionsHandler handles API calls to /wallet/transactions. func (api *API) walletTransactionsHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { startheightStr, endheightStr := req.FormValue("startheight"), req.FormValue("endheight") if startheightStr == "" || endheightStr == "" { WriteError(w, Error{"startheight and endheight must be provided to a /wallet/transactions call."}, http.StatusBadRequest) return } // Get the start and end blocks. start, err := strconv.Atoi(startheightStr) if err != nil { WriteError(w, Error{"parsing integer value for parameter `startheight` failed: " + err.Error()}, http.StatusBadRequest) return } end, err := strconv.Atoi(endheightStr) if err != nil { WriteError(w, Error{"parsing integer value for parameter `endheight` failed: " + err.Error()}, http.StatusBadRequest) return } confirmedTxns, err := api.wallet.Transactions(types.BlockHeight(start), types.BlockHeight(end)) if err != nil { WriteError(w, Error{"error after call to /wallet/transactions: " + err.Error()}, http.StatusBadRequest) return } unconfirmedTxns := api.wallet.UnconfirmedTransactions() WriteJSON(w, WalletTransactionsGET{ ConfirmedTransactions: confirmedTxns, UnconfirmedTransactions: unconfirmedTxns, }) }
// ReceiveConsensusSetUpdate gets called by the consensus set every time there // is a change to the blockchain. func (srv *Server) ReceiveConsensusSetUpdate(cc modules.ConsensusChange) { lockID := srv.mu.Lock() defer srv.mu.Unlock(lockID) srv.blockchainHeight -= types.BlockHeight(len(cc.RevertedBlocks)) srv.blockchainHeight += types.BlockHeight(len(cc.AppliedBlocks)) srv.currentBlock = cc.AppliedBlocks[len(cc.AppliedBlocks)-1] }
// ReceiveUpdatedUnconfirmedTransactions updates the wallet's unconfirmed // transaction set. func (w *Wallet) ReceiveUpdatedUnconfirmedTransactions(txns []types.Transaction, _ modules.ConsensusChange) { if err := w.tg.Add(); err != nil { // Gracefully reject transactions if the wallet's Close method has // closed the wallet's ThreadGroup already. return } defer w.tg.Done() w.mu.Lock() defer w.mu.Unlock() w.unconfirmedProcessedTransactions = nil for _, txn := range txns { // To save on code complexity, relevancy is determined while building // up the wallet transaction. relevant := false pt := modules.ProcessedTransaction{ Transaction: txn, TransactionID: txn.ID(), ConfirmationHeight: types.BlockHeight(math.MaxUint64), ConfirmationTimestamp: types.Timestamp(math.MaxUint64), } for _, sci := range txn.SiacoinInputs { _, exists := w.keys[sci.UnlockConditions.UnlockHash()] if exists { relevant = true } pt.Inputs = append(pt.Inputs, modules.ProcessedInput{ FundType: types.SpecifierSiacoinInput, WalletAddress: exists, RelatedAddress: sci.UnlockConditions.UnlockHash(), Value: w.historicOutputs[types.OutputID(sci.ParentID)], }) } for i, sco := range txn.SiacoinOutputs { _, exists := w.keys[sco.UnlockHash] if exists { relevant = true } pt.Outputs = append(pt.Outputs, modules.ProcessedOutput{ FundType: types.SpecifierSiacoinOutput, MaturityHeight: types.BlockHeight(math.MaxUint64), WalletAddress: exists, RelatedAddress: sco.UnlockHash, Value: sco.Value, }) w.historicOutputs[types.OutputID(txn.SiacoinOutputID(uint64(i)))] = sco.Value } for _, fee := range txn.MinerFees { pt.Outputs = append(pt.Outputs, modules.ProcessedOutput{ FundType: types.SpecifierMinerFee, Value: fee, }) } if relevant { w.unconfirmedProcessedTransactions = append(w.unconfirmedProcessedTransactions, pt) } } }
// ProcessConsensusChange will be called by the consensus set every time there // is a change in the blockchain. Updates will always be called in order. func (r *Renter) ProcessConsensusChange(cc modules.ConsensusChange) { lockID := r.mu.Lock() defer r.mu.Unlock(lockID) r.blockHeight -= types.BlockHeight(len(cc.RevertedBlocks)) r.blockHeight += types.BlockHeight(len(cc.AppliedBlocks)) // Add hosts announced in blocks that were applied. for _, block := range cc.AppliedBlocks { for _, host := range findHostAnnouncements(block) { r.hostDB.InsertHost(host) } } }
// ReceiveTransactionPoolUpdate listens to the transaction pool for changes in // the transaction pool. These changes will be applied to the blocks being // mined. func (m *Miner) ReceiveTransactionPoolUpdate(cc modules.ConsensusChange, unconfirmedTransactions []types.Transaction, _ []modules.SiacoinOutputDiff) { lockID := m.mu.Lock() defer m.mu.Unlock(lockID) defer m.notifySubscribers() m.height -= types.BlockHeight(len(cc.RevertedBlocks)) m.height += types.BlockHeight(len(cc.AppliedBlocks)) // The total encoded size of the transactions cannot exceed the block size. m.transactions = nil remainingSize := int(types.BlockSizeLimit - 5e3) for { if len(unconfirmedTransactions) == 0 { break } remainingSize -= len(encoding.Marshal(unconfirmedTransactions[0])) if remainingSize < 0 { break } m.transactions = append(m.transactions, unconfirmedTransactions[0]) unconfirmedTransactions = unconfirmedTransactions[1:] } // If no blocks have been applied, the block variables do not need to be // updated. if len(cc.AppliedBlocks) == 0 { if build.DEBUG { if len(cc.RevertedBlocks) != 0 { panic("blocks reverted without being added") } } return } // Update the parent, target, and earliest timestamp fields for the miner. m.parent = cc.AppliedBlocks[len(cc.AppliedBlocks)-1].ID() target, exists1 := m.cs.ChildTarget(m.parent) timestamp, exists2 := m.cs.EarliestChildTimestamp(m.parent) if build.DEBUG { if !exists1 { panic("could not get child target") } if !exists2 { panic("could not get child earliest timestamp") } } m.target = target m.earliestTimestamp = timestamp }
// 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) }
// blockHistory returns up to 32 block ids, starting with recent blocks and // then proving exponentially increasingly less recent blocks. The genesis // block is always included as the last block. This block history can be used // to find a common parent that is reasonably recent, usually the most recent // common parent is found, but always a common parent within a factor of 2 is // found. func blockHistory(tx *bolt.Tx) (blockIDs [32]types.BlockID) { height := blockHeight(tx) step := types.BlockHeight(1) // The final step is to include the genesis block, which is why the final // element is skipped during iteration. for i := 0; i < 31; i++ { // Include the next block. blockID, err := getPath(tx, height) if build.DEBUG && err != nil { panic(err) } blockIDs[i] = blockID // Determine the height of the next block to include and then increase // the step size. The height must be decreased first to prevent // underflow. // // `i >= 9` means that the first 10 blocks will be included, and then // skipping will start. if i >= 9 { step *= 2 } if height < step { break } height -= step } // Include the genesis block as the last element blockID, err := getPath(tx, 0) if build.DEBUG && err != nil { panic(err) } blockIDs[31] = blockID return blockIDs }
// createTpoolTester returns a ready-to-use tpool tester, with all modules // initialized. func createTpoolTester(name string) (*tpoolTester, error) { // Initialize the modules. testdir := build.TempDir(modules.TransactionPoolDir, name) g, err := gateway.New("localhost:0", false, filepath.Join(testdir, modules.GatewayDir)) if err != nil { return nil, err } cs, err := consensus.New(g, false, filepath.Join(testdir, modules.ConsensusDir)) if err != nil { return nil, err } tp, err := New(cs, g, filepath.Join(testdir, modules.TransactionPoolDir)) if err != nil { return nil, err } w, err := wallet.New(cs, tp, filepath.Join(testdir, modules.WalletDir)) if err != nil { return nil, err } var key crypto.TwofishKey _, err = rand.Read(key[:]) if err != nil { return nil, err } _, err = w.Encrypt(key) if err != nil { return nil, err } err = w.Unlock(key) if err != nil { return nil, err } m, err := miner.New(cs, tp, w, filepath.Join(testdir, modules.MinerDir)) if err != nil { return nil, err } // Assemble all of the objects into a tpoolTester tpt := &tpoolTester{ cs: cs, gateway: g, tpool: tp, miner: m, wallet: w, walletKey: key, persistDir: testdir, } // Mine blocks until there is money in the wallet. for i := types.BlockHeight(0); i <= types.MaturityDelay; i++ { b, _ := tpt.miner.FindBlock() err = tpt.cs.AcceptBlock(b) if err != nil { return nil, err } } return tpt, nil }
// extend adds blocks to cstAlt until cstAlt has more weight than cstMain. Then // cstMain is caught up, causing cstMain to perform a reorg that extends all // the way to the genesis block. func (rs *reorgSets) extend() { for rs.cstMain.cs.dbBlockHeight() >= rs.cstAlt.cs.dbBlockHeight() { _, err := rs.cstAlt.miner.AddBlock() if err != nil { panic(err) } } for i := types.BlockHeight(1); i <= rs.cstAlt.cs.dbBlockHeight(); i++ { id, err := rs.cstAlt.cs.dbGetPath(i) if err != nil { panic(err) } pb, err := rs.cstAlt.cs.dbGetBlockMap(id) if err != nil { panic(err) } _ = rs.cstMain.cs.AcceptBlock(pb.Block) } // Check that cstMain and cstAlt are even. if rs.cstMain.cs.dbCurrentProcessedBlock().Block.ID() != rs.cstAlt.cs.dbCurrentProcessedBlock().Block.ID() { panic("could not save cstMain into cstAlt") } if rs.cstMain.cs.dbConsensusChecksum() != rs.cstAlt.cs.dbConsensusChecksum() { panic("reorg checksums do not match after extending") } }
// createExplorerTester creates a tester object for the explorer module. func createExplorerTester(name string) (*explorerTester, error) { // Create and assemble the dependencies. testdir := build.TempDir(modules.HostDir, name) g, err := gateway.New(":0", filepath.Join(testdir, modules.GatewayDir)) if err != nil { return nil, err } cs, err := consensus.New(g, filepath.Join(testdir, modules.ConsensusDir)) if err != nil { return nil, err } tp, err := transactionpool.New(cs, g) if err != nil { return nil, err } w, err := wallet.New(cs, tp, filepath.Join(testdir, modules.WalletDir)) if err != nil { return nil, err } key, err := crypto.GenerateTwofishKey() if err != nil { return nil, err } _, err = w.Encrypt(key) if err != nil { return nil, err } err = w.Unlock(key) if err != nil { return nil, err } m, err := miner.New(cs, tp, w, filepath.Join(testdir, modules.RenterDir)) if err != nil { return nil, err } e, err := New(cs, filepath.Join(testdir, modules.ExplorerDir)) if err != nil { return nil, err } et := &explorerTester{ cs: cs, gateway: g, miner: m, tpool: tp, wallet: w, walletKey: key, explorer: e, } // Mine until the wallet has money. for i := types.BlockHeight(0); i <= types.MaturityDelay; i++ { b, _ := et.miner.FindBlock() err = et.cs.AcceptBlock(b) if err != nil { return nil, err } } return et, nil }
// deleteNode recursively deletes its children from the set of known blocks. // The node being deleted should not be a part of the current path. func (cs *ConsensusSet) deleteNode(pb *processedBlock) { // Sanity check - the node being deleted should not be in the current path. if build.DEBUG { if types.BlockHeight(cs.db.pathHeight()) > pb.Height && cs.db.getPath(pb.Height) == pb.Block.ID() { panic(errDeleteCurrentPath) } } // Recusively call 'deleteNode' on of the input node's children. for i := range pb.Children { child := cs.db.getBlockMap(pb.Children[i]) cs.deleteNode(child) } // Remove the node from the block map, and from its parents list of // children. cs.db.rmBlockMap(pb.Block.ID()) parent := cs.db.getBlockMap(pb.Parent) for i := range parent.Children { if parent.Children[i] == pb.Block.ID() { // If 'i' is not the last element, remove it from the array by // copying the remaining array over it. if i < len(parent.Children)-1 { copy(parent.Children[i:], parent.Children[i+1:]) } // Trim the last element. parent.Children = parent.Children[:len(parent.Children)-1] break } } cs.db.updateBlockMap(parent) }
// checkSiacoins counts the number of siacoins in the database and verifies // that it matches the sum of all the coinbases. func (cs *ConsensusSet) checkSiacoins() error { // Calculate the number of expected coins in constant time. deflationBlocks := types.InitialCoinbase - types.MinimumCoinbase expectedSiacoins := types.CalculateCoinbase(0).Add(types.CalculateCoinbase(cs.height())).Div(types.NewCurrency64(2)) if cs.height() < types.BlockHeight(deflationBlocks) { expectedSiacoins = expectedSiacoins.Mul(types.NewCurrency64(uint64(cs.height()) + 1)) } else { expectedSiacoins = expectedSiacoins.Mul(types.NewCurrency64(deflationBlocks + 1)) trailingSiacoins := types.NewCurrency64(uint64(cs.height()) - deflationBlocks).Mul(types.CalculateCoinbase(cs.height())) expectedSiacoins = expectedSiacoins.Add(trailingSiacoins) } totalSiacoins := types.ZeroCurrency cs.db.forEachSiacoinOutputs(func(scoid types.SiacoinOutputID, sco types.SiacoinOutput) { totalSiacoins = totalSiacoins.Add(sco.Value) }) cs.db.forEachFileContracts(func(fcid types.FileContractID, fc types.FileContract) { var payout types.Currency for _, output := range fc.ValidProofOutputs { payout = payout.Add(output.Value) } totalSiacoins = totalSiacoins.Add(payout) }) cs.db.forEachDelayedSiacoinOutputs(func(v types.SiacoinOutputID, dso types.SiacoinOutput) { totalSiacoins = totalSiacoins.Add(dso.Value) }) cs.db.forEachSiafundOutputs(func(sfoid types.SiafundOutputID, sfo types.SiafundOutput) { sfoSiacoins := cs.siafundPool.Sub(sfo.ClaimStart).Div(types.SiafundCount).Mul(sfo.Value) totalSiacoins = totalSiacoins.Add(sfoSiacoins) }) if expectedSiacoins.Cmp(totalSiacoins) != 0 { return errSiacoinMiscount } return nil }
// createMinerTester creates a minerTester that's ready for use. func createMinerTester(name string) (*minerTester, error) { testdir := build.TempDir(modules.MinerDir, name) // Create the modules. g, err := gateway.New(":0", filepath.Join(testdir, modules.GatewayDir)) if err != nil { return nil, err } cs, err := consensus.New(g, filepath.Join(testdir, modules.ConsensusDir)) if err != nil { return nil, err } tp, err := transactionpool.New(cs, g) if err != nil { return nil, err } w, err := wallet.New(cs, tp, filepath.Join(testdir, modules.WalletDir)) if err != nil { return nil, err } var key crypto.TwofishKey _, err = rand.Read(key[:]) if err != nil { return nil, err } _, err = w.Encrypt(key) if err != nil { return nil, err } err = w.Unlock(key) if err != nil { return nil, err } m, err := New(cs, tp, w, filepath.Join(testdir, modules.MinerDir)) if err != nil { return nil, err } // Assemble the minerTester. mt := &minerTester{ gateway: g, cs: cs, tpool: tp, wallet: w, walletKey: key, miner: m, } // Mine until the wallet has money. for i := types.BlockHeight(0); i <= types.MaturityDelay; i++ { b, _ := m.FindBlock() err = cs.AcceptBlock(b) if err != nil { return nil, err } } return mt, nil }
// loadDiffs is a transitional function to load the processed blocks // from disk and move the diffs into memory func (cs *ConsensusSet) loadDiffs() { height := cs.db.pathHeight() // Load all blocks from disk, skipping the genesis block. for i := types.BlockHeight(1); i < height; i++ { bid := cs.db.getPath(i) pb := cs.db.getBlockMap(bid) lockID := cs.mu.Lock() cs.updateSubscribers(nil, []*processedBlock{pb}) cs.mu.Unlock(lockID) // Yield the processor so that other goroutines have a chance to grab // the lock before it is immeditately grabbed again in the tight loop. runtime.Gosched() } // Do a consistency check after loading the database. if height > 1 && build.DEBUG { err := cs.db.startConsistencyGuard() if err != nil { panic(err) } err = cs.checkConsistency() if err != nil { panic(err) } cs.db.stopConsistencyGuard() } }
// createWalletTester takes a testing.T and creates a WalletTester. func createWalletTester(name string) (*walletTester, error) { // Create the modules testdir := build.TempDir(modules.WalletDir, name) g, err := gateway.New(":0", filepath.Join(testdir, modules.GatewayDir)) if err != nil { return nil, err } cs, err := consensus.New(g, filepath.Join(testdir, modules.ConsensusDir)) if err != nil { return nil, err } tp, err := transactionpool.New(cs, g) if err != nil { return nil, err } w, err := New(cs, tp, filepath.Join(testdir, modules.WalletDir)) if err != nil { return nil, err } var masterKey crypto.TwofishKey _, err = rand.Read(masterKey[:]) if err != nil { return nil, err } _, err = w.Encrypt(masterKey) if err != nil { return nil, err } err = w.Unlock(masterKey) if err != nil { return nil, err } m, err := miner.New(cs, tp, w, filepath.Join(testdir, modules.WalletDir)) if err != nil { return nil, err } // Assemble all componenets into a wallet tester. wt := &walletTester{ cs: cs, gateway: g, tpool: tp, miner: m, wallet: w, walletMasterKey: masterKey, persistDir: testdir, } // Mine blocks until there is money in the wallet. for i := types.BlockHeight(0); i <= types.MaturityDelay; i++ { b, _ := wt.miner.FindBlock() err := wt.cs.AcceptBlock(b) if err != nil { return nil, err } } return wt, nil }
// createConsensusSetTester creates a consensusSetTester that's ready for use. func createConsensusSetTester(name string) (*consensusSetTester, error) { testdir := build.TempDir(modules.ConsensusDir, name) // Create modules. g, err := gateway.New(":0", filepath.Join(testdir, modules.GatewayDir)) if err != nil { return nil, err } cs, err := New(g, filepath.Join(testdir, modules.ConsensusDir)) if err != nil { return nil, err } tp, err := transactionpool.New(cs, g) if err != nil { return nil, err } w, err := wallet.New(cs, tp, filepath.Join(testdir, modules.WalletDir)) if err != nil { return nil, err } key, err := crypto.GenerateTwofishKey() if err != nil { return nil, err } _, err = w.Encrypt(key) if err != nil { return nil, err } err = w.Unlock(key) if err != nil { return nil, err } m, err := miner.New(cs, tp, w, filepath.Join(testdir, modules.MinerDir)) if err != nil { return nil, err } // Assemble all objects into a consensusSetTester. cst := &consensusSetTester{ gateway: g, miner: m, tpool: tp, wallet: w, walletKey: key, cs: cs, persistDir: testdir, } // Mine until the wallet has money. for i := types.BlockHeight(0); i <= types.MaturityDelay; i++ { b, _ := cst.miner.FindBlock() err = cst.cs.AcceptBlock(b) if err != nil { return nil, err } } return cst, nil }
// newHostDBTester creates a ready-to-use hostdb tester with money in the // wallet. func newHostDBTester(name string) (*hostdbTester, error) { // Create the modules. testdir := build.TempDir("hostdb", name) g, err := gateway.New(":0", filepath.Join(testdir, modules.GatewayDir)) if err != nil { return nil, err } cs, err := consensus.New(g, filepath.Join(testdir, modules.ConsensusDir)) if err != nil { return nil, err } tp, err := transactionpool.New(cs, g) if err != nil { return nil, err } w, err := modWallet.New(cs, tp, filepath.Join(testdir, modules.WalletDir)) if err != nil { return nil, err } key, err := crypto.GenerateTwofishKey() if err != nil { return nil, err } _, err = w.Encrypt(key) if err != nil { return nil, err } err = w.Unlock(key) if err != nil { return nil, err } hdb, err := New(cs, w, tp, filepath.Join(testdir, modules.RenterDir)) if err != nil { return nil, err } m, err := miner.New(cs, tp, w, filepath.Join(testdir, modules.MinerDir)) if err != nil { return nil, err } // Assemble all pieces into a hostdb tester. ht := &hostdbTester{ cs: cs, gateway: g, miner: m, tpool: tp, wallet: w, hostdb: hdb, } // Mine blocks until there is money in the wallet. for i := types.BlockHeight(0); i <= types.MaturityDelay; i++ { _, err := ht.miner.AddBlock() if err != nil { return nil, err } } return ht, nil }
// ConsensusSetDigestSubscribe accepts a new digest subscriber who will receive // a call to ProcessConsensusDigest every time there is a change in the // consensus set. func (cs *ConsensusSet) ConsensusSetDigestSubscribe(subscriber modules.ConsensusSetDigestSubscriber) { cs.mu.Lock() cs.digestSubscribers = append(cs.digestSubscribers, subscriber) cs.mu.Demote() defer cs.mu.DemotedUnlock() var currentPath []types.BlockID err := cs.db.View(func(tx *bolt.Tx) error { // Get the whole current path into memory to be sent as the first // digest. // // TODO: Change this construction to something simpler. height := blockHeight(tx) for i := types.BlockHeight(0); i <= height; i++ { id, err := getPath(tx, i) if err != nil { return err } currentPath = append(currentPath, id) } subscriber.ProcessConsensusDigest(nil, currentPath) return nil }) if build.DEBUG && err != nil { panic(err) } }
// ProcessConsensusChange will be called by the consensus set every time there // is a change in the blockchain. Updates will always be called in order. func (hdb *HostDB) ProcessConsensusChange(cc modules.ConsensusChange) { hdb.mu.Lock() defer hdb.mu.Unlock() if hdb.blockHeight != 0 || cc.AppliedBlocks[len(cc.AppliedBlocks)-1].ID() != types.GenesisBlock.ID() { hdb.blockHeight += types.BlockHeight(len(cc.AppliedBlocks)) hdb.blockHeight -= types.BlockHeight(len(cc.RevertedBlocks)) } // Add hosts announced in blocks that were applied. for _, block := range cc.AppliedBlocks { for _, host := range findHostAnnouncements(block) { hdb.insertHost(host) } } }
// newRenterTester creates a ready-to-use renter tester with money in the // wallet. func newRenterTester(name string) (*renterTester, error) { // Create the modules. testdir := build.TempDir("renter", name) g, err := gateway.New("localhost:0", false, filepath.Join(testdir, modules.GatewayDir)) if err != nil { return nil, err } cs, err := consensus.New(g, false, filepath.Join(testdir, modules.ConsensusDir)) if err != nil { return nil, err } tp, err := transactionpool.New(cs, g, filepath.Join(testdir, modules.TransactionPoolDir)) if err != nil { return nil, err } w, err := wallet.New(cs, tp, filepath.Join(testdir, modules.WalletDir)) if err != nil { return nil, err } key, err := crypto.GenerateTwofishKey() if err != nil { return nil, err } _, err = w.Encrypt(key) if err != nil { return nil, err } err = w.Unlock(key) if err != nil { return nil, err } r, err := New(cs, w, tp, filepath.Join(testdir, modules.RenterDir)) if err != nil { return nil, err } m, err := miner.New(cs, tp, w, filepath.Join(testdir, modules.MinerDir)) if err != nil { return nil, err } // Assemble all pieces into a renter tester. rt := &renterTester{ cs: cs, gateway: g, miner: m, tpool: tp, wallet: w, renter: r, } // Mine blocks until there is money in the wallet. for i := types.BlockHeight(0); i <= types.MaturityDelay; i++ { _, err := rt.miner.AddBlock() if err != nil { return nil, err } } return rt, nil }
// load pulls all the blocks that have been saved to disk into memory, using // them to fill out the State. func (cs *State) load(saveDir string) error { db, err := persist.OpenDB(filepath.Join(saveDir, "chain.db")) if err != nil { return err } // Check the height. If the height is 0, then it's a new file and the // genesis block should be added. height, err := db.Height() if err != nil { return err } if height == 0 { // add genesis block cs.db = db return db.AddBlock(cs.blockRoot.block) } // Check that the db's genesis block matches our genesis block. b, err := db.Block(0) if err != nil { return err } // If this happens, print a warning and start a new db. if b.ID() != cs.currentPath[0] { println("WARNING: blockchain has wrong genesis block. A new blockchain will be created.") db.Close() err := os.Rename(filepath.Join(saveDir, "chain.db"), filepath.Join(saveDir, "chain.db.bck")) if err != nil { return err } // Now that chain.db no longer exists, recursing will create a new // empty db and add the genesis block to it. return cs.load(saveDir) } // load blocks from the db, starting after the genesis block // NOTE: during load, the state uses the NilDB. This prevents AcceptBlock // from adding duplicate blocks to the real database. cs.db = persist.NilDB for i := types.BlockHeight(1); i < height; i++ { b, err := db.Block(i) if err != nil { // should never happen return err } // Blocks loaded from disk are trusted, don't bother with verification. lockID := cs.mu.Lock() cs.verificationRigor = partialVerification err = cs.acceptBlock(b) cs.mu.Unlock(lockID) if err != nil { return err } } // start using the real db cs.db = db return nil }
// newTestingWallet is a helper function that creates a ready-to-use wallet // and mines some coins into it. func newTestingWallet(testdir string, cs modules.ConsensusSet, tp modules.TransactionPool) (modules.Wallet, error) { w, err := modWallet.New(cs, tp, filepath.Join(testdir, modules.WalletDir)) if err != nil { return nil, err } key, err := crypto.GenerateTwofishKey() if err != nil { return nil, err } if !w.Encrypted() { _, err = w.Encrypt(key) if err != nil { return nil, err } } err = w.Unlock(key) if err != nil { return nil, err } // give it some money m, err := miner.New(cs, tp, w, filepath.Join(testdir, modules.MinerDir)) if err != nil { return nil, err } for i := types.BlockHeight(0); i <= types.MaturityDelay; i++ { _, err := m.AddBlock() if err != nil { return nil, err } } return w, nil }
// createAuthenticatedServerTester creates an authenticated server tester // object that is ready for testing, including money in the wallet and all // modules initalized. func createAuthenticatedServerTester(name string, password string) (*serverTester, error) { // createAuthenticatedServerTester should not get called during short // tests, as it takes a long time to run. if testing.Short() { panic("assembleServerTester called during short tests") } // Create the testing directory. testdir := build.TempDir("authenticated-api", name) key, err := crypto.GenerateTwofishKey() if err != nil { return nil, err } st, err := assembleAuthenticatedServerTester(password, key, testdir) if err != nil { return nil, err } // Mine blocks until the wallet has confirmed money. for i := types.BlockHeight(0); i <= types.MaturityDelay; i++ { _, err := st.miner.AddBlock() if err != nil { return nil, err } } return st, nil }
// createServerTester creates a server tester object that is ready for testing, // including money in the wallet and all modules initialized. func createServerTester(name string) (*serverTester, error) { // createServerTester is expensive, and therefore should not be called // during short tests. if testing.Short() { panic("createServerTester called during short tests") } // Create the testing directory. testdir := build.TempDir("api", name) key, err := crypto.GenerateTwofishKey() if err != nil { return nil, err } st, err := assembleServerTester(key, testdir) if err != nil { return nil, err } // Mine blocks until the wallet has confirmed money. for i := types.BlockHeight(0); i <= types.MaturityDelay; i++ { _, err := st.miner.AddBlock() if err != nil { return nil, err } } return st, nil }
// Height returns the current blockchain height. func (db *boltDB) Height() (types.BlockHeight, error) { var height types.BlockHeight err := db.View(func(tx *bolt.Tx) error { height = types.BlockHeight(tx.Bucket([]byte("chain")).Stats().KeyN) return nil }) return height, err }
// TestPersistCompat04 checks that the compatibility loader for version 0.4.x // obligations is functioning. func TestPersistCompat04(t *testing.T) { if testing.Short() { t.SkipNow() } ht, err := newHostTester("TestPersistCompat04") if err != nil { t.Fatal(err) } // Upload a file and then save the host as a compatibility host. _, err = ht.uploadFile("TestPersistCompat04 - 1", renewDisabled) if err != nil { t.Fatal(err) } // Mine a block so that the file contract ends up in the blockchain. _, err = ht.miner.AddBlock() if err != nil { t.Fatal(err) } err = ht.host.Close() if err != nil { t.Fatal(err) } c04h := ht.buildCompat04Host() if err != nil { t.Fatal(err) } // Save the compatibility file, replacing the usual file with an old // format. err = persist.SaveFile(compat04Metadata, c04h, filepath.Join(ht.host.persistDir, settingsFile)) if err != nil { t.Fatal(err) } // Re-open the host, which will be loading from the compatibility file. rebootHost, err := New(ht.cs, ht.tpool, ht.wallet, ":0", filepath.Join(ht.persistDir, modules.HostDir)) if err != nil { t.Fatal(err) } if len(rebootHost.obligationsByID) != 1 { t.Fatal(len(rebootHost.obligationsByID)) } // Mine until the storage proof goes through, and the obligation gets // cleared. for i := types.BlockHeight(0); i <= testUploadDuration+confirmationRequirement+defaultWindowSize; i++ { _, err := ht.miner.AddBlock() if err != nil { t.Fatal(err) } } if len(rebootHost.obligationsByID) != 0 { t.Error("obligations did not clear") } if rebootHost.revenue.IsZero() { t.Error("host is reporting no revenue after doing a compatibility storage proof") } }