// 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) }
// 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) }
// 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 }