decred.org/dcrwallet/v3@v3.1.0/wallet/udb/testdata/v5.db.go (about)

     1  // Copyright (c) 2017 The Decred developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  
     5  // This file should compiled from the commit the file was introduced, otherwise
     6  // it may not compile due to API changes, or may not create the database with
     7  // the correct old version.  This file should not be updated for API changes.
     8  
     9  package main
    10  
    11  import (
    12  	"bytes"
    13  	"compress/gzip"
    14  	"encoding/binary"
    15  	"errors"
    16  	"fmt"
    17  	"io"
    18  	"log"
    19  	"os"
    20  	"time"
    21  
    22  	"github.com/decred/dcrd/blockchain"
    23  	"github.com/decred/dcrd/blockchain/stake"
    24  	"github.com/decred/dcrd/chaincfg"
    25  	"github.com/decred/dcrd/chaincfg/chainec"
    26  	"github.com/decred/dcrd/chaincfg/chainhash"
    27  	"github.com/decred/dcrd/dcrec/secp256k1"
    28  	"github.com/decred/dcrd/txscript"
    29  	"github.com/decred/dcrd/wire"
    30  	"github.com/decred/dcrutil"
    31  	"github.com/decred/dcrutil/hdkeychain"
    32  	_ "github.com/decred/dcrwallet/wallet/internal/bdb"
    33  	"github.com/decred/dcrwallet/wallet/internal/txsizes"
    34  	"github.com/decred/dcrwallet/wallet/txrules"
    35  	"github.com/decred/dcrwallet/wallet/udb"
    36  	"github.com/decred/dcrwallet/wallet/walletdb"
    37  	"github.com/decred/dcrwallet/walletseed"
    38  )
    39  
    40  const dbname = "v5.db"
    41  
    42  var (
    43  	pubPass  = []byte("public")
    44  	privPass = []byte("private")
    45  	privKey  = []byte{31: 1}
    46  	addr     dcrutil.Address
    47  )
    48  
    49  var chainParams = &chaincfg.TestNet2Params
    50  
    51  var (
    52  	epoch     time.Time
    53  	votebits  = stake.VoteBits{Bits: 1}
    54  	subsidies = blockchain.NewSubsidyCache(0, chainParams)
    55  )
    56  
    57  const ticketFeeLimits = 0x5800 // This is dark magic
    58  
    59  func main() {
    60  	err := setup()
    61  	if err != nil {
    62  		fmt.Fprintf(os.Stderr, "setup: %v\n", err)
    63  		os.Exit(1)
    64  	}
    65  	err = compress()
    66  	if err != nil {
    67  		fmt.Fprintf(os.Stderr, "compress: %v\n", err)
    68  		os.Exit(1)
    69  	}
    70  }
    71  
    72  func setup() error {
    73  	os.Remove(dbname)
    74  	db, err := walletdb.Create("bdb", dbname)
    75  	if err != nil {
    76  		return err
    77  	}
    78  	defer db.Close()
    79  	seed, err := walletseed.GenerateRandomSeed(hdkeychain.RecommendedSeedLen)
    80  	if err != nil {
    81  		return err
    82  	}
    83  	err = udb.Initialize(db, chainParams, seed, pubPass, privPass)
    84  	if err != nil {
    85  		return err
    86  	}
    87  
    88  	amgr, txmgr, _, err := udb.Open(db, chainParams, pubPass)
    89  	if err != nil {
    90  		return err
    91  	}
    92  
    93  	return walletdb.Update(db, func(dbtx walletdb.ReadWriteTx) error {
    94  		amgrns := dbtx.ReadWriteBucket([]byte("waddrmgr"))
    95  		txmgrns := dbtx.ReadWriteBucket([]byte("wtxmgr"))
    96  
    97  		err := amgr.Unlock(amgrns, privPass)
    98  		if err != nil {
    99  			return err
   100  		}
   101  
   102  		privKey, _ := secp256k1.PrivKeyFromBytes(secp256k1.S256(), privKey)
   103  		wif, err := dcrutil.NewWIF(privKey, chainParams, chainec.ECTypeSecp256k1)
   104  		if err != nil {
   105  			return err
   106  		}
   107  		maddr, err := amgr.ImportPrivateKey(amgrns, wif)
   108  		if err != nil {
   109  			return err
   110  		}
   111  		addr = maddr.Address()
   112  
   113  		// Add some fake blocks
   114  		var prevBlock = chainParams.GenesisHash
   115  		for height := 1; height < 8; height++ {
   116  			var buf bytes.Buffer
   117  			err := (&wire.BlockHeader{
   118  				Version:      1,
   119  				PrevBlock:    *prevBlock,
   120  				StakeVersion: 1,
   121  				VoteBits:     1,
   122  				Height:       uint32(height),
   123  			}).Serialize(&buf)
   124  			if err != nil {
   125  				return err
   126  			}
   127  			headerData := udb.BlockHeaderData{
   128  				BlockHash: chainhash.Hash{31: byte(height)},
   129  			}
   130  			copy(headerData.SerializedHeader[:], buf.Bytes())
   131  			err = txmgr.ExtendMainChain(txmgrns, &headerData)
   132  			if err != nil {
   133  				return err
   134  			}
   135  			prevBlock = &headerData.BlockHash
   136  		}
   137  
   138  		// Create 6 tickets:
   139  		//
   140  		//  0: Unmined, unspent
   141  		//  1: Mined at height 2, unspent
   142  		//  2: Mined at height 3, spent by unmined vote
   143  		//  3: Mined at height 4, spent by mined vote in block 5
   144  		//  4: Mined at height 5, spent by unmined revocation
   145  		//  5: Mined at height 6, spent by mined revocation in block 7
   146  		//
   147  		// These tickets have the following hashes:
   148  		//
   149  		//  0: 7bc19eb0bf3a57be73d6879b6c411404b14b0156353dd47c5e0456768704bfd1
   150  		//  1: a6abeb0127c347b5f38ebc2401134b324612d5b1ad9a9b8bdf6a91521842b7b1
   151  		//  2: 1107757fa4f238803192c617c7b60bf35bdc57bc0fc94b408c71239ff9eaeb98
   152  		//  3: 3fd00cda28c4d148e0cd38e1d646ba1365116b3ddd9a49aca4483bef80513ff9
   153  		//  4: f4bdebefaa174470182960046fa53f554108b8ea09a86de5306a14c3a0124566
   154  		//  5: bca8c2649860585f10b27d774b354ea7b80007e9ad79c090ea05596d63995cf5
   155  		for i := uint32(0); i < 6; i++ {
   156  			height := int32(i + 1)
   157  			ticket, err := createUnsignedTicketPurchase(&wire.OutPoint{Index: i}, 50e8, 50e8-3e5)
   158  			if err != nil {
   159  				return err
   160  			}
   161  			ticketRec, err := udb.NewTxRecordFromMsgTx(ticket, epoch)
   162  			if err != nil {
   163  				return err
   164  			}
   165  			var block *udb.BlockMeta
   166  			switch i {
   167  			case 0:
   168  				err := txmgr.InsertMemPoolTx(txmgrns, ticketRec)
   169  				if err != nil {
   170  					return err
   171  				}
   172  			default:
   173  				block = &udb.BlockMeta{
   174  					Block: udb.Block{
   175  						Hash:   chainhash.Hash{31: byte(height)},
   176  						Height: height,
   177  					},
   178  					VoteBits: 1,
   179  				}
   180  				err := txmgr.InsertMinedTx(txmgrns, amgrns, ticketRec, &block.Hash)
   181  				if err != nil {
   182  					return err
   183  				}
   184  			}
   185  			err = txmgr.AddCredit(txmgrns, ticketRec, block, 0, false, udb.ImportedAddrAccount)
   186  			if err != nil {
   187  				return err
   188  			}
   189  			log.Printf("Added ticket %v", &ticketRec.Hash)
   190  
   191  			switch i {
   192  			case 0, 1:
   193  				// Skip adding a spender
   194  				continue
   195  			}
   196  
   197  			var spender *wire.MsgTx
   198  			switch i {
   199  			case 2, 3:
   200  				spender, err = createUnsignedVote(&ticketRec.Hash, ticket,
   201  					height+1, &chainhash.Hash{31: byte(height + 1)}, votebits,
   202  					subsidies, chainParams)
   203  			case 4, 5:
   204  				spender, err = createUnsignedRevocation(&ticketRec.Hash, ticket, 1e5)
   205  			}
   206  			if err != nil {
   207  				return err
   208  			}
   209  			spenderRec, err := udb.NewTxRecordFromMsgTx(spender, epoch)
   210  			if err != nil {
   211  				return err
   212  			}
   213  			block = nil
   214  			switch i {
   215  			case 2, 4:
   216  				err := txmgr.InsertMemPoolTx(txmgrns, spenderRec)
   217  				if err != nil {
   218  					return err
   219  				}
   220  			case 3, 5:
   221  				block = &udb.BlockMeta{
   222  					Block: udb.Block{
   223  						Hash:   chainhash.Hash{31: byte(height + 1)},
   224  						Height: height + 1,
   225  					},
   226  					VoteBits: 1,
   227  				}
   228  				err := txmgr.InsertMinedTx(txmgrns, amgrns, spenderRec, &block.Hash)
   229  				if err != nil {
   230  					return err
   231  				}
   232  			}
   233  			err = txmgr.AddCredit(txmgrns, spenderRec, block, 0, false, udb.ImportedAddrAccount)
   234  			if err != nil {
   235  				return err
   236  			}
   237  		}
   238  
   239  		return nil
   240  	})
   241  }
   242  
   243  func compress() error {
   244  	db, err := os.Open(dbname)
   245  	if err != nil {
   246  		return err
   247  	}
   248  	defer os.Remove(dbname)
   249  	defer db.Close()
   250  	dbgz, err := os.Create(dbname + ".gz")
   251  	if err != nil {
   252  		return err
   253  	}
   254  	defer dbgz.Close()
   255  	gz := gzip.NewWriter(dbgz)
   256  	_, err = io.Copy(gz, db)
   257  	if err != nil {
   258  		return err
   259  	}
   260  	return gz.Close()
   261  }
   262  
   263  func createUnsignedTicketPurchase(prevOut *wire.OutPoint,
   264  	inputAmount, ticketPrice dcrutil.Amount) (*wire.MsgTx, error) {
   265  
   266  	tx := wire.NewMsgTx()
   267  	txIn := wire.NewTxIn(prevOut, nil)
   268  	txIn.ValueIn = inputAmount
   269  	tx.AddTxIn(txIn)
   270  
   271  	pkScript, err := txscript.PayToSStx(addr)
   272  	if err != nil {
   273  		return nil, err
   274  	}
   275  
   276  	tx.AddTxOut(wire.NewTxOut(int64(ticketPrice), pkScript))
   277  
   278  	_, amountsCommitted, err := stake.SStxNullOutputAmounts(
   279  		[]int64{int64(inputAmount)}, []int64{0}, int64(ticketPrice))
   280  	if err != nil {
   281  		return nil, err
   282  	}
   283  
   284  	pkScript, err = txscript.GenerateSStxAddrPush(addr,
   285  		dcrutil.Amount(amountsCommitted[0]), ticketFeeLimits)
   286  	if err != nil {
   287  		return nil, err
   288  	}
   289  	tx.AddTxOut(wire.NewTxOut(0, pkScript))
   290  
   291  	pkScript, err = txscript.PayToSStxChange(addr)
   292  	if err != nil {
   293  		return nil, err
   294  	}
   295  	tx.AddTxOut(wire.NewTxOut(0, pkScript))
   296  
   297  	_, err = stake.IsSStx(tx)
   298  	return tx, err
   299  }
   300  
   301  // newVoteScript generates a voting script from the passed VoteBits, for
   302  // use in a vote.
   303  func newVoteScript(voteBits stake.VoteBits) ([]byte, error) {
   304  	b := make([]byte, 2+len(voteBits.ExtendedBits))
   305  	binary.LittleEndian.PutUint16(b[0:2], voteBits.Bits)
   306  	copy(b[2:], voteBits.ExtendedBits[:])
   307  	return txscript.GenerateProvablyPruneableOut(b)
   308  }
   309  
   310  // createUnsignedVote creates an unsigned vote transaction that votes using the
   311  // ticket specified by a ticket purchase hash and transaction with the provided
   312  // vote bits.  The block height and hash must be of the previous block the vote
   313  // is voting on.
   314  func createUnsignedVote(ticketHash *chainhash.Hash, ticketPurchase *wire.MsgTx,
   315  	blockHeight int32, blockHash *chainhash.Hash, voteBits stake.VoteBits,
   316  	subsidyCache *blockchain.SubsidyCache, params *chaincfg.Params) (*wire.MsgTx, error) {
   317  
   318  	// Parse the ticket purchase transaction to determine the required output
   319  	// destinations for vote rewards or revocations.
   320  	ticketPayKinds, ticketHash160s, ticketValues, _, _, _ :=
   321  		stake.TxSStxStakeOutputInfo(ticketPurchase)
   322  
   323  	// Calculate the subsidy for votes at this height.
   324  	subsidy := blockchain.CalcStakeVoteSubsidy(subsidyCache, int64(blockHeight),
   325  		params)
   326  
   327  	// Calculate the output values from this vote using the subsidy.
   328  	voteRewardValues := stake.CalculateRewards(ticketValues,
   329  		ticketPurchase.TxOut[0].Value, subsidy)
   330  
   331  	// Begin constructing the vote transaction.
   332  	vote := wire.NewMsgTx()
   333  
   334  	// Add stakebase input to the vote.
   335  	stakebaseOutPoint := wire.NewOutPoint(&chainhash.Hash{}, ^uint32(0),
   336  		wire.TxTreeRegular)
   337  	stakebaseInput := wire.NewTxIn(stakebaseOutPoint, nil)
   338  	stakebaseInput.ValueIn = subsidy
   339  	vote.AddTxIn(stakebaseInput)
   340  
   341  	// Votes reference the ticket purchase with the second input.
   342  	ticketOutPoint := wire.NewOutPoint(ticketHash, 0, wire.TxTreeStake)
   343  	ticketInput := wire.NewTxIn(ticketOutPoint, nil)
   344  	ticketInput.ValueIn = ticketPurchase.TxOut[ticketOutPoint.Index].Value
   345  	vote.AddTxIn(ticketInput)
   346  
   347  	// The first output references the previous block the vote is voting on.
   348  	// This function never errors.
   349  	blockRefScript, _ := txscript.GenerateSSGenBlockRef(*blockHash,
   350  		uint32(blockHeight))
   351  	vote.AddTxOut(wire.NewTxOut(0, blockRefScript))
   352  
   353  	// The second output contains the votebits encode as a null data script.
   354  	voteScript, err := newVoteScript(voteBits)
   355  	if err != nil {
   356  		return nil, err
   357  	}
   358  	vote.AddTxOut(wire.NewTxOut(0, voteScript))
   359  
   360  	// All remaining outputs pay to the output destinations and amounts tagged
   361  	// by the ticket purchase.
   362  	for i, hash160 := range ticketHash160s {
   363  		scriptFn := txscript.PayToSSGenPKHDirect
   364  		if ticketPayKinds[i] { // P2SH
   365  			scriptFn = txscript.PayToSSGenSHDirect
   366  		}
   367  		// Error is checking for a nil hash160, just ignore it.
   368  		script, _ := scriptFn(hash160)
   369  		vote.AddTxOut(wire.NewTxOut(voteRewardValues[i], script))
   370  	}
   371  
   372  	return vote, nil
   373  }
   374  
   375  // createUnsignedRevocation creates an unsigned revocation transaction that
   376  // revokes a missed or expired ticket.  Revocations must carry a relay fee and
   377  // this function can error if the revocation contains no suitable output to
   378  // decrease the estimated relay fee from.
   379  func createUnsignedRevocation(ticketHash *chainhash.Hash, ticketPurchase *wire.MsgTx, feePerKB dcrutil.Amount) (*wire.MsgTx, error) {
   380  	// Parse the ticket purchase transaction to determine the required output
   381  	// destinations for vote rewards or revocations.
   382  	ticketPayKinds, ticketHash160s, ticketValues, _, _, _ :=
   383  		stake.TxSStxStakeOutputInfo(ticketPurchase)
   384  
   385  	// Calculate the output values for the revocation.  Revocations do not
   386  	// contain any subsidy.
   387  	revocationValues := stake.CalculateRewards(ticketValues,
   388  		ticketPurchase.TxOut[0].Value, 0)
   389  
   390  	// Begin constructing the revocation transaction.
   391  	revocation := wire.NewMsgTx()
   392  
   393  	// Revocations reference the ticket purchase with the first (and only)
   394  	// input.
   395  	ticketOutPoint := wire.NewOutPoint(ticketHash, 0, wire.TxTreeStake)
   396  	ticketInput := wire.NewTxIn(ticketOutPoint, nil)
   397  	ticketInput.ValueIn = ticketPurchase.TxOut[ticketOutPoint.Index].Value
   398  	revocation.AddTxIn(ticketInput)
   399  
   400  	// All remaining outputs pay to the output destinations and amounts tagged
   401  	// by the ticket purchase.
   402  	for i, hash160 := range ticketHash160s {
   403  		scriptFn := txscript.PayToSSRtxPKHDirect
   404  		if ticketPayKinds[i] { // P2SH
   405  			scriptFn = txscript.PayToSSRtxSHDirect
   406  		}
   407  		// Error is checking for a nil hash160, just ignore it.
   408  		script, _ := scriptFn(hash160)
   409  		revocation.AddTxOut(wire.NewTxOut(revocationValues[i], script))
   410  	}
   411  
   412  	// Revocations must pay a fee but do so by decreasing one of the output
   413  	// values instead of increasing the input value and using a change output.
   414  	// Calculate the estimated signed serialize size.
   415  	sizeEstimate := txsizes.EstimateSerializeSize(1, revocation.TxOut, false)
   416  	feeEstimate := txrules.FeeForSerializeSize(feePerKB, sizeEstimate)
   417  
   418  	// Reduce the output value of one of the outputs to accommodate for the relay
   419  	// fee.  To avoid creating dust outputs, a suitable output value is reduced
   420  	// by the fee estimate only if it is large enough to not create dust.  This
   421  	// code does not currently handle reducing the output values of multiple
   422  	// commitment outputs to accommodate for the fee.
   423  	for _, output := range revocation.TxOut {
   424  		if dcrutil.Amount(output.Value) > feeEstimate {
   425  			amount := dcrutil.Amount(output.Value) - feeEstimate
   426  			if !txrules.IsDustAmount(amount, len(output.PkScript), feePerKB) {
   427  				output.Value = int64(amount)
   428  				return revocation, nil
   429  			}
   430  		}
   431  	}
   432  	return nil, errors.New("no suitable revocation outputs to pay relay fee")
   433  }