blockbook/blockbook.go

226 lines
4.8 KiB
Go
Raw Normal View History

2017-08-28 09:50:57 -06:00
package main
import (
"errors"
2017-08-28 09:50:57 -06:00
"flag"
"log"
"time"
)
type BlockParser interface {
ParseBlock(b []byte) (*Block, error)
}
2017-09-06 07:36:55 -06:00
var (
ErrTxNotFound = errors.New("transaction not found")
)
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)
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 {
GetAddresses(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
2017-08-28 09:50:57 -06:00
}
var (
chain = flag.String("chain", "mainnet", "none | mainnet | regtest | testnet3 | simnet")
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")
rpcCache = flag.Int("rpccache", 50000, "number to tx replies to cache")
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")
)
func main() {
flag.Parse()
timeout := time.Duration(*rpcTimeout) * time.Second
rpc := NewBitcoinRPC(*rpcURL, *rpcUser, *rpcPass, timeout)
if *rpcCache > 0 {
rpc.EnableCache(*rpcCache)
}
2017-09-06 03:02:49 -06:00
if *chain != "none" {
2017-08-28 09:50:57 -06:00
for _, p := range GetChainParams() {
if p.Name == *chain {
rpc.Parser = &BitcoinBlockParser{Params: p}
}
}
if rpc.Parser == nil {
log.Fatal("unknown chain")
}
}
db, err := NewRocksDB(*dbPath)
if err != nil {
log.Fatal(err)
}
defer db.Close()
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 = indexBlocks(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
}
2017-09-06 07:36:55 -06:00
func (b *Block) GetAllAddresses(outpoints Outpoints) (map[string][]string, error) {
2017-08-28 09:50:57 -06:00
addrs := make(map[string][]string, 0)
for _, tx := range b.Txs {
2017-09-06 07:36:55 -06:00
ta, err := b.GetTxAddresses(outpoints, tx)
2017-08-28 09:50:57 -06:00
if err != nil {
return nil, err
}
2017-09-06 07:36:55 -06:00
for _, addr := range ta {
2017-08-28 09:50:57 -06:00
addrs[addr] = append(addrs[addr], tx.Txid)
}
}
return addrs, nil
}
2017-09-06 07:36:55 -06:00
func (b *Block) GetTxAddresses(outpoints Outpoints, tx *Tx) ([]string, error) {
seen := make(map[string]struct{}) // Only unique values.
2017-09-06 07:36:55 -06:00
// Process outputs.
for _, o := range tx.Vout {
for _, a := range o.ScriptPubKey.Addresses {
seen[a] = struct{}{}
}
}
2017-09-06 07:36:55 -06:00
// Process inputs. For each input, we need to take a look to the
// outpoint index.
for _, i := range tx.Vin {
if i.Coinbase != "" {
continue
2017-08-28 09:50:57 -06:00
}
2017-09-06 07:36:55 -06:00
// Lookup output in in the outpoint index. In case it's not
// found, take a look in this block.
va, err := outpoints.GetAddresses(i.Txid, i.Vout)
if err == ErrTxNotFound {
va, err = b.GetAddresses(i.Txid, i.Vout)
2017-08-28 09:50:57 -06:00
}
if err != nil {
return nil, err
}
2017-09-06 07:36:55 -06:00
for _, a := range va {
seen[a] = struct{}{}
2017-08-28 09:50:57 -06:00
}
}
2017-09-06 07:36:55 -06:00
// Convert the result set into a slice.
addrs := make([]string, len(seen))
i := 0
for a := range seen {
addrs[i] = a
i++
}
2017-08-28 09:50:57 -06:00
return addrs, nil
}
2017-09-06 07:36:55 -06:00
func (b *Block) GetAddresses(txid string, vout uint32) ([]string, error) {
// TODO: Lookup transaction in constant time.
for _, tx := range b.Txs {
if tx.Txid == txid {
return tx.Vout[vout].ScriptPubKey.Addresses, nil
}
}
return nil, ErrTxNotFound
}
2017-09-04 06:16:37 -06:00
func indexBlocks(
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 getBlocks(lower, higher, blocks, bch)
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
}
2017-09-06 07:36:55 -06:00
func getBlocks(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
}
}