2017-08-28 09:50:57 -06:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2017-09-06 03:03:23 -06:00
|
|
|
"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 {
|
2017-09-06 03:03:23 -06:00
|
|
|
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 03:03:23 -06:00
|
|
|
|
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 03:03:23 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
}
|
2017-09-06 03:03:23 -06:00
|
|
|
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
|
2017-09-06 03:03:23 -06:00
|
|
|
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
|
|
|
}
|
|
|
|
}
|