d2193e980a
* Generate secondary hashXNotification(s) on every headerNotification. * Attempt LBCD connection with rpc.Client. * Optional --daemon-url. * Correct HashXStatusKey field. Should be HASHX_LEN. * Connect to lbcd using lbcd/rpcclient library. * Handle deprecation of node.js 12 actions. * Add --daemon-ca-path argument and establish HTTPS connection if specified. * Remove dead code. Tighten definition of TransactionBroadcastReq. * Correct default daemon URL.
924 lines
23 KiB
Go
924 lines
23 KiB
Go
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 [1]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)[0])
|
|
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
|
|
}
|