201 lines
4.4 KiB
Go
201 lines
4.4 KiB
Go
package main
|
|
|
|
import (
|
|
"flag"
|
|
"log"
|
|
"time"
|
|
)
|
|
|
|
type BlockParser interface {
|
|
ParseBlock(b []byte) (*Block, error)
|
|
}
|
|
|
|
type BlockOracle interface {
|
|
GetBestBlockHash() (string, error)
|
|
GetBlockHash(height uint32) (string, error)
|
|
GetBlock(hash string) (*Block, error)
|
|
}
|
|
|
|
type OutpointAddressOracle interface {
|
|
GetOutpointAddresses(txid string, vout uint32) ([]string, error)
|
|
}
|
|
|
|
type AddressTransactionOracle interface {
|
|
GetAddressTransactions(address string, lower uint32, higher uint32, fn func(txids []string) error) error
|
|
}
|
|
|
|
type BlockIndex interface {
|
|
ConnectBlock(block *Block, txids map[string][]string) error
|
|
DisconnectBlock(block *Block, txids map[string][]string) error
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
if *chain != "" {
|
|
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 != "" {
|
|
if err = db.GetAddressTransactions(address, height, until, printResult); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
} else {
|
|
if err = indexBlocks(rpc, rpc, db, height, until); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func printResult(txids []string) error {
|
|
for i, txid := range txids {
|
|
log.Printf("%d: %s", i, txid)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (b *Block) CollectBlockAddresses(o OutpointAddressOracle) (map[string][]string, error) {
|
|
addrs := make(map[string][]string, 0)
|
|
|
|
for _, tx := range b.Txs {
|
|
voutAddrs, err := tx.CollectAddresses(o)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, addr := range voutAddrs {
|
|
addrs[addr] = append(addrs[addr], tx.Txid)
|
|
}
|
|
}
|
|
|
|
return addrs, nil
|
|
}
|
|
|
|
func (tx *Tx) CollectAddresses(o OutpointAddressOracle) ([]string, error) {
|
|
addrs := make([]string, 0)
|
|
seen := make(map[string]struct{})
|
|
|
|
for _, vout := range tx.Vout {
|
|
for _, addr := range vout.ScriptPubKey.Addresses {
|
|
if _, found := seen[addr]; !found {
|
|
addrs = append(addrs, addr)
|
|
seen[addr] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, vin := range tx.Vin {
|
|
if vin.Coinbase != "" {
|
|
continue
|
|
}
|
|
vinAddrs, err := o.GetOutpointAddresses(vin.Txid, vin.Vout)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, addr := range vinAddrs {
|
|
if _, found := seen[addr]; !found {
|
|
addrs = append(addrs, addr)
|
|
seen[addr] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
|
|
return addrs, nil
|
|
}
|
|
|
|
func indexBlocks(
|
|
blocks BlockOracle,
|
|
outpoints OutpointAddressOracle,
|
|
index BlockIndex,
|
|
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
|
|
}
|
|
addrs, err := res.block.CollectBlockAddresses(outpoints)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := index.IndexBlock(res.block, addrs); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type blockResult struct {
|
|
block *Block
|
|
err error
|
|
}
|
|
|
|
func getBlocks(lower uint32, higher uint32, blocks BlockOracle, results chan<- blockResult) {
|
|
defer close(results)
|
|
|
|
height := lower
|
|
hash, err := blocks.GetBlockHash(height)
|
|
if err != nil {
|
|
results <- blockResult{err: err}
|
|
return
|
|
}
|
|
|
|
for height <= higher {
|
|
block, err := blocks.GetBlock(hash)
|
|
if err != nil {
|
|
results <- blockResult{err: err}
|
|
return
|
|
}
|
|
hash = block.Next
|
|
height = block.Height + 1
|
|
results <- blockResult{block: block}
|
|
}
|
|
}
|