diff --git a/bitcoin/bitcoinwire.go b/bitcoin/bitcoinwire.go index 9f90351e..4b674c56 100644 --- a/bitcoin/bitcoinwire.go +++ b/bitcoin/bitcoinwire.go @@ -68,13 +68,13 @@ func (p *BitcoinBlockParser) ParseBlock(b []byte) (*Block, error) { } vout := make([]Vout, len(t.TxOut)) for i, out := range t.TxOut { - addrs, err := p.parseOutputScript(out.PkScript) - if err != nil { - addrs = []string{} - } + // addrs, err := p.parseOutputScript(out.PkScript) + // if err != nil { + // addrs = []string{} + // } s := ScriptPubKey{ - Hex: hex.EncodeToString(out.PkScript), - Addresses: addrs, + Hex: hex.EncodeToString(out.PkScript), + // missing Addresses, // missing: Asm, // missing: Type, } diff --git a/bitcoin/types.go b/bitcoin/types.go index d99fe617..223fe477 100644 --- a/bitcoin/types.go +++ b/bitcoin/types.go @@ -1,5 +1,10 @@ package bitcoin +import ( + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcutil" +) + type ScriptSig struct { Asm string `json:"asm"` Hex string `json:"hex"` @@ -26,11 +31,17 @@ type Vout struct { ScriptPubKey ScriptPubKey `json:"scriptPubKey"` } -func (vout *Vout) GetAddress() string { - if len(vout.ScriptPubKey.Addresses) != 1 { - return "" // output address not intelligible +// AddressToOutputScript converts bitcoin address to ScriptPubKey +func AddressToOutputScript(address string) ([]byte, error) { + da, err := btcutil.DecodeAddress(address, GetChainParams()[0]) + if err != nil { + return nil, err } - return vout.ScriptPubKey.Addresses[0] + script, err := txscript.PayToAddrScript(da) + if err != nil { + return nil, err + } + return script, nil } type Tx struct { diff --git a/blockbook.go b/blockbook.go index 811ce727..7b01083d 100644 --- a/blockbook.go +++ b/blockbook.go @@ -28,7 +28,7 @@ type Blockchain interface { type Index interface { GetBestBlock() (uint32, string, error) GetBlockHash(height uint32) (string, error) - GetTransactions(address string, lower uint32, higher uint32, fn func(txid string) error) error + GetTransactions(outputScript []byte, lower uint32, higher uint32, fn func(txid string) error) error ConnectBlock(block *bitcoin.Block) error DisconnectBlock(block *bitcoin.Block) error DisconnectBlocks(lower uint32, higher uint32) error @@ -147,7 +147,11 @@ func main() { address := *queryAddress if address != "" { - if err = db.GetTransactions(address, height, until, printResult); err != nil { + script, err := bitcoin.AddressToOutputScript(address) + if err != nil { + log.Fatalf("GetTransactions %v", err) + } + if err = db.GetTransactions(script, height, until, printResult); err != nil { log.Fatalf("GetTransactions %v", err) } } else { diff --git a/db/rocksdb.go b/db/rocksdb.go index 21e6a609..4902e991 100644 --- a/db/rocksdb.go +++ b/db/rocksdb.go @@ -9,7 +9,6 @@ import ( "log" "github.com/bsm/go-vlq" - "github.com/btcsuite/btcutil/base58" "github.com/tecbot/gorocksdb" ) @@ -84,14 +83,16 @@ func (d *RocksDB) Close() error { return nil } -func (d *RocksDB) GetTransactions(address string, lower uint32, higher uint32, fn func(txid string) error) (err error) { - log.Printf("rocksdb: address get %s %d-%d ", address, lower, higher) +// GetTransactions finds all input/output transactions for address specified by outputScript. +// Transaction are passed to callback function. +func (d *RocksDB) GetTransactions(outputScript []byte, lower uint32, higher uint32, fn func(txid string) error) (err error) { + log.Printf("rocksdb: address get %s %d-%d ", unpackOutputScript(outputScript), lower, higher) - kstart, err := packOutputKey(address, lower) + kstart, err := packOutputKey(outputScript, lower) if err != nil { return err } - kstop, err := packOutputKey(address, higher) + kstop, err := packOutputKey(outputScript, higher) if err != nil { return err } @@ -192,9 +193,9 @@ func (d *RocksDB) writeOutputs( for _, tx := range block.Txs { for _, output := range tx.Vout { - address := output.GetAddress() - if address != "" { - records[address] = append(records[address], outpoint{ + outputScript := output.ScriptPubKey.Hex + if outputScript != "" { + records[outputScript] = append(records[outputScript], outpoint{ txid: tx.Txid, vout: output.N, }) @@ -202,15 +203,20 @@ func (d *RocksDB) writeOutputs( } } - for address, outpoints := range records { - key, err := packOutputKey(address, block.Height) + for outputScript, outpoints := range records { + bOutputScript, err := packOutputScript(outputScript) if err != nil { - log.Printf("rocksdb: warning: %v", err) + log.Printf("rocksdb: packOutputScript warning: %v - %d %s", err, block.Height, outputScript) + continue + } + key, err := packOutputKey(bOutputScript, block.Height) + if err != nil { + log.Printf("rocksdb: packOutputKey warning: %v - %d %s", err, block.Height, outputScript) continue } val, err := packOutputValue(outpoints) if err != nil { - log.Printf("rocksdb: warning: %v", err) + log.Printf("rocksdb: packOutputValue warning: %v", err) continue } @@ -225,14 +231,10 @@ func (d *RocksDB) writeOutputs( return nil } -func packOutputKey(address string, height uint32) ([]byte, error) { - baddress, err := packAddress(address) - if err != nil { - return nil, err - } +func packOutputKey(outputScript []byte, height uint32) ([]byte, error) { bheight := packUint(height) - buf := make([]byte, 0, len(baddress)+len(bheight)) - buf = append(buf, baddress...) + buf := make([]byte, 0, len(outputScript)+len(bheight)) + buf = append(buf, outputScript...) buf = append(buf, bheight...) return buf, nil } @@ -453,34 +455,6 @@ func unpackVaruint(buf []byte) (uint32, int) { return uint32(i), ofs } -func packAddress(address string) ([]byte, error) { - var buf []byte - if len(address) == 42 || len(address) == 50 { - var err error - buf, err = hex.DecodeString(address) - if err != nil { - return nil, err - } - } else { - buf = base58.Decode(address) - } - if len(buf) == 25 { - return buf[:len(buf)-4], nil // Slice off the checksum - } - if len(buf) == 21 { // address without checksum - return buf, nil - } - return nil, ErrInvalidAddress - -} - -func unpackAddress(buf []byte) (string, error) { - if len(buf) < 2 { - return "", ErrInvalidAddress - } - return base58.CheckEncode(buf[1:], buf[0]), nil -} - func packTxid(txid string) ([]byte, error) { return hex.DecodeString(txid) } @@ -496,3 +470,11 @@ func packBlockValue(hash string) ([]byte, error) { func unpackBlockValue(buf []byte) (string, error) { return hex.EncodeToString(buf), nil } + +func packOutputScript(script string) ([]byte, error) { + return hex.DecodeString(script) +} + +func unpackOutputScript(buf []byte) string { + return hex.EncodeToString(buf) +} diff --git a/server/https.go b/server/https.go index 9424a9ef..468af285 100644 --- a/server/https.go +++ b/server/https.go @@ -1,6 +1,7 @@ package server import ( + "blockbook/bitcoin" "blockbook/db" "context" "encoding/json" @@ -120,24 +121,27 @@ func (s *HttpServer) transactions(w http.ResponseWriter, r *http.Request) { type transactionList struct { Txid []string `json:"txid"` } - txList := transactionList{} address := mux.Vars(r)["address"] higher, err := strconv.ParseUint(mux.Vars(r)["higher"], 10, 32) - lower, err2 := strconv.ParseUint(mux.Vars(r)["lower"], 10, 32) - if err == nil && err2 == nil { - err = s.db.GetTransactions(address, uint32(lower), uint32(higher), func(txid string) error { - txList.Txid = append(txList.Txid, txid) - return nil - }) - } else { - if err == nil { - err = err2 - } - } if err != nil { respondError(w, err, fmt.Sprintf("address %s", address)) - } else { - json.NewEncoder(w).Encode(txList) } + lower, err := strconv.ParseUint(mux.Vars(r)["lower"], 10, 32) + if err != nil { + respondError(w, err, fmt.Sprintf("address %s", address)) + } + script, err := bitcoin.AddressToOutputScript(address) + if err != nil { + respondError(w, err, fmt.Sprintf("address %s", address)) + } + txList := transactionList{} + err = s.db.GetTransactions(script, uint32(lower), uint32(higher), func(txid string) error { + txList.Txid = append(txList.Txid, txid) + return nil + }) + if err != nil { + respondError(w, err, fmt.Sprintf("address %s", address)) + } + json.NewEncoder(w).Encode(txList) }