mirror of
https://github.com/FlipsideCrypto/dcrd.git
synced 2026-02-06 19:06:51 +00:00
checkdevpremine: Remove utility.
This tool is no longer necessary now that the agreed upon period of time has passed.
This commit is contained in:
parent
c6f9474348
commit
7647b2594d
@ -1,75 +0,0 @@
|
||||
checkdevpremine
|
||||
===============
|
||||
|
||||
The checkdevpremine utility allows transactions to be tested whether or not they
|
||||
have inputs that trace back to the original dev premine coins.
|
||||
|
||||
It works by using the dcrd RPC server to request all of the relevant input
|
||||
transactions all the way back to the coinbase output that orignated the coins.
|
||||
Those coinbase outpoints are then compiled into a list and compared against the
|
||||
original dev premine outpoints. This is also known as checking for taint.
|
||||
|
||||
The utility only accepts one parameter which can either be a single transaction
|
||||
hash or a JSON-array of transaction hashes in order to facilite checking
|
||||
multiple at once. That parameter may also be a single dash, `-`, in order to
|
||||
indicate it should be read from stdin.
|
||||
|
||||
Any outpoints which are found to be part of the original dev premine will be
|
||||
listed unless the `--quiet` flag is provided to suppress the output.
|
||||
|
||||
In addition, in order to facilitate programmatic access the tool returns the
|
||||
following codes to the Operating System:
|
||||
|
||||
|Return Code|Description|
|
||||
|---|---|
|
||||
|0|The transaction(s) do _NOT_ have any inputs which trace back to the dev premine coins|
|
||||
|1|One of more of the transactions _DO_ have at least one input which traces back to the dev premine coins|
|
||||
|2|Some type of error such as inability to talk to the RPC server occurred|
|
||||
|
||||
These codes in addition with the `--quiet` flag allow a fully automated check
|
||||
with no visible output.
|
||||
|
||||
## Configuring
|
||||
|
||||
In order to connect and authenticate to the dcrd RPC server, the `--rpcuser` and
|
||||
`--rpcpass` options must be specified. These can be placed into a config file
|
||||
named `checkdevpremine.conf` at the location shown in the help output of the
|
||||
utility (`checkdevpremine -h`).
|
||||
|
||||
Config file example:
|
||||
|
||||
```
|
||||
rpcuser=your_dcrd_RPC_server_username
|
||||
rpcpass=your_dcrd_RPC_server_password
|
||||
```
|
||||
|
||||
## Example Usage
|
||||
|
||||
Checking a single transaction with visible output:
|
||||
```bash
|
||||
$ checkdevpremine 25afd7d33ceb8698f5d81eb7ee14a7532419ce1e1c65cc5032e37696f26c5cac
|
||||
```
|
||||
|
||||
Checking for multiple transactions with visible output:
|
||||
```bash
|
||||
$ checkdevpremine "[\"25afd7d33ceb8698f5d81eb7ee14a7532419ce1e1c65cc5032e37696f26c5cac\", \"b1527b63c7a76ea28e57604082bec0e8195cd7a33bd10c68296a45bce77bd2db\"]"
|
||||
```
|
||||
|
||||
Mixing tools in order to check all of the transactions in the latest block using
|
||||
[jq](https://stedolan.github.io/jq/) to extract the transaction hashes and feed
|
||||
them to `checkdevpremine` via stdin along with `--quiet` to suppress output:
|
||||
```bash
|
||||
$ dcrctl getbestblockhash | dcrctl getblock - | jq -c .tx | checkdevpremine --quiet -; echo $?
|
||||
0
|
||||
```
|
||||
|
||||
## Installation and Updating
|
||||
|
||||
```bash
|
||||
$ go get -u github.com/decred/dcrd/cmd/checkdevpremine
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
The checkdevpremine utility is licensed under the [copyfree](http://copyfree.org)
|
||||
ISC License.
|
||||
@ -1,275 +0,0 @@
|
||||
// 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/rpcclient"
|
||||
"github.com/decred/dcrd/wire"
|
||||
)
|
||||
|
||||
// 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 no single argument 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 *rpcclient.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
|
||||
if stake.IsSSGen(tx.MsgTx()) {
|
||||
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 := &rpcclient.ConnConfig{
|
||||
Host: cfg.RPCServer,
|
||||
Endpoint: "ws",
|
||||
User: cfg.RPCUser,
|
||||
Pass: cfg.RPCPassword,
|
||||
DisableTLS: cfg.NoTLS,
|
||||
Certificates: certs,
|
||||
}
|
||||
client, err := rpcclient.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())
|
||||
}
|
||||
@ -1,178 +0,0 @@
|
||||
// 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 (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/decred/dcrd/dcrutil"
|
||||
|
||||
flags "github.com/jessevdk/go-flags"
|
||||
)
|
||||
|
||||
var (
|
||||
dcrdHomeDir = dcrutil.AppDataDir("dcrd", false)
|
||||
appHomeDir = dcrutil.AppDataDir("checkdevpremine", false)
|
||||
defaultConfigFile = filepath.Join(appHomeDir, "checkdevpremine.conf")
|
||||
defaultRPCServer = "localhost"
|
||||
defaultRPCCertFile = filepath.Join(dcrdHomeDir, "rpc.cert")
|
||||
)
|
||||
|
||||
// config defines the configuration options for dcrctl.
|
||||
//
|
||||
// See loadConfig for details on the configuration load process.
|
||||
type config struct {
|
||||
ConfigFile string `short:"C" long:"configfile" description:"Path to configuration file"`
|
||||
RPCUser string `short:"u" long:"rpcuser" description:"RPC username"`
|
||||
RPCPassword string `short:"P" long:"rpcpass" default-mask:"-" description:"RPC password"`
|
||||
RPCServer string `short:"s" long:"rpcserver" description:"RPC server to connect to"`
|
||||
RPCCert string `short:"c" long:"rpccert" description:"RPC server certificate chain for validation"`
|
||||
NoTLS bool `long:"notls" description:"Disable TLS"`
|
||||
Quiet bool `long:"quiet" description:"Do not print any found outpoints which can be useful if only relying on the return code"`
|
||||
}
|
||||
|
||||
// normalizeAddress returns addr with the default port appended if there is not
|
||||
// already a port specified.
|
||||
func normalizeAddress(addr string) string {
|
||||
_, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return net.JoinHostPort(addr, "9109")
|
||||
}
|
||||
return addr
|
||||
}
|
||||
|
||||
// cleanAndExpandPath expands environment variables and leading ~ in the
|
||||
// passed path, cleans the result, and returns it.
|
||||
func cleanAndExpandPath(path string) string {
|
||||
// NOTE: The os.ExpandEnv doesn't work with Windows cmd.exe-style
|
||||
// %VARIABLE%, but the variables can still be expanded via POSIX-style
|
||||
// $VARIABLE.
|
||||
path = os.ExpandEnv(path)
|
||||
|
||||
if !strings.HasPrefix(path, "~") {
|
||||
return filepath.Clean(path)
|
||||
}
|
||||
|
||||
// Expand initial ~ to the current user's home directory, or ~otheruser
|
||||
// to otheruser's home directory. On Windows, both forward and backward
|
||||
// slashes can be used.
|
||||
path = path[1:]
|
||||
|
||||
var pathSeparators string
|
||||
if runtime.GOOS == "windows" {
|
||||
pathSeparators = string(os.PathSeparator) + "/"
|
||||
} else {
|
||||
pathSeparators = string(os.PathSeparator)
|
||||
}
|
||||
|
||||
userName := ""
|
||||
if i := strings.IndexAny(path, pathSeparators); i != -1 {
|
||||
userName = path[:i]
|
||||
path = path[i:]
|
||||
}
|
||||
|
||||
homeDir := ""
|
||||
var u *user.User
|
||||
var err error
|
||||
if userName == "" {
|
||||
u, err = user.Current()
|
||||
} else {
|
||||
u, err = user.Lookup(userName)
|
||||
}
|
||||
if err == nil {
|
||||
homeDir = u.HomeDir
|
||||
}
|
||||
// Fallback to CWD if user lookup fails or user has no home directory.
|
||||
if homeDir == "" {
|
||||
homeDir = "."
|
||||
}
|
||||
|
||||
return filepath.Join(homeDir, path)
|
||||
}
|
||||
|
||||
// loadConfig initializes and parses the config using a config file and command
|
||||
// line options.
|
||||
//
|
||||
// The configuration proceeds as follows:
|
||||
// 1) Start with a default config with sane settings
|
||||
// 2) Pre-parse the command line to check for an alternative config file
|
||||
// 3) Load configuration file overwriting defaults with any specified options
|
||||
// 4) Parse CLI options and overwrite/add any specified options
|
||||
//
|
||||
// The above results in functioning properly without any config settings
|
||||
// while still allowing the user to override settings with config files and
|
||||
// command line options. Command line options always take precedence.
|
||||
func loadConfig() (*config, []string, error) {
|
||||
// Default config.
|
||||
cfg := config{
|
||||
ConfigFile: defaultConfigFile,
|
||||
RPCServer: defaultRPCServer,
|
||||
RPCCert: defaultRPCCertFile,
|
||||
}
|
||||
|
||||
// Create the home directory if it doesn't already exist.
|
||||
err := os.MkdirAll(dcrdHomeDir, 0700)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
// Pre-parse the command line options to see if an alternative config
|
||||
// file, the version flag, or the list commands flag was specified. Any
|
||||
// errors aside from the help message error can be ignored here since
|
||||
// they will be caught by the final parse below.
|
||||
preCfg := cfg
|
||||
preParser := flags.NewParser(&preCfg, flags.HelpFlag)
|
||||
_, err = preParser.Parse()
|
||||
if err != nil {
|
||||
if e, ok := err.(*flags.Error); ok && e.Type == flags.ErrHelp {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
fmt.Fprintln(os.Stderr, "")
|
||||
fmt.Fprintln(os.Stderr, "The special parameter `-` "+
|
||||
"indicates that a parameter should be read "+
|
||||
"from the\nnext unread line from standard "+
|
||||
"input.")
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Load additional config from file.
|
||||
appName := filepath.Base(os.Args[0])
|
||||
appName = strings.TrimSuffix(appName, filepath.Ext(appName))
|
||||
usageMessage := fmt.Sprintf("Use %s -h to show options", appName)
|
||||
parser := flags.NewParser(&cfg, flags.Default)
|
||||
err = flags.NewIniParser(parser).ParseFile(preCfg.ConfigFile)
|
||||
if err != nil {
|
||||
if _, ok := err.(*os.PathError); !ok {
|
||||
fmt.Fprintf(os.Stderr, "Error parsing config file: %v\n",
|
||||
err)
|
||||
fmt.Fprintln(os.Stderr, usageMessage)
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Parse command line options again to ensure they take precedence.
|
||||
remainingArgs, err := parser.Parse()
|
||||
if err != nil {
|
||||
if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp {
|
||||
fmt.Fprintln(os.Stderr, usageMessage)
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Handle environment variable expansion in the RPC certificate path.
|
||||
cfg.RPCCert = cleanAndExpandPath(cfg.RPCCert)
|
||||
|
||||
// Add default port to RPC server if needed.
|
||||
cfg.RPCServer = normalizeAddress(cfg.RPCServer)
|
||||
|
||||
return &cfg, remainingArgs, nil
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user