Sync index on message from zeroMQ

pull/1/head
Martin Boehm 2018-01-31 15:03:06 +01:00
parent e22e1a946d
commit 71d669c0b9
2 changed files with 62 additions and 60 deletions

View File

@ -35,8 +35,10 @@ func New(binding string, callback func(*MQMessage)) (*MQ, error) {
} }
socket.SetSubscribe("hashblock") socket.SetSubscribe("hashblock")
socket.SetSubscribe("hashtx") socket.SetSubscribe("hashtx")
socket.SetSubscribe("rawblock") // for now do not use raw subscriptions - we would have to handle skipped/lost notifications from zeromq
socket.SetSubscribe("rawtx") // on each notification we do sync or syncmempool respectively
// socket.SetSubscribe("rawblock")
// socket.SetSubscribe("rawtx")
socket.Connect(binding) socket.Connect(binding)
glog.Info("MQ listening to ", binding) glog.Info("MQ listening to ", binding)
mq := &MQ{context, socket, true, make(chan bool)} mq := &MQ{context, socket, true, make(chan bool)}

View File

@ -18,23 +18,6 @@ import (
"github.com/pkg/profile" "github.com/pkg/profile"
) )
type Blockchain interface {
GetBestBlockHash() (string, error)
GetBestBlockHeight() (uint32, error)
GetBlockHash(height uint32) (string, error)
GetBlockHeader(hash string) (*bitcoin.BlockHeader, error)
GetBlock(hash string) (*bitcoin.Block, error)
}
type Index interface {
GetBestBlock() (uint32, string, error)
GetBlockHash(height uint32) (string, error)
GetTransactions(outputScript []byte, lower uint32, higher uint32, fn func(txid string) error) error
ConnectBlock(block *bitcoin.Block) error
DisconnectBlock(block *bitcoin.Block) error
DisconnectBlocks(lower uint32, higher uint32) error
}
var ( var (
rpcURL = flag.String("rpcurl", "http://localhost:8332", "url of bitcoin RPC service") rpcURL = flag.String("rpcurl", "http://localhost:8332", "url of bitcoin RPC service")
rpcUser = flag.String("rpcuser", "rpc", "rpc username") rpcUser = flag.String("rpcuser", "rpc", "rpc username")
@ -49,9 +32,9 @@ var (
queryAddress = flag.String("address", "", "query contents of this address") queryAddress = flag.String("address", "", "query contents of this address")
resync = flag.Bool("resync", false, "resync until tip") synchronize = flag.Bool("sync", false, "synchronizes until tip, if together with zeromq, keeps index synchronized")
repair = flag.Bool("repair", false, "repair the database") repair = flag.Bool("repair", false, "repair the database")
prof = flag.Bool("prof", false, "profile program execution") prof = flag.Bool("prof", false, "profile program execution")
syncChunk = flag.Int("chunk", 100, "block chunk size for processing") syncChunk = flag.Int("chunk", 100, "block chunk size for processing")
syncWorkers = flag.Int("workers", 8, "number of workers to process blocks") syncWorkers = flag.Int("workers", 8, "number of workers to process blocks")
@ -63,12 +46,20 @@ var (
zeroMQBinding = flag.String("zeromq", "", "binding to zeromq, if missing no zeromq connection") zeroMQBinding = flag.String("zeromq", "", "binding to zeromq, if missing no zeromq connection")
) )
var (
syncChannel = make(chan struct{})
chain *bitcoin.BitcoinRPC
index *db.RocksDB
)
func main() { func main() {
flag.Parse() flag.Parse()
// override setting for glog to log only to stderr, to match the http handler // override setting for glog to log only to stderr, to match the http handler
flag.Lookup("logtostderr").Value.Set("true") flag.Lookup("logtostderr").Value.Set("true")
defer glog.Flush()
if *prof { if *prof {
defer profile.Start().Stop() defer profile.Start().Stop()
} }
@ -80,33 +71,34 @@ func main() {
return return
} }
rpc := bitcoin.NewBitcoinRPC( chain = bitcoin.NewBitcoinRPC(
*rpcURL, *rpcURL,
*rpcUser, *rpcUser,
*rpcPass, *rpcPass,
time.Duration(*rpcTimeout)*time.Second) time.Duration(*rpcTimeout)*time.Second)
if *parse { if *parse {
rpc.Parser = &bitcoin.BitcoinBlockParser{ chain.Parser = &bitcoin.BitcoinBlockParser{
Params: bitcoin.GetChainParams()[0], Params: bitcoin.GetChainParams()[0],
} }
} }
db, err := db.NewRocksDB(*dbPath) var err error
index, err = db.NewRocksDB(*dbPath)
if err != nil { if err != nil {
glog.Fatalf("NewRocksDB %v", err) glog.Fatalf("NewRocksDB %v", err)
} }
defer db.Close() defer index.Close()
if *rollbackHeight >= 0 { if *rollbackHeight >= 0 {
bestHeight, _, err := db.GetBestBlock() bestHeight, _, err := index.GetBestBlock()
if err != nil { if err != nil {
glog.Fatalf("rollbackHeight: %v", err) glog.Fatalf("rollbackHeight: %v", err)
} }
if uint32(*rollbackHeight) > bestHeight { if uint32(*rollbackHeight) > bestHeight {
glog.Infof("nothing to rollback, rollbackHeight %d, bestHeight: %d", *rollbackHeight, bestHeight) glog.Infof("nothing to rollback, rollbackHeight %d, bestHeight: %d", *rollbackHeight, bestHeight)
} else { } else {
err = db.DisconnectBlocks(uint32(*rollbackHeight), bestHeight) err = index.DisconnectBlocks(uint32(*rollbackHeight), bestHeight)
if err != nil { if err != nil {
glog.Fatalf("rollbackHeight: %v", err) glog.Fatalf("rollbackHeight: %v", err)
} }
@ -114,31 +106,37 @@ func main() {
return return
} }
if *resync { if *synchronize {
if err := resyncIndex(rpc, db); err != nil { if err := resyncIndex(); err != nil {
glog.Fatal("resyncIndex ", err) glog.Fatal("resyncIndex ", err)
} }
} }
var httpServer *server.HttpServer var httpServer *server.HttpServer
if *httpServerBinding != "" { if *httpServerBinding != "" {
httpServer, err = server.New(*httpServerBinding, db) httpServer, err = server.New(*httpServerBinding, index)
if err != nil { if err != nil {
glog.Fatalf("https: %v", err) glog.Fatal("https: ", err)
} }
go func() { go func() {
err = httpServer.Run() err = httpServer.Run()
if err != nil { if err != nil {
glog.Fatalf("https: %v", err) glog.Fatal("https: ", err)
} }
}() }()
} }
var mq *bitcoin.MQ var mq *bitcoin.MQ
if *zeroMQBinding != "" { if *zeroMQBinding != "" {
mq, err = bitcoin.New(*zeroMQBinding, mqHandler) if !*synchronize {
if err != nil { glog.Error("zeromq connection without synchronization does not make sense, ignoring zeromq parameter")
glog.Fatalf("mq: %v", err) } else {
go syncLoop()
mq, err = bitcoin.New(*zeroMQBinding, mqHandler)
if err != nil {
glog.Fatal("mq: ", err)
}
} }
} }
@ -155,13 +153,11 @@ func main() {
if err != nil { if err != nil {
glog.Fatalf("GetTransactions %v", err) glog.Fatalf("GetTransactions %v", err)
} }
if err = db.GetTransactions(script, height, until, printResult); err != nil { if err = index.GetTransactions(script, height, until, printResult); err != nil {
glog.Fatalf("GetTransactions %v", err) glog.Fatalf("GetTransactions %v", err)
} }
} else if !*resync { } else if !*synchronize {
if err = connectBlocksParallel( if err = connectBlocksParallel(
rpc,
db,
height, height,
until, until,
*syncChunk, *syncChunk,
@ -172,14 +168,28 @@ func main() {
} }
} }
if httpServer != nil { if httpServer != nil || mq != nil {
waitForSignalAndShutdown(httpServer, mq, 5*time.Second) waitForSignalAndShutdown(httpServer, mq, 5*time.Second)
} }
}
func syncLoop() {
for range syncChannel {
resyncIndex()
}
} }
func mqHandler(m *bitcoin.MQMessage) { func mqHandler(m *bitcoin.MQMessage) {
body := hex.EncodeToString(m.Body) body := hex.EncodeToString(m.Body)
glog.Infof("MQ: %s-%d %s", m.Topic, m.Sequence, body) glog.Infof("MQ: %s-%d %s", m.Topic, m.Sequence, body)
if m.Topic == "hashblock" {
syncChannel <- struct{}{}
} else if m.Topic == "hashtx" {
} else {
glog.Errorf("MQ: unknown message %s-%d %s", m.Topic, m.Sequence, body)
}
} }
func waitForSignalAndShutdown(s *server.HttpServer, mq *bitcoin.MQ, timeout time.Duration) { func waitForSignalAndShutdown(s *server.HttpServer, mq *bitcoin.MQ, timeout time.Duration) {
@ -205,7 +215,8 @@ func waitForSignalAndShutdown(s *server.HttpServer, mq *bitcoin.MQ, timeout time
} }
} }
glog.Flush() close(syncChannel)
} }
func printResult(txid string) error { func printResult(txid string) error {
@ -213,7 +224,7 @@ func printResult(txid string) error {
return nil return nil
} }
func resyncIndex(chain Blockchain, index Index) error { func resyncIndex() error {
remote, err := chain.GetBestBlockHash() remote, err := chain.GetBestBlockHash()
if err != nil { if err != nil {
return err return err
@ -268,7 +279,7 @@ func resyncIndex(chain Blockchain, index Index) error {
if err != nil { if err != nil {
return err return err
} }
return resyncIndex(chain, index) return resyncIndex()
} }
} }
@ -301,8 +312,6 @@ func resyncIndex(chain Blockchain, index Index) error {
if chainBestHeight-startHeight > uint32(*syncChunk) { if chainBestHeight-startHeight > uint32(*syncChunk) {
glog.Infof("resync: parallel sync of blocks %d-%d", startHeight, chainBestHeight) glog.Infof("resync: parallel sync of blocks %d-%d", startHeight, chainBestHeight)
err = connectBlocksParallel( err = connectBlocksParallel(
chain,
index,
startHeight, startHeight,
chainBestHeight, chainBestHeight,
*syncChunk, *syncChunk,
@ -313,23 +322,21 @@ func resyncIndex(chain Blockchain, index Index) error {
} }
// after parallel load finish the sync using standard way, // after parallel load finish the sync using standard way,
// new blocks may have been created in the meantime // new blocks may have been created in the meantime
return resyncIndex(chain, index) return resyncIndex()
} }
} }
return connectBlocks(chain, index, hash) return connectBlocks(hash)
} }
func connectBlocks( func connectBlocks(
chain Blockchain,
index Index,
hash string, hash string,
) error { ) error {
bch := make(chan blockResult, 8) bch := make(chan blockResult, 8)
done := make(chan struct{}) done := make(chan struct{})
defer close(done) defer close(done)
go getBlockChain(hash, chain, bch, done) go getBlockChain(hash, bch, done)
var lastRes blockResult var lastRes blockResult
for res := range bch { for res := range bch {
@ -351,8 +358,6 @@ func connectBlocks(
} }
func connectBlocksParallel( func connectBlocksParallel(
chain Blockchain,
index Index,
lower uint32, lower uint32,
higher uint32, higher uint32,
chunkSize int, chunkSize int,
@ -371,7 +376,7 @@ func connectBlocksParallel(
if high > higher { if high > higher {
high = higher high = higher
} }
err := connectBlockChunk(chain, index, low, high) err := connectBlockChunk(low, high)
if err != nil { if err != nil {
if e, ok := err.(*bitcoin.RPCError); ok && (e.Message == "Block height out of range" || e.Message == "Block not found") { if e, ok := err.(*bitcoin.RPCError); ok && (e.Message == "Block height out of range" || e.Message == "Block not found") {
break break
@ -390,12 +395,10 @@ func connectBlocksParallel(
} }
func connectBlockChunk( func connectBlockChunk(
chain Blockchain,
index Index,
lower uint32, lower uint32,
higher uint32, higher uint32,
) error { ) error {
connected, err := isBlockConnected(chain, index, higher) connected, err := isBlockConnected(higher)
if err != nil || connected { if err != nil || connected {
// if higher is over the best block, continue with lower block, otherwise return error // if higher is over the best block, continue with lower block, otherwise return error
if e, ok := err.(*bitcoin.RPCError); !ok || e.Message != "Block height out of range" { if e, ok := err.(*bitcoin.RPCError); !ok || e.Message != "Block height out of range" {
@ -432,8 +435,6 @@ func connectBlockChunk(
} }
func isBlockConnected( func isBlockConnected(
chain Blockchain,
index Index,
height uint32, height uint32,
) (bool, error) { ) (bool, error) {
local, err := index.GetBlockHash(height) local, err := index.GetBlockHash(height)
@ -457,7 +458,6 @@ type blockResult struct {
func getBlockChain( func getBlockChain(
hash string, hash string,
chain Blockchain,
out chan blockResult, out chan blockResult,
done chan struct{}, done chan struct{},
) { ) {