Add some blockchain RPC handlers and database fetching routines.

This commit is contained in:
Jonathan Moody 2022-08-29 14:51:00 -05:00
parent 9403d84a83
commit fe18c70bf7
7 changed files with 554 additions and 2 deletions

View file

@ -406,6 +406,69 @@ func Iter(db *grocksdb.DB, opts *IterOptions) <-chan *prefixes.PrefixRowKV {
return ch return ch
} }
func (db *ReadOnlyDBColumnFamily) selectFrom(prefix []byte, startKey, stopKey prefixes.BaseKey) ([]*IterOptions, error) {
handle, err := db.EnsureHandle(prefixes.HashXHistory)
if err != nil {
return nil, err
}
// Prefix and handle
options := NewIterateOptions().WithPrefix(prefix).WithCfHandle(handle)
// Start and stop bounds
options = options.WithStart(startKey.PackKey()).WithStop(stopKey.PackKey()).WithIncludeStop(true)
// Don't include the key
options = options.WithIncludeKey(true).WithIncludeValue(true)
return []*IterOptions{options}, nil
}
func iterate(db *grocksdb.DB, opts []*IterOptions) <-chan []*prefixes.PrefixRowKV {
out := make(chan []*prefixes.PrefixRowKV)
routine := func() {
for _, o := range opts {
for kv := range IterCF(db, o) {
row := make([]*prefixes.PrefixRowKV, 0, 1)
row = append(row, kv)
out <- row
}
}
close(out)
}
go routine()
return out
}
func innerJoin(db *grocksdb.DB, in <-chan []*prefixes.PrefixRowKV, selectFn func([]*prefixes.PrefixRowKV) ([]*IterOptions, error)) <-chan []*prefixes.PrefixRowKV {
out := make(chan []*prefixes.PrefixRowKV)
routine := func() {
for kvs := range in {
selected, err := selectFn(kvs)
if err != nil {
out <- []*prefixes.PrefixRowKV{{Error: err}}
close(out)
return
}
for kv := range iterate(db, selected) {
row := make([]*prefixes.PrefixRowKV, 0, len(kvs)+1)
row = append(row, kvs...)
row = append(row, kv...)
out <- row
}
}
close(out)
return
}
go routine()
return out
}
func checkForError(kvs []*prefixes.PrefixRowKV) error {
for _, kv := range kvs {
if kv.Error != nil {
return kv.Error
}
}
return nil
}
// //
// GetDB functions that open and return a db // GetDB functions that open and return a db
// //

View file

