// 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") } }
// 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 }
// 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") } }
// 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) }
// explorerHashHandler handles GET requests to /explorer/hash/:hash. func (srv *Server) explorerHashHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { // The hash is scanned as an address, because an address can be typecast to // all other necessary types, and will correctly decode hashes whether or // not they have a checksum. hash, err := scanAddress(ps.ByName("hash")) if err != nil { writeError(w, err.Error(), http.StatusBadRequest) return } // Try the hash as a block id. block, height, exists := srv.explorer.Block(types.BlockID(hash)) if exists { writeJSON(w, ExplorerHashGET{ HashType: "blockid", Block: srv.buildExplorerBlock(height, block), }) return } // Try the hash as a transaction id. block, height, exists = srv.explorer.Transaction(types.TransactionID(hash)) if exists { var txn types.Transaction for _, t := range block.Transactions { if t.ID() == types.TransactionID(hash) { txn = t } } writeJSON(w, ExplorerHashGET{ HashType: "transactionid", Transaction: srv.buildExplorerTransaction(height, block.ID(), txn), }) return } // Try the hash as a siacoin output id. txids := srv.explorer.SiacoinOutputID(types.SiacoinOutputID(hash)) if len(txids) != 0 { txns, blocks := srv.buildTransactionSet(txids) writeJSON(w, ExplorerHashGET{ HashType: "siacoinoutputid", Blocks: blocks, Transactions: txns, }) return } // Try the hash as a file contract id. txids = srv.explorer.FileContractID(types.FileContractID(hash)) if len(txids) != 0 { txns, blocks := srv.buildTransactionSet(txids) writeJSON(w, ExplorerHashGET{ HashType: "filecontractid", Blocks: blocks, Transactions: txns, }) return } // Try the hash as a siafund output id. txids = srv.explorer.SiafundOutputID(types.SiafundOutputID(hash)) if len(txids) != 0 { txns, blocks := srv.buildTransactionSet(txids) writeJSON(w, ExplorerHashGET{ HashType: "siafundoutputid", Blocks: blocks, Transactions: txns, }) return } // Try the hash as an unlock hash. Unlock hash is checked last because // unlock hashes do not have collision-free guarantees. Someone can create // an unlock hash that collides with another object id. They will not be // able to use the unlock hash, but they can disrupt the explorer. This is // handled by checking the unlock hash last. Anyone intentionally creating // a colliding unlock hash (such a collision can only happen if done // intentionally) will be unable to find their unlock hash in the // blockchain through the explorer hash lookup. txids = srv.explorer.UnlockHash(types.UnlockHash(hash)) if len(txids) != 0 { txns, blocks := srv.buildTransactionSet(txids) writeJSON(w, ExplorerHashGET{ HashType: "unlockhash", Blocks: blocks, Transactions: txns, }) return } // Hash not found, return an error. writeError(w, "unrecognized hash used as input to /explorer/hash", http.StatusBadRequest) }
// explorerHashHandler handles GET requests to /explorer/hash/:hash. func (api *API) explorerHashHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { // Scan the hash as a hash. If that fails, try scanning the hash as an // address. hash, err := scanHash(ps.ByName("hash")) if err != nil { addr, err := scanAddress(ps.ByName("hash")) if err != nil { WriteError(w, Error{err.Error()}, http.StatusBadRequest) return } hash = crypto.Hash(addr) } // TODO: lookups on the zero hash are too expensive to allow. Need a // better way to handle this case. if hash == (crypto.Hash{}) { WriteError(w, Error{"can't lookup the empty unlock hash"}, http.StatusBadRequest) return } // Try the hash as a block id. block, height, exists := api.explorer.Block(types.BlockID(hash)) if exists { WriteJSON(w, ExplorerHashGET{ HashType: "blockid", Block: api.buildExplorerBlock(height, block), }) return } // Try the hash as a transaction id. block, height, exists = api.explorer.Transaction(types.TransactionID(hash)) if exists { var txn types.Transaction for _, t := range block.Transactions { if t.ID() == types.TransactionID(hash) { txn = t } } WriteJSON(w, ExplorerHashGET{ HashType: "transactionid", Transaction: api.buildExplorerTransaction(height, block.ID(), txn), }) return } // Try the hash as a siacoin output id. txids := api.explorer.SiacoinOutputID(types.SiacoinOutputID(hash)) if len(txids) != 0 { txns, blocks := api.buildTransactionSet(txids) WriteJSON(w, ExplorerHashGET{ HashType: "siacoinoutputid", Blocks: blocks, Transactions: txns, }) return } // Try the hash as a file contract id. txids = api.explorer.FileContractID(types.FileContractID(hash)) if len(txids) != 0 { txns, blocks := api.buildTransactionSet(txids) WriteJSON(w, ExplorerHashGET{ HashType: "filecontractid", Blocks: blocks, Transactions: txns, }) return } // Try the hash as a siafund output id. txids = api.explorer.SiafundOutputID(types.SiafundOutputID(hash)) if len(txids) != 0 { txns, blocks := api.buildTransactionSet(txids) WriteJSON(w, ExplorerHashGET{ HashType: "siafundoutputid", Blocks: blocks, Transactions: txns, }) return } // Try the hash as an unlock hash. Unlock hash is checked last because // unlock hashes do not have collision-free guarantees. Someone can create // an unlock hash that collides with another object id. They will not be // able to use the unlock hash, but they can disrupt the explorer. This is // handled by checking the unlock hash last. Anyone intentionally creating // a colliding unlock hash (such a collision can only happen if done // intentionally) will be unable to find their unlock hash in the // blockchain through the explorer hash lookup. txids = api.explorer.UnlockHash(types.UnlockHash(hash)) if len(txids) != 0 { txns, blocks := api.buildTransactionSet(txids) WriteJSON(w, ExplorerHashGET{ HashType: "unlockhash", Blocks: blocks, Transactions: txns, }) return } // Hash not found, return an error. WriteError(w, Error{"unrecognized hash used as input to /explorer/hash"}, http.StatusBadRequest) }
// Add a couple blocks to the database, then perform lookups to see if // they were added and crossed referenced correctly func (et *explorerTester) testAddBlock(t *testing.T) error { // This block will *NOT* be valid, but should contain // addresses that can cross reference each other. b1 := types.Block{ ParentID: types.BlockID(genHashNum(1)), Nonce: [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, Timestamp: 3, MinerPayouts: []types.SiacoinOutput{types.SiacoinOutput{ Value: types.NewCurrency64(4), UnlockHash: types.UnlockHash(genHashNum(5)), }}, Transactions: nil, } // This should not error at least... lockID := et.explorer.mu.Lock() err := et.explorer.addBlockDB(b1) et.explorer.mu.Unlock(lockID) if err != nil { return errors.New("Error inserting basic block: " + err.Error()) } // Again, not a valid block at all. b2 := types.Block{ ParentID: b1.ID(), Nonce: [8]byte{7, 7, 7, 7, 7, 7, 7, 7}, Timestamp: 8, MinerPayouts: nil, Transactions: []types.Transaction{types.Transaction{ SiacoinInputs: []types.SiacoinInput{types.SiacoinInput{ ParentID: b1.MinerPayoutID(0), }}, FileContracts: []types.FileContract{types.FileContract{ UnlockHash: types.UnlockHash(genHashNum(10)), }}, }}, } lockID = et.explorer.mu.Lock() err = et.explorer.addBlockDB(b2) et.explorer.mu.Unlock(lockID) if err != nil { return errors.New("Error inserting block 2: " + err.Error()) } // Now query the database to see if it has been linked properly lockID = et.explorer.mu.RLock() bytes, err := et.explorer.db.GetFromBucket("Blocks", encoding.Marshal(b1.ID())) et.explorer.mu.RUnlock(lockID) var b types.Block err = encoding.Unmarshal(bytes, &b) if err != nil { return errors.New("Could not decode loaded block") } if b.ID() != b1.ID() { return errors.New("Block 1 not stored properly") } // Query to see if the input is added to the output field lockID = et.explorer.mu.RLock() bytes, err = et.explorer.db.GetFromBucket("SiacoinOutputs", encoding.Marshal(b1.MinerPayoutID(0))) et.explorer.mu.RUnlock(lockID) if err != nil { t.Fatal(err.Error()) } if bytes == nil { return errors.New("Output is nil") } var ot outputTransactions err = encoding.Unmarshal(bytes, &ot) if err != nil { return errors.New("Could not decode loaded block") } if ot.InputTx == (types.TransactionID{}) { return errors.New("Input not added as output") } return nil }