blockbook/blockbook.go

361 lines
7.2 KiB
Go
Raw Normal View History

2017-08-28 09:50:57 -06:00
package main
import (
"flag"
"log"
"time"
)
type BlockParser interface {
ParseBlock(b []byte) (*Block, error)
}
2017-09-06 07:36:55 -06:00
type Blocks interface {
2017-09-06 02:59:40 -06:00
GetBestBlockHash() (string, error)
2017-08-28 09:50:57 -06:00
GetBlockHash(height uint32) (string, error)
GetBlockHeader(hash string) (*BlockHeader, error)
2017-09-06 02:59:40 -06:00
GetBlock(hash string) (*Block, error)
2017-08-28 09:50:57 -06:00
}
2017-09-06 07:36:55 -06:00
type Outpoints interface {
// GetAddress looks up a transaction output and returns its address.
2017-09-12 16:53:53 -06:00
// Address can be empty string in case it's not found or not
// intelligable.
GetAddress(txid string, vout uint32) (string, error)
2017-08-28 09:50:57 -06:00
}
2017-09-06 07:36:55 -06:00
type Addresses interface {
GetTransactions(address string, lower uint32, higher uint32, fn func(txids []string) error) error
2017-08-28 09:50:57 -06:00
}
2017-09-06 07:36:55 -06:00
type Indexer interface {
2017-09-06 03:02:38 -06:00
ConnectBlock(block *Block, txids map[string][]string) error
DisconnectBlock(block *Block, txids map[string][]string) error
GetLastBlockHash() (string, error)
}
func (b *Block) GetAllAddresses(outpoints Outpoints) (map[string][]string, error) {
addrs := make(map[string][]string, 0) // Address to a list of txids.
2017-09-12 16:36:08 -06:00
for i, _ := range b.Txs {
tx := &b.Txs[i]
ta, err := b.GetTxAddresses(outpoints, tx)
if err != nil {
return nil, err
}
for a, _ := range ta {
addrs[a] = append(addrs[a], tx.Txid)
}
}
return addrs, nil
}
func (b *Block) GetTxAddresses(outpoints Outpoints, tx *Tx) (map[string]struct{}, error) {
addrs := make(map[string]struct{}) // Only unique values.
// Process outputs.
for _, o := range tx.Vout {
a := o.GetAddress()
if a != "" {
addrs[a] = struct{}{}
}
}
// Process inputs. For each input, we need to take a look to the
// outpoint index.
for _, i := range tx.Vin {
if i.Coinbase != "" {
continue
}
// Lookup output in in the outpoint index. In case it's not
// found, take a look in this block.
a, err := outpoints.GetAddress(i.Txid, i.Vout)
if err != nil {
return nil, err
}
2017-09-12 16:53:53 -06:00
if a == "" {
a = b.GetAddress(i.Txid, i.Vout)
}
if a != "" {
addrs[a] = struct{}{}
2017-09-12 16:53:53 -06:00
} else {
log.Printf("warn: output not found: %s:%d", i.Txid, i.Vout)
}
}
return addrs, nil
}
2017-09-12 16:53:53 -06:00
func (b *Block) GetAddress(txid string, vout uint32) string {
2017-09-12 16:36:08 -06:00
for i, _ := range b.Txs {
if b.Txs[i].Txid == txid {
2017-09-12 16:53:53 -06:00
return b.Txs[i].GetAddress(vout)
}
}
2017-09-12 16:53:53 -06:00
return "" // tx not found
}
2017-09-12 16:53:53 -06:00
func (t *Tx) GetAddress(vout uint32) string {
if vout < uint32(len(t.Vout)) {
return t.Vout[vout].GetAddress()
}
2017-09-12 16:53:53 -06:00
return "" // output not found
}
func (o *Vout) GetAddress() string {
2017-09-12 16:53:53 -06:00
if len(o.ScriptPubKey.Addresses) == 1 {
return o.ScriptPubKey.Addresses[0]
}
2017-09-12 16:53:53 -06:00
return "" // output address not intelligible
2017-08-28 09:50:57 -06:00
}
var (
rpcURL = flag.String("rpcurl", "http://localhost:8332", "url of bitcoin RPC service")
rpcUser = flag.String("rpcuser", "rpc", "rpc username")
rpcPass = flag.String("rpcpass", "rpc", "rpc password")
rpcTimeout = flag.Uint("rpctimeout", 25, "rpc timeout in seconds")
dbPath = flag.String("path", "./data", "path to address index directory")
blockHeight = flag.Int("blockheight", -1, "height of the starting block")
blockUntil = flag.Int("blockuntil", -1, "height of the final block")
queryAddress = flag.String("address", "", "query contents of this address")
resync = flag.Bool("resync", false, "resync until tip")
2017-09-12 08:53:40 -06:00
repair = flag.Bool("repair", false, "repair the database")
2017-08-28 09:50:57 -06:00
)
func main() {
flag.Parse()
2017-09-12 08:53:40 -06:00
if *repair {
if err := RepairRocksDB(*dbPath); err != nil {
log.Fatal(err)
}
return
}
2017-08-28 09:50:57 -06:00
timeout := time.Duration(*rpcTimeout) * time.Second
rpc := NewBitcoinRPC(*rpcURL, *rpcUser, *rpcPass, timeout)
db, err := NewRocksDB(*dbPath)
if err != nil {
log.Fatal(err)
}
defer db.Close()
2017-09-12 16:36:08 -06:00
if *resync {
if err := resyncIndex(rpc, db, db); err != nil {
log.Fatal(err)
}
}
2017-08-28 09:50:57 -06:00
if *blockHeight >= 0 {
if *blockUntil < 0 {
*blockUntil = *blockHeight
}
height := uint32(*blockHeight)
until := uint32(*blockUntil)
address := *queryAddress
if address != "" {
2017-09-06 07:36:55 -06:00
if err = db.GetTransactions(address, height, until, printResult); err != nil {
2017-08-28 09:50:57 -06:00
log.Fatal(err)
}
} else {
if err = connectBlockRange(rpc, db, db, height, until); err != nil {
2017-08-28 09:50:57 -06:00
log.Fatal(err)
}
}
}
}
func printResult(txids []string) error {
for i, txid := range txids {
log.Printf("%d: %s", i, txid)
}
return nil
}
func resyncIndex(
blocks Blocks,
outpoints Outpoints,
index Indexer,
) error {
best, err := blocks.GetBestBlockHash()
if err != nil {
return err
}
last, err := index.GetLastBlockHash()
if err != nil {
last = ""
}
2017-08-28 09:50:57 -06:00
// If the local block is missing, we're indexing from the genesis block.
if last == "" {
log.Printf("resync: genesis")
hash, err := blocks.GetBlockHash(0)
2017-08-28 09:50:57 -06:00
if err != nil {
return err
2017-08-28 09:50:57 -06:00
}
return connectBlock(blocks, outpoints, index, hash)
2017-08-28 09:50:57 -06:00
}
// If the locally indexed block is the same as the best block on the
// network, we're done.
if last == best {
log.Printf("resync: synced on %s", last)
return nil
}
// Is local tip on the best chain?
header, err := blocks.GetBlockHeader(last)
forked := false
if err != nil {
if e, ok := err.(*RPCError); ok && e.Message == "Block not found" {
forked = true
} else {
return err
}
} else {
if header.Confirmations < 0 {
forked = true
}
}
if forked {
log.Printf("resync: local is forked")
// TODO: resync after disconnecting
return disconnectBlock(blocks, outpoints, index, header.Hash)
} else {
log.Printf("resync: local is behind")
return connectBlock(blocks, outpoints, index, header.Next)
}
}
2017-08-28 09:50:57 -06:00
func connectBlock(
blocks Blocks,
outpoints Outpoints,
index Indexer,
hash string,
) error {
2017-09-11 07:06:16 -06:00
bch := make(chan blockResult, 8)
done := make(chan struct{})
defer close(done)
go getBlockChain(hash, blocks, bch, done)
for res := range bch {
err := res.err
block := res.block
if err != nil {
return err
2017-08-28 09:50:57 -06:00
}
addrs, err := block.GetAllAddresses(outpoints)
2017-08-28 09:50:57 -06:00
if err != nil {
return err
2017-08-28 09:50:57 -06:00
}
if err := index.ConnectBlock(block, addrs); err != nil {
return err
2017-08-28 09:50:57 -06:00
}
2017-09-06 07:36:55 -06:00
}
return nil
2017-08-28 09:50:57 -06:00
}
func disconnectBlock(
blocks Blocks,
outpoints Outpoints,
index Indexer,
hash string,
) error {
return nil
2017-09-06 07:36:55 -06:00
}
func connectBlockRange(
2017-09-06 07:36:55 -06:00
blocks Blocks,
outpoints Outpoints,
index Indexer,
2017-09-04 06:16:37 -06:00
lower uint32,
higher uint32,
) error {
bch := make(chan blockResult, 3)
go getBlockRange(lower, higher, blocks, bch)
2017-09-04 06:16:37 -06:00
for res := range bch {
if res.err != nil {
return res.err
}
2017-09-06 07:36:55 -06:00
addrs, err := res.block.GetAllAddresses(outpoints)
2017-08-28 09:50:57 -06:00
if err != nil {
return err
}
if err := index.ConnectBlock(res.block, addrs); err != nil {
2017-08-28 09:50:57 -06:00
return err
}
2017-09-04 06:16:37 -06:00
}
return nil
}
type blockResult struct {
block *Block
err error
}
func getBlockRange(
lower uint32,
higher uint32,
blocks Blocks,
results chan<- blockResult,
) {
2017-09-04 06:16:37 -06:00
defer close(results)
2017-09-06 02:59:40 -06:00
height := lower
hash, err := blocks.GetBlockHash(height)
if err != nil {
results <- blockResult{err: err}
return
}
2017-09-06 02:59:40 -06:00
for height <= higher {
block, err := blocks.GetBlock(hash)
2017-09-04 06:16:37 -06:00
if err != nil {
results <- blockResult{err: err}
return
2017-08-28 09:50:57 -06:00
}
2017-09-06 02:59:40 -06:00
hash = block.Next
height = block.Height + 1
2017-09-04 06:16:37 -06:00
results <- blockResult{block: block}
2017-08-28 09:50:57 -06:00
}
}
2017-09-11 07:06:16 -06:00
func getBlockChain(
hash string,
blocks Blocks,
out chan blockResult,
done chan struct{},
) {
defer close(out)
for hash != "" {
select {
case <-done:
return
default:
}
block, err := blocks.GetBlock(hash)
if err != nil {
out <- blockResult{err: err}
return
}
hash = block.Next
out <- blockResult{block: block}
}
}