Abstract bitcoinrpc in preparation for alt coins
This commit is contained in:
parent
b024c020e8
commit
f80ae32846
@ -2,6 +2,7 @@ package bchain
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -11,15 +12,75 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
vlq "github.com/bsm/go-vlq"
|
||||||
|
"github.com/btcsuite/btcd/blockchain"
|
||||||
|
"github.com/btcsuite/btcd/chaincfg"
|
||||||
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/btcsuite/btcutil"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
"github.com/juju/errors"
|
"github.com/juju/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RPCError struct {
|
// BitcoinRPC is an interface to JSON-RPC bitcoind service.
|
||||||
Code int `json:"code"`
|
type BitcoinRPC struct {
|
||||||
Message string `json:"message"`
|
client http.Client
|
||||||
|
rpcURL string
|
||||||
|
user string
|
||||||
|
password string
|
||||||
|
parser *BitcoinBlockParser
|
||||||
|
testnet bool
|
||||||
|
network string
|
||||||
|
mempool *Mempool
|
||||||
|
parseBlocks bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBitcoinRPC returns new BitcoinRPC instance.
|
||||||
|
func NewBitcoinRPC(url string, user string, password string, timeout time.Duration, parse bool) (BlockChain, error) {
|
||||||
|
transport := &http.Transport{
|
||||||
|
Dial: (&net.Dialer{KeepAlive: 600 * time.Second}).Dial,
|
||||||
|
MaxIdleConns: 100,
|
||||||
|
MaxIdleConnsPerHost: 100, // necessary to not to deplete ports
|
||||||
|
}
|
||||||
|
s := &BitcoinRPC{
|
||||||
|
client: http.Client{Timeout: timeout, Transport: transport},
|
||||||
|
rpcURL: url,
|
||||||
|
user: user,
|
||||||
|
password: password,
|
||||||
|
parseBlocks: parse,
|
||||||
|
}
|
||||||
|
chainName, err := s.GetBlockChainInfo()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// always create parser
|
||||||
|
s.parser = &BitcoinBlockParser{
|
||||||
|
Params: GetChainParams(chainName),
|
||||||
|
}
|
||||||
|
|
||||||
|
// parameters for getInfo request
|
||||||
|
if s.parser.Params.Net == wire.MainNet {
|
||||||
|
s.testnet = false
|
||||||
|
s.network = "livenet"
|
||||||
|
} else {
|
||||||
|
s.testnet = true
|
||||||
|
s.network = "testnet"
|
||||||
|
}
|
||||||
|
|
||||||
|
s.mempool = NewMempool(s)
|
||||||
|
|
||||||
|
glog.Info("rpc: block chain ", s.parser.Params.Name)
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BitcoinRPC) IsTestnet() bool {
|
||||||
|
return b.testnet
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BitcoinRPC) GetNetworkName() string {
|
||||||
|
return b.network
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *RPCError) Error() string {
|
func (e *RPCError) Error() string {
|
||||||
@ -184,52 +245,6 @@ type resSendRawTransaction struct {
|
|||||||
Result string `json:"result"`
|
Result string `json:"result"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// BitcoinRPC is an interface to JSON-RPC bitcoind service.
|
|
||||||
type BitcoinRPC struct {
|
|
||||||
client http.Client
|
|
||||||
URL string
|
|
||||||
User string
|
|
||||||
Password string
|
|
||||||
Parser *BitcoinBlockParser
|
|
||||||
Testnet bool
|
|
||||||
Network string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBitcoinRPC returns new BitcoinRPC instance.
|
|
||||||
func NewBitcoinRPC(url string, user string, password string, timeout time.Duration, parse bool) (*BitcoinRPC, error) {
|
|
||||||
transport := &http.Transport{
|
|
||||||
Dial: (&net.Dialer{KeepAlive: 600 * time.Second}).Dial,
|
|
||||||
MaxIdleConns: 100,
|
|
||||||
MaxIdleConnsPerHost: 100, // necessary to not to deplete ports
|
|
||||||
}
|
|
||||||
s := &BitcoinRPC{
|
|
||||||
client: http.Client{Timeout: timeout, Transport: transport},
|
|
||||||
URL: url,
|
|
||||||
User: user,
|
|
||||||
Password: password,
|
|
||||||
}
|
|
||||||
chain, err := s.GetBlockChainInfo()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// always create parser
|
|
||||||
s.Parser = &BitcoinBlockParser{
|
|
||||||
Params: GetChainParams(chain),
|
|
||||||
}
|
|
||||||
|
|
||||||
// parameters for getInfo request
|
|
||||||
if s.Parser.Params.Net == wire.MainNet {
|
|
||||||
s.Testnet = false
|
|
||||||
s.Network = "livenet"
|
|
||||||
} else {
|
|
||||||
s.Testnet = true
|
|
||||||
s.Network = "testnet"
|
|
||||||
}
|
|
||||||
glog.Info("rpc: block chain ", s.Parser.Params.Name)
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBestBlockHash returns hash of the tip of the best-block-chain.
|
// GetBestBlockHash returns hash of the tip of the best-block-chain.
|
||||||
func (b *BitcoinRPC) GetBestBlockHash() (string, error) {
|
func (b *BitcoinRPC) GetBestBlockHash() (string, error) {
|
||||||
|
|
||||||
@ -321,7 +336,7 @@ func (b *BitcoinRPC) GetBlockHeader(hash string) (*BlockHeader, error) {
|
|||||||
|
|
||||||
// GetBlock returns block with given hash.
|
// GetBlock returns block with given hash.
|
||||||
func (b *BitcoinRPC) GetBlock(hash string) (*Block, error) {
|
func (b *BitcoinRPC) GetBlock(hash string) (*Block, error) {
|
||||||
if b.Parser == nil {
|
if !b.parseBlocks {
|
||||||
return b.GetBlockFull(hash)
|
return b.GetBlockFull(hash)
|
||||||
}
|
}
|
||||||
header, err := b.GetBlockHeader(hash)
|
header, err := b.GetBlockHeader(hash)
|
||||||
@ -332,7 +347,7 @@ func (b *BitcoinRPC) GetBlock(hash string) (*Block, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
block, err := b.Parser.ParseBlock(data)
|
block, err := b.parser.ParseBlock(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Annotatef(err, "hash %v", hash)
|
return nil, errors.Annotatef(err, "hash %v", hash)
|
||||||
}
|
}
|
||||||
@ -343,14 +358,14 @@ func (b *BitcoinRPC) GetBlock(hash string) (*Block, error) {
|
|||||||
// GetBlockWithoutHeader is an optimization - it does not call GetBlockHeader to get prev, next hashes
|
// GetBlockWithoutHeader is an optimization - it does not call GetBlockHeader to get prev, next hashes
|
||||||
// instead it sets to header only block hash and height passed in parameters
|
// instead it sets to header only block hash and height passed in parameters
|
||||||
func (b *BitcoinRPC) GetBlockWithoutHeader(hash string, height uint32) (*Block, error) {
|
func (b *BitcoinRPC) GetBlockWithoutHeader(hash string, height uint32) (*Block, error) {
|
||||||
if b.Parser == nil {
|
if !b.parseBlocks {
|
||||||
return b.GetBlockFull(hash)
|
return b.GetBlockFull(hash)
|
||||||
}
|
}
|
||||||
data, err := b.GetBlockRaw(hash)
|
data, err := b.GetBlockRaw(hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
block, err := b.Parser.ParseBlock(data)
|
block, err := b.parser.ParseBlock(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Annotatef(err, "%v %v", height, hash)
|
return nil, errors.Annotatef(err, "%v %v", height, hash)
|
||||||
}
|
}
|
||||||
@ -466,6 +481,22 @@ func (b *BitcoinRPC) GetTransaction(txid string) (*Tx, error) {
|
|||||||
return &res.Result, nil
|
return &res.Result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResyncMempool gets mempool transactions and maps output scripts to transactions.
|
||||||
|
// ResyncMempool is not reentrant, it should be called from a single thread.
|
||||||
|
func (b *BitcoinRPC) ResyncMempool(onNewTxAddr func(txid string, addr string)) error {
|
||||||
|
return b.mempool.Resync(onNewTxAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMempoolTransactions returns slice of mempool transactions for given output script.
|
||||||
|
func (b *BitcoinRPC) GetMempoolTransactions(outputScript []byte) ([]string, error) {
|
||||||
|
return b.mempool.GetTransactions(outputScript)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMempoolSpentOutput returns transaction in mempool which spends given outpoint
|
||||||
|
func (b *BitcoinRPC) GetMempoolSpentOutput(outputTxid string, vout uint32) string {
|
||||||
|
return b.mempool.GetSpentOutput(outputTxid, vout)
|
||||||
|
}
|
||||||
|
|
||||||
// EstimateSmartFee returns fee estimation.
|
// EstimateSmartFee returns fee estimation.
|
||||||
func (b *BitcoinRPC) EstimateSmartFee(blocks int, conservative bool) (float64, error) {
|
func (b *BitcoinRPC) EstimateSmartFee(blocks int, conservative bool) (float64, error) {
|
||||||
glog.V(1).Info("rpc: estimatesmartfee ", blocks)
|
glog.V(1).Info("rpc: estimatesmartfee ", blocks)
|
||||||
@ -512,11 +543,11 @@ func (b *BitcoinRPC) call(req interface{}, res interface{}) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
httpReq, err := http.NewRequest("POST", b.URL, bytes.NewBuffer(httpData))
|
httpReq, err := http.NewRequest("POST", b.rpcURL, bytes.NewBuffer(httpData))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
httpReq.SetBasicAuth(b.User, b.Password)
|
httpReq.SetBasicAuth(b.user, b.password)
|
||||||
httpRes, err := b.client.Do(httpReq)
|
httpRes, err := b.client.Do(httpReq)
|
||||||
// in some cases the httpRes can contain data even if it returns error
|
// in some cases the httpRes can contain data even if it returns error
|
||||||
// see http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/
|
// see http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/
|
||||||
@ -531,3 +562,156 @@ func (b *BitcoinRPC) call(req interface{}, res interface{}) error {
|
|||||||
defer io.Copy(ioutil.Discard, httpRes.Body)
|
defer io.Copy(ioutil.Discard, httpRes.Body)
|
||||||
return json.NewDecoder(httpRes.Body).Decode(&res)
|
return json.NewDecoder(httpRes.Body).Decode(&res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetChainParser returns BlockChainParser
|
||||||
|
func (b *BitcoinRPC) GetChainParser() BlockChainParser {
|
||||||
|
return b.parser
|
||||||
|
}
|
||||||
|
|
||||||
|
// bitcoinwire parsing
|
||||||
|
|
||||||
|
type BitcoinBlockParser struct {
|
||||||
|
Params *chaincfg.Params
|
||||||
|
}
|
||||||
|
|
||||||
|
// getChainParams contains network parameters for the main Bitcoin network,
|
||||||
|
// the regression test Bitcoin network, the test Bitcoin network and
|
||||||
|
// the simulation test Bitcoin network, in this order
|
||||||
|
func GetChainParams(chain string) *chaincfg.Params {
|
||||||
|
switch chain {
|
||||||
|
case "test":
|
||||||
|
return &chaincfg.TestNet3Params
|
||||||
|
case "regtest":
|
||||||
|
return &chaincfg.RegressionNetParams
|
||||||
|
}
|
||||||
|
return &chaincfg.MainNetParams
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddressToOutputScript converts bitcoin address to ScriptPubKey
|
||||||
|
func (p *BitcoinBlockParser) AddressToOutputScript(address string) ([]byte, error) {
|
||||||
|
da, err := btcutil.DecodeAddress(address, p.Params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
script, err := txscript.PayToAddrScript(da)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return script, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OutputScriptToAddresses converts ScriptPubKey to bitcoin addresses
|
||||||
|
func (p *BitcoinBlockParser) OutputScriptToAddresses(script []byte) ([]string, error) {
|
||||||
|
_, addresses, _, err := txscript.ExtractPkScriptAddrs(script, p.Params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rv := make([]string, len(addresses))
|
||||||
|
for i, a := range addresses {
|
||||||
|
rv[i] = a.EncodeAddress()
|
||||||
|
}
|
||||||
|
return rv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *BitcoinBlockParser) txFromMsgTx(t *wire.MsgTx, parseAddresses bool) Tx {
|
||||||
|
vin := make([]Vin, len(t.TxIn))
|
||||||
|
for i, in := range t.TxIn {
|
||||||
|
if blockchain.IsCoinBaseTx(t) {
|
||||||
|
vin[i] = Vin{
|
||||||
|
Coinbase: hex.EncodeToString(in.SignatureScript),
|
||||||
|
Sequence: in.Sequence,
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
s := ScriptSig{
|
||||||
|
Hex: hex.EncodeToString(in.SignatureScript),
|
||||||
|
// missing: Asm,
|
||||||
|
}
|
||||||
|
vin[i] = Vin{
|
||||||
|
Txid: in.PreviousOutPoint.Hash.String(),
|
||||||
|
Vout: in.PreviousOutPoint.Index,
|
||||||
|
Sequence: in.Sequence,
|
||||||
|
ScriptSig: s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vout := make([]Vout, len(t.TxOut))
|
||||||
|
for i, out := range t.TxOut {
|
||||||
|
addrs := []string{}
|
||||||
|
if parseAddresses {
|
||||||
|
addrs, _ = p.OutputScriptToAddresses(out.PkScript)
|
||||||
|
}
|
||||||
|
s := ScriptPubKey{
|
||||||
|
Hex: hex.EncodeToString(out.PkScript),
|
||||||
|
Addresses: addrs,
|
||||||
|
// missing: Asm,
|
||||||
|
// missing: Type,
|
||||||
|
}
|
||||||
|
vout[i] = Vout{
|
||||||
|
Value: float64(out.Value) / 1E8,
|
||||||
|
N: uint32(i),
|
||||||
|
ScriptPubKey: s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tx := Tx{
|
||||||
|
Txid: t.TxHash().String(),
|
||||||
|
// skip: Version,
|
||||||
|
LockTime: t.LockTime,
|
||||||
|
Vin: vin,
|
||||||
|
Vout: vout,
|
||||||
|
// skip: BlockHash,
|
||||||
|
// skip: Confirmations,
|
||||||
|
// skip: Time,
|
||||||
|
// skip: Blocktime,
|
||||||
|
}
|
||||||
|
return tx
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseTx parses byte array containing transaction and returns Tx struct
|
||||||
|
func (p *BitcoinBlockParser) ParseTx(b []byte) (*Tx, error) {
|
||||||
|
t := wire.MsgTx{}
|
||||||
|
r := bytes.NewReader(b)
|
||||||
|
if err := t.Deserialize(r); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tx := p.txFromMsgTx(&t, true)
|
||||||
|
tx.Hex = hex.EncodeToString(b)
|
||||||
|
return &tx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseBlock parses raw block to our Block struct
|
||||||
|
func (p *BitcoinBlockParser) ParseBlock(b []byte) (*Block, error) {
|
||||||
|
w := wire.MsgBlock{}
|
||||||
|
r := bytes.NewReader(b)
|
||||||
|
|
||||||
|
if err := w.Deserialize(r); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
txs := make([]Tx, len(w.Transactions))
|
||||||
|
for ti, t := range w.Transactions {
|
||||||
|
txs[ti] = p.txFromMsgTx(t, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Block{Txs: txs}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PackTx packs transaction to byte array
|
||||||
|
func (p *BitcoinBlockParser) PackTx(tx *Tx, height uint32, blockTime int64) ([]byte, error) {
|
||||||
|
buf := make([]byte, 4+vlq.MaxLen64+len(tx.Hex)/2)
|
||||||
|
binary.BigEndian.PutUint32(buf[0:4], height)
|
||||||
|
vl := vlq.PutInt(buf[4:4+vlq.MaxLen64], blockTime)
|
||||||
|
hl, err := hex.Decode(buf[4+vl:], []byte(tx.Hex))
|
||||||
|
return buf[0 : 4+vl+hl], err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnpackTx unpacks transaction from byte array
|
||||||
|
func (p *BitcoinBlockParser) UnpackTx(buf []byte) (*Tx, uint32, error) {
|
||||||
|
height := binary.BigEndian.Uint32(buf)
|
||||||
|
bt, l := vlq.Int(buf[4:])
|
||||||
|
tx, err := p.ParseTx(buf[4+l:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
tx.Blocktime = bt
|
||||||
|
return tx, height, nil
|
||||||
|
}
|
||||||
|
|||||||
@ -1,20 +1,122 @@
|
|||||||
package db
|
package bchain
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"blockbook/bchain"
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
var testTx1 = bchain.Tx{
|
func TestAddressToOutputScript(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
address string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "P2PKH",
|
||||||
|
args: args{address: "1JKgN43B9SyLuZH19H5ECvr4KcfrbVHzZ6"},
|
||||||
|
want: "76a914be027bf3eac907bd4ac8cb9c5293b6f37662722088ac",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "P2SH",
|
||||||
|
args: args{address: "321x69Cb9HZLWwAWGiUBT1U81r1zPLnEjL"},
|
||||||
|
want: "a9140394b3cf9a44782c10105b93962daa8dba304d7f87",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "P2WPKH",
|
||||||
|
args: args{address: "bc1qrsf2l34jvqnq0lduyz0j5pfu2nkd93nnq0qggn"},
|
||||||
|
want: "00141c12afc6b2602607fdbc209f2a053c54ecd2c673",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "P2WSH",
|
||||||
|
args: args{address: "bc1qqwtn5s8vjnqdzrm0du885c46ypzt05vakmljhasx28shlv5a355sw5exgr"},
|
||||||
|
want: "002003973a40ec94c0d10f6f6f0e7a62ba2044b7d19db6ff2bf60651e17fb29d8d29",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
parser := &BitcoinBlockParser{Params: GetChainParams("main")}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := parser.AddressToOutputScript(tt.args.address)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("AddressToOutputScript() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h := hex.EncodeToString(got)
|
||||||
|
if !reflect.DeepEqual(h, tt.want) {
|
||||||
|
t.Errorf("AddressToOutputScript() = %v, want %v", h, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOutputScriptToAddresses(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
script string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want []string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "P2PKH",
|
||||||
|
args: args{script: "76a914be027bf3eac907bd4ac8cb9c5293b6f37662722088ac"},
|
||||||
|
want: []string{"1JKgN43B9SyLuZH19H5ECvr4KcfrbVHzZ6"},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "P2SH",
|
||||||
|
args: args{script: "a9140394b3cf9a44782c10105b93962daa8dba304d7f87"},
|
||||||
|
want: []string{"321x69Cb9HZLWwAWGiUBT1U81r1zPLnEjL"},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "P2WPKH",
|
||||||
|
args: args{script: "00141c12afc6b2602607fdbc209f2a053c54ecd2c673"},
|
||||||
|
want: []string{"bc1qrsf2l34jvqnq0lduyz0j5pfu2nkd93nnq0qggn"},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "P2WSH",
|
||||||
|
args: args{script: "002003973a40ec94c0d10f6f6f0e7a62ba2044b7d19db6ff2bf60651e17fb29d8d29"},
|
||||||
|
want: []string{"bc1qqwtn5s8vjnqdzrm0du885c46ypzt05vakmljhasx28shlv5a355sw5exgr"},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
parser := &BitcoinBlockParser{Params: GetChainParams("main")}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
b, _ := hex.DecodeString(tt.args.script)
|
||||||
|
got, err := parser.OutputScriptToAddresses(b)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("OutputScriptToAddresses() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("OutputScriptToAddresses() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var testTx1 = Tx{
|
||||||
Hex: "01000000017f9a22c9cbf54bd902400df746f138f37bcf5b4d93eb755820e974ba43ed5f42040000006a4730440220037f4ed5427cde81d55b9b6a2fd08c8a25090c2c2fff3a75c1a57625ca8a7118022076c702fe55969fa08137f71afd4851c48e31082dd3c40c919c92cdbc826758d30121029f6da5623c9f9b68a9baf9c1bc7511df88fa34c6c2f71f7c62f2f03ff48dca80feffffff019c9700000000000017a9146144d57c8aff48492c9dfb914e120b20bad72d6f8773d00700",
|
Hex: "01000000017f9a22c9cbf54bd902400df746f138f37bcf5b4d93eb755820e974ba43ed5f42040000006a4730440220037f4ed5427cde81d55b9b6a2fd08c8a25090c2c2fff3a75c1a57625ca8a7118022076c702fe55969fa08137f71afd4851c48e31082dd3c40c919c92cdbc826758d30121029f6da5623c9f9b68a9baf9c1bc7511df88fa34c6c2f71f7c62f2f03ff48dca80feffffff019c9700000000000017a9146144d57c8aff48492c9dfb914e120b20bad72d6f8773d00700",
|
||||||
Blocktime: 1519053802,
|
Blocktime: 1519053802,
|
||||||
Txid: "056e3d82e5ffd0e915fb9b62797d76263508c34fe3e5dbed30dd3e943930f204",
|
Txid: "056e3d82e5ffd0e915fb9b62797d76263508c34fe3e5dbed30dd3e943930f204",
|
||||||
LockTime: 512115,
|
LockTime: 512115,
|
||||||
Vin: []bchain.Vin{
|
Vin: []Vin{
|
||||||
{
|
{
|
||||||
ScriptSig: bchain.ScriptSig{
|
ScriptSig: ScriptSig{
|
||||||
Hex: "4730440220037f4ed5427cde81d55b9b6a2fd08c8a25090c2c2fff3a75c1a57625ca8a7118022076c702fe55969fa08137f71afd4851c48e31082dd3c40c919c92cdbc826758d30121029f6da5623c9f9b68a9baf9c1bc7511df88fa34c6c2f71f7c62f2f03ff48dca80",
|
Hex: "4730440220037f4ed5427cde81d55b9b6a2fd08c8a25090c2c2fff3a75c1a57625ca8a7118022076c702fe55969fa08137f71afd4851c48e31082dd3c40c919c92cdbc826758d30121029f6da5623c9f9b68a9baf9c1bc7511df88fa34c6c2f71f7c62f2f03ff48dca80",
|
||||||
},
|
},
|
||||||
Txid: "425fed43ba74e9205875eb934d5bcf7bf338f146f70d4002d94bf5cbc9229a7f",
|
Txid: "425fed43ba74e9205875eb934d5bcf7bf338f146f70d4002d94bf5cbc9229a7f",
|
||||||
@ -22,11 +124,11 @@ var testTx1 = bchain.Tx{
|
|||||||
Sequence: 4294967294,
|
Sequence: 4294967294,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Vout: []bchain.Vout{
|
Vout: []Vout{
|
||||||
{
|
{
|
||||||
Value: 0.00038812,
|
Value: 0.00038812,
|
||||||
N: 0,
|
N: 0,
|
||||||
ScriptPubKey: bchain.ScriptPubKey{
|
ScriptPubKey: ScriptPubKey{
|
||||||
Hex: "a9146144d57c8aff48492c9dfb914e120b20bad72d6f87",
|
Hex: "a9146144d57c8aff48492c9dfb914e120b20bad72d6f87",
|
||||||
Addresses: []string{
|
Addresses: []string{
|
||||||
"3AZKvpKhSh1o8t1QrX3UeXG9d2BhCRnbcK",
|
"3AZKvpKhSh1o8t1QrX3UeXG9d2BhCRnbcK",
|
||||||
@ -37,14 +139,14 @@ var testTx1 = bchain.Tx{
|
|||||||
}
|
}
|
||||||
var testTxPacked1 = "0001e2408ba8d7af5401000000017f9a22c9cbf54bd902400df746f138f37bcf5b4d93eb755820e974ba43ed5f42040000006a4730440220037f4ed5427cde81d55b9b6a2fd08c8a25090c2c2fff3a75c1a57625ca8a7118022076c702fe55969fa08137f71afd4851c48e31082dd3c40c919c92cdbc826758d30121029f6da5623c9f9b68a9baf9c1bc7511df88fa34c6c2f71f7c62f2f03ff48dca80feffffff019c9700000000000017a9146144d57c8aff48492c9dfb914e120b20bad72d6f8773d00700"
|
var testTxPacked1 = "0001e2408ba8d7af5401000000017f9a22c9cbf54bd902400df746f138f37bcf5b4d93eb755820e974ba43ed5f42040000006a4730440220037f4ed5427cde81d55b9b6a2fd08c8a25090c2c2fff3a75c1a57625ca8a7118022076c702fe55969fa08137f71afd4851c48e31082dd3c40c919c92cdbc826758d30121029f6da5623c9f9b68a9baf9c1bc7511df88fa34c6c2f71f7c62f2f03ff48dca80feffffff019c9700000000000017a9146144d57c8aff48492c9dfb914e120b20bad72d6f8773d00700"
|
||||||
|
|
||||||
var testTx2 = bchain.Tx{
|
var testTx2 = Tx{
|
||||||
Hex: "010000000001019d64f0c72a0d206001decbffaa722eb1044534c74eee7a5df8318e42a4323ec10000000017160014550da1f5d25a9dae2eafd6902b4194c4c6500af6ffffffff02809698000000000017a914cd668d781ece600efa4b2404dc91fd26b8b8aed8870553d7360000000017a914246655bdbd54c7e477d0ea2375e86e0db2b8f80a8702473044022076aba4ad559616905fa51d4ddd357fc1fdb428d40cb388e042cdd1da4a1b7357022011916f90c712ead9a66d5f058252efd280439ad8956a967e95d437d246710bc9012102a80a5964c5612bb769ef73147b2cf3c149bc0fd4ecb02f8097629c94ab013ffd00000000",
|
Hex: "010000000001019d64f0c72a0d206001decbffaa722eb1044534c74eee7a5df8318e42a4323ec10000000017160014550da1f5d25a9dae2eafd6902b4194c4c6500af6ffffffff02809698000000000017a914cd668d781ece600efa4b2404dc91fd26b8b8aed8870553d7360000000017a914246655bdbd54c7e477d0ea2375e86e0db2b8f80a8702473044022076aba4ad559616905fa51d4ddd357fc1fdb428d40cb388e042cdd1da4a1b7357022011916f90c712ead9a66d5f058252efd280439ad8956a967e95d437d246710bc9012102a80a5964c5612bb769ef73147b2cf3c149bc0fd4ecb02f8097629c94ab013ffd00000000",
|
||||||
Blocktime: 1235678901,
|
Blocktime: 1235678901,
|
||||||
Txid: "474e6795760ebe81cb4023dc227e5a0efe340e1771c89a0035276361ed733de7",
|
Txid: "474e6795760ebe81cb4023dc227e5a0efe340e1771c89a0035276361ed733de7",
|
||||||
LockTime: 0,
|
LockTime: 0,
|
||||||
Vin: []bchain.Vin{
|
Vin: []Vin{
|
||||||
{
|
{
|
||||||
ScriptSig: bchain.ScriptSig{
|
ScriptSig: ScriptSig{
|
||||||
Hex: "160014550da1f5d25a9dae2eafd6902b4194c4c6500af6",
|
Hex: "160014550da1f5d25a9dae2eafd6902b4194c4c6500af6",
|
||||||
},
|
},
|
||||||
Txid: "c13e32a4428e31f85d7aee4ec7344504b12e72aaffcbde0160200d2ac7f0649d",
|
Txid: "c13e32a4428e31f85d7aee4ec7344504b12e72aaffcbde0160200d2ac7f0649d",
|
||||||
@ -52,11 +154,11 @@ var testTx2 = bchain.Tx{
|
|||||||
Sequence: 4294967295,
|
Sequence: 4294967295,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Vout: []bchain.Vout{
|
Vout: []Vout{
|
||||||
{
|
{
|
||||||
Value: .1,
|
Value: .1,
|
||||||
N: 0,
|
N: 0,
|
||||||
ScriptPubKey: bchain.ScriptPubKey{
|
ScriptPubKey: ScriptPubKey{
|
||||||
Hex: "a914cd668d781ece600efa4b2404dc91fd26b8b8aed887",
|
Hex: "a914cd668d781ece600efa4b2404dc91fd26b8b8aed887",
|
||||||
Addresses: []string{
|
Addresses: []string{
|
||||||
"2NByHN6A8QYkBATzxf4pRGbCSHD5CEN2TRu",
|
"2NByHN6A8QYkBATzxf4pRGbCSHD5CEN2TRu",
|
||||||
@ -66,7 +168,7 @@ var testTx2 = bchain.Tx{
|
|||||||
{
|
{
|
||||||
Value: 9.20081157,
|
Value: 9.20081157,
|
||||||
N: 1,
|
N: 1,
|
||||||
ScriptPubKey: bchain.ScriptPubKey{
|
ScriptPubKey: ScriptPubKey{
|
||||||
Hex: "a914246655bdbd54c7e477d0ea2375e86e0db2b8f80a87",
|
Hex: "a914246655bdbd54c7e477d0ea2375e86e0db2b8f80a87",
|
||||||
Addresses: []string{
|
Addresses: []string{
|
||||||
"2MvZguYaGjM7JihBgNqgLF2Ca2Enb76Hj9D",
|
"2MvZguYaGjM7JihBgNqgLF2Ca2Enb76Hj9D",
|
||||||
@ -77,11 +179,12 @@ var testTx2 = bchain.Tx{
|
|||||||
}
|
}
|
||||||
var testTxPacked2 = "0007c91a899ab7da6a010000000001019d64f0c72a0d206001decbffaa722eb1044534c74eee7a5df8318e42a4323ec10000000017160014550da1f5d25a9dae2eafd6902b4194c4c6500af6ffffffff02809698000000000017a914cd668d781ece600efa4b2404dc91fd26b8b8aed8870553d7360000000017a914246655bdbd54c7e477d0ea2375e86e0db2b8f80a8702473044022076aba4ad559616905fa51d4ddd357fc1fdb428d40cb388e042cdd1da4a1b7357022011916f90c712ead9a66d5f058252efd280439ad8956a967e95d437d246710bc9012102a80a5964c5612bb769ef73147b2cf3c149bc0fd4ecb02f8097629c94ab013ffd00000000"
|
var testTxPacked2 = "0007c91a899ab7da6a010000000001019d64f0c72a0d206001decbffaa722eb1044534c74eee7a5df8318e42a4323ec10000000017160014550da1f5d25a9dae2eafd6902b4194c4c6500af6ffffffff02809698000000000017a914cd668d781ece600efa4b2404dc91fd26b8b8aed8870553d7360000000017a914246655bdbd54c7e477d0ea2375e86e0db2b8f80a8702473044022076aba4ad559616905fa51d4ddd357fc1fdb428d40cb388e042cdd1da4a1b7357022011916f90c712ead9a66d5f058252efd280439ad8956a967e95d437d246710bc9012102a80a5964c5612bb769ef73147b2cf3c149bc0fd4ecb02f8097629c94ab013ffd00000000"
|
||||||
|
|
||||||
func Test_packTx(t *testing.T) {
|
func Test_PackTx(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
tx bchain.Tx
|
tx Tx
|
||||||
height uint32
|
height uint32
|
||||||
blockTime int64
|
blockTime int64
|
||||||
|
parser *BitcoinBlockParser
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -90,21 +193,31 @@ func Test_packTx(t *testing.T) {
|
|||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "btc-1",
|
name: "btc-1",
|
||||||
args: args{testTx1, 123456, 1519053802},
|
args: args{
|
||||||
|
tx: testTx1,
|
||||||
|
height: 123456,
|
||||||
|
blockTime: 1519053802,
|
||||||
|
parser: &BitcoinBlockParser{Params: GetChainParams("main")},
|
||||||
|
},
|
||||||
want: testTxPacked1,
|
want: testTxPacked1,
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "testnet-1",
|
name: "testnet-1",
|
||||||
args: args{testTx2, 510234, 1235678901},
|
args: args{
|
||||||
|
tx: testTx2,
|
||||||
|
height: 510234,
|
||||||
|
blockTime: 1235678901,
|
||||||
|
parser: &BitcoinBlockParser{Params: GetChainParams("test")},
|
||||||
|
},
|
||||||
want: testTxPacked2,
|
want: testTxPacked2,
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := packTx(&tt.args.tx, tt.args.height, tt.args.blockTime)
|
got, err := tt.args.parser.PackTx(&tt.args.tx, tt.args.height, tt.args.blockTime)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("packTx() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("packTx() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
return
|
||||||
@ -117,15 +230,15 @@ func Test_packTx(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_unpackTx(t *testing.T) {
|
func Test_UnpackTx(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
packedTx string
|
packedTx string
|
||||||
parser *bchain.BitcoinBlockParser
|
parser *BitcoinBlockParser
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
args args
|
args args
|
||||||
want *bchain.Tx
|
want *Tx
|
||||||
want1 uint32
|
want1 uint32
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
@ -133,7 +246,7 @@ func Test_unpackTx(t *testing.T) {
|
|||||||
name: "btc-1",
|
name: "btc-1",
|
||||||
args: args{
|
args: args{
|
||||||
packedTx: testTxPacked1,
|
packedTx: testTxPacked1,
|
||||||
parser: &bchain.BitcoinBlockParser{Params: bchain.GetChainParams("main")},
|
parser: &BitcoinBlockParser{Params: GetChainParams("main")},
|
||||||
},
|
},
|
||||||
want: &testTx1,
|
want: &testTx1,
|
||||||
want1: 123456,
|
want1: 123456,
|
||||||
@ -143,7 +256,7 @@ func Test_unpackTx(t *testing.T) {
|
|||||||
name: "testnet-1",
|
name: "testnet-1",
|
||||||
args: args{
|
args: args{
|
||||||
packedTx: testTxPacked2,
|
packedTx: testTxPacked2,
|
||||||
parser: &bchain.BitcoinBlockParser{Params: bchain.GetChainParams("test")},
|
parser: &BitcoinBlockParser{Params: GetChainParams("test")},
|
||||||
},
|
},
|
||||||
want: &testTx2,
|
want: &testTx2,
|
||||||
want1: 510234,
|
want1: 510234,
|
||||||
@ -153,7 +266,7 @@ func Test_unpackTx(t *testing.T) {
|
|||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
b, _ := hex.DecodeString(tt.args.packedTx)
|
b, _ := hex.DecodeString(tt.args.packedTx)
|
||||||
got, got1, err := unpackTx(b, tt.args.parser)
|
got, got1, err := tt.args.parser.UnpackTx(b)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("unpackTx() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("unpackTx() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
return
|
||||||
@ -1,138 +0,0 @@
|
|||||||
package bchain
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/hex"
|
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/blockchain"
|
|
||||||
"github.com/btcsuite/btcutil"
|
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/chaincfg"
|
|
||||||
"github.com/btcsuite/btcd/txscript"
|
|
||||||
"github.com/btcsuite/btcd/wire"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetChainParams contains network parameters for the main Bitcoin network,
|
|
||||||
// the regression test Bitcoin network, the test Bitcoin network and
|
|
||||||
// the simulation test Bitcoin network, in this order
|
|
||||||
func GetChainParams(chain string) *chaincfg.Params {
|
|
||||||
switch chain {
|
|
||||||
case "test":
|
|
||||||
return &chaincfg.TestNet3Params
|
|
||||||
case "regtest":
|
|
||||||
return &chaincfg.RegressionNetParams
|
|
||||||
}
|
|
||||||
return &chaincfg.MainNetParams
|
|
||||||
}
|
|
||||||
|
|
||||||
type BitcoinBlockParser struct {
|
|
||||||
Params *chaincfg.Params
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddressToOutputScript converts bitcoin address to ScriptPubKey
|
|
||||||
func (p *BitcoinBlockParser) AddressToOutputScript(address string) ([]byte, error) {
|
|
||||||
da, err := btcutil.DecodeAddress(address, p.Params)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
script, err := txscript.PayToAddrScript(da)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return script, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// OutputScriptToAddresses converts ScriptPubKey to bitcoin addresses
|
|
||||||
func (p *BitcoinBlockParser) OutputScriptToAddresses(script []byte) ([]string, error) {
|
|
||||||
_, addresses, _, err := txscript.ExtractPkScriptAddrs(script, p.Params)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rv := make([]string, len(addresses))
|
|
||||||
for i, a := range addresses {
|
|
||||||
rv[i] = a.EncodeAddress()
|
|
||||||
}
|
|
||||||
return rv, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseTx parses byte array containing transaction and returns Tx struct
|
|
||||||
func (p *BitcoinBlockParser) ParseTx(b []byte) (*Tx, error) {
|
|
||||||
t := wire.MsgTx{}
|
|
||||||
r := bytes.NewReader(b)
|
|
||||||
if err := t.Deserialize(r); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tx := p.txFromMsgTx(&t, true)
|
|
||||||
tx.Hex = hex.EncodeToString(b)
|
|
||||||
return &tx, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *BitcoinBlockParser) txFromMsgTx(t *wire.MsgTx, parseAddresses bool) Tx {
|
|
||||||
vin := make([]Vin, len(t.TxIn))
|
|
||||||
for i, in := range t.TxIn {
|
|
||||||
if blockchain.IsCoinBaseTx(t) {
|
|
||||||
vin[i] = Vin{
|
|
||||||
Coinbase: hex.EncodeToString(in.SignatureScript),
|
|
||||||
Sequence: in.Sequence,
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
s := ScriptSig{
|
|
||||||
Hex: hex.EncodeToString(in.SignatureScript),
|
|
||||||
// missing: Asm,
|
|
||||||
}
|
|
||||||
vin[i] = Vin{
|
|
||||||
Txid: in.PreviousOutPoint.Hash.String(),
|
|
||||||
Vout: in.PreviousOutPoint.Index,
|
|
||||||
Sequence: in.Sequence,
|
|
||||||
ScriptSig: s,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
vout := make([]Vout, len(t.TxOut))
|
|
||||||
for i, out := range t.TxOut {
|
|
||||||
addrs := []string{}
|
|
||||||
if parseAddresses {
|
|
||||||
addrs, _ = p.OutputScriptToAddresses(out.PkScript)
|
|
||||||
}
|
|
||||||
s := ScriptPubKey{
|
|
||||||
Hex: hex.EncodeToString(out.PkScript),
|
|
||||||
Addresses: addrs,
|
|
||||||
// missing: Asm,
|
|
||||||
// missing: Type,
|
|
||||||
}
|
|
||||||
vout[i] = Vout{
|
|
||||||
Value: float64(out.Value) / 1E8,
|
|
||||||
N: uint32(i),
|
|
||||||
ScriptPubKey: s,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tx := Tx{
|
|
||||||
Txid: t.TxHash().String(),
|
|
||||||
// skip: Version,
|
|
||||||
LockTime: t.LockTime,
|
|
||||||
Vin: vin,
|
|
||||||
Vout: vout,
|
|
||||||
// skip: BlockHash,
|
|
||||||
// skip: Confirmations,
|
|
||||||
// skip: Time,
|
|
||||||
// skip: Blocktime,
|
|
||||||
}
|
|
||||||
return tx
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseBlock parses raw block to our Block struct
|
|
||||||
func (p *BitcoinBlockParser) ParseBlock(b []byte) (*Block, error) {
|
|
||||||
w := wire.MsgBlock{}
|
|
||||||
r := bytes.NewReader(b)
|
|
||||||
|
|
||||||
if err := w.Deserialize(r); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
txs := make([]Tx, len(w.Transactions))
|
|
||||||
for ti, t := range w.Transactions {
|
|
||||||
txs[ti] = p.txFromMsgTx(t, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Block{Txs: txs}, nil
|
|
||||||
}
|
|
||||||
@ -1,110 +0,0 @@
|
|||||||
package bchain
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/hex"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAddressToOutputScript(t *testing.T) {
|
|
||||||
type args struct {
|
|
||||||
address string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
want string
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "P2PKH",
|
|
||||||
args: args{address: "1JKgN43B9SyLuZH19H5ECvr4KcfrbVHzZ6"},
|
|
||||||
want: "76a914be027bf3eac907bd4ac8cb9c5293b6f37662722088ac",
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "P2SH",
|
|
||||||
args: args{address: "321x69Cb9HZLWwAWGiUBT1U81r1zPLnEjL"},
|
|
||||||
want: "a9140394b3cf9a44782c10105b93962daa8dba304d7f87",
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "P2WPKH",
|
|
||||||
args: args{address: "bc1qrsf2l34jvqnq0lduyz0j5pfu2nkd93nnq0qggn"},
|
|
||||||
want: "00141c12afc6b2602607fdbc209f2a053c54ecd2c673",
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "P2WSH",
|
|
||||||
args: args{address: "bc1qqwtn5s8vjnqdzrm0du885c46ypzt05vakmljhasx28shlv5a355sw5exgr"},
|
|
||||||
want: "002003973a40ec94c0d10f6f6f0e7a62ba2044b7d19db6ff2bf60651e17fb29d8d29",
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
parser := &BitcoinBlockParser{Params: GetChainParams("main")}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
got, err := parser.AddressToOutputScript(tt.args.address)
|
|
||||||
if (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("AddressToOutputScript() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
h := hex.EncodeToString(got)
|
|
||||||
if !reflect.DeepEqual(h, tt.want) {
|
|
||||||
t.Errorf("AddressToOutputScript() = %v, want %v", h, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOutputScriptToAddresses(t *testing.T) {
|
|
||||||
type args struct {
|
|
||||||
script string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
want []string
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "P2PKH",
|
|
||||||
args: args{script: "76a914be027bf3eac907bd4ac8cb9c5293b6f37662722088ac"},
|
|
||||||
want: []string{"1JKgN43B9SyLuZH19H5ECvr4KcfrbVHzZ6"},
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "P2SH",
|
|
||||||
args: args{script: "a9140394b3cf9a44782c10105b93962daa8dba304d7f87"},
|
|
||||||
want: []string{"321x69Cb9HZLWwAWGiUBT1U81r1zPLnEjL"},
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "P2WPKH",
|
|
||||||
args: args{script: "00141c12afc6b2602607fdbc209f2a053c54ecd2c673"},
|
|
||||||
want: []string{"bc1qrsf2l34jvqnq0lduyz0j5pfu2nkd93nnq0qggn"},
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "P2WSH",
|
|
||||||
args: args{script: "002003973a40ec94c0d10f6f6f0e7a62ba2044b7d19db6ff2bf60651e17fb29d8d29"},
|
|
||||||
want: []string{"bc1qqwtn5s8vjnqdzrm0du885c46ypzt05vakmljhasx28shlv5a355sw5exgr"},
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
parser := &BitcoinBlockParser{Params: GetChainParams("main")}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
b, _ := hex.DecodeString(tt.args.script)
|
|
||||||
got, err := parser.OutputScriptToAddresses(b)
|
|
||||||
if (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("OutputScriptToAddresses() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(got, tt.want) {
|
|
||||||
t.Errorf("OutputScriptToAddresses() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -25,7 +25,7 @@ type inputOutput struct {
|
|||||||
|
|
||||||
// Mempool is mempool handle.
|
// Mempool is mempool handle.
|
||||||
type Mempool struct {
|
type Mempool struct {
|
||||||
chain *BitcoinRPC
|
chain BlockChain
|
||||||
mux sync.Mutex
|
mux sync.Mutex
|
||||||
txToInputOutput map[string]inputOutput
|
txToInputOutput map[string]inputOutput
|
||||||
scriptToTx map[string][]outpoint
|
scriptToTx map[string][]outpoint
|
||||||
@ -33,7 +33,7 @@ type Mempool struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewMempool creates new mempool handler.
|
// NewMempool creates new mempool handler.
|
||||||
func NewMempool(chain *BitcoinRPC) *Mempool {
|
func NewMempool(chain BlockChain) *Mempool {
|
||||||
return &Mempool{chain: chain}
|
return &Mempool{chain: chain}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,8 +54,8 @@ func (m *Mempool) GetTransactions(outputScript []byte) ([]string, error) {
|
|||||||
return txs, nil
|
return txs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetInput returns transaction which spends given outpoint
|
// GetSpentOutput returns transaction which spends given outpoint
|
||||||
func (m *Mempool) GetInput(outputTxid string, vout uint32) string {
|
func (m *Mempool) GetSpentOutput(outputTxid string, vout uint32) string {
|
||||||
o := outpoint{txid: outputTxid, vout: vout}
|
o := outpoint{txid: outputTxid, vout: vout}
|
||||||
return m.inputs[o]
|
return m.inputs[o]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -58,3 +58,40 @@ type BlockHeader struct {
|
|||||||
Height uint32 `json:"height"`
|
Height uint32 `json:"height"`
|
||||||
Confirmations int `json:"confirmations"`
|
Confirmations int `json:"confirmations"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RPCError struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BlockChain interface {
|
||||||
|
// chain info
|
||||||
|
IsTestnet() bool
|
||||||
|
GetNetworkName() string
|
||||||
|
// requests
|
||||||
|
GetBestBlockHash() (string, error)
|
||||||
|
GetBestBlockHeight() (uint32, error)
|
||||||
|
GetBlockHash(height uint32) (string, error)
|
||||||
|
GetBlockHeader(hash string) (*BlockHeader, error)
|
||||||
|
GetBlock(hash string) (*Block, error)
|
||||||
|
GetBlockWithoutHeader(hash string, height uint32) (*Block, error)
|
||||||
|
GetMempool() ([]string, error)
|
||||||
|
GetTransaction(txid string) (*Tx, error)
|
||||||
|
EstimateSmartFee(blocks int, conservative bool) (float64, error)
|
||||||
|
SendRawTransaction(tx string) (string, error)
|
||||||
|
// mempool
|
||||||
|
ResyncMempool(onNewTxAddr func(txid string, addr string)) error
|
||||||
|
GetMempoolTransactions(outputScript []byte) ([]string, error)
|
||||||
|
GetMempoolSpentOutput(outputTxid string, vout uint32) string
|
||||||
|
// parser
|
||||||
|
GetChainParser() BlockChainParser
|
||||||
|
}
|
||||||
|
|
||||||
|
type BlockChainParser interface {
|
||||||
|
AddressToOutputScript(address string) ([]byte, error)
|
||||||
|
OutputScriptToAddresses(script []byte) ([]string, error)
|
||||||
|
ParseTx(b []byte) (*Tx, error)
|
||||||
|
ParseBlock(b []byte) (*Block, error)
|
||||||
|
PackTx(tx *Tx, height uint32, blockTime int64) ([]byte, error)
|
||||||
|
UnpackTx(buf []byte) (*Tx, uint32, error)
|
||||||
|
}
|
||||||
|
|||||||
20
blockbook.go
20
blockbook.go
@ -70,8 +70,7 @@ var (
|
|||||||
chanSyncMempool = make(chan struct{})
|
chanSyncMempool = make(chan struct{})
|
||||||
chanSyncIndexDone = make(chan struct{})
|
chanSyncIndexDone = make(chan struct{})
|
||||||
chanSyncMempoolDone = make(chan struct{})
|
chanSyncMempoolDone = make(chan struct{})
|
||||||
chain *bchain.BitcoinRPC
|
chain bchain.BlockChain
|
||||||
mempool *bchain.Mempool
|
|
||||||
index *db.RocksDB
|
index *db.RocksDB
|
||||||
txCache *db.TxCache
|
txCache *db.TxCache
|
||||||
syncWorker *db.SyncWorker
|
syncWorker *db.SyncWorker
|
||||||
@ -107,9 +106,7 @@ func main() {
|
|||||||
glog.Fatal("NewBitcoinRPC ", err)
|
glog.Fatal("NewBitcoinRPC ", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
mempool = bchain.NewMempool(chain)
|
index, err = db.NewRocksDB(*dbPath, chain.GetChainParser())
|
||||||
|
|
||||||
index, err = db.NewRocksDB(*dbPath, chain.Parser)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Fatalf("NewRocksDB %v", err)
|
glog.Fatalf("NewRocksDB %v", err)
|
||||||
}
|
}
|
||||||
@ -143,8 +140,8 @@ func main() {
|
|||||||
glog.Error("resyncIndex ", err)
|
glog.Error("resyncIndex ", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = mempool.Resync(nil); err != nil {
|
if err = chain.ResyncMempool(nil); err != nil {
|
||||||
glog.Error("resyncIndex ", err)
|
glog.Error("resyncMempool ", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -156,7 +153,7 @@ func main() {
|
|||||||
|
|
||||||
var httpServer *server.HTTPServer
|
var httpServer *server.HTTPServer
|
||||||
if *httpServerBinding != "" {
|
if *httpServerBinding != "" {
|
||||||
httpServer, err = server.NewHTTPServer(*httpServerBinding, *certFiles, index, mempool, chain, txCache)
|
httpServer, err = server.NewHTTPServer(*httpServerBinding, *certFiles, index, chain, txCache)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Error("https: ", err)
|
glog.Error("https: ", err)
|
||||||
return
|
return
|
||||||
@ -176,7 +173,7 @@ func main() {
|
|||||||
|
|
||||||
var socketIoServer *server.SocketIoServer
|
var socketIoServer *server.SocketIoServer
|
||||||
if *socketIoBinding != "" {
|
if *socketIoBinding != "" {
|
||||||
socketIoServer, err = server.NewSocketIoServer(*socketIoBinding, *certFiles, index, mempool, chain, txCache, *explorerURL)
|
socketIoServer, err = server.NewSocketIoServer(*socketIoBinding, *certFiles, index, chain, txCache, *explorerURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Error("socketio: ", err)
|
glog.Error("socketio: ", err)
|
||||||
return
|
return
|
||||||
@ -224,7 +221,7 @@ func main() {
|
|||||||
address := *queryAddress
|
address := *queryAddress
|
||||||
|
|
||||||
if address != "" {
|
if address != "" {
|
||||||
script, err := chain.Parser.AddressToOutputScript(address)
|
script, err := chain.GetChainParser().AddressToOutputScript(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Error("GetTransactions ", err)
|
glog.Error("GetTransactions ", err)
|
||||||
return
|
return
|
||||||
@ -299,7 +296,7 @@ func syncMempoolLoop() {
|
|||||||
glog.Info("syncMempoolLoop starting")
|
glog.Info("syncMempoolLoop starting")
|
||||||
// resync mempool about every minute if there are no chanSyncMempool requests, with debounce 1 second
|
// resync mempool about every minute if there are no chanSyncMempool requests, with debounce 1 second
|
||||||
tickAndDebounce(resyncMempoolPeriodMs*time.Millisecond, debounceResyncMempoolMs*time.Millisecond, chanSyncMempool, func() {
|
tickAndDebounce(resyncMempoolPeriodMs*time.Millisecond, debounceResyncMempoolMs*time.Millisecond, chanSyncMempool, func() {
|
||||||
if err := mempool.Resync(onNewTxAddr); err != nil {
|
if err := chain.ResyncMempool(onNewTxAddr); err != nil {
|
||||||
glog.Error("syncMempoolLoop ", errors.ErrorStack(err))
|
glog.Error("syncMempoolLoop ", errors.ErrorStack(err))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -313,6 +310,7 @@ func onNewTxAddr(txid string, addr string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func mqHandler(m *bchain.MQMessage) {
|
func mqHandler(m *bchain.MQMessage) {
|
||||||
|
// TODO - is coin specific, item for abstraction
|
||||||
body := hex.EncodeToString(m.Body)
|
body := hex.EncodeToString(m.Body)
|
||||||
glog.V(1).Infof("MQ: %s-%d %s", m.Topic, m.Sequence, body)
|
glog.V(1).Infof("MQ: %s-%d %s", m.Topic, m.Sequence, body)
|
||||||
if m.Topic == "hashblock" {
|
if m.Topic == "hashblock" {
|
||||||
|
|||||||
@ -34,7 +34,7 @@ type RocksDB struct {
|
|||||||
wo *gorocksdb.WriteOptions
|
wo *gorocksdb.WriteOptions
|
||||||
ro *gorocksdb.ReadOptions
|
ro *gorocksdb.ReadOptions
|
||||||
cfh []*gorocksdb.ColumnFamilyHandle
|
cfh []*gorocksdb.ColumnFamilyHandle
|
||||||
chainParser *bchain.BitcoinBlockParser
|
chainParser bchain.BlockChainParser
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -94,7 +94,7 @@ func openDB(path string) (*gorocksdb.DB, []*gorocksdb.ColumnFamilyHandle, error)
|
|||||||
|
|
||||||
// NewRocksDB opens an internal handle to RocksDB environment. Close
|
// NewRocksDB opens an internal handle to RocksDB environment. Close
|
||||||
// needs to be called to release it.
|
// needs to be called to release it.
|
||||||
func NewRocksDB(path string, parser *bchain.BitcoinBlockParser) (d *RocksDB, err error) {
|
func NewRocksDB(path string, parser bchain.BlockChainParser) (d *RocksDB, err error) {
|
||||||
glog.Infof("rocksdb: open %s", path)
|
glog.Infof("rocksdb: open %s", path)
|
||||||
db, cfh, err := openDB(path)
|
db, cfh, err := openDB(path)
|
||||||
wo := gorocksdb.NewDefaultWriteOptions()
|
wo := gorocksdb.NewDefaultWriteOptions()
|
||||||
@ -542,7 +542,7 @@ func (d *RocksDB) GetTx(txid string) (*bchain.Tx, uint32, error) {
|
|||||||
defer val.Free()
|
defer val.Free()
|
||||||
data := val.Data()
|
data := val.Data()
|
||||||
if len(data) > 4 {
|
if len(data) > 4 {
|
||||||
return unpackTx(data, d.chainParser)
|
return d.chainParser.UnpackTx(data)
|
||||||
}
|
}
|
||||||
return nil, 0, nil
|
return nil, 0, nil
|
||||||
}
|
}
|
||||||
@ -553,7 +553,7 @@ func (d *RocksDB) PutTx(tx *bchain.Tx, height uint32, blockTime int64) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
buf, err := packTx(tx, height, blockTime)
|
buf, err := d.chainParser.PackTx(tx, height, blockTime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -571,6 +571,7 @@ func (d *RocksDB) DeleteTx(txid string) error {
|
|||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
|
|
||||||
|
// TODO - this may be coin specific, refactor
|
||||||
const txIdUnpackedLen = 32
|
const txIdUnpackedLen = 32
|
||||||
|
|
||||||
var ErrInvalidAddress = errors.New("invalid address")
|
var ErrInvalidAddress = errors.New("invalid address")
|
||||||
@ -640,23 +641,3 @@ func packOutputScript(script string) ([]byte, error) {
|
|||||||
func unpackOutputScript(buf []byte) string {
|
func unpackOutputScript(buf []byte) string {
|
||||||
return hex.EncodeToString(buf)
|
return hex.EncodeToString(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func packTx(tx *bchain.Tx, height uint32, blockTime int64) ([]byte, error) {
|
|
||||||
bt := packVarint64(blockTime)
|
|
||||||
buf := make([]byte, 4+len(bt)+len(tx.Hex)/2)
|
|
||||||
binary.BigEndian.PutUint32(buf[0:4], height)
|
|
||||||
copy(buf[4:], bt)
|
|
||||||
_, err := hex.Decode(buf[4+len(bt):], []byte(tx.Hex))
|
|
||||||
return buf, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func unpackTx(buf []byte, parser *bchain.BitcoinBlockParser) (*bchain.Tx, uint32, error) {
|
|
||||||
height := unpackUint(buf)
|
|
||||||
bt, l := unpackVarint64(buf[4:])
|
|
||||||
tx, err := parser.ParseTx(buf[4+l:])
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
tx.Blocktime = bt
|
|
||||||
return tx, height, nil
|
|
||||||
}
|
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import (
|
|||||||
// SyncWorker is handle to SyncWorker
|
// SyncWorker is handle to SyncWorker
|
||||||
type SyncWorker struct {
|
type SyncWorker struct {
|
||||||
db *RocksDB
|
db *RocksDB
|
||||||
chain *bchain.BitcoinRPC
|
chain bchain.BlockChain
|
||||||
syncWorkers, syncChunk int
|
syncWorkers, syncChunk int
|
||||||
dryRun bool
|
dryRun bool
|
||||||
startHeight uint32
|
startHeight uint32
|
||||||
@ -22,7 +22,7 @@ type SyncWorker struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewSyncWorker creates new SyncWorker and returns its handle
|
// NewSyncWorker creates new SyncWorker and returns its handle
|
||||||
func NewSyncWorker(db *RocksDB, chain *bchain.BitcoinRPC, syncWorkers, syncChunk int, minStartHeight int, dryRun bool, chanOsSignal chan os.Signal) (*SyncWorker, error) {
|
func NewSyncWorker(db *RocksDB, chain bchain.BlockChain, syncWorkers, syncChunk int, minStartHeight int, dryRun bool, chanOsSignal chan os.Signal) (*SyncWorker, error) {
|
||||||
if minStartHeight < 0 {
|
if minStartHeight < 0 {
|
||||||
minStartHeight = 0
|
minStartHeight = 0
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,11 +9,11 @@ import (
|
|||||||
// TxCache is handle to TxCacheServer
|
// TxCache is handle to TxCacheServer
|
||||||
type TxCache struct {
|
type TxCache struct {
|
||||||
db *RocksDB
|
db *RocksDB
|
||||||
chain *bchain.BitcoinRPC
|
chain bchain.BlockChain
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTxCache creates new TxCache interface and returns its handle
|
// NewTxCache creates new TxCache interface and returns its handle
|
||||||
func NewTxCache(db *RocksDB, chain *bchain.BitcoinRPC) (*TxCache, error) {
|
func NewTxCache(db *RocksDB, chain bchain.BlockChain) (*TxCache, error) {
|
||||||
return &TxCache{
|
return &TxCache{
|
||||||
db: db,
|
db: db,
|
||||||
chain: chain,
|
chain: chain,
|
||||||
|
|||||||
@ -18,26 +18,26 @@ import (
|
|||||||
|
|
||||||
// HTTPServer is handle to HttpServer
|
// HTTPServer is handle to HttpServer
|
||||||
type HTTPServer struct {
|
type HTTPServer struct {
|
||||||
https *http.Server
|
https *http.Server
|
||||||
certFiles string
|
certFiles string
|
||||||
db *db.RocksDB
|
db *db.RocksDB
|
||||||
txCache *db.TxCache
|
txCache *db.TxCache
|
||||||
mempool *bchain.Mempool
|
chain bchain.BlockChain
|
||||||
chain *bchain.BitcoinRPC
|
chainParser bchain.BlockChainParser
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHTTPServer creates new REST interface to blockbook and returns its handle
|
// NewHTTPServer creates new REST interface to blockbook and returns its handle
|
||||||
func NewHTTPServer(httpServerBinding string, certFiles string, db *db.RocksDB, mempool *bchain.Mempool, chain *bchain.BitcoinRPC, txCache *db.TxCache) (*HTTPServer, error) {
|
func NewHTTPServer(httpServerBinding string, certFiles string, db *db.RocksDB, chain bchain.BlockChain, txCache *db.TxCache) (*HTTPServer, error) {
|
||||||
https := &http.Server{
|
https := &http.Server{
|
||||||
Addr: httpServerBinding,
|
Addr: httpServerBinding,
|
||||||
}
|
}
|
||||||
s := &HTTPServer{
|
s := &HTTPServer{
|
||||||
https: https,
|
https: https,
|
||||||
certFiles: certFiles,
|
certFiles: certFiles,
|
||||||
db: db,
|
db: db,
|
||||||
txCache: txCache,
|
txCache: txCache,
|
||||||
mempool: mempool,
|
chain: chain,
|
||||||
chain: chain,
|
chainParser: chain.GetChainParser(),
|
||||||
}
|
}
|
||||||
|
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
@ -135,7 +135,7 @@ func (s *HTTPServer) blockHash(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
func (s *HTTPServer) getAddress(r *http.Request) (address string, script []byte, err error) {
|
func (s *HTTPServer) getAddress(r *http.Request) (address string, script []byte, err error) {
|
||||||
address = mux.Vars(r)["address"]
|
address = mux.Vars(r)["address"]
|
||||||
script, err = s.chain.Parser.AddressToOutputScript(address)
|
script, err = s.chainParser.AddressToOutputScript(address)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,7 +164,7 @@ func (s *HTTPServer) unconfirmedTransactions(w http.ResponseWriter, r *http.Requ
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
respondError(w, err, fmt.Sprint("unconfirmedTransactions for address", address))
|
respondError(w, err, fmt.Sprint("unconfirmedTransactions for address", address))
|
||||||
}
|
}
|
||||||
txs, err := s.mempool.GetTransactions(script)
|
txs, err := s.chain.GetMempoolTransactions(script)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respondError(w, err, fmt.Sprint("unconfirmedTransactions for address", address))
|
respondError(w, err, fmt.Sprint("unconfirmedTransactions for address", address))
|
||||||
}
|
}
|
||||||
@ -197,7 +197,7 @@ func (s *HTTPServer) transactions(w http.ResponseWriter, r *http.Request) {
|
|||||||
err = s.db.GetTransactions(script, lower, higher, func(txid string, vout uint32, isOutput bool) error {
|
err = s.db.GetTransactions(script, lower, higher, func(txid string, vout uint32, isOutput bool) error {
|
||||||
txList.Txid = append(txList.Txid, txid)
|
txList.Txid = append(txList.Txid, txid)
|
||||||
if isOutput {
|
if isOutput {
|
||||||
input := s.mempool.GetInput(txid, vout)
|
input := s.chain.GetMempoolSpentOutput(txid, vout)
|
||||||
if input != "" {
|
if input != "" {
|
||||||
txList.Txid = append(txList.Txid, txid)
|
txList.Txid = append(txList.Txid, txid)
|
||||||
}
|
}
|
||||||
@ -207,7 +207,7 @@ func (s *HTTPServer) transactions(w http.ResponseWriter, r *http.Request) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
respondError(w, err, fmt.Sprint("transactions for address", address))
|
respondError(w, err, fmt.Sprint("transactions for address", address))
|
||||||
}
|
}
|
||||||
txs, err := s.mempool.GetTransactions(script)
|
txs, err := s.chain.GetMempoolTransactions(script)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respondError(w, err, fmt.Sprint("transactions for address", address))
|
respondError(w, err, fmt.Sprint("transactions for address", address))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,13 +24,13 @@ type SocketIoServer struct {
|
|||||||
https *http.Server
|
https *http.Server
|
||||||
db *db.RocksDB
|
db *db.RocksDB
|
||||||
txCache *db.TxCache
|
txCache *db.TxCache
|
||||||
mempool *bchain.Mempool
|
chain bchain.BlockChain
|
||||||
chain *bchain.BitcoinRPC
|
chainParser bchain.BlockChainParser
|
||||||
explorerURL string
|
explorerURL string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSocketIoServer creates new SocketIo interface to blockbook and returns its handle
|
// NewSocketIoServer creates new SocketIo interface to blockbook and returns its handle
|
||||||
func NewSocketIoServer(binding string, certFiles string, db *db.RocksDB, mempool *bchain.Mempool, chain *bchain.BitcoinRPC, txCache *db.TxCache, explorerURL string) (*SocketIoServer, error) {
|
func NewSocketIoServer(binding string, certFiles string, db *db.RocksDB, chain bchain.BlockChain, txCache *db.TxCache, explorerURL string) (*SocketIoServer, error) {
|
||||||
server := gosocketio.NewServer(transport.GetDefaultWebsocketTransport())
|
server := gosocketio.NewServer(transport.GetDefaultWebsocketTransport())
|
||||||
|
|
||||||
server.On(gosocketio.OnConnection, func(c *gosocketio.Channel) {
|
server.On(gosocketio.OnConnection, func(c *gosocketio.Channel) {
|
||||||
@ -64,8 +64,8 @@ func NewSocketIoServer(binding string, certFiles string, db *db.RocksDB, mempool
|
|||||||
server: server,
|
server: server,
|
||||||
db: db,
|
db: db,
|
||||||
txCache: txCache,
|
txCache: txCache,
|
||||||
mempool: mempool,
|
|
||||||
chain: chain,
|
chain: chain,
|
||||||
|
chainParser: chain.GetChainParser(),
|
||||||
explorerURL: explorerURL,
|
explorerURL: explorerURL,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -241,7 +241,7 @@ func (s *SocketIoServer) getAddressTxids(addr []string, rr *reqRange) (res resul
|
|||||||
txids := make([]string, 0)
|
txids := make([]string, 0)
|
||||||
lower, higher := uint32(rr.To), uint32(rr.Start)
|
lower, higher := uint32(rr.To), uint32(rr.Start)
|
||||||
for _, address := range addr {
|
for _, address := range addr {
|
||||||
script, err := s.chain.Parser.AddressToOutputScript(address)
|
script, err := s.chainParser.AddressToOutputScript(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
@ -249,7 +249,7 @@ func (s *SocketIoServer) getAddressTxids(addr []string, rr *reqRange) (res resul
|
|||||||
err = s.db.GetTransactions(script, lower, higher, func(txid string, vout uint32, isOutput bool) error {
|
err = s.db.GetTransactions(script, lower, higher, func(txid string, vout uint32, isOutput bool) error {
|
||||||
txids = append(txids, txid)
|
txids = append(txids, txid)
|
||||||
if isOutput && rr.QueryMempol {
|
if isOutput && rr.QueryMempol {
|
||||||
input := s.mempool.GetInput(txid, vout)
|
input := s.chain.GetMempoolSpentOutput(txid, vout)
|
||||||
if input != "" {
|
if input != "" {
|
||||||
txids = append(txids, txid)
|
txids = append(txids, txid)
|
||||||
}
|
}
|
||||||
@ -261,7 +261,7 @@ func (s *SocketIoServer) getAddressTxids(addr []string, rr *reqRange) (res resul
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if rr.QueryMempoolOnly || rr.QueryMempol {
|
if rr.QueryMempoolOnly || rr.QueryMempol {
|
||||||
mtxids, err := s.mempool.GetTransactions(script)
|
mtxids, err := s.chain.GetMempoolTransactions(script)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
@ -580,8 +580,8 @@ func (s *SocketIoServer) getInfo() (res resultGetInfo, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
res.Result.Blocks = int(height)
|
res.Result.Blocks = int(height)
|
||||||
res.Result.Testnet = s.chain.Testnet
|
res.Result.Testnet = s.chain.IsTestnet()
|
||||||
res.Result.Network = s.chain.Network
|
res.Result.Network = s.chain.GetNetworkName()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user