// removeTailTransaction removes the most recent transaction from the pool. The // most recent transaction is guaranteed not to have any dependents or // children. func (tp *TransactionPool) removeTailTransaction() { // Sanity check - the transaction list should not be empty if // removeTailTransaction has been called. if len(tp.transactionList) == 0 { if build.DEBUG { panic("calling removeTailTransaction when transaction list is empty") } return } // Grab the most recent transaction and remove it from the unconfirmed // consensus set piecemeal. t := tp.transactionList[len(tp.transactionList)-1] tp.removeSiacoinInputs(t) tp.removeSiacoinOutputs(t) tp.removeFileContracts(t) tp.removeFileContractRevisions(t) tp.removeStorageProofs(t) tp.removeSiafundInputs(t) tp.removeSiafundOutputs(t) // Sanity check - transaction hash should be in the list of transactions. if build.DEBUG { _, exists := tp.transactions[crypto.HashObject(t)] if !exists { panic("transaction not available in transaction list") } } // Remove the transaction from the transaction lists. delete(tp.transactions, crypto.HashObject(t)) tp.transactionList = tp.transactionList[:len(tp.transactionList)-1] return }
// TestIntegrationBlankEncryption probes the encryption process when the user // supplies a blank encryption key during the encryption process. func TestIntegrationBlankEncryption(t *testing.T) { // Create the wallet. wt, err := createBlankWalletTester("TestIntegrationBlankEncryption") if err != nil { t.Fatal(err) } defer wt.closeWt() // Encrypt the wallet using a blank key. seed, err := wt.wallet.Encrypt(crypto.TwofishKey{}) if err != nil { t.Error(err) } // Try unlocking the wallet using a blank key. err = wt.wallet.Unlock(crypto.TwofishKey{}) if err != modules.ErrBadEncryptionKey { t.Fatal(err) } // Try unlocking the wallet using the correct key. err = wt.wallet.Unlock(crypto.TwofishKey(crypto.HashObject(seed))) if err != nil { t.Fatal(err) } err = wt.wallet.Lock() if err != nil { t.Fatal(err) } postEncryptionTesting(wt.miner, wt.wallet, crypto.TwofishKey(crypto.HashObject(seed))) }
// encryptionKeys enumerates the possible encryption keys that can be derived // from an input string. func encryptionKeys(seedStr string) (validKeys []crypto.TwofishKey) { dicts := []mnemonics.DictionaryID{"english", "german", "japanese"} for _, dict := range dicts { seed, err := modules.StringToSeed(seedStr, dict) if err != nil { continue } validKeys = append(validKeys, crypto.TwofishKey(crypto.HashObject(seed))) } validKeys = append(validKeys, crypto.TwofishKey(crypto.HashObject(seedStr))) return validKeys }
// TestIntegrationMinerHeader checks that the header GET and POST calls are // useful tools for mining blocks. func TestIntegrationMinerHeader(t *testing.T) { if testing.Short() { t.SkipNow() } st, err := createServerTester("TestIntegrationMinerHeader") if err != nil { t.Fatal(err) } defer st.server.Close() startingHeight := st.cs.Height() // Get a header that can be used for mining. resp, err := HttpGET("http://" + st.server.listener.Addr().String() + "/miner/header") if err != nil { t.Fatal(err) } defer resp.Body.Close() targetAndHeader, err := ioutil.ReadAll(resp.Body) if err != nil { t.Fatal(err) } // Twiddle the header bits until a block has been found. // // Note: this test treats the target as hardcoded, if the testing target is // changed, this test will also need to be changed. if types.RootTarget[0] != 128 { t.Fatal("test will fail because the testing constants have been unexpectedly changed") } var header [80]byte copy(header[:], targetAndHeader[32:]) headerHash := crypto.HashObject(header) for headerHash[0] >= types.RootTarget[0] { header[35]++ headerHash = crypto.HashObject(header) } // Submit the solved header through the api and check that the height of // the blockchain increases. resp, err = HttpPOST("http://"+st.server.listener.Addr().String()+"/miner/header", string(header[:])) if err != nil { t.Fatal(err) } defer resp.Body.Close() time.Sleep(500 * time.Millisecond) if st.cs.Height() != startingHeight+1 { t.Errorf("block height did not increase after trying to mine a block through the api, started at %v and ended at %v", startingHeight, st.cs.Height()) } }
// acceptTransactionSet verifies that a transaction set is allowed to be in the // transaction pool, and then adds it to the transaction pool. func (tp *TransactionPool) acceptTransactionSet(ts []types.Transaction) error { if len(ts) == 0 { return errEmptySet } // Remove all transactions that have been confirmed in the transaction set. err := tp.db.Update(func(tx *bolt.Tx) error { oldTS := ts ts = []types.Transaction{} for _, txn := range oldTS { if !tp.transactionConfirmed(tx, txn.ID()) { ts = append(ts, txn) } } return nil }) if err != nil { return err } // If no transactions remain, return a dublicate error. if len(ts) == 0 { return modules.ErrDuplicateTransactionSet } // Check the composition of the transaction set, including fees and // IsStandard rules. err = tp.checkTransactionSetComposition(ts) if err != nil { return err } // Check for conflicts with other transactions, which would indicate a // double-spend. Legal children of a transaction set will also trigger the // conflict-detector. oids := relatedObjectIDs(ts) var conflicts []TransactionSetID for _, oid := range oids { conflict, exists := tp.knownObjects[oid] if exists { conflicts = append(conflicts, conflict) } } if len(conflicts) > 0 { return tp.handleConflicts(ts, conflicts) } cc, err := tp.consensusSet.TryTransactionSet(ts) if err != nil { return modules.NewConsensusConflict(err.Error()) } // Add the transaction set to the pool. setID := TransactionSetID(crypto.HashObject(ts)) tp.transactionSets[setID] = ts for _, oid := range oids { tp.knownObjects[oid] = setID } tp.transactionSetDiffs[setID] = cc tp.transactionListSize += len(encoding.Marshal(ts)) return nil }
// TestDeleteNonexistentSector checks that attempting to delete a storage // sector that doesn't exist will fail with the appropriate error. func TestDeleteNonexistentSector(t *testing.T) { if testing.Short() { t.SkipNow() } st, err := createServerTester("TestDeleteNonexistentSector") if err != nil { t.Fatal(err) } defer st.server.Close() // These calls to delete imaginary sectors should fail for a few reasons: // - the given sector root strings are invalid // - the renter hasn't uploaded anything // - the host has no storage folders yet // Right now, the calls fail for the first reason. This test will report if that behavior changes. badHash := crypto.HashObject("fake object").String() err = st.stdPostAPI("/host/storage/sectors/delete/"+badHash, url.Values{}) if err == nil || err.Error() != storagemanager.ErrSectorNotFound.Error() { t.Fatalf("expected error to be %v; got %v", storagemanager.ErrSectorNotFound, err) } wrongSize := "wrong size string" err = st.stdPostAPI("/host/storage/sectors/delete/"+wrongSize, url.Values{}) if err == nil || err.Error() != crypto.ErrHashWrongLen.Error() { t.Fatalf("expected error to be %v; got %v", crypto.ErrHashWrongLen, err) } }
// repeatCheckHelper recursively goes through nodes in the host map and adds // them to the repeat maps. func (hn *hostNode) repeatCheckHelper(ipMap, pkMap map[string]struct{}) error { ipStr := string(hn.hostEntry.NetAddress) pkStr := crypto.HashObject(hn.hostEntry.PublicKey).String() _, exists := ipMap[ipStr] if exists && hn.taken { return errors.New("found a duplicate ip address in the hostdb: " + ipStr) } _, exists = pkMap[pkStr] if exists && hn.taken { return errors.New("found a duplicate pubkey in the hostdb: " + ipStr + " " + pkStr) } if hn.taken { ipMap[ipStr] = struct{}{} pkMap[pkStr] = struct{}{} } if hn.left != nil { err := hn.left.repeatCheckHelper(ipMap, pkMap) if err != nil { return err } } if hn.right != nil { err := hn.right.repeatCheckHelper(ipMap, pkMap) if err != nil { return err } } return nil }
// GetHashInfo returns sufficient data about the hash that was // provided to do more extensive lookups func (e *Explorer) GetHashInfo(hash []byte) (interface{}, error) { if len(hash) < crypto.HashSize { return nil, errors.New("requested hash not long enough") } lockID := e.mu.RLock() defer e.mu.RUnlock(lockID) // Perform a lookup to tell which type of hash it is typeBytes, err := e.db.GetFromBucket("Hashes", hash[:crypto.HashSize]) if err != nil { return nil, err } var hashType int err = encoding.Unmarshal(typeBytes, &hashType) if err != nil { return nil, err } switch hashType { case hashBlock: var id types.BlockID copy(id[:], hash[:]) return e.db.getBlock(types.BlockID(id)) case hashTransaction: var id crypto.Hash copy(id[:], hash[:]) return e.db.getTransaction(id) case hashFilecontract: var id types.FileContractID copy(id[:], hash[:]) return e.db.getFileContract(id) case hashCoinOutputID: var id types.SiacoinOutputID copy(id[:], hash[:]) return e.db.getSiacoinOutput(id) case hashFundOutputID: var id types.SiafundOutputID copy(id[:], hash[:]) return e.db.getSiafundOutput(id) case hashUnlockHash: // Check that the address is valid before doing a lookup if len(hash) != crypto.HashSize+types.UnlockHashChecksumSize { return nil, errors.New("address does not have a valid checksum") } var id types.UnlockHash copy(id[:], hash[:crypto.HashSize]) uhChecksum := crypto.HashObject(id) givenChecksum := hash[crypto.HashSize : crypto.HashSize+types.UnlockHashChecksumSize] if !bytes.Equal(givenChecksum, uhChecksum[:types.UnlockHashChecksumSize]) { return nil, errors.New("address does not have a valid checksum") } return e.db.getAddressTransactions(id) default: return nil, errors.New("bad hash type") } }
// checkTransactionSetComposition checks if the transaction set is valid given // the state of the pool. It does not check that each individual transaction // would be legal in the next block, but does check things like miner fees and // IsStandard. func (tp *TransactionPool) checkTransactionSetComposition(ts []types.Transaction) error { // Check that the transaction set is not already known. setID := TransactionSetID(crypto.HashObject(ts)) _, exists := tp.transactionSets[setID] if exists { return modules.ErrDuplicateTransactionSet } // Check that the transaction set has enough fees to justify adding it to // the transaction list. err := tp.checkMinerFees(ts) if err != nil { return err } // All checks after this are expensive. // // TODO: There is no DoS prevention mechanism in place to prevent repeated // expensive verifications of invalid transactions that are created on the // fly. // Check that all transactions follow 'Standard.md' guidelines. err = tp.IsStandardTransactionSet(ts) if err != nil { return err } return nil }
// DecodeAnnouncement decodes announcement bytes into a host announcement, // verifying the prefix and the signature. func DecodeAnnouncement(fullAnnouncement []byte) (na NetAddress, spk types.SiaPublicKey, err error) { // Read the first part of the announcement to get the intended host // announcement. var ha HostAnnouncement dec := encoding.NewDecoder(bytes.NewReader(fullAnnouncement)) err = dec.Decode(&ha) if err != nil { return "", types.SiaPublicKey{}, err } // Check that the announcement was registered as a host announcement. if ha.Specifier != PrefixHostAnnouncement { return "", types.SiaPublicKey{}, ErrAnnNotAnnouncement } // Check that the public key is a recognized type of public key. if ha.PublicKey.Algorithm != types.SignatureEd25519 { return "", types.SiaPublicKey{}, ErrAnnUnrecognizedSignature } // Read the signature out of the reader. var sig crypto.Signature err = dec.Decode(&sig) if err != nil { return "", types.SiaPublicKey{}, err } // Verify the signature. var pk crypto.PublicKey copy(pk[:], ha.PublicKey.Key) annHash := crypto.HashObject(ha) err = crypto.VerifyHash(annHash, pk, sig) if err != nil { return "", types.SiaPublicKey{}, err } return ha.NetAddress, ha.PublicKey, nil }
// SeedToString converts a wallet seed to a human friendly string. func SeedToString(seed Seed, did mnemonics.DictionaryID) (string, error) { fullChecksum := crypto.HashObject(seed) checksumSeed := append(seed[:], fullChecksum[:SeedChecksumSize]...) phrase, err := mnemonics.ToPhrase(checksumSeed, did) if err != nil { return "", err } return phrase.String(), nil }
// TestIntegrationBlocksMined checks that the BlocksMined function correctly // indicates the number of real blocks and stale blocks that have been mined. func TestIntegrationBlocksMined(t *testing.T) { mt, err := createMinerTester("TestIntegrationBlocksMined") if err != nil { t.Fatal(err) } // Get an unsolved header. unsolvedHeader, target, err := mt.miner.HeaderForWork() if err != nil { t.Fatal(err) } // Unsolve the header. for { unsolvedHeader.Nonce[0]++ id := crypto.HashObject(unsolvedHeader) if bytes.Compare(target[:], id[:]) < 0 { break } } // Get two solved headers. header1, target, err := mt.miner.HeaderForWork() if err != nil { t.Fatal(err) } header1 = solveHeader(header1, target) header2, target, err := mt.miner.HeaderForWork() if err != nil { t.Fatal(err) } header2 = solveHeader(header2, target) // Submit the unsolved header followed by the two solved headers, this // should result in 1 real block mined and 1 stale block mined. err = mt.miner.SubmitHeader(unsolvedHeader) if err != modules.ErrBlockUnsolved { t.Fatal(err) } err = mt.miner.SubmitHeader(header1) if err != nil { t.Fatal(err) } err = mt.miner.SubmitHeader(header2) if err != modules.ErrNonExtendingBlock { t.Fatal(err) } goodBlocks, staleBlocks := mt.miner.BlocksMined() if goodBlocks != 1 { t.Error("expexting 1 good block") } if staleBlocks != 1 { t.Error(len(mt.miner.blocksFound)) t.Error("expecting 1 stale block, got", staleBlocks) } }
// solveHeader takes a block header as input and returns a solved block header // as output. func solveHeader(header types.BlockHeader, target types.Target) types.BlockHeader { // Solve the header. for { // Increment the nonce first to guarantee that a new header is formed // - this helps check for pointer errors. header.Nonce[0]++ id := crypto.HashObject(header) if bytes.Compare(target[:], id[:]) >= 0 { break } } return header }
// SubmitHeader accepts a block header. func (m *Miner) SubmitHeader(bh types.BlockHeader) error { if err := m.tg.Add(); err != nil { return err } defer m.tg.Done() // Because a call to managedSubmitBlock is required at the end of this // function, the first part needs to be wrapped in an anonymous function // for lock safety. var b types.Block err := func() error { m.mu.Lock() defer m.mu.Unlock() // Lookup the block that corresponds to the provided header. nonce := bh.Nonce bh.Nonce = [8]byte{} bPointer, bExists := m.blockMem[bh] arbData, arbExists := m.arbDataMem[bh] if !bExists || !arbExists { return errLateHeader } // Block is going to be passed to external memory, but the memory pointed // to by the transactions slice is still being modified - needs to be // copied. Same with the memory being pointed to by the arb data slice. b = *bPointer txns := make([]types.Transaction, len(b.Transactions)) copy(txns, b.Transactions) b.Transactions = txns b.Transactions[0].ArbitraryData = [][]byte{arbData[:]} b.Nonce = nonce // Sanity check - block should have same id as header. bh.Nonce = nonce if types.BlockID(crypto.HashObject(bh)) != b.ID() { m.log.Critical("block reconstruction failed") } return nil }() if err != nil { m.log.Println("ERROR during call to SubmitHeader, pre SubmitBlock:", err) return err } err = m.managedSubmitBlock(b) if err != nil { m.log.Println("ERROR returned by managedSubmitBlock:", err) return err } return nil }
// StringToSeed converts a string to a wallet seed. func StringToSeed(str string, did mnemonics.DictionaryID) (Seed, error) { // Decode the string into the checksummed byte slice. checksumSeedBytes, err := mnemonics.FromString(str, did) if err != nil { return Seed{}, err } // Copy the seed from the checksummed slice. var seed Seed copy(seed[:], checksumSeedBytes) fullChecksum := crypto.HashObject(seed) if len(checksumSeedBytes) != crypto.EntropySize+SeedChecksumSize || !bytes.Equal(fullChecksum[:SeedChecksumSize], checksumSeedBytes[crypto.EntropySize:]) { return Seed{}, errors.New("seed failed checksum verification") } return seed, nil }
// acceptTransactionSet verifies that a transaction set is allowed to be in the // transaction pool, and then adds it to the transaction pool. func (tp *TransactionPool) acceptTransactionSet(ts []types.Transaction) error { if len(ts) == 0 { return errEmptySet } // Check the composition of the transaction set, including fees and // IsStandard rules. err := tp.checkTransactionSetComposition(ts) if err != nil { return err } // Check for conflicts with other transactions, which would indicate a // double-spend. Legal children of a transaction set will also trigger the // conflict-detector. oids := relatedObjectIDs(ts) var conflicts []TransactionSetID for _, oid := range oids { conflict, exists := tp.knownObjects[oid] if exists { conflicts = append(conflicts, conflict) } } if len(conflicts) > 0 { return tp.handleConflicts(ts, conflicts) } cc, err := tp.consensusSet.TryTransactionSet(ts) if err != nil { return modules.NewConsensusConflict(err.Error()) } // Add the transaction set to the pool. setID := TransactionSetID(crypto.HashObject(ts)) tp.transactionSets[setID] = ts for _, oid := range oids { tp.knownObjects[oid] = setID } tp.transactionSetDiffs[setID] = cc tp.transactionListSize += len(encoding.Marshal(ts)) return nil }
// initEncryption checks that the provided encryption key is the valid // encryption key for the wallet. If encryption has not yet been established // for the wallet, an encryption key is created. func (w *Wallet) initEncryption(masterKey crypto.TwofishKey) (modules.Seed, error) { // Check if the wallet encryption key has already been set. if len(w.persist.EncryptionVerification) != 0 { return modules.Seed{}, errReencrypt } // Create a random seed and use it to generate the seed file for the // wallet. var seed modules.Seed _, err := rand.Read(seed[:]) if err != nil { return modules.Seed{}, err } // If the input key is blank, use the seed to create the master key. // Otherwise, use the input key. if masterKey == (crypto.TwofishKey{}) { masterKey = crypto.TwofishKey(crypto.HashObject(seed)) } err = w.createSeed(masterKey, seed) if err != nil { return modules.Seed{}, err } // Establish the encryption verification using the masterKey. After this // point, the wallet is encrypted. uk := uidEncryptionKey(masterKey, w.persist.UID) encryptionBase := make([]byte, encryptionVerificationLen) w.persist.EncryptionVerification, err = uk.EncryptBytes(encryptionBase) if err != nil { return modules.Seed{}, err } err = w.saveSettings() if err != nil { return modules.Seed{}, err } return seed, nil }
// TestIntegrationHeaderForWorkUpdates checks that HeaderForWork starts // returning headers on the new block after a block has been submitted to the // consensus set. func TestIntegrationHeaderForWorkUpdates(t *testing.T) { if testing.Short() { t.SkipNow() } mt, err := createMinerTester("TestIntegrationHeaderForWorkUpdates") if err != nil { t.Fatal(err) } // Get a header to advance into the header memory. _, _, err = mt.miner.HeaderForWork() if err != nil { t.Fatal(err) } // Submit a block, which should trigger a header change. _, err = mt.miner.AddBlock() if err != nil { t.Fatal(err) } // Get a header to grind on. header, target, err := mt.miner.HeaderForWork() if err != nil { t.Fatal(err) } solvedHeader := solveHeader(header, target) // Submit the header. err = mt.miner.SubmitHeader(solvedHeader) if err != nil { t.Fatal(err) } if !mt.cs.InCurrentPath(types.BlockID(crypto.HashObject(solvedHeader))) { t.Error("header from solved block is not in the current path") } }
// walletInitHandler handles API calls to /wallet/init. func (api *API) walletInitHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { var encryptionKey crypto.TwofishKey if req.FormValue("encryptionpassword") != "" { encryptionKey = crypto.TwofishKey(crypto.HashObject(req.FormValue("encryptionpassword"))) } seed, err := api.wallet.Encrypt(encryptionKey) if err != nil { WriteError(w, Error{"error when calling /wallet/init: " + err.Error()}, http.StatusBadRequest) return } dictID := mnemonics.DictionaryID(req.FormValue("dictionary")) if dictID == "" { dictID = "english" } seedStr, err := modules.SeedToString(seed, dictID) if err != nil { WriteError(w, Error{"error when calling /wallet/init: " + err.Error()}, http.StatusBadRequest) return } WriteJSON(w, WalletInitPOST{ PrimarySeed: seedStr, }) }
// SubmitHeader accepts a block header. func (m *Miner) SubmitHeader(bh types.BlockHeader) error { m.mu.Lock() // Lookup the block that corresponds to the provided header. var b types.Block nonce := bh.Nonce bh.Nonce = [8]byte{} bPointer, bExists := m.blockMem[bh] arbData, arbExists := m.arbDataMem[bh] if !bExists || !arbExists { m.log.Println("ERROR:", errLateHeader) m.mu.Unlock() return errLateHeader } // Block is going to be passed to external memory, but the memory pointed // to by the transactions slice is still being modified - needs to be // copied. Same with the memory being pointed to by the arb data slice. b = *bPointer txns := make([]types.Transaction, len(b.Transactions)) copy(txns, b.Transactions) b.Transactions = txns b.Transactions[0].ArbitraryData = [][]byte{arbData[:]} b.Nonce = nonce // Sanity check - block should have same id as header. if build.DEBUG { bh.Nonce = nonce if types.BlockID(crypto.HashObject(bh)) != b.ID() { panic("block reconstruction failed") } } m.mu.Unlock() return m.SubmitBlock(b) }
// walletEncryptHandlerPOST handles a POST call to /wallet/encrypt. func (srv *Server) walletEncryptHandlerPOST(w http.ResponseWriter, req *http.Request) { var encryptionKey crypto.TwofishKey if req.FormValue("encryptionpassword") != "" { encryptionKey = crypto.TwofishKey(crypto.HashObject(req.FormValue("encryptionpassword"))) } seed, err := srv.wallet.Encrypt(encryptionKey) if err != nil { writeError(w, "error when calling /wallet/encrypt: "+err.Error(), http.StatusBadRequest) return } dictID := mnemonics.DictionaryID(req.FormValue("dictionary")) if dictID == "" { dictID = "english" } seedStr, err := modules.SeedToString(seed, dictID) if err != nil { writeError(w, "error when calling /wallet/encrypt: "+err.Error(), http.StatusBadRequest) return } writeJSON(w, WalletEncryptPOST{ PrimarySeed: seedStr, }) }
// handleConflicts detects whether the conflicts in the transaction pool are // legal children of the new transaction pool set or not. func (tp *TransactionPool) handleConflicts(ts []types.Transaction, conflicts []TransactionSetID) error { // Create a list of all the transaction ids that compose the set of // conflicts. conflictMap := make(map[types.TransactionID]TransactionSetID) for _, conflict := range conflicts { conflictSet := tp.transactionSets[conflict] for _, conflictTxn := range conflictSet { conflictMap[conflictTxn.ID()] = conflict } } // Discard all duplicate transactions from the input transaction set. var dedupSet []types.Transaction for _, t := range ts { _, exists := conflictMap[t.ID()] if exists { continue } dedupSet = append(dedupSet, t) } if len(dedupSet) == 0 { return modules.ErrDuplicateTransactionSet } // If transactions were pruned, it's possible that the set of // dependencies/conflicts has also reduced. To minimize computational load // on the consensus set, we want to prune out all of the conflicts that are // no longer relevant. As an example, consider the transaction set {A}, the // set {B}, and the new set {A, C}, where C is dependent on B. {A} and {B} // are both conflicts, but after deduplication {A} is no longer a conflict. // This is recursive, but it is guaranteed to run only once as the first // deduplication is guaranteed to be complete. if len(dedupSet) < len(ts) { oids := relatedObjectIDs(dedupSet) var conflicts []TransactionSetID for _, oid := range oids { conflict, exists := tp.knownObjects[oid] if exists { conflicts = append(conflicts, conflict) } } return tp.handleConflicts(dedupSet, conflicts) } // Merge all of the conflict sets with the input set (input set goes last // to preserve dependency ordering), and see if the set as a whole is both // small enough to be legal and valid as a set. If no, return an error. If // yes, add the new set to the pool, and eliminate the old set. The output // diff objects can be repeated, (no need to remove those). Just need to // remove the conflicts from tp.transactionSets. var superset []types.Transaction supersetMap := make(map[TransactionSetID]struct{}) for _, conflict := range conflictMap { supersetMap[conflict] = struct{}{} } for conflict := range supersetMap { superset = append(superset, tp.transactionSets[conflict]...) } superset = append(superset, dedupSet...) // Check the composition of the transaction set, including fees and // IsStandard rules (this is a new set, the rules must be rechecked). err := tp.checkTransactionSetComposition(superset) if err != nil { return err } // Check that the transaction set is valid. cc, err := tp.consensusSet.TryTransactionSet(superset) if err != nil { return modules.NewConsensusConflict(err.Error()) } // Remove the conflicts from the transaction pool. The diffs do not need to // be removed, they will be overwritten later in the function. for _, conflict := range conflictMap { conflictSet := tp.transactionSets[conflict] tp.transactionListSize -= len(encoding.Marshal(conflictSet)) delete(tp.transactionSets, conflict) delete(tp.transactionSetDiffs, conflict) } // Add the transaction set to the pool. setID := TransactionSetID(crypto.HashObject(superset)) tp.transactionSets[setID] = superset for _, diff := range cc.SiacoinOutputDiffs { tp.knownObjects[ObjectID(diff.ID)] = setID } for _, diff := range cc.FileContractDiffs { tp.knownObjects[ObjectID(diff.ID)] = setID } for _, diff := range cc.SiafundOutputDiffs { tp.knownObjects[ObjectID(diff.ID)] = setID } tp.transactionSetDiffs[setID] = cc tp.transactionListSize += len(encoding.Marshal(superset)) return nil }
// TestIntegrationBlocksMined checks that the BlocksMined function correctly // indicates the number of real blocks and stale blocks that have been mined. func TestIntegrationBlocksMined(t *testing.T) { mt, err := createMinerTester("TestIntegrationBlocksMined") if err != nil { t.Fatal(err) } // Get an unsolved header. unsolvedHeader, target, err := mt.miner.HeaderForWork() if err != nil { t.Fatal(err) } // Unsolve the header - necessary because the target is very low when // mining. for { unsolvedHeader.Nonce[0]++ id := crypto.HashObject(unsolvedHeader) if bytes.Compare(target[:], id[:]) < 0 { break } } // Get two solved headers. header1, target, err := mt.miner.HeaderForWork() if err != nil { t.Fatal(err) } header1 = solveHeader(header1, target) header2, target, err := mt.miner.HeaderForWork() if err != nil { t.Fatal(err) } header2 = solveHeader(header2, target) // Submit the unsolved header followed by the two solved headers, this // should result in 1 real block mined and 1 stale block mined. err = mt.miner.SubmitHeader(unsolvedHeader) if err != modules.ErrBlockUnsolved { t.Fatal(err) } err = mt.miner.SubmitHeader(header1) if err != nil { t.Fatal(err) } err = mt.miner.SubmitHeader(header2) if err != modules.ErrNonExtendingBlock { t.Fatal(err) } goodBlocks, staleBlocks := mt.miner.BlocksMined() if goodBlocks != 1 { t.Error("expexting 1 good block") } if staleBlocks != 1 { t.Error("expecting 1 stale block, got", staleBlocks) } // Reboot the miner and verify that the block record has persisted. err = mt.miner.Close() if err != nil { t.Fatal(err) } rebootMiner, err := New(mt.cs, mt.tpool, mt.wallet, filepath.Join(mt.persistDir, modules.MinerDir)) if err != nil { t.Fatal(err) } goodBlocks, staleBlocks = rebootMiner.BlocksMined() if goodBlocks != 1 { t.Error("expexting 1 good block") } if staleBlocks != 1 { t.Error("expecting 1 stale block, got", staleBlocks) } }
// ID returns the ID of a Block, which is calculated by hashing the // concatenation of the block's parent's ID, nonce, and the result of the // b.MerkleRoot(). func (b Block) ID() BlockID { return BlockID(crypto.HashObject(b.Header())) }
// SiaClaimOutputID returns the ID of the SiacoinOutput that is created when // the siafund output is spent. The ID is the hash the SiafundOutputID. func (id SiafundOutputID) SiaClaimOutputID() SiacoinOutputID { return SiacoinOutputID(crypto.HashObject(id)) }
// String returns the hex representation of the unlock hash as a string - this // includes a checksum. func (uh UnlockHash) String() string { uhChecksum := crypto.HashObject(uh) return fmt.Sprintf("%x%x", uh[:], uhChecksum[:UnlockHashChecksumSize]) }
// TestPrimarySeed checks that the correct seed is returned when calling // PrimarySeed. func TestPrimarySeed(t *testing.T) { if testing.Short() { t.SkipNow() } // Create a wallet and fetch the seed at startup. dir := build.TempDir(modules.WalletDir, "TestPrimarySeed") g, err := gateway.New(":0", filepath.Join(dir, modules.GatewayDir)) if err != nil { t.Fatal(err) } cs, err := consensus.New(g, filepath.Join(dir, modules.ConsensusDir)) if err != nil { t.Fatal(err) } tp, err := transactionpool.New(cs, g) if err != nil { t.Fatal(err) } w, err := New(cs, tp, filepath.Join(dir, modules.WalletDir)) if err != nil { t.Fatal(err) } seed, err := w.Encrypt(crypto.TwofishKey{}) if err != nil { t.Fatal(err) } err = w.Unlock(crypto.TwofishKey(crypto.HashObject(seed))) if err != nil { t.Fatal(err) } primarySeed, progress, err := w.PrimarySeed() if err != nil { t.Fatal(err) } if !bytes.Equal(primarySeed[:], seed[:]) { t.Error("PrimarySeed is returning a value inconsitent with the seed returned by Encrypt") } if progress != 0 { t.Error("primary seed is returning the wrong progress") } _, err = w.NextAddress() if err != nil { t.Fatal(err) } _, progress, err = w.PrimarySeed() if err != nil { t.Fatal(err) } if progress != 1 { t.Error("primary seed is returning the wrong progress") } // Lock then unlock the wallet and check the responses. err = w.Lock() if err != nil { t.Fatal(err) } _, _, err = w.PrimarySeed() if err != modules.ErrLockedWallet { t.Error("unexpected err:", err) } err = w.Unlock(crypto.TwofishKey(crypto.HashObject(seed))) if err != nil { t.Fatal(err) } primarySeed, progress, err = w.PrimarySeed() if err != nil { t.Fatal(err) } if !bytes.Equal(primarySeed[:], seed[:]) { t.Error("PrimarySeed is returning a value inconsitent with the seed returned by Encrypt") } if progress != 1 { t.Error("progress reporting an unexpected value") } }
// TestLoadSeed checks that a seed can be successfully recovered from a wallet, // and then remain available on subsequent loads of the wallet. func TestLoadSeed(t *testing.T) { if testing.Short() { t.SkipNow() } wt, err := createWalletTester("TestLoadSeed") if err != nil { t.Fatal(err) } seed, _, err := wt.wallet.PrimarySeed() if err != nil { t.Fatal(err) } allSeeds, err := wt.wallet.AllSeeds() if err != nil { t.Fatal(err) } if len(allSeeds) != 1 { t.Error("AllSeeds should be returning the primary seed.") } if allSeeds[0] != seed { t.Error("AllSeeds returned the wrong seed") } dir := filepath.Join(build.TempDir(modules.WalletDir, "TestLoadSeed - 0"), modules.WalletDir) w, err := New(wt.cs, wt.tpool, dir) if err != nil { t.Fatal(err) } newSeed, err := w.Encrypt(crypto.TwofishKey{}) if err != nil { t.Fatal(err) } err = w.Unlock(crypto.TwofishKey(crypto.HashObject(newSeed))) if err != nil { t.Fatal(err) } // Balance of wallet should be 0. siacoinBal, _, _ := w.ConfirmedBalance() if siacoinBal.Cmp(types.NewCurrency64(0)) != 0 { t.Error("fresh wallet should not have a balance") } err = w.LoadSeed(crypto.TwofishKey(crypto.HashObject(newSeed)), seed) if err != nil { t.Fatal(err) } allSeeds, err = w.AllSeeds() if err != nil { t.Fatal(err) } if len(allSeeds) != 2 { t.Error("AllSeeds should be returning the primary seed with the recovery seed.") } if !bytes.Equal(allSeeds[0][:], newSeed[:]) { t.Error("AllSeeds returned the wrong seed") } if !bytes.Equal(allSeeds[1][:], seed[:]) { t.Error("AllSeeds returned the wrong seed") } // Rather than worry about a rescan, which isn't implemented and has // synchronization difficulties, just load a new wallet from the same // settings file - the same effect is achieved without the difficulties. w2, err := New(wt.cs, wt.tpool, dir) if err != nil { t.Fatal(err) } err = w2.Unlock(crypto.TwofishKey(crypto.HashObject(newSeed))) if err != nil { t.Fatal(err) } siacoinBal2, _, _ := w2.ConfirmedBalance() if siacoinBal2.Cmp(types.NewCurrency64(0)) <= 0 { t.Error("wallet failed to load a seed with money in it") } allSeeds, err = w2.AllSeeds() if err != nil { t.Fatal(err) } if len(allSeeds) != 2 { t.Error("AllSeeds should be returning the primary seed with the recovery seed.") } if !bytes.Equal(allSeeds[0][:], newSeed[:]) { t.Error("AllSeeds returned the wrong seed") } if !bytes.Equal(allSeeds[1][:], seed[:]) { t.Error("AllSeeds returned the wrong seed") } }
// TestPrimarySeed checks that the correct seed is returned when calling // PrimarySeed. func TestPrimarySeed(t *testing.T) { if testing.Short() { t.SkipNow() } // Start with a blank wallet tester. wt, err := createBlankWalletTester("TestPrimarySeed") if err != nil { t.Fatal(err) } // Create a seed and unlock the wallet. seed, err := wt.wallet.Encrypt(crypto.TwofishKey{}) if err != nil { t.Fatal(err) } err = wt.wallet.Unlock(crypto.TwofishKey(crypto.HashObject(seed))) if err != nil { t.Fatal(err) } // Try getting an address, see that the seed advances correctly. primarySeed, progress, err := wt.wallet.PrimarySeed() if err != nil { t.Fatal(err) } if !bytes.Equal(primarySeed[:], seed[:]) { t.Error("PrimarySeed is returning a value inconsitent with the seed returned by Encrypt") } if progress != 0 { t.Error("primary seed is returning the wrong progress") } _, err = wt.wallet.NextAddress() if err != nil { t.Fatal(err) } _, progress, err = wt.wallet.PrimarySeed() if err != nil { t.Fatal(err) } if progress != 1 { t.Error("primary seed is returning the wrong progress") } // Lock then unlock the wallet and check the responses. err = wt.wallet.Lock() if err != nil { t.Fatal(err) } _, _, err = wt.wallet.PrimarySeed() if err != modules.ErrLockedWallet { t.Error("unexpected err:", err) } err = wt.wallet.Unlock(crypto.TwofishKey(crypto.HashObject(seed))) if err != nil { t.Fatal(err) } primarySeed, progress, err = wt.wallet.PrimarySeed() if err != nil { t.Fatal(err) } if !bytes.Equal(primarySeed[:], seed[:]) { t.Error("PrimarySeed is returning a value inconsitent with the seed returned by Encrypt") } if progress != 1 { t.Error("progress reporting an unexpected value") } }
// ReceiveConsensusSetUpdate gets called to inform the transaction pool of // changes to the consensus set. func (tp *TransactionPool) ReceiveConsensusSetUpdate(cc modules.ConsensusChange) { lockID := tp.mu.Lock() defer tp.mu.Unlock(lockID) // Save all of the reverted transactions. Transactions need to appear in // 'unconfirmedTxns' in the same order that they would appear in the // blockchain. 'revertedBlocks' is backwards (first element has highest // height), so each time a new block processed, the transactions need to be // prepended to the list of unconfirmed transactions. var unconfirmedTxns []types.Transaction for _, block := range cc.RevertedBlocks { unconfirmedTxns = append(block.Transactions, unconfirmedTxns...) } // Delete the hashes of each unconfirmed transaction from the 'already // seen' list. for _, txn := range unconfirmedTxns { // Sanity check - transaction should be in the list of already seen // transactions. if build.DEBUG { _, exists := tp.transactions[crypto.HashObject(txn)] if !exists { panic("transaction should be in the list of already seen transactions") } } delete(tp.transactions, crypto.HashObject(txn)) } // Add all of the current unconfirmed transactions to the unconfirmed // transaction list. unconfirmedTxns = append(unconfirmedTxns, tp.transactionList...) // Purge the pool of unconfirmed transactions so that there is no // interference from unconfirmed transactions during the application of // potentially conflicting transactions that have been added to the // blockchain. tp.purge() // Apply consensus set diffs and adjust the height. tp.applyDiffs(cc.SiacoinOutputDiffs, cc.FileContractDiffs, cc.SiafundOutputDiffs, modules.DiffApply) tp.consensusSetHeight -= types.BlockHeight(len(cc.RevertedBlocks)) tp.consensusSetHeight += types.BlockHeight(len(cc.AppliedBlocks)) // Mark all of the newly applied transactions as 'already seen'. for _, block := range cc.AppliedBlocks { for _, txn := range block.Transactions { tp.transactions[crypto.HashObject(txn)] = struct{}{} } } // Add all potential unconfirmed transactions back into the pool after // checking that they are still valid. for _, txn := range unconfirmedTxns { // Skip transactions that are now in the consensus set or are otherwise // repeats. _, exists := tp.transactions[crypto.HashObject(txn)] if exists { continue } // Check that the transaction is still valid given the updated // consensus set. err := tp.validUnconfirmedTransaction(txn) if err != nil { continue } // Add the transaction back to the pool. tp.addTransactionToPool(txn) } // Inform subscribers that an update has executed. tp.updateSubscribers(cc, tp.transactionList, tp.unconfirmedSiacoinOutputDiffs()) }