decred.org/dcrwallet/v3@v3.1.0/cmd/movefunds/main.go (about)

     1  /*
     2   * Copyright (c) 2016-2020 The Decred developers
     3   *
     4   * Permission to use, copy, modify, and distribute this software for any
     5   * purpose with or without fee is hereby granted, provided that the above
     6   * copyright notice and this permission notice appear in all copies.
     7   *
     8   * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     9   * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
    10   * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
    11   * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
    12   * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
    13   * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
    14   * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
    15   */
    16  
    17  package main
    18  
    19  import (
    20  	"bytes"
    21  	"encoding/hex"
    22  	"encoding/json"
    23  	"fmt"
    24  	"os"
    25  
    26  	"decred.org/dcrwallet/v3/rpc/jsonrpc/types"
    27  	"decred.org/dcrwallet/v3/wallet/txauthor"
    28  	"github.com/decred/dcrd/chaincfg/chainhash"
    29  	"github.com/decred/dcrd/chaincfg/v3"
    30  	"github.com/decred/dcrd/dcrutil/v4"
    31  	"github.com/decred/dcrd/txscript/v4/stdaddr"
    32  	"github.com/decred/dcrd/wire"
    33  )
    34  
    35  // params is the global representing the chain parameters. It is assigned
    36  // in main.
    37  var params *chaincfg.Params
    38  
    39  // configJSON is a configuration file used for transaction generation.
    40  type configJSON struct {
    41  	TxFee         int64  `json:"txfee"`
    42  	SendToAddress string `json:"sendtoaddress"`
    43  	Network       string `json:"network"`
    44  	DcrctlArgs    string `json:"dcrctlargs"`
    45  }
    46  
    47  func saneOutputValue(amount dcrutil.Amount) bool {
    48  	return amount >= 0 && amount <= dcrutil.MaxAmount
    49  }
    50  
    51  func parseOutPoint(input *types.ListUnspentResult) (wire.OutPoint, error) {
    52  	txHash, err := chainhash.NewHashFromStr(input.TxID)
    53  	if err != nil {
    54  		return wire.OutPoint{}, err
    55  	}
    56  	return wire.OutPoint{Hash: *txHash, Index: input.Vout, Tree: input.Tree}, nil
    57  }
    58  
    59  // noInputValue describes an error returned by the input source when no inputs
    60  // were selected because each previous output value was zero.  Callers of
    61  // txauthor.NewUnsignedTransaction need not report these errors to the user.
    62  type noInputValue struct {
    63  }
    64  
    65  func (noInputValue) Error() string { return "no input value" }
    66  
    67  // makeInputSource creates an InputSource that creates inputs for every unspent
    68  // output with non-zero output values.  The target amount is ignored since every
    69  // output is consumed.  The InputSource does not return any previous output
    70  // scripts as they are not needed for creating the unsinged transaction and are
    71  // looked up again by the wallet during the call to signrawtransaction.
    72  func makeInputSource(outputs []types.ListUnspentResult) (dcrutil.Amount, txauthor.InputSource) {
    73  	var (
    74  		totalInputValue   dcrutil.Amount
    75  		inputs            = make([]*wire.TxIn, 0, len(outputs))
    76  		redeemScriptSizes = make([]int, 0, len(outputs))
    77  		sourceErr         error
    78  	)
    79  	for _, output := range outputs {
    80  
    81  		outputAmount, err := dcrutil.NewAmount(output.Amount)
    82  		if err != nil {
    83  			sourceErr = fmt.Errorf("invalid amount `%v` in listunspent result",
    84  				output.Amount)
    85  			break
    86  		}
    87  		if outputAmount == 0 {
    88  			continue
    89  		}
    90  		if !saneOutputValue(outputAmount) {
    91  			sourceErr = fmt.Errorf(
    92  				"impossible output amount `%v` in listunspent result",
    93  				outputAmount)
    94  			break
    95  		}
    96  		totalInputValue += outputAmount
    97  
    98  		previousOutPoint, err := parseOutPoint(&output)
    99  		if err != nil {
   100  			sourceErr = fmt.Errorf(
   101  				"invalid data in listunspent result: %v", err)
   102  			break
   103  		}
   104  
   105  		txIn := wire.NewTxIn(&previousOutPoint, int64(outputAmount), nil)
   106  		inputs = append(inputs, txIn)
   107  	}
   108  
   109  	if sourceErr == nil && totalInputValue == 0 {
   110  		sourceErr = noInputValue{}
   111  	}
   112  
   113  	return totalInputValue, func(dcrutil.Amount) (*txauthor.InputDetail, error) {
   114  		inputDetail := txauthor.InputDetail{
   115  			Amount:            totalInputValue,
   116  			Inputs:            inputs,
   117  			Scripts:           nil,
   118  			RedeemScriptSizes: redeemScriptSizes,
   119  		}
   120  		return &inputDetail, sourceErr
   121  	}
   122  }
   123  
   124  func main() {
   125  	// Create an inputSource from the loaded utxos.
   126  	unspentFile, err := os.Open("unspent.json")
   127  	if err != nil {
   128  		fmt.Printf("error opening unspent file unspent.json: %v", err)
   129  		return
   130  	}
   131  
   132  	var utxos []types.ListUnspentResult
   133  
   134  	jsonParser := json.NewDecoder(unspentFile)
   135  	if err = jsonParser.Decode(&utxos); err != nil {
   136  		fmt.Printf("error parsing unspent file: %v", err)
   137  		return
   138  	}
   139  
   140  	targetAmount, inputSource := makeInputSource(utxos)
   141  
   142  	// Load and parse the movefunds config.
   143  	configFile, err := os.Open("config.json")
   144  	if err != nil {
   145  		fmt.Printf("error opening config file config.json: %v", err)
   146  		return
   147  	}
   148  
   149  	cfg := new(configJSON)
   150  
   151  	jsonParser = json.NewDecoder(configFile)
   152  	if err = jsonParser.Decode(cfg); err != nil {
   153  		fmt.Printf("error parsing config file: %v", err)
   154  		return
   155  	}
   156  
   157  	switch cfg.Network {
   158  	case "testnet":
   159  		params = chaincfg.TestNet3Params()
   160  	case "mainnet":
   161  		params = chaincfg.MainNetParams()
   162  	case "simnet":
   163  		params = chaincfg.SimNetParams()
   164  	default:
   165  		fmt.Printf("unknown network specified: %s", cfg.Network)
   166  		return
   167  	}
   168  
   169  	addr, err := stdaddr.DecodeAddress(cfg.SendToAddress, params)
   170  	if err != nil {
   171  		fmt.Printf("failed to parse address %s: %v", cfg.SendToAddress, err)
   172  		return
   173  	}
   174  
   175  	fee, err := dcrutil.NewAmount(float64(cfg.TxFee))
   176  	if err != nil {
   177  		fmt.Printf("invalid fee rate: %v", err)
   178  		return
   179  	}
   180  
   181  	// Create the unsigned transaction.
   182  	pkScriptVer, pkScript := addr.PaymentScript()
   183  
   184  	txOuts := []*wire.TxOut{
   185  		{
   186  			Value:    int64(targetAmount - fee),
   187  			Version:  pkScriptVer,
   188  			PkScript: pkScript,
   189  		},
   190  	}
   191  	atx, err := txauthor.NewUnsignedTransaction(txOuts, fee, inputSource, nil, params.MaxTxSize)
   192  	if err != nil {
   193  		fmt.Printf("failed to create unsigned transaction: %s", err)
   194  		return
   195  	}
   196  
   197  	if atx.Tx.SerializeSize() > params.MaxTxSize {
   198  		fmt.Printf("tx too big: got %v, max %v", atx.Tx.SerializeSize(),
   199  			params.MaxTxSize)
   200  		return
   201  	}
   202  
   203  	// Generate the signrawtransaction command.
   204  	txB, err := atx.Tx.Bytes()
   205  	if err != nil {
   206  		fmt.Println("Failed to serialize tx: ", err.Error())
   207  		return
   208  	}
   209  
   210  	// The command to sign the transaction.
   211  	var buf bytes.Buffer
   212  	buf.WriteString("dcrctl ")
   213  	buf.WriteString(cfg.DcrctlArgs)
   214  	buf.WriteString(" signrawtransaction ")
   215  	buf.WriteString(hex.EncodeToString(txB))
   216  	buf.WriteString(" '[")
   217  	last := len(atx.Tx.TxIn) - 1
   218  	for i, in := range atx.Tx.TxIn {
   219  		buf.WriteString("{\"txid\":\"")
   220  		buf.WriteString(in.PreviousOutPoint.Hash.String())
   221  		buf.WriteString("\",\"vout\":")
   222  		buf.WriteString(fmt.Sprintf("%v", in.PreviousOutPoint.Index))
   223  		buf.WriteString(",\"tree\":")
   224  		buf.WriteString(fmt.Sprintf("%v", in.PreviousOutPoint.Tree))
   225  		buf.WriteString(",\"scriptpubkey\":\"")
   226  		buf.WriteString(hex.EncodeToString(in.SignatureScript))
   227  		buf.WriteString("\",\"redeemscript\":\"\"}")
   228  		if i != last {
   229  			buf.WriteString(",")
   230  		}
   231  	}
   232  	buf.WriteString("]' ")
   233  	buf.WriteString("| jq -r .hex")
   234  	err = os.WriteFile("sign.sh", buf.Bytes(), 0755)
   235  	if err != nil {
   236  		fmt.Println("Failed to write signing script: ", err.Error())
   237  		return
   238  	}
   239  
   240  	fmt.Println("Successfully wrote transaction to sign script.")
   241  }