@ -6,8 +6,10 @@ import (
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"log" "log"
"math"
"github.com/lbryio/herald.go/db/prefixes" "github.com/lbryio/herald.go/db/prefixes"
"github.com/lbryio/lbcd/chaincfg/chainhash"
"github.com/linxGnu/grocksdb" "github.com/linxGnu/grocksdb"
) )
@ -82,6 +84,187 @@ func (db *ReadOnlyDBColumnFamily) GetHeader(height uint32) ([]byte, error) {
return rawValue, nil return rawValue, nil
} }
func (db *ReadOnlyDBColumnFamily) GetHeaders(height uint32, count uint32) ([][112]byte, error) {
handle, err := db.EnsureHandle(prefixes.Header)
if err != nil {
return nil, err
}
startKeyRaw := prefixes.NewHeaderKey(0).PackKey()
endKeyRaw := prefixes.NewHeaderKey(height + count).PackKey()
options := NewIterateOptions().WithPrefix([]byte{prefixes.Header}).WithCfHandle(handle)
options = options.WithIncludeKey(false).WithIncludeValue(true) //.WithIncludeStop(true)
options = options.WithStart(startKeyRaw).WithStop(endKeyRaw)
result := make([][112]byte, 0, count)
for kv := range IterCF(db.DB, options) {
h := [112]byte{}
copy(h[:], kv.Value.(*prefixes.BlockHeaderValue).Header[:112])
result = append(result, h)
}
return result, nil
}
func (db *ReadOnlyDBColumnFamily) GetBalance(hashX []byte) (uint64, uint64, error) {
handle, err := db.EnsureHandle(prefixes.UTXO)
if err != nil {
return 0, 0, err
}
startKey := prefixes.UTXOKey{
Prefix: []byte{prefixes.UTXO},
HashX: hashX,
TxNum: 0,
Nout: 0,
}
endKey := prefixes.UTXOKey{
Prefix: []byte{prefixes.UTXO},
HashX: hashX,
TxNum: math.MaxUint32,
Nout: math.MaxUint16,
}
startKeyRaw := startKey.PackKey()
endKeyRaw := endKey.PackKey()
// Prefix and handle
options := NewIterateOptions().WithPrefix([]byte{prefixes.UTXO}).WithCfHandle(handle)
// Start and stop bounds
options = options.WithStart(startKeyRaw).WithStop(endKeyRaw).WithIncludeStop(true)
// Don't include the key
options = options.WithIncludeKey(false).WithIncludeValue(true)
ch := IterCF(db.DB, options)
var confirmed uint64 = 0
var unconfirmed uint64 = 0 // TODO
for kv := range ch {
confirmed += kv.Value.(*prefixes.UTXOValue).Amount
}
return confirmed, unconfirmed, nil
}
type TXOInfo struct {
TxHash *chainhash.Hash
TxPos uint16
Height uint32
Value uint64
}
func (db *ReadOnlyDBColumnFamily) GetUnspent(hashX []byte) ([]TXOInfo, error) {
startKey := &prefixes.UTXOKey{
Prefix: []byte{prefixes.UTXO},
HashX: hashX,
TxNum: 0,
Nout: 0,
}
endKey := &prefixes.UTXOKey{
Prefix: []byte{prefixes.UTXO},
HashX: hashX,
TxNum: math.MaxUint32,
Nout: math.MaxUint16,
}
selectedUTXO, err := db.selectFrom([]byte{prefixes.UTXO}, startKey, endKey)
if err != nil {
return nil, err
}
selectTxHashByTxNum := func(in []*prefixes.PrefixRowKV) ([]*IterOptions, error) {
historyKey := in[0].Key.(*prefixes.UTXOKey)
out := make([]*IterOptions, 0, 100)
startKey := &prefixes.TxHashKey{
Prefix: []byte{prefixes.TxHash},
TxNum: historyKey.TxNum,
}
endKey := &prefixes.TxHashKey{
Prefix: []byte{prefixes.TxHash},
TxNum: historyKey.TxNum,
}
selectedTxHash, err := db.selectFrom([]byte{prefixes.TxHash}, startKey, endKey)
if err != nil {
return nil, err
}
out = append(out, selectedTxHash...)
return out, nil
}
results := make([]TXOInfo, 0, 1000)
for kvs := range innerJoin(db.DB, iterate(db.DB, selectedUTXO), selectTxHashByTxNum) {
if err := checkForError(kvs); err != nil {
return results, err
}
utxoKey := kvs[0].Key.(*prefixes.UTXOKey)
utxoValue := kvs[0].Value.(*prefixes.UTXOValue)
txhashValue := kvs[1].Value.(*prefixes.TxHashValue)
results = append(results,
TXOInfo{
TxHash: txhashValue.TxHash,
TxPos: utxoKey.Nout,
Height: 0, // TODO
Value: utxoValue.Amount,
},
)
}
return results, nil
}
type TxInfo struct {
TxHash *chainhash.Hash
Height uint32
}
func (db *ReadOnlyDBColumnFamily) GetHistory(hashX []byte) ([]TxInfo, error) {
startKey := &prefixes.HashXHistoryKey{
Prefix: []byte{prefixes.HashXHistory},
HashX: hashX,
Height: 0,
}
endKey := &prefixes.HashXHistoryKey{
Prefix: []byte{prefixes.UTXO},
HashX: hashX,
Height: math.MaxUint32,
}
selectedHistory, err := db.selectFrom([]byte{prefixes.HashXHistory}, startKey, endKey)
if err != nil {
return nil, err
}
selectTxHashByTxNums := func(in []*prefixes.PrefixRowKV) ([]*IterOptions, error) {
historyValue := in[0].Value.(*prefixes.HashXHistoryValue)
out := make([]*IterOptions, 0, 100)
for _, txnum := range historyValue.TxNums {
startKey := &prefixes.TxHashKey{
Prefix: []byte{prefixes.TxHash},
TxNum: txnum,
}
endKey := &prefixes.TxHashKey{
Prefix: []byte{prefixes.TxHash},
TxNum: txnum,
}
selectedTxHash, err := db.selectFrom([]byte{prefixes.TxHash}, startKey, endKey)
if err != nil {
return nil, err
}
out = append(out, selectedTxHash...)
}
return out, nil
}
results := make([]TxInfo, 0, 1000)
for kvs := range innerJoin(db.DB, iterate(db.DB, selectedHistory), selectTxHashByTxNums) {
if err := checkForError(kvs); err != nil {
return results, err
}
historyKey := kvs[0].Key.(*prefixes.HashXHistoryKey)
txHashValue := kvs[1].Value.(*prefixes.TxHashValue)
results = append(results, TxInfo{
TxHash: txHashValue.TxHash,
Height: historyKey.Height,
})
}
return results, nil
}
// GetStreamsAndChannelRepostedByChannelHashes returns a map of streams and channel hashes that are reposted by the given channel hashes. // GetStreamsAndChannelRepostedByChannelHashes returns a map of streams and channel hashes that are reposted by the given channel hashes.
func (db *ReadOnlyDBColumnFamily) GetStreamsAndChannelRepostedByChannelHashes(reposterChannelHashes [][]byte) (map[string][]byte, map[string][]byte, error) { func (db *ReadOnlyDBColumnFamily) GetStreamsAndChannelRepostedByChannelHashes(reposterChannelHashes [][]byte) (map[string][]byte, map[string][]byte, error) {
handle, err := db.EnsureHandle(prefixes.ChannelToClaim) handle, err := db.EnsureHandle(prefixes.ChannelToClaim)

View file

@ -125,6 +125,7 @@ type PrefixRowKV struct {
Value BaseValue Value BaseValue
RawKey []byte RawKey []byte
RawValue []byte RawValue []byte
Error error
} }
type BaseKey interface { type BaseKey interface {

8
go.mod
View file

@ -10,6 +10,7 @@ require (
github.com/go-restruct/restruct v1.2.0-alpha github.com/go-restruct/restruct v1.2.0-alpha
github.com/gorilla/mux v1.7.3 github.com/gorilla/mux v1.7.3
github.com/gorilla/rpc v1.2.0 github.com/gorilla/rpc v1.2.0
github.com/lbryio/lbcutil v1.0.202
github.com/lbryio/lbry.go/v3 v3.0.1-beta github.com/lbryio/lbry.go/v3 v3.0.1-beta
github.com/linxGnu/grocksdb v1.6.42 github.com/linxGnu/grocksdb v1.6.42
github.com/olivere/elastic/v7 v7.0.24 github.com/olivere/elastic/v7 v7.0.24
@ -23,15 +24,17 @@ require (
gopkg.in/karalabe/cookiejar.v1 v1.0.0-20141109175019-e1490cae028c gopkg.in/karalabe/cookiejar.v1 v1.0.0-20141109175019-e1490cae028c
) )
require golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b // indirect require (
)
require ( require (
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang/protobuf v1.5.2 // indirect github.com/golang/protobuf v1.5.2 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/lbryio/lbcd v0.22.201-beta-rc1 github.com/lbryio/lbcd v0.22.201-beta-rc4
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
@ -39,6 +42,7 @@ require (
github.com/prometheus/common v0.26.0 // indirect github.com/prometheus/common v0.26.0 // indirect
github.com/prometheus/procfs v0.6.0 // indirect github.com/prometheus/procfs v0.6.0 // indirect
github.com/stretchr/testify v1.7.0 // indirect github.com/stretchr/testify v1.7.0 // indirect
golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b // indirect
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect

7
go.sum
View file

@ -63,6 +63,7 @@ github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJm
github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8= github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8=
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btcd v0.22.0-beta/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA= github.com/btcsuite/btcd v0.22.0-beta/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
@ -359,8 +360,12 @@ github.com/lbryio/lbcd v0.22.100-beta-rc5/go.mod h1:9PbFSlHYX7WlnDQwcTxHVf1W35VA
github.com/lbryio/lbcd v0.22.200-beta/go.mod h1:kNuzGWf808ipTGB0y0WogzsGv5BVM4Qv85Z+JYwC9FA= github.com/lbryio/lbcd v0.22.200-beta/go.mod h1:kNuzGWf808ipTGB0y0WogzsGv5BVM4Qv85Z+JYwC9FA=
github.com/lbryio/lbcd v0.22.201-beta-rc1 h1:FmzzApVj2RBXloLM2w9tLvN2xyTZjeyh+QC7GIw/wwo= github.com/lbryio/lbcd v0.22.201-beta-rc1 h1:FmzzApVj2RBXloLM2w9tLvN2xyTZjeyh+QC7GIw/wwo=
github.com/lbryio/lbcd v0.22.201-beta-rc1/go.mod h1:kNuzGWf808ipTGB0y0WogzsGv5BVM4Qv85Z+JYwC9FA= github.com/lbryio/lbcd v0.22.201-beta-rc1/go.mod h1:kNuzGWf808ipTGB0y0WogzsGv5BVM4Qv85Z+JYwC9FA=
github.com/lbryio/lbcd v0.22.201-beta-rc4 h1:Xh751Bh/GWRcP5bI6NJ2+zueo2otTcTWapFvFbryP5c=
github.com/lbryio/lbcd v0.22.201-beta-rc4/go.mod h1:Jgo48JDINhdOgHHR83J70Q6G42x3WAo9DI//QogcL+E=
github.com/lbryio/lbcutil v1.0.201/go.mod h1:gDHc/b+Rdz3J7+VB8e5/Bl9roVf8Q5/8FQCyuK9dXD0= github.com/lbryio/lbcutil v1.0.201/go.mod h1:gDHc/b+Rdz3J7+VB8e5/Bl9roVf8Q5/8FQCyuK9dXD0=
github.com/lbryio/lbcutil v1.0.202-rc3/go.mod h1:LGPtVBBzh4cFXfLFb8ginlFcbA2QwumLNFd0yk/as2o= github.com/lbryio/lbcutil v1.0.202-rc3/go.mod h1:LGPtVBBzh4cFXfLFb8ginlFcbA2QwumLNFd0yk/as2o=
github.com/lbryio/lbcutil v1.0.202 h1:L0aRMs2bdCUAicD8Xe4NmUEvevDDea3qkIpCSACnftI=
github.com/lbryio/lbcutil v1.0.202/go.mod h1:LGPtVBBzh4cFXfLFb8ginlFcbA2QwumLNFd0yk/as2o=
github.com/lbryio/lbry.go/v2 v2.7.1/go.mod h1:sUhhSKqPNkiwgBqvBzJIqfLLzGH8hkDGrrO/HcaXzFc= github.com/lbryio/lbry.go/v2 v2.7.1/go.mod h1:sUhhSKqPNkiwgBqvBzJIqfLLzGH8hkDGrrO/HcaXzFc=
github.com/lbryio/lbry.go/v3 v3.0.1-beta h1:oIpQ5czhtdVSoWZCiOHE9SrqnNsahyCnMhXvXsd2IiM= github.com/lbryio/lbry.go/v3 v3.0.1-beta h1:oIpQ5czhtdVSoWZCiOHE9SrqnNsahyCnMhXvXsd2IiM=
github.com/lbryio/lbry.go/v3 v3.0.1-beta/go.mod h1:v03OVXSBGNZNDfGoAVyjQV/ZOzBGQyTnWs3jpkssxGM= github.com/lbryio/lbry.go/v3 v3.0.1-beta/go.mod h1:v03OVXSBGNZNDfGoAVyjQV/ZOzBGQyTnWs3jpkssxGM=
@ -370,6 +375,8 @@ github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-b
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/linxGnu/grocksdb v1.6.42 h1:nJLoXFuzwBwQQQrXTUgRGRz1QRm7y8pR6CNV/gwrbqs= github.com/linxGnu/grocksdb v1.6.42 h1:nJLoXFuzwBwQQQrXTUgRGRz1QRm7y8pR6CNV/gwrbqs=
github.com/linxGnu/grocksdb v1.6.42/go.mod h1:JcMMDBFaDNhRXFYcYXmgQwb/RarSld1PulTI7UzE+w0= github.com/linxGnu/grocksdb v1.6.42/go.mod h1:JcMMDBFaDNhRXFYcYXmgQwb/RarSld1PulTI7UzE+w0=
github.com/linxGnu/grocksdb v1.7.0 h1:UyFDykX0CUfxDN10cqlFho/rwt9K6KoDaLXL9Ej5z9g=
github.com/linxGnu/grocksdb v1.7.0/go.mod h1:JcMMDBFaDNhRXFYcYXmgQwb/RarSld1PulTI7UzE+w0=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/lyoshenka/bencode v0.0.0-20180323155644-b7abd7672df5/go.mod h1:H0aPCWffGOaDcjkw1iB7W9DVLp6GXmfcJY/7YZCWPA4= github.com/lyoshenka/bencode v0.0.0-20180323155644-b7abd7672df5/go.mod h1:H0aPCWffGOaDcjkw1iB7W9DVLp6GXmfcJY/7YZCWPA4=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=

291
server/blockchain.go Normal file
View file

@ -0,0 +1,291 @@
package server
import (
"bytes"
"compress/zlib"
"crypto/sha256"
"encoding/base64"
"encoding/binary"
"encoding/hex"
"errors"
"github.com/lbryio/lbcd/chaincfg"
"github.com/lbryio/lbcd/txscript"
"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 = 112
const HASHX_SIZE = 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 {
return &blockGetHeaderResp{
Version: binary.LittleEndian.Uint32(header[0:]),
PrevBlockHash: hex.EncodeToString(header[4:46]),
MerkleRoot: hex.EncodeToString(header[36:68]),
ClaimTrieRoot: hex.EncodeToString(header[68:100]),
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 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_SIZE]
}
type addressGetBalanceReq struct {
Address string
}
type addressGetBalanceResp struct {
Confirmed uint64
Unconfirmed uint64
}
// 'blockchain.address.get_balance'
func (req *addressGetBalanceReq) Handle(s *Server) (*addressGetBalanceResp, error) {
address, err := lbcutil.DecodeAddress(req.Address, s.Coin)
if err != nil {
return nil, err
}
script, err := txscript.PayToAddrScript(address)
if err != nil {
return nil, err
}
hashX := hashXScript(script, s.Coin)
confirmed, unconfirmed, err := s.DB.GetBalance(hashX)
if err != nil {
return nil, err
}
return &addressGetBalanceResp{confirmed, unconfirmed}, err
}
type addressGetHistoryReq struct {
Address string
}
type TxInfo struct {
TxHash string
Height uint32
}
type TxInfoFee struct {
TxInfo
Fee uint64
}
type addressGetHistoryResp struct {
Confirmed []TxInfo
Unconfirmed []TxInfoFee
}
// 'blockchain.address.get_history'
func (req *addressGetHistoryReq) Handle(s *Server) (*addressGetHistoryResp, error) {
address, err := lbcutil.DecodeAddress(req.Address, s.Coin)
if err != nil {
return nil, err
}
script, err := txscript.PayToAddrScript(address)
if err != nil {
return nil, err
}
hashX := hashXScript(script, s.Coin)
dbTXs, err := s.DB.GetHistory(hashX)
confirmed := make([]TxInfo, 0, len(dbTXs))
for _, tx := range dbTXs {
confirmed = append(confirmed,
TxInfo{
TxHash: hex.EncodeToString(tx.TxHash[:]),
Height: tx.Height,
})
}
result := &addressGetHistoryResp{
Confirmed: confirmed,
Unconfirmed: []TxInfoFee{}, // TODO
}
return result, nil
}
type addressGetMempoolReq struct {
Address string
}
type addressGetMempoolResp []TxInfoFee
// 'blockchain.address.get_mempool'
func (req *addressGetMempoolReq) Handle(s *Server) (*addressGetMempoolResp, error) {
address, err := lbcutil.DecodeAddress(req.Address, s.Coin)
if err != nil {
return nil, err
}
script, err := txscript.PayToAddrScript(address)
if err != nil {
return nil, err
}
// TODO...
hashX := hashXScript(script, s.Coin)
dbTXs, err := s.DB.GetHistory(hashX)
confirmed := make([]TxInfo, 0, len(dbTXs))
for _, tx := range dbTXs {
confirmed = append(confirmed,
TxInfo{
TxHash: hex.EncodeToString(tx.TxHash[:]),
Height: tx.Height,
})
}
unconfirmed := make([]TxInfoFee, 0, 100)
result := addressGetMempoolResp(unconfirmed)
return &result, nil
}
type addressListUnspentReq struct {
Address string
}
type TXOInfo struct {
TxHash string
TxPos uint16
Height uint32
Value uint64
}
type addressListUnspentResp []TXOInfo
// 'blockchain.address.listunspent'
func (req *addressListUnspentReq) Handle(s *Server) (*addressListUnspentResp, error) {
address, err := lbcutil.DecodeAddress(req.Address, s.Coin)
if err != nil {
return nil, err
}
script, err := txscript.PayToAddrScript(address)
if err != nil {
return nil, err
}
hashX := hashXScript(script, s.Coin)
dbTXOs, err := s.DB.GetUnspent(hashX)
unspent := make([]TXOInfo, 0, len(dbTXOs))
for _, txo := range dbTXOs {
unspent = append(unspent,
TXOInfo{
TxHash: hex.EncodeToString(txo.TxHash[:]),
TxPos: txo.TxPos,
Height: txo.Height,
Value: txo.Value,
})
}
result := addressListUnspentResp(unspent)
return &result, nil
}

View file

@ -22,6 +22,7 @@ import (
"github.com/lbryio/herald.go/internal/metrics" "github.com/lbryio/herald.go/internal/metrics"
"github.com/lbryio/herald.go/meta" "github.com/lbryio/herald.go/meta"
pb "github.com/lbryio/herald.go/protobuf/go" pb "github.com/lbryio/herald.go/protobuf/go"
"github.com/lbryio/lbcd/chaincfg"
"github.com/olivere/elastic/v7" "github.com/olivere/elastic/v7"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/client_golang/prometheus/promhttp"
@ -38,6 +39,7 @@ type Server struct {
DB *db.ReadOnlyDBColumnFamily DB *db.ReadOnlyDBColumnFamily
EsClient *elastic.Client EsClient *elastic.Client
QueryCache *ttlcache.Cache QueryCache *ttlcache.Cache
Coin *chaincfg.Params
S256 *hash.Hash S256 *hash.Hash
LastRefreshCheck time.Time LastRefreshCheck time.Time
RefreshDelta time.Duration RefreshDelta time.Duration
@ -260,6 +262,7 @@ func MakeHubServer(ctx context.Context, args *Args) *Server {
EsClient: client, EsClient: client,
QueryCache: cache, QueryCache: cache,
S256: &s256, S256: &s256,
Coin: &chaincfg.MainNetParams,
LastRefreshCheck: time.Now(), LastRefreshCheck: time.Now(),
RefreshDelta: refreshDelta, RefreshDelta: refreshDelta,
NumESRefreshes: 0, NumESRefreshes: 0,