diff --git a/Gopkg.lock b/Gopkg.lock index 1cac8e92..a40eb931 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -31,6 +31,12 @@ packages = [".","base58","bech32"] revision = "501929d3d046174c3d39f0ea54ece471aa17238c" +[[projects]] + branch = "master" + name = "github.com/cpacia/bchutil" + packages = ["."] + revision = "12e86f41eb040d3b85b5d8e3a3a4bed035517c52" + [[projects]] name = "github.com/ethereum/go-ethereum" packages = [".","common","common/hexutil","common/math","core/types","crypto","crypto/secp256k1","crypto/sha3","ethclient","ethdb","log","metrics","params","rlp","rpc","trie"] @@ -184,6 +190,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "a463c234bc11d9917876a827f692392845ed89571edc1484ae3e932f555d484b" + inputs-digest = "e632a1e904953397e9eae00f30a86bffab2d303232c7bac47a16e1ce663043bf" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index a70bb0c3..e0144613 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -72,3 +72,7 @@ [[constraint]] name = "github.com/golang/protobuf" version = "1.0.0" + +[[constraint]] + branch = "master" + name = "github.com/cpacia/bchutil" diff --git a/bchain/coins/bch/bcashparser.go b/bchain/coins/bch/bcashparser.go new file mode 100644 index 00000000..3df5f65e --- /dev/null +++ b/bchain/coins/bch/bcashparser.go @@ -0,0 +1,75 @@ +package bch + +import ( + "blockbook/bchain/coins/btc" + "strings" + + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcutil" + "github.com/cpacia/bchutil" +) + +var prefixes []string + +func init() { + prefixes = make([]string, 0, len(bchutil.Prefixes)) + for _, prefix := range bchutil.Prefixes { + prefixes = append(prefixes, prefix) + } +} + +// BCashParser handle +type BCashParser struct { + *btc.BitcoinParser +} + +// GetChainParams contains network parameters for the main Bitcoin Cash network, +// the regression test Bitcoin Cash network, the test Bitcoin Cash network and +// the simulation test Bitcoin Cash network, in this order +func GetChainParams(chain string) *chaincfg.Params { + var params *chaincfg.Params + switch chain { + case "test": + params = &chaincfg.TestNet3Params + params.Net = bchutil.TestnetMagic + case "regtest": + params = &chaincfg.RegressionNetParams + params.Net = bchutil.Regtestmagic + default: + params = &chaincfg.MainNetParams + params.Net = bchutil.MainnetMagic + } + + return params +} + +// GetAddrIDFromAddress returns internal address representation of given address +func (p *BCashParser) GetAddrIDFromAddress(address string) ([]byte, error) { + return p.AddressToOutputScript(address) +} + +// AddressToOutputScript converts bitcoin address to ScriptPubKey +func (p *BCashParser) AddressToOutputScript(address string) ([]byte, error) { + if strings.Contains(address, ":") { + da, err := bchutil.DecodeAddress(address, p.Params) + if err != nil { + return nil, err + } + script, err := bchutil.PayToAddrScript(da) + if err != nil { + return nil, err + } + return script, nil + } else { + 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 + } +} diff --git a/bchain/coins/bch/bcashrpc.go b/bchain/coins/bch/bcashrpc.go new file mode 100644 index 00000000..222a7b78 --- /dev/null +++ b/bchain/coins/bch/bcashrpc.go @@ -0,0 +1,180 @@ +package bch + +import ( + "blockbook/bchain" + "blockbook/bchain/coins/btc" + "encoding/hex" + "encoding/json" + + "github.com/cpacia/bchutil" + "github.com/golang/glog" + "github.com/juju/errors" +) + +// BCashRPC is an interface to JSON-RPC bitcoind service. +type BCashRPC struct { + *btc.BitcoinRPC +} + +// NewBCashRPC returns new BCashRPC instance. +func NewBCashRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) { + b, err := btc.NewBitcoinRPC(config, pushHandler) + if err != nil { + return nil, err + } + + s := &BCashRPC{ + b.(*btc.BitcoinRPC), + } + + return s, nil +} + +func (b *BCashRPC) Initialize() error { + b.Mempool = bchain.NewUTXOMempool(b) + + chainName, err := b.GetBlockChainInfo() + if err != nil { + return err + } + + params := GetChainParams(chainName) + + // always create parser + b.Parser = &BCashParser{ + &btc.BitcoinParser{ + Params: params, + }, + } + + // parameters for getInfo request + if params.Net == bchutil.MainnetMagic { + b.Testnet = false + b.Network = "livenet" + } else { + b.Testnet = true + b.Network = "testnet" + } + + glog.Info("rpc: block chain ", params.Name) + + return nil +} + +// getblock + +type cmdGetBlock struct { + Method string `json:"method"` + Params struct { + BlockHash string `json:"blockhash"` + Verbose bool `json:"verbose"` + } `json:"params"` +} + +type resGetBlockRaw struct { + Error *bchain.RPCError `json:"error"` + Result string `json:"result"` +} + +type resGetBlockThin struct { + Error *bchain.RPCError `json:"error"` + Result bchain.ThinBlock `json:"result"` +} + +// GetBlock returns block with given hash. +func (b *BCashRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) { + var err error + if hash == "" && height > 0 { + hash, err = b.GetBlockHash(height) + if err != nil { + return nil, err + } + } + // XXX + // // optimization + // if height > 0 { + // return b.getBlockWithoutHeader(hash, height) + // } + header, err := b.GetBlockHeader(hash) + if err != nil { + return nil, err + } + data, err := b.GetBlockRaw(hash) + if err != nil { + return nil, err + } + block, err := b.Parser.ParseBlock(data) + if err != nil { + return nil, errors.Annotatef(err, "hash %v", hash) + } + block.BlockHeader = *header + return block, nil +} + +// GetBlockRaw returns block with given hash as bytes. +func (b *BCashRPC) GetBlockRaw(hash string) ([]byte, error) { + glog.V(1).Info("rpc: getblock (verbose=0) ", hash) + + res := resGetBlockRaw{} + req := cmdGetBlock{Method: "getblock"} + req.Params.BlockHash = hash + req.Params.Verbose = false + err := b.Call(&req, &res) + + if err != nil { + return nil, errors.Annotatef(err, "hash %v", hash) + } + if res.Error != nil { + if isErrBlockNotFound(res.Error) { + return nil, bchain.ErrBlockNotFound + } + return nil, errors.Annotatef(res.Error, "hash %v", hash) + } + return hex.DecodeString(res.Result) +} + +// GetBlockList returns block with given hash by downloading block +// transactions one by one. +func (b *BCashRPC) GetBlockList(hash string) (*bchain.Block, error) { + glog.V(1).Info("rpc: getblock (verbose=1) ", hash) + + res := resGetBlockThin{} + req := cmdGetBlock{Method: "getblock"} + req.Params.BlockHash = hash + req.Params.Verbose = true + err := b.Call(&req, &res) + + if err != nil { + return nil, errors.Annotatef(err, "hash %v", hash) + } + if res.Error != nil { + if isErrBlockNotFound(res.Error) { + return nil, bchain.ErrBlockNotFound + } + return nil, errors.Annotatef(res.Error, "hash %v", hash) + } + + txs := make([]bchain.Tx, len(res.Result.Txids)) + for i, txid := range res.Result.Txids { + tx, err := b.GetTransaction(txid) + if err != nil { + return nil, err + } + txs[i] = *tx + } + block := &bchain.Block{ + BlockHeader: res.Result.BlockHeader, + Txs: txs, + } + return block, nil +} + +// GetBlockFull returns block with given hash. +func (b *BCashRPC) GetBlockFull(hash string) (*bchain.Block, error) { + return nil, errors.New("Not implemented") +} + +func isErrBlockNotFound(err *bchain.RPCError) bool { + return err.Message == "Block not found" || + err.Message == "Block height out of range" +} diff --git a/bchain/coins/blockchain.go b/bchain/coins/blockchain.go index 973df631..39f01e6e 100644 --- a/bchain/coins/blockchain.go +++ b/bchain/coins/blockchain.go @@ -2,6 +2,7 @@ package coins import ( "blockbook/bchain" + "blockbook/bchain/coins/bch" "blockbook/bchain/coins/btc" "blockbook/bchain/coins/eth" "blockbook/bchain/coins/zec" @@ -25,6 +26,8 @@ func init() { blockChainFactories["zec"] = zec.NewZCashRPC blockChainFactories["eth"] = eth.NewEthereumRPC blockChainFactories["eth-testnet"] = eth.NewEthereumRPC + blockChainFactories["bch"] = bch.NewBCashRPC + blockChainFactories["bch-testnet"] = bch.NewBCashRPC } // NewBlockChain creates bchain.BlockChain of type defined by parameter coin