Thread safe cache with separate ingestor

This commit is contained in:
Aditya Kulkarni
2019-09-25 21:15:32 -07:00
parent fbb75e8f20
commit a8cc2424a2
6 changed files with 193 additions and 131 deletions

View File

@@ -23,6 +23,8 @@ import (
var log *logrus.Entry var log *logrus.Entry
var logger = logrus.New() var logger = logrus.New()
var cacheSize = 10000
func init() { func init() {
logger.SetFormatter(&logrus.TextFormatter{ logger.SetFormatter(&logrus.TextFormatter{
//DisableColors: true, //DisableColors: true,
@@ -77,7 +79,6 @@ func loggerFromContext(ctx context.Context) *logrus.Entry {
type Options struct { type Options struct {
bindAddr string `json:"bind_address,omitempty"` bindAddr string `json:"bind_address,omitempty"`
dbPath string `json:"db_path"`
tlsCertPath string `json:"tls_cert_path,omitempty"` tlsCertPath string `json:"tls_cert_path,omitempty"`
tlsKeyPath string `json:"tls_cert_key,omitempty"` tlsKeyPath string `json:"tls_cert_key,omitempty"`
logLevel uint64 `json:"log_level,omitempty"` logLevel uint64 `json:"log_level,omitempty"`
@@ -88,7 +89,6 @@ type Options struct {
func main() { func main() {
opts := &Options{} opts := &Options{}
flag.StringVar(&opts.bindAddr, "bind-addr", "127.0.0.1:9067", "the address to listen on") flag.StringVar(&opts.bindAddr, "bind-addr", "127.0.0.1:9067", "the address to listen on")
flag.StringVar(&opts.dbPath, "db-path", "", "the path to a sqlite database file")
flag.StringVar(&opts.tlsCertPath, "tls-cert", "", "the path to a TLS certificate (optional)") flag.StringVar(&opts.tlsCertPath, "tls-cert", "", "the path to a TLS certificate (optional)")
flag.StringVar(&opts.tlsKeyPath, "tls-key", "", "the path to a TLS key file (optional)") flag.StringVar(&opts.tlsKeyPath, "tls-key", "", "the path to a TLS key file (optional)")
flag.Uint64Var(&opts.logLevel, "log-level", uint64(logrus.InfoLevel), "log level (logrus 1-7)") flag.Uint64Var(&opts.logLevel, "log-level", uint64(logrus.InfoLevel), "log level (logrus 1-7)")
@@ -98,7 +98,7 @@ func main() {
// TODO support config from file and env vars // TODO support config from file and env vars
flag.Parse() flag.Parse()
if opts.dbPath == "" || opts.zcashConfPath == "" { if opts.zcashConfPath == "" {
flag.Usage() flag.Usage()
os.Exit(1) os.Exit(1)
} }
@@ -161,7 +161,7 @@ func main() {
} }
// Get the sapling activation height from the RPC // Get the sapling activation height from the RPC
saplingHeight, chainName, branchID, err := common.GetSaplingInfo(rpcClient) saplingHeight, blockHeight, chainName, branchID, err := common.GetSaplingInfo(rpcClient)
if err != nil { if err != nil {
log.WithFields(logrus.Fields{ log.WithFields(logrus.Fields{
"error": err, "error": err,
@@ -170,12 +170,24 @@ func main() {
log.Info("Got sapling height ", saplingHeight, " chain ", chainName, " branchID ", branchID) log.Info("Got sapling height ", saplingHeight, " chain ", chainName, " branchID ", branchID)
// Initialize the cache
cache := common.NewBlockCache(cacheSize)
stopChan := make(chan bool, 1)
// Start the block cache importer at latestblock - 100k(cache size)
cacheStart := blockHeight - cacheSize
if cacheStart < saplingHeight {
cacheStart = saplingHeight
}
go common.BlockIngestor(rpcClient, cache, log, stopChan, cacheStart)
// Compact transaction service initialization // Compact transaction service initialization
service, err := frontend.NewSQLiteStreamer(rpcClient, log) service, err := frontend.NewSQLiteStreamer(rpcClient, cache, log)
if err != nil { if err != nil {
log.WithFields(logrus.Fields{ log.WithFields(logrus.Fields{
"db_path": opts.dbPath, "error": err,
"error": err,
}).Fatal("couldn't create SQL backend") }).Fatal("couldn't create SQL backend")
} }
defer service.(*frontend.SqlStreamer).GracefulStop() defer service.(*frontend.SqlStreamer).GracefulStop()
@@ -200,7 +212,10 @@ func main() {
log.WithFields(logrus.Fields{ log.WithFields(logrus.Fields{
"signal": s.String(), "signal": s.String(),
}).Info("caught signal, stopping gRPC server") }).Info("caught signal, stopping gRPC server")
// Stop the server
server.GracefulStop() server.GracefulStop()
// Stop the block ingestor
stopChan <- true
}() }()
log.Infof("Starting gRPC server on %s", opts.bindAddr) log.Infof("Starting gRPC server on %s", opts.bindAddr)

View File

@@ -2,6 +2,7 @@ package common
import ( import (
"bytes" "bytes"
"sync"
"github.com/adityapk00/lightwalletd/walletrpc" "github.com/adityapk00/lightwalletd/walletrpc"
"github.com/pkg/errors" "github.com/pkg/errors"
@@ -14,9 +15,11 @@ type BlockCache struct {
LastBlock int LastBlock int
m map[int]*walletrpc.CompactBlock m map[int]*walletrpc.CompactBlock
mutex sync.RWMutex
} }
func New(maxEntries int) *BlockCache { func NewBlockCache(maxEntries int) *BlockCache {
return &BlockCache{ return &BlockCache{
MaxEntries: maxEntries, MaxEntries: maxEntries,
FirstBlock: -1, FirstBlock: -1,
@@ -26,25 +29,18 @@ func New(maxEntries int) *BlockCache {
} }
func (c *BlockCache) Add(height int, block *walletrpc.CompactBlock) error { func (c *BlockCache) Add(height int, block *walletrpc.CompactBlock) error {
c.mutex.Lock()
defer c.mutex.Unlock()
//println("Cache add", height) //println("Cache add", height)
if c.FirstBlock == -1 && c.LastBlock == -1 { if c.FirstBlock == -1 && c.LastBlock == -1 {
// If this is the first block, prep the data structure // If this is the first block, prep the data structure
c.FirstBlock = height c.FirstBlock = height
c.LastBlock = height - 1 c.LastBlock = height - 1
} else if height >= c.FirstBlock && height <= c.LastBlock {
// Overwriting an existing entry. If so, then remove all
// subsequent blocks, since this might be a reorg
for i := height; i <= c.LastBlock; i++ {
//println("Deleteing at height", i)
delete(c.m, i)
}
c.LastBlock = height - 1
}
if height != c.LastBlock+1 {
return errors.New("Blocks need to be added sequentially")
} }
// Don't allow out-of-order blocks. This is more of a sanity check than anything
// If there is a reorg, then the ingestor needs to handle it.
if c.m[height-1] != nil && !bytes.Equal(block.PrevHash, c.m[height-1].Hash) { if c.m[height-1] != nil && !bytes.Equal(block.PrevHash, c.m[height-1].Hash) {
return errors.New("Prev hash of the block didn't match") return errors.New("Prev hash of the block didn't match")
} }
@@ -66,6 +62,9 @@ func (c *BlockCache) Add(height int, block *walletrpc.CompactBlock) error {
} }
func (c *BlockCache) Get(height int) *walletrpc.CompactBlock { func (c *BlockCache) Get(height int) *walletrpc.CompactBlock {
c.mutex.RLock()
defer c.mutex.RUnlock()
//println("Cache get", height) //println("Cache get", height)
if c.LastBlock == -1 || c.FirstBlock == -1 { if c.LastBlock == -1 || c.FirstBlock == -1 {
return nil return nil
@@ -79,3 +78,10 @@ func (c *BlockCache) Get(height int) *walletrpc.CompactBlock {
//println("Cache returned") //println("Cache returned")
return c.m[height] return c.m[height]
} }
func (c *BlockCache) GetLatestBlock() int {
c.mutex.RLock()
defer c.mutex.RUnlock()
return c.LastBlock
}

View File

@@ -1,19 +1,21 @@
package common package common
import ( import (
"bytes"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"fmt"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/adityapk00/lightwalletd/parser" "github.com/adityapk00/lightwalletd/parser"
"github.com/adityapk00/lightwalletd/walletrpc" "github.com/adityapk00/lightwalletd/walletrpc"
"github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcd/rpcclient"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus"
) )
func GetSaplingInfo(rpcClient *rpcclient.Client) (int, string, string, error) { func GetSaplingInfo(rpcClient *rpcclient.Client) (int, int, string, string, error) {
result, rpcErr := rpcClient.RawRequest("getblockchaininfo", make([]json.RawMessage, 0)) result, rpcErr := rpcClient.RawRequest("getblockchaininfo", make([]json.RawMessage, 0))
var err error var err error
@@ -25,15 +27,15 @@ func GetSaplingInfo(rpcClient *rpcclient.Client) (int, string, string, error) {
errCode, err = strconv.ParseInt(errParts[0], 10, 32) errCode, err = strconv.ParseInt(errParts[0], 10, 32)
//Check to see if we are requesting a height the zcashd doesn't have yet //Check to see if we are requesting a height the zcashd doesn't have yet
if err == nil && errCode == -8 { if err == nil && errCode == -8 {
return -1, "", "", nil return -1, -1, "", "", nil
} }
return -1, "", "", errors.Wrap(rpcErr, "error requesting block") return -1, -1, "", "", errors.Wrap(rpcErr, "error requesting block")
} }
var f interface{} var f interface{}
err = json.Unmarshal(result, &f) err = json.Unmarshal(result, &f)
if err != nil { if err != nil {
return -1, "", "", errors.Wrap(err, "error reading JSON response") return -1, -1, "", "", errors.Wrap(err, "error reading JSON response")
} }
chainName := f.(map[string]interface{})["chain"].(string) chainName := f.(map[string]interface{})["chain"].(string)
@@ -42,10 +44,12 @@ func GetSaplingInfo(rpcClient *rpcclient.Client) (int, string, string, error) {
saplingJSON := upgradeJSON.(map[string]interface{})["76b809bb"] // Sapling ID saplingJSON := upgradeJSON.(map[string]interface{})["76b809bb"] // Sapling ID
saplingHeight := saplingJSON.(map[string]interface{})["activationheight"].(float64) saplingHeight := saplingJSON.(map[string]interface{})["activationheight"].(float64)
blockHeight := f.(map[string]interface{})["headers"].(float64)
consensus := f.(map[string]interface{})["consensus"] consensus := f.(map[string]interface{})["consensus"]
branchID := consensus.(map[string]interface{})["nextblock"].(string) branchID := consensus.(map[string]interface{})["nextblock"].(string)
return int(saplingHeight), chainName, branchID, nil return int(saplingHeight), int(blockHeight), chainName, branchID, nil
} }
func getBlockFromRPC(rpcClient *rpcclient.Client, height int) (*walletrpc.CompactBlock, error) { func getBlockFromRPC(rpcClient *rpcclient.Client, height int) (*walletrpc.CompactBlock, error) {
@@ -91,6 +95,78 @@ func getBlockFromRPC(rpcClient *rpcclient.Client, height int) (*walletrpc.Compac
return block.ToCompact(), nil return block.ToCompact(), nil
} }
func BlockIngestor(rpcClient *rpcclient.Client, cache *BlockCache, log *logrus.Entry,
stopChan chan bool, startHeight int) {
reorgCount := -1
height := startHeight
timeoutCount := 0
hash := ""
phash := ""
// Start listening for new blocks
for {
select {
case <-stopChan:
break
case <-time.After(15 * time.Second):
for {
if reorgCount > 0 {
reorgCount = -1
height -= 10
}
block, err := getBlockFromRPC(rpcClient, height)
if err != nil {
log.WithFields(logrus.Fields{
"height": height,
"error": err,
}).Warn("error with getblock")
timeoutCount++
if timeoutCount == 3 {
log.WithFields(logrus.Fields{
"timeouts": timeoutCount,
}).Warn("unable to issue RPC call to zcashd node 3 times")
break
}
}
if block != nil {
log.Info("Ingestor adding block to cache: ", height)
cache.Add(height, block)
if timeoutCount > 0 {
timeoutCount--
}
phash = hex.EncodeToString(block.PrevHash)
//check for reorgs once we have inital block hash from startup
if hash != phash && reorgCount != -1 {
reorgCount++
log.WithFields(logrus.Fields{
"height": height,
"hash(reversed)": hash,
"phash(reversed)": phash,
"reorg": reorgCount,
}).Warn("REORG")
} else {
hash = hex.EncodeToString(block.Hash)
}
if reorgCount == -1 {
hash = hex.EncodeToString(block.Hash)
reorgCount = 0
}
height++
} else {
break
}
}
}
}
}
func GetBlock(rpcClient *rpcclient.Client, cache *BlockCache, height int) (*walletrpc.CompactBlock, error) { func GetBlock(rpcClient *rpcclient.Client, cache *BlockCache, height int) (*walletrpc.CompactBlock, error) {
// First, check the cache to see if we have the block // First, check the cache to see if we have the block
block := cache.Get(height) block := cache.Get(height)
@@ -98,51 +174,19 @@ func GetBlock(rpcClient *rpcclient.Client, cache *BlockCache, height int) (*wall
return block, nil return block, nil
} }
// If a block was not found, make sure user is requesting a historical block
if height > cache.GetLatestBlock() {
return nil, errors.New(
fmt.Sprintf(
"Block requested is newer than latest block. Requested: %d Latest: %d",
height, cache.GetLatestBlock()))
}
block, err := getBlockFromRPC(rpcClient, height) block, err := getBlockFromRPC(rpcClient, height)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Store the block in the cache, but test for reorg first
prevBlock := cache.Get(height - 1)
if prevBlock != nil {
if !bytes.Equal(prevBlock.Hash, block.PrevHash) {
// Reorg!
reorgCount := 0
cacheBlock := cache.Get(height - reorgCount)
rpcBlocks := []*walletrpc.CompactBlock{}
for ; reorgCount <= 100 &&
cacheBlock != nil &&
!bytes.Equal(block.PrevHash, cacheBlock.Hash); reorgCount++ {
block, err = getBlockFromRPC(rpcClient, height-reorgCount-1)
if err != nil {
return nil, err
}
_ = append(rpcBlocks, block)
cacheBlock = cache.Get(height - reorgCount - 2)
}
if reorgCount == 100 {
return nil, errors.New("Max reorg depth exceeded")
}
// At this point, the block.prevHash == cache.hash
// Store all blocks starting with 'block'
for i := len(rpcBlocks) - 1; i >= 0; i-- {
cache.Add(int(rpcBlocks[i].Height), rpcBlocks[i])
}
}
}
cache.Add(height, block)
return block, nil return block, nil
} }

View File

@@ -27,39 +27,25 @@ type SqlStreamer struct {
log *logrus.Entry log *logrus.Entry
} }
func NewSQLiteStreamer(client *rpcclient.Client, log *logrus.Entry) (walletrpc.CompactTxStreamerServer, error) { func NewSQLiteStreamer(client *rpcclient.Client, cache *common.BlockCache, log *logrus.Entry) (walletrpc.CompactTxStreamerServer, error) {
return &SqlStreamer{common.New(100000), client, log}, nil return &SqlStreamer{cache, client, log}, nil
} }
func (s *SqlStreamer) GracefulStop() error { func (s *SqlStreamer) GracefulStop() error {
return nil return nil
} }
func (s *SqlStreamer) GetCache() *common.BlockCache {
return s.cache
}
func (s *SqlStreamer) GetLatestBlock(ctx context.Context, placeholder *walletrpc.ChainSpec) (*walletrpc.BlockID, error) { func (s *SqlStreamer) GetLatestBlock(ctx context.Context, placeholder *walletrpc.ChainSpec) (*walletrpc.BlockID, error) {
result, rpcErr := s.client.RawRequest("getinfo", make([]json.RawMessage, 0)) latestBlock := s.cache.GetLatestBlock()
var err error if latestBlock == -1 {
var errCode int64 return nil, errors.New("Cache is empty. Server is probably not yet ready.")
// For some reason, the error responses are not JSON
if rpcErr != nil {
errParts := strings.SplitN(rpcErr.Error(), ":", 2)
errCode, err = strconv.ParseInt(errParts[0], 10, 32)
//Check to see if we are requesting a height the zcashd doesn't have yet
if err == nil && errCode == -8 {
return nil, errors.New("Don't have the requested block")
}
return nil, err
} }
var f interface{}
err = json.Unmarshal(result, &f)
if err != nil {
return nil, err
}
latestBlock := f.(map[string]interface{})["blocks"].(float64)
// TODO: also return block hashes here // TODO: also return block hashes here
return &walletrpc.BlockID{Height: uint64(latestBlock)}, nil return &walletrpc.BlockID{Height: uint64(latestBlock)}, nil
} }
@@ -243,7 +229,7 @@ func (s *SqlStreamer) GetTransaction(ctx context.Context, txf *walletrpc.TxFilte
// GetLightdInfo gets the LightWalletD (this server) info // GetLightdInfo gets the LightWalletD (this server) info
func (s *SqlStreamer) GetLightdInfo(ctx context.Context, in *walletrpc.Empty) (*walletrpc.LightdInfo, error) { func (s *SqlStreamer) GetLightdInfo(ctx context.Context, in *walletrpc.Empty) (*walletrpc.LightdInfo, error) {
saplingHeight, chainName, consensusBranchId, err := common.GetSaplingInfo(s.client) saplingHeight, blockHeight, chainName, consensusBranchId, err := common.GetSaplingInfo(s.client)
if err != nil { if err != nil {
s.log.WithFields(logrus.Fields{ s.log.WithFields(logrus.Fields{
@@ -261,6 +247,7 @@ func (s *SqlStreamer) GetLightdInfo(ctx context.Context, in *walletrpc.Empty) (*
ChainName: chainName, ChainName: chainName,
SaplingActivationHeight: uint64(saplingHeight), SaplingActivationHeight: uint64(saplingHeight),
ConsensusBranchId: consensusBranchId, ConsensusBranchId: consensusBranchId,
BlockHeight: uint64(blockHeight),
}, nil }, nil
} }

View File

@@ -346,6 +346,7 @@ type LightdInfo struct {
ChainName string `protobuf:"bytes,4,opt,name=chainName,proto3" json:"chainName,omitempty"` ChainName string `protobuf:"bytes,4,opt,name=chainName,proto3" json:"chainName,omitempty"`
SaplingActivationHeight uint64 `protobuf:"varint,5,opt,name=saplingActivationHeight,proto3" json:"saplingActivationHeight,omitempty"` SaplingActivationHeight uint64 `protobuf:"varint,5,opt,name=saplingActivationHeight,proto3" json:"saplingActivationHeight,omitempty"`
ConsensusBranchId string `protobuf:"bytes,6,opt,name=consensusBranchId,proto3" json:"consensusBranchId,omitempty"` ConsensusBranchId string `protobuf:"bytes,6,opt,name=consensusBranchId,proto3" json:"consensusBranchId,omitempty"`
BlockHeight uint64 `protobuf:"varint,7,opt,name=blockHeight,proto3" json:"blockHeight,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"` XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"` XXX_sizecache int32 `json:"-"`
@@ -418,6 +419,13 @@ func (m *LightdInfo) GetConsensusBranchId() string {
return "" return ""
} }
func (m *LightdInfo) GetBlockHeight() uint64 {
if m != nil {
return m.BlockHeight
}
return 0
}
type TransparentAddress struct { type TransparentAddress struct {
Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
@@ -520,47 +528,48 @@ func init() {
func init() { proto.RegisterFile("service.proto", fileDescriptor_a0b84a42fa06f626) } func init() { proto.RegisterFile("service.proto", fileDescriptor_a0b84a42fa06f626) }
var fileDescriptor_a0b84a42fa06f626 = []byte{ var fileDescriptor_a0b84a42fa06f626 = []byte{
// 630 bytes of a gzipped FileDescriptorProto // 643 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x55, 0x5d, 0x4f, 0x13, 0x41, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x55, 0xdd, 0x6e, 0x13, 0x3d,
0x14, 0xa5, 0xd0, 0xed, 0xc7, 0x6d, 0x81, 0x30, 0x11, 0x6d, 0x1a, 0x54, 0x1c, 0x63, 0xe2, 0x83, 0x10, 0xed, 0x4f, 0xb6, 0x69, 0x26, 0xfd, 0x51, 0xad, 0xaf, 0x1f, 0x51, 0x54, 0xa0, 0x18, 0x21,
0xd9, 0x10, 0xc4, 0xe8, 0x83, 0x2f, 0xb4, 0x7e, 0x35, 0x41, 0xa3, 0xd3, 0x3e, 0xe1, 0x03, 0x19, 0x71, 0x81, 0x56, 0x55, 0x29, 0x82, 0x0b, 0x6e, 0xda, 0xf2, 0x57, 0xa9, 0x20, 0x70, 0x72, 0x55,
0x76, 0x87, 0xee, 0x4a, 0xbb, 0xb3, 0x99, 0x19, 0x4a, 0xf5, 0xc7, 0xf8, 0xfb, 0xfc, 0x19, 0xce, 0x2e, 0x2a, 0x77, 0xd7, 0xcd, 0x2e, 0x4d, 0xd6, 0x2b, 0xdb, 0x4d, 0x03, 0x8f, 0xc0, 0x43, 0xf0,
0xc7, 0x96, 0x6e, 0x03, 0x4b, 0xfb, 0xb6, 0x77, 0xe6, 0xde, 0x73, 0xce, 0x3d, 0x73, 0x6f, 0x0b, 0xac, 0xd8, 0xe3, 0x4d, 0xb3, 0x51, 0xd9, 0x26, 0x77, 0x3b, 0xe3, 0x99, 0x73, 0x8e, 0x8f, 0x67,
0x9b, 0x92, 0x89, 0x49, 0x1c, 0x30, 0x3f, 0x15, 0x5c, 0x71, 0xb4, 0x1b, 0x50, 0x19, 0xf9, 0x7f, 0x12, 0x58, 0xd7, 0x42, 0x0d, 0xd3, 0x48, 0x84, 0xb9, 0x92, 0x46, 0x92, 0xed, 0x88, 0xeb, 0x24,
0xfc, 0x6b, 0x3a, 0x1a, 0x31, 0xe5, 0xcb, 0xf0, 0xd2, 0x17, 0x69, 0xd0, 0xde, 0x0d, 0xf8, 0x38, 0xfc, 0x15, 0xde, 0xf0, 0x7e, 0x5f, 0x98, 0x50, 0xc7, 0x57, 0xa1, 0xca, 0xa3, 0xf6, 0x76, 0x24,
0xa5, 0x81, 0x3a, 0xbb, 0xe0, 0x62, 0x4c, 0x95, 0x74, 0xd9, 0xf8, 0x0d, 0x54, 0x3b, 0x23, 0x1e, 0x07, 0x39, 0x8f, 0xcc, 0xf9, 0xa5, 0x54, 0x03, 0x6e, 0xb4, 0xaf, 0xa6, 0xaf, 0xa0, 0x7e, 0xd4,
0x5c, 0xf6, 0x3e, 0xa0, 0x87, 0x50, 0x89, 0x58, 0x3c, 0x8c, 0x54, 0xab, 0xb4, 0x5f, 0x7a, 0x59, 0x97, 0xd1, 0xd5, 0xc9, 0x3b, 0xf2, 0x3f, 0xac, 0x24, 0x22, 0xed, 0x25, 0xa6, 0xb5, 0xb8, 0xbb,
0x26, 0x59, 0x84, 0x10, 0x94, 0x23, 0x0d, 0xd9, 0x5a, 0xd7, 0xa7, 0x4d, 0x62, 0xbf, 0xb1, 0x02, 0xf8, 0xbc, 0xc6, 0x8a, 0x88, 0x10, 0xa8, 0x25, 0x16, 0xb2, 0xb5, 0x64, 0xb3, 0x6b, 0x0c, 0xbf,
0xb0, 0x65, 0x84, 0x26, 0x43, 0x86, 0x8e, 0xc0, 0x93, 0x8a, 0x0a, 0x57, 0xd8, 0x38, 0x7c, 0xe2, 0xa9, 0x01, 0xc0, 0x36, 0xc6, 0xb3, 0x9e, 0x20, 0x07, 0x10, 0x68, 0xc3, 0x95, 0x6f, 0x6c, 0xee,
0xdf, 0x29, 0xc1, 0xcf, 0x88, 0x88, 0x4b, 0x46, 0x07, 0xb0, 0xc1, 0x92, 0xd0, 0xc2, 0x2e, 0xaf, 0x3f, 0x0a, 0xff, 0x29, 0x21, 0x2c, 0x88, 0x98, 0x2f, 0x26, 0x7b, 0xb0, 0x2c, 0xb2, 0x18, 0x61,
0x31, 0xa9, 0xf8, 0x17, 0xd4, 0x06, 0xd3, 0x4f, 0xf1, 0x48, 0x31, 0x61, 0x38, 0xcf, 0xcd, 0xdd, 0x67, 0xf7, 0xb8, 0x52, 0xfa, 0x03, 0x56, 0xbb, 0xa3, 0x0f, 0x69, 0xdf, 0x08, 0xe5, 0x38, 0x2f,
0xaa, 0x9c, 0x36, 0x19, 0x3d, 0x00, 0x2f, 0x4e, 0x42, 0x36, 0xb5, 0xac, 0x65, 0xe2, 0x82, 0x9b, 0xdc, 0xd9, 0xbc, 0x9c, 0x58, 0x4c, 0xfe, 0x83, 0x20, 0xcd, 0x62, 0x31, 0x42, 0xd6, 0x1a, 0xf3,
0x0e, 0x37, 0x72, 0x1d, 0xbe, 0x87, 0x2d, 0x42, 0xaf, 0x07, 0x82, 0x26, 0x52, 0xbb, 0x16, 0xf3, 0xc1, 0xed, 0x0d, 0x97, 0x4b, 0x37, 0x7c, 0x0b, 0x1b, 0x8c, 0xdf, 0x74, 0x15, 0xcf, 0xb4, 0x75,
0xc4, 0x64, 0x85, 0x54, 0x51, 0x4b, 0xa8, 0xb3, 0xcc, 0x77, 0xce, 0xb3, 0xf5, 0xbc, 0x67, 0xf8, 0x2d, 0x95, 0x99, 0xab, 0x8a, 0xb9, 0xe1, 0x48, 0x68, 0xab, 0xdc, 0x77, 0xc9, 0xb3, 0xa5, 0xb2,
0x3b, 0x34, 0xfb, 0x5a, 0x31, 0x61, 0x32, 0xe5, 0x89, 0x64, 0x68, 0x0f, 0xea, 0x4c, 0x08, 0x2e, 0x67, 0xf4, 0x2b, 0xac, 0x75, 0xac, 0x62, 0x26, 0x74, 0x2e, 0x33, 0x2d, 0xc8, 0x0e, 0x34, 0x84,
0xba, 0x3c, 0x64, 0x16, 0xc0, 0x23, 0xf3, 0x03, 0x84, 0xa1, 0x69, 0x83, 0xaf, 0x4c, 0x4a, 0x3a, 0x52, 0x52, 0x1d, 0xcb, 0x58, 0x20, 0x40, 0xc0, 0x26, 0x09, 0x42, 0x61, 0x0d, 0x83, 0xcf, 0x42,
0x64, 0x16, 0xab, 0x4e, 0x16, 0xce, 0x70, 0x03, 0xea, 0xdd, 0x88, 0xc6, 0x49, 0x3f, 0x65, 0x01, 0x6b, 0xde, 0x13, 0x88, 0xd5, 0x60, 0x53, 0x39, 0xda, 0x84, 0xc6, 0x71, 0xc2, 0xd3, 0xac, 0x93,
0xae, 0x82, 0xf7, 0x71, 0x9c, 0xaa, 0xdf, 0xf8, 0x5f, 0x09, 0xe0, 0xc4, 0x30, 0x86, 0xbd, 0xe4, 0x8b, 0x88, 0xd6, 0x21, 0x78, 0x3f, 0xc8, 0xcd, 0x4f, 0xfa, 0x7b, 0x09, 0xe0, 0xd4, 0x31, 0xc6,
0x82, 0xa3, 0x16, 0x54, 0x27, 0x4c, 0x48, 0xad, 0xd6, 0x92, 0xd4, 0xc9, 0x2c, 0x34, 0x42, 0x27, 0x27, 0xd9, 0xa5, 0x24, 0x2d, 0xa8, 0x0f, 0x85, 0xd2, 0x56, 0x2d, 0x92, 0x34, 0xd8, 0x38, 0x74,
0x5a, 0x10, 0x17, 0x19, 0x78, 0x16, 0x19, 0x6a, 0x45, 0xc3, 0x50, 0xf4, 0xaf, 0xd2, 0x94, 0xeb, 0x42, 0x87, 0x56, 0x90, 0x54, 0x05, 0x78, 0x11, 0x39, 0x6a, 0xc3, 0xe3, 0x58, 0x75, 0xae, 0xf3,
0x17, 0x34, 0x16, 0xd4, 0xc8, 0xc2, 0x99, 0x11, 0x1f, 0x18, 0xea, 0x6f, 0x74, 0xcc, 0x5a, 0x65, 0x5c, 0xda, 0x17, 0x74, 0x16, 0xac, 0xb2, 0xa9, 0x9c, 0x13, 0x1f, 0x39, 0xea, 0x2f, 0x7c, 0x20,
0x5b, 0x3e, 0x3f, 0x40, 0xef, 0xe0, 0x91, 0xa4, 0xe9, 0x28, 0x4e, 0x86, 0xc7, 0xda, 0xa7, 0x09, 0x5a, 0x35, 0x6c, 0x9f, 0x24, 0xc8, 0x1b, 0x78, 0xa0, 0x79, 0xde, 0x4f, 0xb3, 0xde, 0xa1, 0xf5,
0x35, 0x5e, 0x7d, 0x71, 0x9e, 0x78, 0xd6, 0x93, 0xa2, 0x6b, 0xf4, 0x0a, 0x76, 0x02, 0xe3, 0x4e, 0x69, 0xc8, 0x9d, 0x57, 0x9f, 0xbc, 0x27, 0x01, 0x7a, 0x52, 0x75, 0x4c, 0x5e, 0xc0, 0x56, 0xe4,
0x22, 0xaf, 0x64, 0x47, 0x1b, 0x1d, 0x44, 0xbd, 0xb0, 0x55, 0xb1, 0xf8, 0xb7, 0x2f, 0xb0, 0x0f, 0xdc, 0xc9, 0xf4, 0xb5, 0x3e, 0xb2, 0x46, 0x47, 0xc9, 0x49, 0xdc, 0x5a, 0x41, 0xfc, 0xbb, 0x07,
0xc8, 0xbe, 0x46, 0x4a, 0x05, 0x4b, 0xd4, 0xb1, 0xd6, 0xa7, 0x9d, 0x31, 0x1d, 0x53, 0xf7, 0x39, 0x64, 0x17, 0x9a, 0xf8, 0x86, 0x05, 0x76, 0x1d, 0xb1, 0xcb, 0x29, 0x1a, 0x02, 0xc1, 0xf7, 0xca,
0xeb, 0x38, 0x0b, 0xb1, 0x80, 0xc7, 0xb7, 0xf3, 0xed, 0x38, 0x64, 0x13, 0x54, 0x58, 0x8a, 0xde, 0xb9, 0x12, 0x99, 0x39, 0xb4, 0x37, 0xb0, 0xde, 0x39, 0x4f, 0xb8, 0xff, 0x1c, 0x7b, 0x52, 0x84,
0x82, 0x27, 0xcc, 0x60, 0x67, 0xb3, 0xf9, 0xec, 0xbe, 0xd9, 0xb2, 0x1b, 0x40, 0x5c, 0xfe, 0xe1, 0x54, 0xc1, 0xc3, 0xbb, 0xf5, 0x38, 0x30, 0xc5, 0x8c, 0x55, 0xb6, 0x92, 0xd7, 0x10, 0x28, 0x37,
0x5f, 0x0f, 0x76, 0xba, 0x6e, 0xcf, 0x06, 0xd3, 0xbe, 0x12, 0x4c, 0x1b, 0x24, 0xd0, 0x00, 0xb6, 0xfa, 0xc5, 0xf4, 0x3e, 0xb9, 0x6f, 0xfa, 0x70, 0x47, 0x98, 0xaf, 0xdf, 0xff, 0x13, 0xc0, 0xd6,
0x3e, 0x33, 0x75, 0x42, 0x15, 0x93, 0xca, 0xd6, 0xa0, 0xfd, 0x02, 0xc4, 0x9b, 0x17, 0x6e, 0x2f, 0xb1, 0xdf, 0xc4, 0xee, 0xa8, 0x63, 0x94, 0xb0, 0x16, 0x2a, 0xd2, 0x85, 0x8d, 0x8f, 0xc2, 0x9c,
0x99, 0x67, 0xbc, 0x86, 0x7e, 0x40, 0x4d, 0xa3, 0x3a, 0xbc, 0x25, 0xd9, 0xed, 0xe7, 0x45, 0x7c, 0x72, 0x23, 0xb4, 0xc1, 0x1e, 0xb2, 0x5b, 0x81, 0x78, 0x3b, 0x03, 0xed, 0x19, 0x13, 0x4f, 0x17,
0x4e, 0xab, 0x4d, 0xd3, 0x90, 0x3f, 0x61, 0x73, 0x06, 0xe9, 0x16, 0x7b, 0x79, 0xe7, 0x2b, 0x42, 0xc8, 0x37, 0x58, 0xb5, 0xa8, 0x1e, 0x6f, 0x46, 0x75, 0xfb, 0x69, 0x15, 0x9f, 0xd7, 0x8a, 0x65,
0x1f, 0x94, 0xd0, 0xa9, 0x75, 0x21, 0xbf, 0x50, 0x4f, 0x0b, 0x4a, 0x67, 0x3b, 0xde, 0x7e, 0x51, 0x16, 0xf2, 0x3b, 0xac, 0x8f, 0x21, 0xfd, 0xea, 0xcf, 0xbe, 0xf9, 0x9c, 0xd0, 0x7b, 0x8b, 0xe4,
0x90, 0xb0, 0xb8, 0x98, 0x5a, 0xf8, 0x19, 0x6c, 0x9b, 0x75, 0xcb, 0x83, 0xaf, 0x56, 0x5b, 0x28, 0x0c, 0x5d, 0x28, 0xaf, 0xdc, 0xe3, 0x8a, 0xd6, 0xf1, 0xaf, 0x40, 0xfb, 0x59, 0x45, 0xc1, 0xf4,
0x3f, 0xbf, 0xbd, 0x9a, 0x40, 0xc0, 0xb6, 0x16, 0x9f, 0x0d, 0xd1, 0x60, 0x1a, 0x87, 0x12, 0x1d, 0xea, 0x5a, 0xe1, 0xe7, 0xb0, 0xe9, 0x16, 0xb2, 0x0c, 0x3e, 0x5f, 0x6f, 0xa5, 0xfc, 0xf2, 0x7e,
0x15, 0xa9, 0xbf, 0x6f, 0xe8, 0x56, 0x6e, 0x49, 0x1b, 0x46, 0xec, 0x6b, 0xe4, 0xb6, 0x7b, 0xaf, 0x5b, 0x02, 0x05, 0x9b, 0x56, 0x7c, 0x31, 0x44, 0xdd, 0x51, 0x1a, 0x6b, 0x72, 0x50, 0xa5, 0xfe,
0xa0, 0xd6, 0xfe, 0x14, 0xb4, 0x8b, 0xde, 0x6a, 0x0e, 0x80, 0xd7, 0x3a, 0x8d, 0xd3, 0xba, 0xbb, 0xbe, 0xa1, 0x9b, 0xfb, 0x4a, 0xd6, 0x30, 0x86, 0xaf, 0x51, 0xda, 0xff, 0x9d, 0x8a, 0x5e, 0xfc,
0xd6, 0x37, 0xe7, 0x15, 0xfb, 0x17, 0xf0, 0xfa, 0x7f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x84, 0x2f, 0xb1, 0x68, 0x57, 0xbd, 0xd5, 0x04, 0x80, 0x2e, 0x1c, 0x35, 0xcf, 0x1a, 0xfe, 0xd8, 0x9e, 0x5c,
0xbf, 0xf7, 0x41, 0x06, 0x00, 0x00, 0xac, 0xe0, 0x9f, 0xc4, 0xcb, 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x75, 0x7c, 0x9a, 0xee, 0x63,
0x06, 0x00, 0x00,
} }
// Reference imports to suppress errors if they are not otherwise used. // Reference imports to suppress errors if they are not otherwise used.

View File

@@ -51,6 +51,7 @@ message LightdInfo {
string chainName = 4; string chainName = 4;
uint64 saplingActivationHeight = 5; uint64 saplingActivationHeight = 5;
string consensusBranchId = 6; // This should really be u32 or []byte, but string for readability string consensusBranchId = 6; // This should really be u32 or []byte, but string for readability
uint64 blockHeight = 7;
} }
message TransparentAddress { message TransparentAddress {