herald.go/server/blockchain.go
2022-09-07 14:32:34 -05:00

411 lines
10 KiB
Go

package server
import (
"bytes"
"compress/zlib"
"crypto/sha256"
"encoding/base64"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"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"
)
type RpcReq interface {
}
type RpcResp interface {
}
type RpcHandler interface {
Handle() (RpcResp, error)
}
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
}
type blockGetChunkReq uint32
type blockGetChunkResp string
// 'blockchain.block.get_chunk'
func (req *blockGetChunkReq) Handle(s *Server) (*blockGetChunkResp, error) {
index := uint32(*req)
db_headers, err := s.DB.GetHeaders(index*CHUNK_SIZE, CHUNK_SIZE)
if err != nil {
return nil, err
}
raw := make([]byte, 0, HEADER_SIZE*len(db_headers))
for _, h := range db_headers {
raw = append(raw, h[:]...)
}
headers := blockGetChunkResp(hex.EncodeToString(raw))
return &headers, err
}
type blockGetHeaderReq uint32
type blockGetHeaderResp 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"`
}
// 'blockchain.block.get_header'
func (req *blockGetHeaderReq) Handle(s *Server) (*blockGetHeaderResp, error) {
height := uint32(*req)
headers, err := s.DB.GetHeaders(height, 1)
if err != nil {
return nil, err
}
if len(headers) < 1 {
return nil, errors.New("not found")
}
decode := func(header *[HEADER_SIZE]byte, height uint32) *blockGetHeaderResp {
var h1, h2, h3 chainhash.Hash
h1.SetBytes(header[4:36])
h2.SetBytes(header[36:68])
h3.SetBytes(header[68:100])
return &blockGetHeaderResp{
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,
}
}
return decode(&headers[0], height), nil
}
type blockHeadersReq struct {
StartHeight uint32 `json:"start_height"`
Count uint32 `json:"count"`
CpHeight uint32 `json:"cp_height"`
B64 bool `json:"b64"`
}
type blockHeadersResp struct {
Base64 string `json:"base64,omitempty"`
Hex string `json:"hex,omitempty"`
Count uint32 `json:"count"`
Max uint32 `json:"max"`
Branch string `json:"branch,omitempty"`
Root string `json:"root,omitempty"`
}
// 'blockchain.block.headers'
func (req *blockHeadersReq) Handle(s *Server) (*blockHeadersResp, error) {
count := min(req.Count, MAX_CHUNK_SIZE)
db_headers, err := s.DB.GetHeaders(req.StartHeight, count)
if err != nil {
return nil, 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
}
return result, 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 (req *addressGetBalanceReq) Handle(s *Server) (*addressGetBalanceResp, error) {
address, err := lbcutil.DecodeAddress(req.Address, s.Chain)
if err != nil {
return nil, err
}
script, err := txscript.PayToAddrScript(address)
if err != nil {
return nil, err
}
hashX := hashXScript(script, s.Chain)
confirmed, unconfirmed, err := s.DB.GetBalance(hashX)
if err != nil {
return nil, err
}
return &addressGetBalanceResp{confirmed, unconfirmed}, err
}
type scripthashGetBalanceReq struct {
ScriptHash string `json:"scripthash"`
}
type scripthashGetBalanceResp struct {
Confirmed uint64 `json:"confirmed"`
Unconfirmed uint64 `json:"unconfirmed"`
}
// 'blockchain.scripthash.get_balance'
func (req *scripthashGetBalanceReq) Handle(s *Server) (*scripthashGetBalanceResp, error) {
scripthash, err := decodeScriptHash(req.ScriptHash)
if err != nil {
return nil, err
}
hashX := hashX(scripthash)
confirmed, unconfirmed, err := s.DB.GetBalance(hashX)
if err != nil {
return nil, err
}
return &scripthashGetBalanceResp{confirmed, unconfirmed}, err
}
type addressGetHistoryReq struct {
Address string `json:"address"`
}
type TxInfo struct {
TxHash string `json:"tx_hash"`
Height uint32 `json:"height"`
}
type TxInfoFee struct {
TxInfo
Fee uint64 `json:"fee"`
}
type addressGetHistoryResp struct {
Confirmed []TxInfo `json:"confirmed"`
Unconfirmed []TxInfoFee `json:"unconfirmed"`
}
// 'blockchain.address.get_history'
func (req *addressGetHistoryReq) Handle(s *Server) (*addressGetHistoryResp, error) {
address, err := lbcutil.DecodeAddress(req.Address, s.Chain)
if err != nil {
return nil, err
}
script, err := txscript.PayToAddrScript(address)
if err != nil {
return nil, err
}
hashX := hashXScript(script, s.Chain)
dbTXs, err := s.DB.GetHistory(hashX)
if err != nil {
return nil, err
}
confirmed := make([]TxInfo, 0, len(dbTXs))
for _, tx := range dbTXs {
confirmed = append(confirmed,
TxInfo{
TxHash: tx.TxHash.String(),
Height: tx.Height,
})
}
result := &addressGetHistoryResp{
Confirmed: confirmed,
Unconfirmed: []TxInfoFee{}, // TODO
}
return result, nil
}
type scripthashGetHistoryReq struct {
ScriptHash string `json:"scripthash"`
}
type scripthashGetHistoryResp struct {
Confirmed []TxInfo `json:"confirmed"`
Unconfirmed []TxInfoFee `json:"unconfirmed"`
}
// 'blockchain.scripthash.get_history'
func (req *scripthashGetHistoryReq) Handle(s *Server) (*scripthashGetHistoryResp, error) {
scripthash, err := decodeScriptHash(req.ScriptHash)
if err != nil {
return nil, err
}
hashX := hashX(scripthash)
dbTXs, err := s.DB.GetHistory(hashX)
if err != nil {
return nil, 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
}
return result, nil
}
type addressGetMempoolReq struct {
Address string `json:"address"`
}
type addressGetMempoolResp []TxInfoFee
// 'blockchain.address.get_mempool'
func (req *addressGetMempoolReq) Handle(s *Server) (*addressGetMempoolResp, error) {
address, err := lbcutil.DecodeAddress(req.Address, s.Chain)
if err != nil {
return nil, err
}
script, err := txscript.PayToAddrScript(address)
if err != nil {
return nil, err
}
hashX := hashXScript(script, s.Chain)
// TODO...
internal.ReverseBytesInPlace(hashX)
unconfirmed := make([]TxInfoFee, 0, 100)
result := addressGetMempoolResp(unconfirmed)
return &result, nil
}
type scripthashGetMempoolReq struct {
ScriptHash string `json:"scripthash"`
}
type scripthashGetMempoolResp []TxInfoFee
// 'blockchain.scripthash.get_mempool'
func (req *scripthashGetMempoolReq) Handle(s *Server) (*scripthashGetMempoolResp, error) {
scripthash, err := decodeScriptHash(req.ScriptHash)
if err != nil {
return nil, err
}
hashX := hashX(scripthash)
// TODO...
internal.ReverseBytesInPlace(hashX)
unconfirmed := make([]TxInfoFee, 0, 100)
result := scripthashGetMempoolResp(unconfirmed)
return &result, nil
}
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 (req *addressListUnspentReq) Handle(s *Server) (*addressListUnspentResp, error) {
address, err := lbcutil.DecodeAddress(req.Address, s.Chain)
if err != nil {
return nil, err
}
script, err := txscript.PayToAddrScript(address)
if err != nil {
return nil, 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)
return &result, nil
}
type scripthashListUnspentReq struct {
ScriptHash string `json:"scripthash"`
}
type scripthashListUnspentResp []TXOInfo
// 'blockchain.scripthash.listunspent'
func (req *scripthashListUnspentReq) Handle(s *Server) (*scripthashListUnspentResp, error) {
scripthash, err := decodeScriptHash(req.ScriptHash)
if err != nil {
return nil, 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)
return &result, nil
}