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

     1  // Copyright (c) 2018 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 be compiled from the commit the file was introduced,
     6  // otherwise it may not compile due to API changes, or may not create the
     7  // database with the correct old version.  This file should not be updated for
     8  // API changes.
     9  
    10  // V11 test database layout and v12 upgrade test plan
    11  //
    12  // The v12 database upgrade introduces buckets to track ticket commitments
    13  // information in the transaction manager namespace (bucketTicketCommitments and
    14  // bucketTicketCommitmentsUsp). These buckets are meant to track outstanding
    15  // ticket commitment outputs for the purposes of correct balance calculation: it
    16  // allows non-voting wallets (eg: funding wallets in solo-voting setups or
    17  // non-voter participants of split tickets) to track their proportional locked
    18  // funds. In standard (single-voter) VSP setups, it also allows to correctly
    19  // discount the pool fee for correct accounting of total locked funds.
    20  //
    21  // The v11 database generated by this file is meant to be used on the v12
    22  // upgrade verification test (verifyV12Upgrade). This database is setup with a
    23  // set of tickets, votes and revocations in order to test that the upgrade was
    24  // done successfully and that the balances generated after the upgrade are
    25  // correct.
    26  //
    27  // Each generated ticket uses an address from an odd-numbered account as vote
    28  // (ticket submission) address, an address from the next account as commitment
    29  // address and a fixed, non-wallet address for pool fee commitment. Votes and
    30  // revocations are generated from a corresponding ticket as needed.
    31  //
    32  // Using different accounts for each combination of
    33  // {mined,unmined}{ticket,vote,revocation} allows the upgrade test to easily
    34  // verify that the upgrade and balance functions are performing correctly by
    35  // checking each account for the expected balance.
    36  
    37  package main
    38  
    39  import (
    40  	"bytes"
    41  	"compress/gzip"
    42  	"fmt"
    43  	"io"
    44  	"math"
    45  	"os"
    46  	"time"
    47  
    48  	"decred.org/dcrwallet/v3/errors"
    49  	_ "decred.org/dcrwallet/v3/wallet/internal/bdb"
    50  	"decred.org/dcrwallet/v3/wallet/udb"
    51  	"decred.org/dcrwallet/v3/wallet/walletdb"
    52  	"github.com/decred/dcrd/blockchain/stake"
    53  	"github.com/decred/dcrd/chaincfg"
    54  	"github.com/decred/dcrd/chaincfg/chainhash"
    55  	"github.com/decred/dcrd/dcrutil"
    56  	"github.com/decred/dcrd/gcs"
    57  	"github.com/decred/dcrd/txscript"
    58  	"github.com/decred/dcrd/wire"
    59  )
    60  
    61  const dbname = "v11.db"
    62  
    63  var (
    64  	epoch    time.Time
    65  	pubPass  = []byte("public")
    66  	privPass = []byte("private")
    67  )
    68  
    69  var chainParams = &chaincfg.TestNet3Params
    70  
    71  func main() {
    72  	err := setup()
    73  	if err != nil {
    74  		fmt.Fprintf(os.Stderr, "setup: %v\n", err)
    75  		os.Exit(1)
    76  	}
    77  	err = compress()
    78  	if err != nil {
    79  		fmt.Fprintf(os.Stderr, "compress: %v\n", err)
    80  		os.Exit(1)
    81  	}
    82  }
    83  
    84  func pay2ssgen(addr dcrutil.Address) []byte {
    85  	s, err := txscript.PayToSSGen(addr)
    86  	if err != nil {
    87  		panic(err)
    88  	}
    89  	return s
    90  }
    91  
    92  func pay2ssrtx(addr dcrutil.Address) []byte {
    93  	s, err := txscript.PayToSSRtx(addr)
    94  	if err != nil {
    95  		panic(err)
    96  	}
    97  	return s
    98  }
    99  
   100  func pay2sstx(addr dcrutil.Address) []byte {
   101  	s, err := txscript.PayToSStx(addr)
   102  	if err != nil {
   103  		panic(err)
   104  	}
   105  	return s
   106  }
   107  
   108  func pay2sstxChange() []byte {
   109  	//OP_SSTXCHANGE OP_DUP OP_HASH160 0000000000000000000000000000000000000000 OP_EQUALVERIFY OP_CHECKSIG
   110  	return []byte{0xbd, 0x76, 0xa9, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   111  		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   112  		0x00, 0x00, 0x88, 0xac}
   113  }
   114  
   115  func sstxAddrPush(addr dcrutil.Address, amount int64) []byte {
   116  	s, err := txscript.GenerateSStxAddrPush(addr, dcrutil.Amount(amount), 0x0058)
   117  	if err != nil {
   118  		panic(err)
   119  	}
   120  	return s
   121  }
   122  
   123  func dummyTxIn(idx *uint32) *wire.TxIn {
   124  	var prevHash chainhash.Hash
   125  	*idx++
   126  	return wire.NewTxIn(wire.NewOutPoint(&prevHash, *idx, 0), 0, nil)
   127  }
   128  
   129  func stakeBaseTxIn() *wire.TxIn {
   130  	var prevHash chainhash.Hash
   131  	return wire.NewTxIn(wire.NewOutPoint(&prevHash, math.MaxUint32, 0), 0, nil)
   132  }
   133  
   134  func ticketSpendTxIn(ticket *wire.MsgTx) *wire.TxIn {
   135  	th := ticket.TxHash()
   136  	return wire.NewTxIn(wire.NewOutPoint(&th, 0, 1), 0, nil)
   137  }
   138  
   139  func setup() error {
   140  	db, err := walletdb.Create("bdb", dbname)
   141  	if err != nil {
   142  		return err
   143  	}
   144  	defer db.Close()
   145  	var seed [32]byte
   146  	err = udb.Initialize(db, chainParams, seed[:], pubPass, privPass)
   147  	if err != nil {
   148  		return err
   149  	}
   150  
   151  	amgr, txmgr, _, err := udb.Open(db, chainParams, pubPass)
   152  	if err != nil {
   153  		return err
   154  	}
   155  
   156  	return walletdb.Update(db, func(tx walletdb.ReadWriteTx) error {
   157  		amgrns := tx.ReadWriteBucket([]byte("waddrmgr"))
   158  		txmgrns := tx.ReadWriteBucket([]byte("wtxmgr"))
   159  		err := amgr.Unlock(amgrns, privPass)
   160  		if err != nil {
   161  			return err
   162  		}
   163  
   164  		// The following are constants used throughout the db setup process.
   165  
   166  		// Assumes the manager on a new wallet only (and always) creates account 0
   167  		lastAcctIdx := uint32(0)
   168  		branchIdx := uint32(0)
   169  		commitmentAmt := int64(1000)
   170  		commitmentAmtReward := int64(300)
   171  		poolFee := int64(100)
   172  		poolFeeReward := int64(30)
   173  		ticketPrice := commitmentAmt + poolFee
   174  		poolFeeAddr, _ := dcrutil.DecodeAddress("TsR28UZRprhgQQhzWns2M6cAwchrNVvbYq2")
   175  		txinIdx := uint32(0)
   176  		var blockHash chainhash.Hash
   177  		var block udb.Block
   178  		var blockMeta udb.BlockMeta
   179  
   180  		// The following are helper functions, defined as closures to generate
   181  		// test data.
   182  
   183  		// Returns the first usable address for the next account of the test.
   184  		nextAddress := func() (dcrutil.Address, error) {
   185  			lastAcctIdx++
   186  			_, err := amgr.NewAccount(amgrns, fmt.Sprintf("%d", lastAcctIdx))
   187  			if err != nil {
   188  				return nil, err
   189  			}
   190  			err = amgr.SyncAccountToAddrIndex(amgrns, lastAcctIdx, 1, branchIdx)
   191  			if err != nil {
   192  				return nil, err
   193  			}
   194  			xpubBranch, err := amgr.AccountBranchExtendedPubKey(tx, lastAcctIdx,
   195  				branchIdx)
   196  			if err != nil {
   197  				return nil, err
   198  			}
   199  			xpubChild, err := xpubBranch.Child(0)
   200  			if err != nil {
   201  				return nil, err
   202  			}
   203  			addr, err := xpubChild.Address(chainParams)
   204  			if err != nil {
   205  				return nil, err
   206  			}
   207  			return addr, nil
   208  		}
   209  
   210  		// Generate a ticket. Generates 2 new accounts, uses an address from the
   211  		// first as vote address and an address from the second as commitment.
   212  		// Also adds a pool fee commitment to an address not from the wallet.
   213  		genTicket := func() (*wire.MsgTx, error) {
   214  			voteAddr, err := nextAddress()
   215  			if err != nil {
   216  				return nil, err
   217  			}
   218  
   219  			commitAddr, err := nextAddress()
   220  			if err != nil {
   221  				return nil, err
   222  			}
   223  
   224  			tx := wire.NewMsgTx()
   225  			tx.AddTxIn(dummyTxIn(&txinIdx))
   226  			tx.AddTxIn(dummyTxIn(&txinIdx))
   227  			tx.AddTxOut(wire.NewTxOut(ticketPrice, pay2sstx(voteAddr)))
   228  			tx.AddTxOut(wire.NewTxOut(0, sstxAddrPush(poolFeeAddr, poolFee)))
   229  			tx.AddTxOut(wire.NewTxOut(0, pay2sstxChange()))
   230  			tx.AddTxOut(wire.NewTxOut(0, sstxAddrPush(commitAddr, commitmentAmt)))
   231  			tx.AddTxOut(wire.NewTxOut(0, pay2sstxChange()))
   232  			return tx, nil
   233  		}
   234  
   235  		// Generate a vote for the given ticket.
   236  		genVote := func(ticket *wire.MsgTx) (*wire.MsgTx, error) {
   237  			poolFeeAddr, err := stake.AddrFromSStxPkScrCommitment(ticket.TxOut[1].PkScript,
   238  				chainParams)
   239  			if err != nil {
   240  				return nil, err
   241  			}
   242  
   243  			commitAddr, err := stake.AddrFromSStxPkScrCommitment(ticket.TxOut[3].PkScript,
   244  				chainParams)
   245  			if err != nil {
   246  				return nil, err
   247  			}
   248  
   249  			blockRef := bytes.Repeat([]byte{0x00}, 36)
   250  			prevBlockScript := append([]byte{0x6a, 0x24}, blockRef...)
   251  
   252  			tx := wire.NewMsgTx()
   253  			tx.AddTxIn(stakeBaseTxIn())
   254  			tx.AddTxIn(ticketSpendTxIn(ticket))
   255  			tx.AddTxOut(wire.NewTxOut(0, prevBlockScript))                      // prev block
   256  			tx.AddTxOut(wire.NewTxOut(0, []byte{0x6a, 0x03, 0x00, 0x00, 0x00})) // vote bits
   257  			tx.AddTxOut(wire.NewTxOut(poolFee+poolFeeReward, pay2ssgen(poolFeeAddr)))
   258  			tx.AddTxOut(wire.NewTxOut(commitmentAmt+commitmentAmtReward, pay2ssgen(commitAddr)))
   259  
   260  			return tx, nil
   261  		}
   262  
   263  		// Generate a revocation for the given ticket.
   264  		genRevoke := func(ticket *wire.MsgTx) (*wire.MsgTx, error) {
   265  			poolFeeAddr, err := stake.AddrFromSStxPkScrCommitment(ticket.TxOut[1].PkScript,
   266  				chainParams)
   267  			if err != nil {
   268  				return nil, err
   269  			}
   270  
   271  			commitAddr, err := stake.AddrFromSStxPkScrCommitment(ticket.TxOut[3].PkScript,
   272  				chainParams)
   273  			if err != nil {
   274  				return nil, err
   275  			}
   276  
   277  			tx := wire.NewMsgTx()
   278  			tx.AddTxIn(ticketSpendTxIn(ticket))
   279  			tx.AddTxOut(wire.NewTxOut(poolFee-poolFeeReward, pay2ssrtx(poolFeeAddr)))
   280  			tx.AddTxOut(wire.NewTxOut(commitmentAmt-commitmentAmtReward, pay2ssrtx(commitAddr)))
   281  			return tx, nil
   282  		}
   283  
   284  		// Insert this transaction and its credits into the tx store. This is a
   285  		// subset of what happens in chainntfns' ProcessTransaction() as of
   286  		// version 11 of the database.
   287  		addTx := func(tx *wire.MsgTx, mined bool) error {
   288  			rec, err := udb.NewTxRecordFromMsgTx(tx, epoch)
   289  			if err != nil {
   290  				return err
   291  			}
   292  			var blockMetaToUse *udb.BlockMeta
   293  			if mined {
   294  				err = txmgr.InsertMinedTx(txmgrns, amgrns, rec, &blockHash)
   295  				if err != nil {
   296  					return err
   297  				}
   298  				blockMetaToUse = &blockMeta
   299  			} else {
   300  				err = txmgr.InsertMemPoolTx(txmgrns, rec)
   301  				if err != nil {
   302  					return err
   303  				}
   304  			}
   305  			for i, txout := range tx.TxOut {
   306  				if txout.Value == 0 {
   307  					continue
   308  				}
   309  
   310  				_, addrs, _, err := txscript.ExtractPkScriptAddrs(txout.Version,
   311  					txout.PkScript, chainParams)
   312  				if err != nil {
   313  					return nil
   314  				}
   315  				if len(addrs) == 0 {
   316  					return errors.New("should have an address")
   317  				}
   318  				ma, err := amgr.Address(amgrns, addrs[0])
   319  				if errors.Is(err, errors.NotExist) {
   320  					continue
   321  				}
   322  				if err != nil {
   323  					return err
   324  				}
   325  				err = txmgr.AddCredit(txmgrns, rec, blockMetaToUse,
   326  					uint32(i), ma.Internal(), ma.Account())
   327  				if err != nil {
   328  					return err
   329  				}
   330  			}
   331  
   332  			return nil
   333  		}
   334  
   335  		// Generate and add a ticket as mined.
   336  		addMinedTicket := func() (*wire.MsgTx, error) {
   337  			ticket, err := genTicket()
   338  			if err != nil {
   339  				return nil, err
   340  			}
   341  			err = addTx(ticket, true)
   342  			if err != nil {
   343  				return nil, err
   344  			}
   345  			return ticket, nil
   346  		}
   347  
   348  		// Add a block to the database. All mined transactions added will be
   349  		// registered on this block. While this breaks many consensus for
   350  		// stake transactions, it is fine for simple database testing.
   351  		prevBlock := chainParams.GenesisHash
   352  		buf := bytes.Buffer{}
   353  		blockHeader := &wire.BlockHeader{
   354  			Version:      1,
   355  			PrevBlock:    *prevBlock,
   356  			StakeVersion: 1,
   357  			VoteBits:     1,
   358  			Height:       uint32(1),
   359  		}
   360  		blockHash = blockHeader.BlockHash()
   361  		headerData := udb.BlockHeaderData{
   362  			BlockHash: blockHash,
   363  		}
   364  		copy(headerData.SerializedHeader[:], buf.Bytes())
   365  		nullgcskey := [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
   366  		nullb := bytes.Repeat([]byte{0}, 16)
   367  		gcsFilter, err := gcs.NewFilter(1, nullgcskey, [][]byte{nullb})
   368  		if err != nil {
   369  			return err
   370  		}
   371  		err = txmgr.ExtendMainChain(txmgrns, blockHeader, gcsFilter)
   372  		if err != nil {
   373  			return err
   374  		}
   375  		block = udb.Block{
   376  			Hash:   headerData.BlockHash,
   377  			Height: int32(1),
   378  		}
   379  		blockMeta = udb.BlockMeta{
   380  			Block: block,
   381  			Time:  epoch,
   382  		}
   383  
   384  		// Start adding the test data. The initial default account is 0.
   385  		var ticket, vote, revoke *wire.MsgTx
   386  
   387  		// Unmined ticket (accounts 1 & 2)
   388  		if ticket, err = genTicket(); err != nil {
   389  			return err
   390  		}
   391  		if err = addTx(ticket, false); err != nil {
   392  			return err
   393  		}
   394  
   395  		// Mined ticket (accounts 3 & 4)
   396  		if ticket, err = genTicket(); err != nil {
   397  			return err
   398  		}
   399  		if err = addTx(ticket, true); err != nil {
   400  			return err
   401  		}
   402  
   403  		// Mined ticket with unmined vote (accounts 5 & 6).
   404  		if ticket, err = addMinedTicket(); err != nil {
   405  			return err
   406  		}
   407  		if vote, err = genVote(ticket); err != nil {
   408  			return err
   409  		}
   410  		if err = addTx(vote, false); err != nil {
   411  			return err
   412  		}
   413  
   414  		// Mined ticket with mined vote (accounts 7 & 8).
   415  		if ticket, err = addMinedTicket(); err != nil {
   416  			return err
   417  		}
   418  		if vote, err = genVote(ticket); err != nil {
   419  			return err
   420  		}
   421  		if err = addTx(vote, true); err != nil {
   422  			return err
   423  		}
   424  
   425  		// Mined ticket with unmined revocation (accounts 9 & 10).
   426  		if ticket, err = addMinedTicket(); err != nil {
   427  			return err
   428  		}
   429  		if revoke, err = genRevoke(ticket); err != nil {
   430  			return err
   431  		}
   432  		if err = addTx(revoke, false); err != nil {
   433  			return err
   434  		}
   435  
   436  		// Mined ticket with mined revocation (accounts 11 & 12).
   437  		if ticket, err = addMinedTicket(); err != nil {
   438  			return err
   439  		}
   440  		if revoke, err = genRevoke(ticket); err != nil {
   441  			return err
   442  		}
   443  		if err = addTx(revoke, true); err != nil {
   444  			return err
   445  		}
   446  
   447  		return nil
   448  	})
   449  }
   450  
   451  func compress() error {
   452  	db, err := os.Open(dbname)
   453  	if err != nil {
   454  		return err
   455  	}
   456  	defer os.Remove(dbname)
   457  	defer db.Close()
   458  	dbgz, err := os.Create(dbname + ".gz")
   459  	if err != nil {
   460  		return err
   461  	}
   462  	defer dbgz.Close()
   463  	gz := gzip.NewWriter(dbgz)
   464  	_, err = io.Copy(gz, db)
   465  	if err != nil {
   466  		return err
   467  	}
   468  	return gz.Close()
   469  }