package server import ( "bytes" "compress/zlib" "crypto/sha256" "encoding/base64" "encoding/binary" "encoding/hex" "encoding/json" "errors" "fmt" "github.com/lbryio/herald.go/db" "github.com/lbryio/herald.go/internal" "github.com/lbryio/lbcd/chaincfg" "github.com/lbryio/lbcd/chaincfg/chainhash" "github.com/lbryio/lbcd/txscript" "github.com/lbryio/lbcd/wire" "github.com/lbryio/lbcutil" "golang.org/x/exp/constraints" log "github.com/sirupsen/logrus" ) // BlockchainBlockService methods handle "blockchain.block.*" RPCs type BlockchainBlockService struct { DB *db.ReadOnlyDBColumnFamily Chain *chaincfg.Params } // BlockchainBlockService methods handle "blockchain.headers.*" RPCs type BlockchainHeadersService struct { DB *db.ReadOnlyDBColumnFamily Chain *chaincfg.Params // needed for subscribe/unsubscribe sessionMgr *sessionManager session *session } // BlockchainAddressService methods handle "blockchain.address.*" RPCs type BlockchainAddressService struct { DB *db.ReadOnlyDBColumnFamily Chain *chaincfg.Params // needed for subscribe/unsubscribe sessionMgr *sessionManager session *session } // BlockchainScripthashService methods handle "blockchain.scripthash.*" RPCs type BlockchainScripthashService struct { DB *db.ReadOnlyDBColumnFamily Chain *chaincfg.Params // needed for subscribe/unsubscribe sessionMgr *sessionManager session *session } // BlockchainTransactionService methods handle "blockchain.transaction.*" RPCs type BlockchainTransactionService struct { DB *db.ReadOnlyDBColumnFamily Chain *chaincfg.Params // needed for broadcast TX sessionMgr *sessionManager } const CHUNK_SIZE = 96 const MAX_CHUNK_SIZE = 40960 const HEADER_SIZE = wire.MaxBlockHeaderPayload const HASHX_LEN = 11 func min[Ord constraints.Ordered](x, y Ord) Ord { if x < y { return x } return y } func max[Ord constraints.Ordered](x, y Ord) Ord { if x > y { return x } return y } type BlockHeaderElectrum struct { Version uint32 `json:"version"` PrevBlockHash string `json:"prev_block_hash"` MerkleRoot string `json:"merkle_root"` ClaimTrieRoot string `json:"claim_trie_root"` Timestamp uint32 `json:"timestamp"` Bits uint32 `json:"bits"` Nonce uint32 `json:"nonce"` BlockHeight uint32 `json:"block_height"` } func newBlockHeaderElectrum(header *[HEADER_SIZE]byte, height uint32) *BlockHeaderElectrum { var h1, h2, h3 chainhash.Hash h1.SetBytes(header[4:36]) h2.SetBytes(header[36:68]) h3.SetBytes(header[68:100]) return &BlockHeaderElectrum{ Version: binary.LittleEndian.Uint32(header[0:]), PrevBlockHash: h1.String(), MerkleRoot: h2.String(), ClaimTrieRoot: h3.String(), Timestamp: binary.LittleEndian.Uint32(header[100:]), Bits: binary.LittleEndian.Uint32(header[104:]), Nonce: binary.LittleEndian.Uint32(header[108:]), BlockHeight: height, } } type BlockGetServerHeightReq struct{} type BlockGetServerHeightResp uint32 // blockchain.block.get_server_height func (s *BlockchainBlockService) Get_server_height(req *BlockGetServerHeightReq, resp **BlockGetServerHeightResp) error { if s.DB == nil || s.DB.LastState == nil { return fmt.Errorf("unknown height") } result := BlockGetServerHeightResp(s.DB.LastState.Height) *resp = &result return nil } type BlockGetChunkReq uint32 type BlockGetChunkResp string // 'blockchain.block.get_chunk' func (s *BlockchainBlockService) Get_chunk(req *BlockGetChunkReq, resp **BlockGetChunkResp) error { index := uint32(*req) db_headers, err := s.DB.GetHeaders(index*CHUNK_SIZE, CHUNK_SIZE) if err != nil { log.Warn(err) return err } raw := make([]byte, 0, HEADER_SIZE*len(db_headers)) for _, h := range db_headers { raw = append(raw, h[:]...) } headers := BlockGetChunkResp(hex.EncodeToString(raw)) *resp = &headers return err } type BlockGetHeaderReq uint32 type BlockGetHeaderResp struct { BlockHeaderElectrum } // 'blockchain.block.get_header' func (s *BlockchainBlockService) Get_header(req *BlockGetHeaderReq, resp **BlockGetHeaderResp) error { height := uint32(*req) headers, err := s.DB.GetHeaders(height, 1) if err != nil { log.Warn(err) return err } if len(headers) < 1 { return errors.New("not found") } *resp = &BlockGetHeaderResp{*newBlockHeaderElectrum(&headers[0], height)} return err } type BlockHeadersReq struct { StartHeight uint32 `json:"start_height"` Count uint32 `json:"count"` CpHeight uint32 `json:"cp_height"` B64 bool `json:"b64"` } func (req *BlockHeadersReq) UnmarshalJSON(b []byte) error { var params [4]interface{} err := json.Unmarshal(b, ¶ms) if err != nil { return err } switch params[0].(type) { case float64: req.StartHeight = uint32(params[0].(float64)) default: return fmt.Errorf("expected numeric argument #0 (start_height)") } switch params[1].(type) { case float64: req.Count = uint32(params[1].(float64)) default: return fmt.Errorf("expected numeric argument #1 (count)") } switch params[2].(type) { case float64: req.CpHeight = uint32(params[2].(float64)) default: return fmt.Errorf("expected numeric argument #2 (cp_height)") } switch params[3].(type) { case bool: req.B64 = params[3].(bool) default: return fmt.Errorf("expected boolean argument #3 (b64)") } return nil } type BlockHeadersResp struct { Base64 string `json:"base64,omitempty"` Hex string `json:"hex"` Count uint32 `json:"count"` Max uint32 `json:"max"` Branch string `json:"branch,omitempty"` Root string `json:"root,omitempty"` } // 'blockchain.block.headers' func (s *BlockchainBlockService) Headers(req *BlockHeadersReq, resp **BlockHeadersResp) error { count := min(req.Count, MAX_CHUNK_SIZE) db_headers, err := s.DB.GetHeaders(req.StartHeight, count) if err != nil { log.Warn(err) return err } count = uint32(len(db_headers)) raw := make([]byte, 0, HEADER_SIZE*count) for _, h := range db_headers { raw = append(raw, h[:]...) } result := &BlockHeadersResp{ Count: count, Max: MAX_CHUNK_SIZE, } if req.B64 { zipped := bytes.Buffer{} w := zlib.NewWriter(&zipped) w.Write(raw) w.Close() result.Base64 = base64.StdEncoding.EncodeToString(zipped.Bytes()) } else { result.Hex = hex.EncodeToString(raw) } if count > 0 && req.CpHeight > 0 { // TODO //last_height := height + count - 1 } *resp = result return err } type HeadersSubscribeReq struct { Raw bool `json:"raw"` } func (req *HeadersSubscribeReq) UnmarshalJSON(b []byte) error { var params [1]interface{} err := json.Unmarshal(b, ¶ms) if err != nil { return err } switch params[0].(type) { case bool: req.Raw = params[0].(bool) default: return fmt.Errorf("expected bool argument #0 (raw)") } return nil } type HeadersSubscribeResp struct { BlockHeaderElectrum } type HeadersSubscribeRawResp struct { Hex string `json:"hex"` Height uint32 `json:"height"` } // 'blockchain.headers.subscribe' func (s *BlockchainHeadersService) Subscribe(req *HeadersSubscribeReq, resp *interface{}) error { if s.sessionMgr == nil || s.session == nil { return errors.New("no session, rpc not supported") } s.sessionMgr.headersSubscribe(s.session, req.Raw, true /*subscribe*/) height := s.DB.Height if s.DB.LastState != nil { height = s.DB.LastState.Height } headers, err := s.DB.GetHeaders(height, 1) if err != nil { s.sessionMgr.headersSubscribe(s.session, req.Raw, false /*subscribe*/) return err } if len(headers) < 1 { return errors.New("not found") } if req.Raw { *resp = &HeadersSubscribeRawResp{ Hex: hex.EncodeToString(headers[0][:]), Height: height, } } else { *resp = &HeadersSubscribeResp{*newBlockHeaderElectrum(&headers[0], height)} } return err } func decodeScriptHash(scripthash string) ([]byte, error) { sh, err := hex.DecodeString(scripthash) if err != nil { return nil, err } if len(sh) != chainhash.HashSize { return nil, fmt.Errorf("invalid scripthash: %v (length %v)", scripthash, len(sh)) } internal.ReverseBytesInPlace(sh) return sh, nil } func hashX(scripthash []byte) []byte { return scripthash[:HASHX_LEN] } func hashXScript(script []byte, coin *chaincfg.Params) []byte { if _, err := txscript.ExtractClaimScript(script); err == nil { baseScript := txscript.StripClaimScriptPrefix(script) if class, addrs, _, err := txscript.ExtractPkScriptAddrs(baseScript, coin); err == nil { switch class { case txscript.PubKeyHashTy, txscript.ScriptHashTy, txscript.PubKeyTy: script, _ := txscript.PayToAddrScript(addrs[0]) return hashXScript(script, coin) } } } sum := sha256.Sum256(script) return sum[:HASHX_LEN] } type AddressGetBalanceReq struct { Address string `json:"address"` } type AddressGetBalanceResp struct { Confirmed uint64 `json:"confirmed"` Unconfirmed uint64 `json:"unconfirmed"` } // 'blockchain.address.get_balance' func (s *BlockchainAddressService) Get_balance(req *AddressGetBalanceReq, resp **AddressGetBalanceResp) error { address, err := lbcutil.DecodeAddress(req.Address, s.Chain) if err != nil { log.Warn(err) return err } script, err := txscript.PayToAddrScript(address) if err != nil { log.Warn(err) return err } hashX := hashXScript(script, s.Chain) confirmed, unconfirmed, err := s.DB.GetBalance(hashX) if err != nil { log.Warn(err) return err } *resp = &AddressGetBalanceResp{confirmed, unconfirmed} return err } type scripthashGetBalanceReq struct { ScriptHash string `json:"scripthash"` } type ScripthashGetBalanceResp struct { Confirmed uint64 `json:"confirmed"` Unconfirmed uint64 `json:"unconfirmed"` } // 'blockchain.scripthash.get_balance' func (s *BlockchainScripthashService) Get_balance(req *scripthashGetBalanceReq, resp **ScripthashGetBalanceResp) error { scripthash, err := decodeScriptHash(req.ScriptHash) if err != nil { log.Warn(err) return err } hashX := hashX(scripthash) confirmed, unconfirmed, err := s.DB.GetBalance(hashX) if err != nil { log.Warn(err) return err } *resp = &ScripthashGetBalanceResp{confirmed, unconfirmed} return err } type AddressGetHistoryReq struct { Address string `json:"address"` } func (req *AddressGetHistoryReq) UnmarshalJSON(b []byte) error { var params [1]interface{} json.Unmarshal(b, ¶ms) err := json.Unmarshal(b, ¶ms) if err != nil { return err } switch params[0].(type) { case string: req.Address = params[0].(string) default: return fmt.Errorf("expected string argument #0 (address)") } return nil } type TxInfo struct { TxHash string `json:"tx_hash"` Height uint32 `json:"height"` } type TxInfoFee struct { TxInfo Fee uint64 `json:"fee"` } type AddressGetHistoryResp []TxInfoFee // 'blockchain.address.get_history' func (s *BlockchainAddressService) Get_history(req *AddressGetHistoryReq, resp **AddressGetHistoryResp) error { address, err := lbcutil.DecodeAddress(req.Address, s.Chain) if err != nil { log.Warn(err) return err } script, err := txscript.PayToAddrScript(address) if err != nil { log.Warn(err) return err } hashX := hashXScript(script, s.Chain) dbTXs, err := s.DB.GetHistory(hashX) if err != nil { log.Warn(err) return err } confirmed := make([]TxInfo, 0, len(dbTXs)) for _, tx := range dbTXs { confirmed = append(confirmed, TxInfo{ TxHash: tx.TxHash.String(), Height: tx.Height, }) } unconfirmed := []TxInfoFee{} // TODO result := make(AddressGetHistoryResp, len(confirmed)+len(unconfirmed)) i := 0 for _, tx := range confirmed { result[i].TxInfo = tx i += 1 } for _, tx := range unconfirmed { result[i] = tx i += 1 } *resp = &result return err } type ScripthashGetHistoryReq struct { ScriptHash string `json:"scripthash"` } type ScripthashGetHistoryResp struct { Confirmed []TxInfo `json:"confirmed"` Unconfirmed []TxInfoFee `json:"unconfirmed"` } // 'blockchain.scripthash.get_history' func (s *BlockchainScripthashService) Get_history(req *ScripthashGetHistoryReq, resp **ScripthashGetHistoryResp) error { scripthash, err := decodeScriptHash(req.ScriptHash) if err != nil { log.Warn(err) return err } hashX := hashX(scripthash) dbTXs, err := s.DB.GetHistory(hashX) if err != nil { log.Warn(err) return err } confirmed := make([]TxInfo, 0, len(dbTXs)) for _, tx := range dbTXs { confirmed = append(confirmed, TxInfo{ TxHash: tx.TxHash.String(), Height: tx.Height, }) } result := &ScripthashGetHistoryResp{ Confirmed: confirmed, Unconfirmed: []TxInfoFee{}, // TODO } *resp = result return err } type AddressGetMempoolReq struct { Address string `json:"address"` } type AddressGetMempoolResp []TxInfoFee // 'blockchain.address.get_mempool' func (s *BlockchainAddressService) Get_mempool(req *AddressGetMempoolReq, resp **AddressGetMempoolResp) error { address, err := lbcutil.DecodeAddress(req.Address, s.Chain) if err != nil { log.Warn(err) return err } script, err := txscript.PayToAddrScript(address) if err != nil { log.Warn(err) return err } hashX := hashXScript(script, s.Chain) // TODO... internal.ReverseBytesInPlace(hashX) unconfirmed := make([]TxInfoFee, 0, 100) result := AddressGetMempoolResp(unconfirmed) *resp = &result return err } type ScripthashGetMempoolReq struct { ScriptHash string `json:"scripthash"` } type ScripthashGetMempoolResp []TxInfoFee // 'blockchain.scripthash.get_mempool' func (s *BlockchainScripthashService) Get_mempool(req *ScripthashGetMempoolReq, resp **ScripthashGetMempoolResp) error { scripthash, err := decodeScriptHash(req.ScriptHash) if err != nil { log.Warn(err) return err } hashX := hashX(scripthash) // TODO... internal.ReverseBytesInPlace(hashX) unconfirmed := make([]TxInfoFee, 0, 100) result := ScripthashGetMempoolResp(unconfirmed) *resp = &result return err } type AddressListUnspentReq struct { Address string `json:"address"` } type TXOInfo struct { TxHash string `json:"tx_hash"` TxPos uint16 `json:"tx_pos"` Height uint32 `json:"height"` Value uint64 `json:"value"` } type AddressListUnspentResp []TXOInfo // 'blockchain.address.listunspent' func (s *BlockchainAddressService) Listunspent(req *AddressListUnspentReq, resp **AddressListUnspentResp) error { address, err := lbcutil.DecodeAddress(req.Address, s.Chain) if err != nil { log.Warn(err) return err } script, err := txscript.PayToAddrScript(address) if err != nil { log.Warn(err) return err } hashX := hashXScript(script, s.Chain) dbTXOs, err := s.DB.GetUnspent(hashX) unspent := make([]TXOInfo, 0, len(dbTXOs)) for _, txo := range dbTXOs { unspent = append(unspent, TXOInfo{ TxHash: txo.TxHash.String(), TxPos: txo.TxPos, Height: txo.Height, Value: txo.Value, }) } result := AddressListUnspentResp(unspent) *resp = &result return err } type ScripthashListUnspentReq struct { ScriptHash string `json:"scripthash"` } type ScripthashListUnspentResp []TXOInfo // 'blockchain.scripthash.listunspent' func (s *BlockchainScripthashService) Listunspent(req *ScripthashListUnspentReq, resp **ScripthashListUnspentResp) error { scripthash, err := decodeScriptHash(req.ScriptHash) if err != nil { log.Warn(err) return err } hashX := hashX(scripthash) dbTXOs, err := s.DB.GetUnspent(hashX) unspent := make([]TXOInfo, 0, len(dbTXOs)) for _, txo := range dbTXOs { unspent = append(unspent, TXOInfo{ TxHash: txo.TxHash.String(), TxPos: txo.TxPos, Height: txo.Height, Value: txo.Value, }) } result := ScripthashListUnspentResp(unspent) *resp = &result return err } type AddressSubscribeReq []string type AddressSubscribeResp []string // 'blockchain.address.subscribe' func (s *BlockchainAddressService) Subscribe(req *AddressSubscribeReq, resp **AddressSubscribeResp) error { if s.sessionMgr == nil || s.session == nil { return errors.New("no session, rpc not supported") } result := make([]string, 0, len(*req)) for _, addr := range *req { address, err := lbcutil.DecodeAddress(addr, s.Chain) if err != nil { return err } script, err := txscript.PayToAddrScript(address) if err != nil { return err } hashX := hashXScript(script, s.Chain) s.sessionMgr.hashXSubscribe(s.session, hashX, addr, true /*subscribe*/) status, err := s.DB.GetStatus(hashX) if err != nil { return err } result = append(result, hex.EncodeToString(status)) } *resp = (*AddressSubscribeResp)(&result) return nil } // 'blockchain.address.unsubscribe' func (s *BlockchainAddressService) Unsubscribe(req *AddressSubscribeReq, resp **AddressSubscribeResp) error { if s.sessionMgr == nil || s.session == nil { return errors.New("no session, rpc not supported") } for _, addr := range *req { address, err := lbcutil.DecodeAddress(addr, s.Chain) if err != nil { return err } script, err := txscript.PayToAddrScript(address) if err != nil { return err } hashX := hashXScript(script, s.Chain) s.sessionMgr.hashXSubscribe(s.session, hashX, addr, false /*subscribe*/) } *resp = (*AddressSubscribeResp)(nil) return nil } type ScripthashSubscribeReq string type ScripthashSubscribeResp string // 'blockchain.scripthash.subscribe' func (s *BlockchainScripthashService) Subscribe(req *ScripthashSubscribeReq, resp **ScripthashSubscribeResp) error { if s.sessionMgr == nil || s.session == nil { return errors.New("no session, rpc not supported") } var result string scripthash, err := decodeScriptHash(string(*req)) if err != nil { return err } hashX := hashX(scripthash) s.sessionMgr.hashXSubscribe(s.session, hashX, string(*req), true /*subscribe*/) status, err := s.DB.GetStatus(hashX) if err != nil { return err } result = hex.EncodeToString(status) *resp = (*ScripthashSubscribeResp)(&result) return nil } // 'blockchain.scripthash.unsubscribe' func (s *BlockchainScripthashService) Unsubscribe(req *ScripthashSubscribeReq, resp **ScripthashSubscribeResp) error { if s.sessionMgr == nil || s.session == nil { return errors.New("no session, rpc not supported") } scripthash, err := decodeScriptHash(string(*req)) if err != nil { return err } hashX := hashX(scripthash) s.sessionMgr.hashXSubscribe(s.session, hashX, string(*req), false /*subscribe*/) *resp = (*ScripthashSubscribeResp)(nil) return nil } type TransactionBroadcastReq string type TransactionBroadcastResp string // 'blockchain.transaction.broadcast' func (s *BlockchainTransactionService) Broadcast(req *TransactionBroadcastReq, resp **TransactionBroadcastResp) error { if s.sessionMgr == nil { return errors.New("no session manager, rpc not supported") } strTx := string(*req) rawTx, err := hex.DecodeString(strTx) if err != nil { return err } txhash, err := s.sessionMgr.broadcastTx(rawTx) if err != nil { return err } result := txhash.String() *resp = (*TransactionBroadcastResp)(&result) return nil } type TransactionGetReq string type TXFullDetail struct { Height int `json:"block_height"` Pos uint32 `json:"pos"` Merkle []string `json:"merkle"` } type TXDetail struct { Height int `json:"block_height"` } type TXGetItem struct { TxHash string TxRaw string Detail interface{} // TXFullDetail or TXDetail struct } type TransactionGetResp TXGetItem // 'blockchain.transaction.get' func (s *BlockchainTransactionService) Get(req *TransactionGetReq, resp **TransactionGetResp) error { txids := [1]string{string(*req)} request := TransactionGetBatchReq(txids[:]) var response *TransactionGetBatchResp err := s.Get_batch(&request, &response) if err != nil { return err } if len(*response) < 1 { return errors.New("tx not found") } switch (*response)[0].Detail.(type) { case TXFullDetail: break case TXDetail: default: return errors.New("tx not confirmed") } *resp = (*TransactionGetResp)(&(*response)[0]) return err } type TransactionGetBatchReq []string func (req *TransactionGetBatchReq) UnmarshalJSON(b []byte) error { var params []interface{} json.Unmarshal(b, ¶ms) if len(params) > 100 { return fmt.Errorf("too many tx hashes in request: %v", len(params)) } for i, txhash := range params { switch params[0].(type) { case string: *req = append(*req, txhash.(string)) default: return fmt.Errorf("expected string argument #%d (tx_hash)", i) } } return nil } type TransactionGetBatchResp []TXGetItem func (resp *TransactionGetBatchResp) MarshalJSON() ([]byte, error) { // encode key/value pairs as variable length JSON object var buf bytes.Buffer enc := json.NewEncoder(&buf) buf.WriteString("{") for i, r := range *resp { if i > 0 { buf.WriteString(",") } txhash, raw, detail := r.TxHash, r.TxRaw, r.Detail err := enc.Encode(txhash) if err != nil { return nil, err } buf.WriteString(":[") err = enc.Encode(raw) if err != nil { return nil, err } buf.WriteString(",") err = enc.Encode(detail) if err != nil { return nil, err } buf.WriteString("]") } buf.WriteString("}") return buf.Bytes(), nil } // 'blockchain.transaction.get_batch' func (s *BlockchainTransactionService) Get_batch(req *TransactionGetBatchReq, resp **TransactionGetBatchResp) error { if len(*req) > 100 { return fmt.Errorf("too many tx hashes in request: %v", len(*req)) } tx_hashes := make([]chainhash.Hash, 0, len(*req)) for i, txid := range *req { tx_hashes = append(tx_hashes, chainhash.Hash{}) if err := chainhash.Decode(&tx_hashes[i], txid); err != nil { return err } } dbResult, err := s.DB.GetTxMerkle(tx_hashes) if err != nil { return err } result := make([]TXGetItem, 0, len(dbResult)) for _, r := range dbResult { merkles := make([]string, len(r.Merkle)) for i, m := range r.Merkle { merkles[i] = m.String() } detail := TXFullDetail{ Height: r.Height, Pos: r.Pos, Merkle: merkles, } result = append(result, TXGetItem{r.TxHash.String(), hex.EncodeToString(r.RawTx), &detail}) } *resp = (*TransactionGetBatchResp)(&result) return err } type TransactionGetMerkleReq struct { TxHash string `json:"tx_hash"` Height uint32 `json:"height"` } type TransactionGetMerkleResp TXGetItem // 'blockchain.transaction.get_merkle' func (s *BlockchainTransactionService) Get_merkle(req *TransactionGetMerkleReq, resp **TransactionGetMerkleResp) error { txids := [1]string{string(req.TxHash)} request := TransactionGetBatchReq(txids[:]) var response *TransactionGetBatchResp err := s.Get_batch(&request, &response) if err != nil { return err } if len(*response) < 1 { return errors.New("tx not found") } switch (*response)[0].Detail.(type) { case TXFullDetail: break case TXDetail: default: return errors.New("tx not confirmed") } *resp = (*TransactionGetMerkleResp)(&(*response)[0]) return err } type TransactionGetHeightReq string type TransactionGetHeightResp uint32 // 'blockchain.transaction.get_height' func (s *BlockchainTransactionService) Get_height(req *TransactionGetHeightReq, resp **TransactionGetHeightResp) error { txid := string(*(req)) txhash, err := chainhash.NewHashFromStr(txid) if err != nil { return err } height, err := s.DB.GetTxHeight(txhash) *resp = (*TransactionGetHeightResp)(&height) return err } type TransactionInfoReq string type TransactionInfoResp TXGetItem // 'blockchain.transaction.info' func (s *BlockchainTransactionService) Info(req *TransactionInfoReq, resp **TransactionInfoResp) error { txids := [1]string{string(*req)} request := TransactionGetBatchReq(txids[:]) var response *TransactionGetBatchResp err := s.Get_batch(&request, &response) if err != nil { return err } if len(*response) < 1 { return errors.New("tx not found") } switch (*response)[0].Detail.(type) { case TXFullDetail: break case TXDetail: default: if (*response)[0].TxHash == "" { return errors.New("no such mempool or blockchain transaction") } } *resp = (*TransactionInfoResp)(&(*response)[0]) return err }