diff --git a/bchain/coins/btc/bitcoinparser.go b/bchain/coins/btc/bitcoinparser.go index aa8ceffe..eab5891d 100644 --- a/bchain/coins/btc/bitcoinparser.go +++ b/bchain/coins/btc/bitcoinparser.go @@ -85,7 +85,7 @@ func outputScriptToAddresses(script []byte, params *chaincfg.Params) ([]string, return rv, nil } -func (p *BitcoinParser) txFromMsgTx(t *wire.MsgTx, parseAddresses bool) bchain.Tx { +func (p *BitcoinParser) TxFromMsgTx(t *wire.MsgTx, parseAddresses bool) bchain.Tx { vin := make([]bchain.Vin, len(t.TxIn)) for i, in := range t.TxIn { if blockchain.IsCoinBaseTx(t) { @@ -145,7 +145,7 @@ func (p *BitcoinParser) ParseTx(b []byte) (*bchain.Tx, error) { if err := t.Deserialize(r); err != nil { return nil, err } - tx := p.txFromMsgTx(&t, true) + tx := p.TxFromMsgTx(&t, true) tx.Hex = hex.EncodeToString(b) for i, vout := range tx.Vout { @@ -172,7 +172,7 @@ func (p *BitcoinParser) ParseBlock(b []byte) (*bchain.Block, error) { txs := make([]bchain.Tx, len(w.Transactions)) for ti, t := range w.Transactions { - txs[ti] = p.txFromMsgTx(t, false) + txs[ti] = p.TxFromMsgTx(t, false) } return &bchain.Block{Txs: txs}, nil diff --git a/bchain/coins/btg/bgoldparser.go b/bchain/coins/btg/bgoldparser.go index af6a5c16..dfa89751 100644 --- a/bchain/coins/btg/bgoldparser.go +++ b/bchain/coins/btg/bgoldparser.go @@ -1,23 +1,54 @@ -package bch +package btg import ( "blockbook/bchain" "blockbook/bchain/coins/btc" + "bytes" "fmt" + "io" "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcd/txscript" - "github.com/btcsuite/btcutil" - "github.com/cpacia/bchutil" - "github.com/schancel/cashaddr-converter/address" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/golang/glog" ) const ( MainnetMagic wire.BitcoinNet = 0x446d47e1 TestnetMagic wire.BitcoinNet = 0x456e48e2 - RegtestMagic wire.BitcoinNet = 0xdab5bffa ) +var ( + MainNetParams chaincfg.Params + TestNetParams chaincfg.Params +) + +func init() { + MainNetParams = chaincfg.MainNetParams + MainNetParams.Net = MainnetMagic + + // Address encoding magics + MainNetParams.PubKeyHashAddrID = 38 // base58 prefix: G + MainNetParams.ScriptHashAddrID = 23 // base58 prefix: A + + TestNetParams = chaincfg.TestNet3Params + TestNetParams.Net = TestnetMagic + + // Human-readable part for Bech32 encoded segwit addresses, as defined in + // BIP 173. + // see https://github.com/satoshilabs/slips/blob/master/slip-0173.md + MainNetParams.Bech32HRPSegwit = "btg" + TestNetParams.Bech32HRPSegwit = "tbtg" + + err := chaincfg.Register(&MainNetParams) + if err == nil { + err = chaincfg.Register(&TestNetParams) + } + if err != nil { + panic(err) + } +} + // BGoldParser handle type BGoldParser struct { *btc.BitcoinParser @@ -25,25 +56,110 @@ type BGoldParser struct { // NewBCashParser returns new BGoldParser instance func NewBGoldParser(params *chaincfg.Params, c *btc.Configuration) *BGoldParser { - return BGoldParser{BitcoinParser: btc.NewBitcoinParser(params, c)} + return &BGoldParser{BitcoinParser: btc.NewBitcoinParser(params, c)} } // 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 = TestnetMagic + return &TestNetParams case "regtest": - params = &chaincfg.RegressionNetParams - params.Net = Regtestmagic + return &chaincfg.RegressionNetParams default: - params = &chaincfg.MainNetParams - params.Net = MainnetMagic + return &MainNetParams + } +} + +// minTxPayload is the minimum payload size for a transaction. Note +// that any realistically usable transaction must have at least one +// input or output, but that is a rule enforced at a higher layer, so +// it is intentionally not included here. +// Version 4 bytes + Varint number of transaction inputs 1 byte + Varint +// number of transaction outputs 1 byte + LockTime 4 bytes + min input +// payload + min output payload. +const minTxPayload = 10 + +// maxTxPerBlock is the maximum number of transactions that could +// possibly fit into a block. +const maxTxPerBlock = (wire.MaxBlockPayload / minTxPayload) + 1 + +const headerConstLength = 44 + (chainhash.HashSize * 3) + +// ParseBlock parses raw block to our Block struct +func (p *BGoldParser) ParseBlock(b []byte) (*bchain.Block, error) { + r := bytes.NewReader(b) + err := skipHeader(r, 0) + if err != nil { + return nil, err } - return params + if glog.V(1) { + off, _ := r.Seek(0, io.SeekCurrent) + glog.Infof("parseblock: skipped %d bytes", off) + } + + w := wire.MsgBlock{} + err = decodeTransactions(r, 0, wire.WitnessEncoding, &w) + if err != nil { + return nil, err + } + + glog.V(1).Infof("parseblock: got %d transactions", len(w.Transactions)) + + txs := make([]bchain.Tx, len(w.Transactions)) + for ti, t := range w.Transactions { + txs[ti] = p.TxFromMsgTx(t, false) + } + + return &bchain.Block{Txs: txs}, nil +} + +func skipHeader(r io.ReadSeeker, pver uint32) error { + _, err := r.Seek(headerConstLength, io.SeekStart) + if err != nil { + return err + } + + size, err := wire.ReadVarInt(r, pver) + if err != nil { + return err + } + + _, err = r.Seek(int64(size), io.SeekCurrent) + if err != nil { + return err + } + + return nil +} + +func decodeTransactions(r io.Reader, pver uint32, enc wire.MessageEncoding, blk *wire.MsgBlock) error { + txCount, err := wire.ReadVarInt(r, pver) + if err != nil { + return err + } + + // Prevent more transactions than could possibly fit into a block. + // It would be possible to cause memory exhaustion and panics without + // a sane upper bound on this count. + if txCount > maxTxPerBlock { + str := fmt.Sprintf("too many transactions to fit into a block "+ + "[count %d, max %d]", txCount, maxTxPerBlock) + return &wire.MessageError{Func: "btg.decodeTransactions", Description: str} + } + + blk.Transactions = make([]*wire.MsgTx, 0, txCount) + for i := uint64(0); i < txCount; i++ { + tx := wire.MsgTx{} + err := tx.BtcDecode(r, pver, enc) + if err != nil { + return err + } + blk.Transactions = append(blk.Transactions, &tx) + } + + return nil } diff --git a/bchain/coins/btg/bgoldparser_test.go b/bchain/coins/btg/bgoldparser_test.go index 9402a0dc..d8714583 100644 --- a/bchain/coins/btg/bgoldparser_test.go +++ b/bchain/coins/btg/bgoldparser_test.go @@ -1,4 +1,4 @@ -package bch +package btg // import ( // "blockbook/bchain" diff --git a/bchain/coins/btg/bgoldrpc.go b/bchain/coins/btg/bgoldrpc.go index c3a88bdb..4de00487 100644 --- a/bchain/coins/btg/bgoldrpc.go +++ b/bchain/coins/btg/bgoldrpc.go @@ -1,14 +1,11 @@ -package bch +package btg import ( "blockbook/bchain" "blockbook/bchain/coins/btc" - "encoding/hex" "encoding/json" - "github.com/cpacia/bchutil" "github.com/golang/glog" - "github.com/juju/errors" ) // BGoldRPC is an interface to JSON-RPC bitcoind service. @@ -40,11 +37,7 @@ func (b *BGoldRPC) Initialize() error { params := GetChainParams(chainName) // always create parser - b.Parser, err = NewBGoldParser(params, b.ChainConfig) - - if err != nil { - return err - } + b.Parser = NewBGoldParser(params, b.ChainConfig) // parameters for getInfo request if params.Net == MainnetMagic { @@ -59,117 +52,3 @@ func (b *BGoldRPC) Initialize() error { 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"` -// } -// -// // estimatesmartfee -// -// type cmdEstimateSmartFee struct { -// Method string `json:"method"` -// Params struct { -// Blocks int `json:"nblocks"` -// } `json:"params"` -// } -// -// type resEstimateSmartFee struct { -// Error *bchain.RPCError `json:"error"` -// Result struct { -// Feerate float64 `json:"feerate"` -// Blocks int `json:"blocks"` -// } `json:"result"` -// } -// -// // GetBlock returns block with given hash. -// func (b *BGoldRPC) 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 -// } -// } -// 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 *BGoldRPC) 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) -// } -// -// // GetBlockFull returns block with given hash. -// func (b *BGoldRPC) GetBlockFull(hash string) (*bchain.Block, error) { -// return nil, errors.New("Not implemented") -// } -// -// // EstimateSmartFee returns fee estimation. -// func (b *BGoldRPC) EstimateSmartFee(blocks int, conservative bool) (float64, error) { -// glog.V(1).Info("rpc: estimatesmartfee ", blocks) -// -// res := resEstimateSmartFee{} -// req := cmdEstimateSmartFee{Method: "estimatesmartfee"} -// req.Params.Blocks = blocks -// // conservative param is omitted -// err := b.Call(&req, &res) -// -// if err != nil { -// return 0, err -// } -// if res.Error != nil { -// return 0, res.Error -// } -// return res.Result.Feerate, nil -// } -// -// func isErrBlockNotFound(err *bchain.RPCError) bool { -// return err.Message == "Block not found" || -// err.Message == "Block height out of range" -// }