dcrd/cmd/checkdevpremine/main.go
Dave Collins fcaaa94d38 Introduce a new utility to show dev premine taint.
This introduces a new utility named checkdevpremine which can be used to
test transactions to determine whether or not they have inputs that
trace back to the original dev premine coins.
2016-04-04 22:00:19 -05:00

277 lines
8.3 KiB
Go

// Copyright (c) 2016 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package main
import (
"bufio"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/decred/dcrd/blockchain"
"github.com/decred/dcrd/blockchain/stake"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/wire"
"github.com/decred/dcrrpcclient"
)
// Codes that are returned to the operating system.
const (
rcNoDevPremineInputs = 0
rcDevPremineInputs = 1
rcError = 2
)
const (
// devCoinMaxIndex is the final index in the block 1 premine transaction
// that involves the original developer premine coins. All coins after
// this index are part of the airdrop.
devCoinMaxIndex = 173
)
var (
// premineTxHash is the hash of the transaction in block one which
// creates the premine coins.
premineTxHash = newHashFromStr("5e29cdb355b3fc7e76c98a9983cd44324b3efdd7815c866e33f6c72292cb8be6")
)
// newHashFromStr converts the passed big-endian hex string into a
// chainhash.Hash. It only differs from the one available in chainhash in that
// it panics on an error since it will only (and must only) be called with
// hard-coded, and therefore known good, hashes.
func newHashFromStr(hexStr string) *chainhash.Hash {
hash, err := chainhash.NewHashFromStr(hexStr)
if err != nil {
panic(err)
}
return hash
}
// usage displays the general usage when the help flag is not displayed and
// and an invalid command was specified. The commandUsage function is used
// instead when a valid command was specified.
func usage(errorMessage string) {
appName := filepath.Base(os.Args[0])
appName = strings.TrimSuffix(appName, filepath.Ext(appName))
fmt.Fprintln(os.Stderr, errorMessage)
fmt.Fprintln(os.Stderr, "Usage:")
fmt.Fprintf(os.Stderr, " %s [OPTIONS] <txhash or JSON_array_of_txhashes>\n\n",
appName)
fmt.Fprintln(os.Stderr, "Specify -h to show available options")
}
// isDevPremineOut return whether or not the provided outpoint is one of the
// original dev premine coins.
func isDevPremineOut(out wire.OutPoint) bool {
return out.Hash.IsEqual(premineTxHash) && out.Index <= devCoinMaxIndex &&
out.Tree == 0
}
// traceDevPremineOuts returns a list of outpoints that are part of the dev
// premine coins and are ancestors of the inputs to the passed transaction hash.
func traceDevPremineOuts(client *dcrrpcclient.Client, txHash *chainhash.Hash) ([]wire.OutPoint, error) {
// Trace the lineage of all inputs to the provided transaction back to
// the coinbase outputs that generated them and add those outpoints to
// a list. Also, keep track of all of the processed transactions in
// order to avoid processing duplicates.
knownCoinbases := make(map[chainhash.Hash]struct{})
processedHashes := make(map[chainhash.Hash]struct{})
coinbaseOuts := make([]wire.OutPoint, 0, 10)
processOuts := []wire.OutPoint{{Hash: *txHash}}
for len(processOuts) > 0 {
// Grab the first outpoint to process and skip it if it has
// already been traced.
outpoint := processOuts[0]
processOuts = processOuts[1:]
if _, exists := processedHashes[outpoint.Hash]; exists {
if _, exists := knownCoinbases[outpoint.Hash]; exists {
coinbaseOuts = append(coinbaseOuts, outpoint)
}
continue
}
processedHashes[outpoint.Hash] = struct{}{}
// Request the transaction for the outpoint from the server.
tx, err := client.GetRawTransaction(&outpoint.Hash)
if err != nil {
return nil, fmt.Errorf("failed to get transaction %v: %v",
&outpoint.Hash, err)
}
// Add the outpoint to the coinbase outputs list when it is part
// of a coinbase transaction. Also, keep track of the fact the
// transaction is a coinbase to use when avoiding duplicate
// checks.
if blockchain.IsCoinBase(tx) {
knownCoinbases[outpoint.Hash] = struct{}{}
coinbaseOuts = append(coinbaseOuts, outpoint)
continue
}
// Add the inputs to the transaction to the list of transactions
// to load and continue tracing.
//
// However, skip the first input to stake generation txns since
// they are creating new coins. The remaining inputs to a
// stake generation transaction still need to be traced since
// they represent the coins that purchased the ticket.
txIns := tx.MsgTx().TxIn
isSSGen, _ := stake.IsSSGen(tx)
if isSSGen {
txIns = txIns[1:]
}
for _, txIn := range txIns {
processOuts = append(processOuts, txIn.PreviousOutPoint)
}
}
// Add any of the outputs that are dev premine outputs to a list.
var devPremineOuts []wire.OutPoint
for _, coinbaseOut := range coinbaseOuts {
if isDevPremineOut(coinbaseOut) {
devPremineOuts = append(devPremineOuts, coinbaseOut)
}
}
return devPremineOuts, nil
}
// realMain is the real main function for the utility. It is necessary to work
// around the fact that deferred functions do not run when os.Exit() is called.
func realMain() int {
// Load configuration and parse command line.
cfg, args, err := loadConfig()
if err != nil {
return rcError
}
// Ensure the user specified a single argument.
if len(args) < 1 {
usage("Transaction hash not specified")
return rcError
}
if len(args) > 1 {
usage("Too many arguments specified")
return rcError
}
// Read the argument from a stdin pipe when it is '-'.
arg0 := args[0]
if arg0 == "-" {
bio := bufio.NewReader(os.Stdin)
param, err := bio.ReadString('\n')
if err != nil && err != io.EOF {
fmt.Fprintf(os.Stderr, "Failed to read data from "+
"stdin: %v\n", err)
return rcError
}
if err == io.EOF && len(param) == 0 {
fmt.Fprintln(os.Stderr, "Not enough lines provided on "+
"stdin")
return rcError
}
arg0 = param
}
arg0 = strings.TrimSpace(arg0)
// Attempt to unmarshal the parameter as a JSON array of strings if it
// looks like JSON input. This allows multiple transactions to be
// specified via the argument. Treat the argument as a single hash if
// it fails to unmarshal.
var txHashes []*chainhash.Hash
if strings.Contains(arg0, "[") && strings.Contains(arg0, "]") {
var txHashStrs []string
if err := json.Unmarshal([]byte(arg0), &txHashStrs); err != nil {
fmt.Fprintf(os.Stderr, "Failed to unmarshal JSON "+
"string array of transaction hashes: %v\n", err)
return rcError
}
for _, txHashStr := range txHashStrs {
txHash, err := chainhash.NewHashFromStr(txHashStr)
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to parse "+
"transaction hash %q: %v\n", txHashStr, err)
return rcError
}
txHashes = append(txHashes, txHash)
}
} else {
// Parse the provided transaction hash string.
arg0 = strings.Trim(arg0, `"`)
txHash, err := chainhash.NewHashFromStr(arg0)
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to parse transaction "+
"hash %q: %v\n", arg0, err)
return rcError
}
txHashes = append(txHashes, txHash)
}
// Connect to dcrd RPC server using websockets.
certs, err := ioutil.ReadFile(cfg.RPCCert)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to read RPC server TLS cert: %v\n",
err)
return rcError
}
connCfg := &dcrrpcclient.ConnConfig{
Host: cfg.RPCServer,
Endpoint: "ws",
User: cfg.RPCUser,
Pass: cfg.RPCPassword,
Certificates: certs,
}
client, err := dcrrpcclient.New(connCfg, nil)
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to connect to dcrd RPC server: "+
"%v\n", err)
return rcError
}
defer client.Shutdown()
// Check all of the provided transactions.
var hasDevPremineOuts bool
for _, txHash := range txHashes {
// Get a list of all dev premine outpoints the are ancestors of
// all inputs to the provided transaction.
devPremineOuts, err := traceDevPremineOuts(client, txHash)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return rcError
}
// List outputs which are dev premine outputs.
if len(devPremineOuts) > 0 {
hasDevPremineOuts = true
// Don't print anything in quiet mode.
if cfg.Quiet {
continue
}
fmt.Printf("Transaction %v contains inputs which "+
"trace back to the following original dev "+
"premine outpoints:\n", txHash)
for _, out := range devPremineOuts {
fmt.Println(out)
}
}
}
// Return the approriate code depending on whether or not any of the
// inputs trace back to a dev premine outpoint.
if hasDevPremineOuts {
return rcDevPremineInputs
}
return rcNoDevPremineInputs
}
func main() {
os.Exit(realMain())
}