github.com/deso-protocol/core@v1.2.9/lib/block_view_follow_test.go (about)

     1  package lib
     2  
     3  import (
     4  	"fmt"
     5  	"github.com/dgraph-io/badger/v3"
     6  	"github.com/stretchr/testify/assert"
     7  	"github.com/stretchr/testify/require"
     8  	"testing"
     9  )
    10  
    11  func _doFollowTxn(t *testing.T, chain *Blockchain, db *badger.DB,
    12  	params *DeSoParams, feeRateNanosPerKB uint64, senderPkBase58Check string,
    13  	followedPkBase58Check string, senderPrivBase58Check string, isUnfollow bool) (
    14  	_utxoOps []*UtxoOperation, _txn *MsgDeSoTxn, _height uint32, _err error) {
    15  
    16  	assert := assert.New(t)
    17  	require := require.New(t)
    18  	_ = assert
    19  	_ = require
    20  
    21  	senderPkBytes, _, err := Base58CheckDecode(senderPkBase58Check)
    22  	require.NoError(err)
    23  
    24  	followedPkBytes, _, err := Base58CheckDecode(followedPkBase58Check)
    25  	require.NoError(err)
    26  
    27  	utxoView, err := NewUtxoView(db, params, nil)
    28  	require.NoError(err)
    29  
    30  	txn, totalInputMake, changeAmountMake, feesMake, err := chain.CreateFollowTxn(
    31  		senderPkBytes, followedPkBytes, isUnfollow, feeRateNanosPerKB, nil, []*DeSoOutput{})
    32  	if err != nil {
    33  		return nil, nil, 0, err
    34  	}
    35  
    36  	require.Equal(totalInputMake, changeAmountMake+feesMake)
    37  
    38  	// Sign the transaction now that its inputs are set up.
    39  	_signTxn(t, txn, senderPrivBase58Check)
    40  
    41  	txHash := txn.Hash()
    42  	// Always use height+1 for validation since it's assumed the transaction will
    43  	// get mined into the next block.
    44  	blockHeight := chain.blockTip().Height + 1
    45  	utxoOps, totalInput, totalOutput, fees, err :=
    46  		utxoView.ConnectTransaction(txn, txHash, getTxnSize(*txn), blockHeight, true /*verifySignature*/, false /*ignoreUtxos*/)
    47  	// ConnectTransaction should treat the amount locked as contributing to the
    48  	// output.
    49  	if err != nil {
    50  		return nil, nil, 0, err
    51  	}
    52  	require.Equal(totalInput, totalOutput+fees)
    53  	require.Equal(totalInput, totalInputMake)
    54  
    55  	// We should have one SPEND UtxoOperation for each input, one ADD operation
    56  	// for each output, and one OperationTypeFollow operation at the end.
    57  	require.Equal(len(txn.TxInputs)+len(txn.TxOutputs)+1, len(utxoOps))
    58  	for ii := 0; ii < len(txn.TxInputs); ii++ {
    59  		require.Equal(OperationTypeSpendUtxo, utxoOps[ii].Type)
    60  	}
    61  	require.Equal(OperationTypeFollow, utxoOps[len(utxoOps)-1].Type)
    62  
    63  	require.NoError(utxoView.FlushToDb())
    64  
    65  	return utxoOps, txn, blockHeight, nil
    66  }
    67  
    68  func TestFollowTxns(t *testing.T) {
    69  	assert := assert.New(t)
    70  	require := require.New(t)
    71  	_ = assert
    72  	_ = require
    73  
    74  	chain, params, db := NewLowDifficultyBlockchain()
    75  	mempool, miner := NewTestMiner(t, chain, params, true /*isSender*/)
    76  	// Make m3 a paramUpdater for this test
    77  	params.ParamUpdaterPublicKeys[MakePkMapKey(m3PkBytes)] = true
    78  
    79  	// Mine a few blocks to give the senderPkString some money.
    80  	_, err := miner.MineAndProcessSingleBlock(0 /*threadIndex*/, mempool)
    81  	require.NoError(err)
    82  	_, err = miner.MineAndProcessSingleBlock(0 /*threadIndex*/, mempool)
    83  	require.NoError(err)
    84  	_, err = miner.MineAndProcessSingleBlock(0 /*threadIndex*/, mempool)
    85  	require.NoError(err)
    86  	_, err = miner.MineAndProcessSingleBlock(0 /*threadIndex*/, mempool)
    87  	require.NoError(err)
    88  
    89  	// Setup some convenience functions for the test.
    90  	txnOps := [][]*UtxoOperation{}
    91  	txns := []*MsgDeSoTxn{}
    92  	expectedSenderBalances := []uint64{}
    93  	expectedRecipientBalances := []uint64{}
    94  
    95  	// We take the block tip to be the blockchain height rather than the
    96  	// header chain height.
    97  	savedHeight := chain.blockTip().Height + 1
    98  	registerOrTransfer := func(username string,
    99  		senderPk string, recipientPk string, senderPriv string) {
   100  
   101  		expectedSenderBalances = append(
   102  			expectedSenderBalances, _getBalance(t, chain, nil, senderPkString))
   103  		expectedRecipientBalances = append(
   104  			expectedRecipientBalances, _getBalance(t, chain, nil, recipientPkString))
   105  
   106  		currentOps, currentTxn, _ := _doBasicTransferWithViewFlush(
   107  			t, chain, db, params, senderPk, recipientPk,
   108  			senderPriv, 7 /*amount to send*/, 11 /*feerate*/)
   109  
   110  		txnOps = append(txnOps, currentOps)
   111  		txns = append(txns, currentTxn)
   112  	}
   113  
   114  	// Fund all the keys.
   115  	registerOrTransfer("", senderPkString, m0Pub, senderPrivString)
   116  	registerOrTransfer("", senderPkString, m0Pub, senderPrivString)
   117  	registerOrTransfer("", senderPkString, m1Pub, senderPrivString)
   118  	registerOrTransfer("", senderPkString, m1Pub, senderPrivString)
   119  	registerOrTransfer("", senderPkString, m1Pub, senderPrivString)
   120  	registerOrTransfer("", senderPkString, m1Pub, senderPrivString)
   121  	registerOrTransfer("", senderPkString, m1Pub, senderPrivString)
   122  	registerOrTransfer("", senderPkString, m1Pub, senderPrivString)
   123  	registerOrTransfer("", senderPkString, m2Pub, senderPrivString)
   124  	registerOrTransfer("", senderPkString, m2Pub, senderPrivString)
   125  	registerOrTransfer("", senderPkString, m2Pub, senderPrivString)
   126  	registerOrTransfer("", senderPkString, m2Pub, senderPrivString)
   127  	registerOrTransfer("", senderPkString, m3Pub, senderPrivString)
   128  	registerOrTransfer("", senderPkString, m3Pub, senderPrivString)
   129  	registerOrTransfer("", senderPkString, m3Pub, senderPrivString)
   130  	registerOrTransfer("", senderPkString, m3Pub, senderPrivString)
   131  
   132  	doFollowTxn := func(
   133  		senderPkBase58Check string, followedPkBase58Check string,
   134  		senderPrivBase58Check string, isUnfollow bool, feeRateNanosPerKB uint64) {
   135  
   136  		expectedSenderBalances = append(
   137  			expectedSenderBalances, _getBalance(t, chain, nil, senderPkString))
   138  		expectedRecipientBalances = append(
   139  			expectedRecipientBalances, _getBalance(t, chain, nil, recipientPkString))
   140  
   141  		currentOps, currentTxn, _, err := _doFollowTxn(
   142  			t, chain, db, params, feeRateNanosPerKB, senderPkBase58Check,
   143  			followedPkBase58Check, senderPrivBase58Check, isUnfollow)
   144  		require.NoError(err)
   145  
   146  		txnOps = append(txnOps, currentOps)
   147  		txns = append(txns, currentTxn)
   148  	}
   149  
   150  	updateProfile := func(
   151  		feeRateNanosPerKB uint64, updaterPkBase58Check string,
   152  		updaterPrivBase58Check string, profilePubKey []byte, newUsername string,
   153  		newDescription string, newProfilePic string, newCreatorBasisPoints uint64,
   154  		newStakeMultipleBasisPoints uint64, isHidden bool) {
   155  
   156  		expectedSenderBalances = append(
   157  			expectedSenderBalances, _getBalance(t, chain, nil, senderPkString))
   158  		expectedRecipientBalances = append(
   159  			expectedRecipientBalances, _getBalance(t, chain, nil, recipientPkString))
   160  
   161  		currentOps, currentTxn, _, err := _updateProfile(
   162  			t, chain, db, params,
   163  			feeRateNanosPerKB, updaterPkBase58Check,
   164  			updaterPrivBase58Check, profilePubKey, newUsername,
   165  			newDescription, newProfilePic, newCreatorBasisPoints,
   166  			newStakeMultipleBasisPoints, isHidden)
   167  
   168  		require.NoError(err)
   169  
   170  		txnOps = append(txnOps, currentOps)
   171  		txns = append(txns, currentTxn)
   172  	}
   173  
   174  	// Attempting to do "m0 -> m1" should fail since m1 doesn't have a profile yet.
   175  	_, _, _, err = _doFollowTxn(
   176  		t, chain, db, params, 10 /*feeRateNanosPerKB*/, m0Pub,
   177  		m1Pub, m0Priv, false /*isUnfollow*/)
   178  	require.Error(err)
   179  	require.Contains(err.Error(), RuleErrorFollowingNonexistentProfile)
   180  
   181  	// Add profiles so they can be followed.
   182  	updateProfile(
   183  		5,             /*feeRateNanosPerKB*/
   184  		m1Pub,         /*updaterPkBase58Check*/
   185  		m1Priv,        /*updaterPrivBase58Check*/
   186  		[]byte{},      /*profilePubKey*/
   187  		"m1",          /*newUsername*/
   188  		"i am the m1", /*newDescription*/
   189  		shortPic,      /*newProfilePic*/
   190  		0,             /*newCreatorBasisPoints*/
   191  		1.25*100*100,  /*newStakeMultipleBasisPoints*/
   192  		false /*isHidden*/)
   193  
   194  	updateProfile(
   195  		5,             /*feeRateNanosPerKB*/
   196  		m2Pub,         /*updaterPkBase58Check*/
   197  		m2Priv,        /*updaterPrivBase58Check*/
   198  		[]byte{},      /*profilePubKey*/
   199  		"m2",          /*newUsername*/
   200  		"i am the m2", /*newDescription*/
   201  		shortPic,      /*newProfilePic*/
   202  		0,             /*newCreatorBasisPoints*/
   203  		1.25*100*100,  /*newStakeMultipleBasisPoints*/
   204  		false /*isHidden*/)
   205  
   206  	updateProfile(
   207  		5,             /*feeRateNanosPerKB*/
   208  		m3Pub,         /*updaterPkBase58Check*/
   209  		m3Priv,        /*updaterPrivBase58Check*/
   210  		[]byte{},      /*profilePubKey*/
   211  		"m3",          /*newUsername*/
   212  		"i am the m3", /*newDescription*/
   213  		shortPic,      /*newProfilePic*/
   214  		0,             /*newCreatorBasisPoints*/
   215  		1.25*100*100,  /*newStakeMultipleBasisPoints*/
   216  		false /*isHidden*/)
   217  
   218  	// m0 -> m1
   219  	doFollowTxn(m0Pub, m1Pub, m0Priv, false /*isUnfollow*/, 10 /*feeRateNanosPerKB*/)
   220  
   221  	// Duplicating "m0 -> m1" should fail.
   222  	_, _, _, err = _doFollowTxn(
   223  		t, chain, db, params, 10 /*feeRateNanosPerKB*/, m0Pub,
   224  		m1Pub, m0Priv, false /*isUnfollow*/)
   225  	require.Error(err)
   226  	require.Contains(err.Error(), RuleErrorFollowEntryAlreadyExists)
   227  
   228  	// m2 -> m1
   229  	doFollowTxn(m2Pub, m1Pub, m2Priv, false /*isUnfollow*/, 10 /*feeRateNanosPerKB*/)
   230  
   231  	// m3 -> m1
   232  	doFollowTxn(m3Pub, m1Pub, m3Priv, false /*isUnfollow*/, 10 /*feeRateNanosPerKB*/)
   233  
   234  	// m3 -> m2
   235  	doFollowTxn(m3Pub, m2Pub, m3Priv, false /*isUnfollow*/, 10 /*feeRateNanosPerKB*/)
   236  
   237  	// m1 -> m2
   238  	doFollowTxn(m1Pub, m2Pub, m1Priv, false /*isUnfollow*/, 10 /*feeRateNanosPerKB*/)
   239  
   240  	// m2 -> m3
   241  	doFollowTxn(m2Pub, m3Pub, m2Priv, false /*isUnfollow*/, 10 /*feeRateNanosPerKB*/)
   242  
   243  	followingM1 := [][]byte{
   244  		_strToPk(t, m0Pub),
   245  		_strToPk(t, m2Pub),
   246  		_strToPk(t, m3Pub),
   247  	}
   248  
   249  	followingM2 := [][]byte{
   250  		_strToPk(t, m1Pub),
   251  		_strToPk(t, m3Pub),
   252  	}
   253  
   254  	followingM3 := [][]byte{
   255  		_strToPk(t, m2Pub),
   256  	}
   257  
   258  	// Verify m0 has no follows.
   259  	{
   260  		followPks, err := DbGetPubKeysFollowingYou(db, _strToPk(t, m0Pub))
   261  		require.NoError(err)
   262  		require.Equal(0, len(followPks))
   263  	}
   264  
   265  	// Verify pks following and check like count m1.
   266  	{
   267  		followPks, err := DbGetPubKeysFollowingYou(db, _strToPk(t, m1Pub))
   268  		require.NoError(err)
   269  		require.Equal(len(followingM1), len(followPks))
   270  		for ii := 0; ii < len(followPks); ii++ {
   271  			require.Contains(followingM1, followPks[ii])
   272  		}
   273  	}
   274  
   275  	// Verify pks following and check like count m2.
   276  	{
   277  		followPks, err := DbGetPubKeysFollowingYou(db, _strToPk(t, m2Pub))
   278  		require.NoError(err)
   279  		require.Equal(len(followingM2), len(followPks))
   280  		for ii := 0; ii < len(followPks); ii++ {
   281  			require.Contains(followingM2, followPks[ii])
   282  		}
   283  	}
   284  
   285  	// Verify pks following and check like count m3.
   286  	{
   287  		followPks, err := DbGetPubKeysFollowingYou(db, _strToPk(t, m3Pub))
   288  		require.NoError(err)
   289  		require.Equal(len(followingM3), len(followPks))
   290  		for ii := 0; ii < len(followPks); ii++ {
   291  			require.Contains(followingM3, followPks[ii])
   292  		}
   293  	}
   294  
   295  	m0Follows := [][]byte{
   296  		_strToPk(t, m1Pub),
   297  	}
   298  
   299  	m1Follows := [][]byte{
   300  		_strToPk(t, m2Pub),
   301  	}
   302  
   303  	m2Follows := [][]byte{
   304  		_strToPk(t, m1Pub),
   305  		_strToPk(t, m3Pub),
   306  	}
   307  
   308  	m3Follows := [][]byte{
   309  		_strToPk(t, m1Pub),
   310  		_strToPk(t, m2Pub),
   311  	}
   312  
   313  	// Verify m0's follows.
   314  	{
   315  		followPks, err := DbGetPubKeysYouFollow(db, _strToPk(t, m0Pub))
   316  		require.NoError(err)
   317  		require.Equal(len(m0Follows), len(followPks))
   318  		for ii := 0; ii < len(followPks); ii++ {
   319  			require.Contains(m0Follows, followPks[ii])
   320  		}
   321  	}
   322  
   323  	// Verify m1's follows.
   324  	{
   325  		followPks, err := DbGetPubKeysYouFollow(db, _strToPk(t, m1Pub))
   326  		require.NoError(err)
   327  		require.Equal(len(m1Follows), len(followPks))
   328  		for ii := 0; ii < len(followPks); ii++ {
   329  			require.Contains(m1Follows, followPks[ii])
   330  		}
   331  	}
   332  
   333  	// Verify m2's follows.
   334  	{
   335  		followPks, err := DbGetPubKeysYouFollow(db, _strToPk(t, m2Pub))
   336  		require.NoError(err)
   337  		require.Equal(len(m2Follows), len(followPks))
   338  		for ii := 0; ii < len(followPks); ii++ {
   339  			require.Contains(m2Follows, followPks[ii])
   340  		}
   341  	}
   342  
   343  	// Verify m3's follows.
   344  	{
   345  		followPks, err := DbGetPubKeysYouFollow(db, _strToPk(t, m3Pub))
   346  		require.NoError(err)
   347  		require.Equal(len(m3Follows), len(followPks))
   348  		for ii := 0; ii < len(followPks); ii++ {
   349  			require.Contains(m3Follows, followPks[ii])
   350  		}
   351  	}
   352  
   353  	// Try an "unfollow".
   354  	//
   355  	// m0 -> m1 (unfollow)
   356  	doFollowTxn(m0Pub, m1Pub, m0Priv, true /*isUnfollow*/, 10 /*feeRateNanosPerKB*/)
   357  
   358  	// m3 -> m2 (unfollow)
   359  	doFollowTxn(m3Pub, m2Pub, m3Priv, true /*isUnfollow*/, 10 /*feeRateNanosPerKB*/)
   360  
   361  	// Duplicating "m0 -> m1" (unfollow) should fail now that the follow entry is deleted.
   362  	_, _, _, err = _doFollowTxn(
   363  		t, chain, db, params, 10 /*feeRateNanosPerKB*/, m0Pub,
   364  		m1Pub, m0Priv, true /*isUnfollow*/)
   365  	require.Error(err)
   366  	require.Contains(err.Error(), RuleErrorCannotUnfollowNonexistentFollowEntry)
   367  
   368  	followingM1 = [][]byte{
   369  		_strToPk(t, m2Pub),
   370  		_strToPk(t, m3Pub),
   371  	}
   372  
   373  	followingM2 = [][]byte{
   374  		_strToPk(t, m1Pub),
   375  	}
   376  
   377  	// Verify pks following and check like count m1.
   378  	{
   379  		followPks, err := DbGetPubKeysFollowingYou(db, _strToPk(t, m1Pub))
   380  		require.NoError(err)
   381  		require.Equal(len(followingM1), len(followPks))
   382  		for ii := 0; ii < len(followPks); ii++ {
   383  			require.Contains(followingM1, followPks[ii])
   384  		}
   385  	}
   386  
   387  	// Verify pks following and check like count m2.
   388  	{
   389  		followPks, err := DbGetPubKeysFollowingYou(db, _strToPk(t, m2Pub))
   390  		require.NoError(err)
   391  		require.Equal(len(followingM2), len(followPks))
   392  		for ii := 0; ii < len(followPks); ii++ {
   393  			require.Contains(followingM2, followPks[ii])
   394  		}
   395  	}
   396  
   397  	m3Follows = [][]byte{
   398  		_strToPk(t, m1Pub),
   399  	}
   400  
   401  	// Verify m0 has no follows.
   402  	{
   403  		followPks, err := DbGetPubKeysYouFollow(db, _strToPk(t, m0Pub))
   404  		require.NoError(err)
   405  		require.Equal(0, len(followPks))
   406  	}
   407  
   408  	// Verify m3's follows.
   409  	{
   410  		followPks, err := DbGetPubKeysYouFollow(db, _strToPk(t, m3Pub))
   411  		require.NoError(err)
   412  		require.Equal(len(m3Follows), len(followPks))
   413  		for ii := 0; ii < len(followPks); ii++ {
   414  			require.Contains(m3Follows, followPks[ii])
   415  		}
   416  	}
   417  
   418  	// ===================================================================================
   419  	// Finish it off with some transactions
   420  	// ===================================================================================
   421  	registerOrTransfer("", senderPkString, m0Pub, senderPrivString)
   422  	registerOrTransfer("", senderPkString, m0Pub, senderPrivString)
   423  	registerOrTransfer("", senderPkString, m0Pub, senderPrivString)
   424  	registerOrTransfer("", senderPkString, m0Pub, senderPrivString)
   425  	registerOrTransfer("", senderPkString, m0Pub, senderPrivString)
   426  	registerOrTransfer("", senderPkString, m0Pub, senderPrivString)
   427  	registerOrTransfer("", senderPkString, m1Pub, senderPrivString)
   428  	registerOrTransfer("", senderPkString, m1Pub, senderPrivString)
   429  	registerOrTransfer("", senderPkString, m1Pub, senderPrivString)
   430  	registerOrTransfer("", senderPkString, m1Pub, senderPrivString)
   431  	registerOrTransfer("", senderPkString, m1Pub, senderPrivString)
   432  	registerOrTransfer("", senderPkString, m1Pub, senderPrivString)
   433  	registerOrTransfer("", m0Pub, m1Pub, m0Priv)
   434  	registerOrTransfer("", m1Pub, m0Pub, m1Priv)
   435  	registerOrTransfer("", m1Pub, m0Pub, m1Priv)
   436  	registerOrTransfer("", m0Pub, m1Pub, m0Priv)
   437  	registerOrTransfer("", m1Pub, m0Pub, m1Priv)
   438  	registerOrTransfer("", m0Pub, m1Pub, m0Priv)
   439  	registerOrTransfer("", m1Pub, m0Pub, m1Priv)
   440  	registerOrTransfer("", m0Pub, m1Pub, m0Priv)
   441  	registerOrTransfer("", m1Pub, m0Pub, m1Priv)
   442  	registerOrTransfer("", m1Pub, m0Pub, m1Priv)
   443  	registerOrTransfer("", m0Pub, m1Pub, m0Priv)
   444  	registerOrTransfer("", m0Pub, m1Pub, m0Priv)
   445  	registerOrTransfer("", m0Pub, m1Pub, m0Priv)
   446  
   447  	// This function tests the final state of applying all transactions to the view.
   448  	testConnectedState := func() {
   449  
   450  		followingM1 = [][]byte{
   451  			_strToPk(t, m2Pub),
   452  			_strToPk(t, m3Pub),
   453  		}
   454  
   455  		followingM2 = [][]byte{
   456  			_strToPk(t, m1Pub),
   457  		}
   458  
   459  		followingM3 := [][]byte{
   460  			_strToPk(t, m2Pub),
   461  		}
   462  
   463  		// Verify m0 has no follows.
   464  		{
   465  			followPks, err := DbGetPubKeysFollowingYou(db, _strToPk(t, m0Pub))
   466  			require.NoError(err)
   467  			require.Equal(0, len(followPks))
   468  		}
   469  
   470  		// Verify pks following and check like count m1.
   471  		{
   472  			followPks, err := DbGetPubKeysFollowingYou(db, _strToPk(t, m1Pub))
   473  			require.NoError(err)
   474  			require.Equal(len(followingM1), len(followPks))
   475  			for ii := 0; ii < len(followPks); ii++ {
   476  				require.Contains(followingM1, followPks[ii])
   477  			}
   478  		}
   479  
   480  		// Verify pks following and check like count m2.
   481  		{
   482  			followPks, err := DbGetPubKeysFollowingYou(db, _strToPk(t, m2Pub))
   483  			require.NoError(err)
   484  			require.Equal(len(followingM2), len(followPks))
   485  			for ii := 0; ii < len(followPks); ii++ {
   486  				require.Contains(followingM2, followPks[ii])
   487  			}
   488  		}
   489  
   490  		// Verify pks following and check like count m3.
   491  		{
   492  			followPks, err := DbGetPubKeysFollowingYou(db, _strToPk(t, m3Pub))
   493  			require.NoError(err)
   494  			require.Equal(len(followingM3), len(followPks))
   495  			for i := 0; i < len(followPks); i++ {
   496  				require.Contains(followingM3, followPks[i])
   497  			}
   498  		}
   499  
   500  		m1Follows := [][]byte{
   501  			_strToPk(t, m2Pub),
   502  		}
   503  
   504  		m2Follows := [][]byte{
   505  			_strToPk(t, m1Pub),
   506  			_strToPk(t, m3Pub),
   507  		}
   508  
   509  		m3Follows = [][]byte{
   510  			_strToPk(t, m1Pub),
   511  		}
   512  
   513  		// Verify m0 has no follows.
   514  		{
   515  			followPks, err := DbGetPubKeysYouFollow(db, _strToPk(t, m0Pub))
   516  			require.NoError(err)
   517  			require.Equal(0, len(followPks))
   518  		}
   519  
   520  		// Verify m1's follows.
   521  		{
   522  			followPks, err := DbGetPubKeysYouFollow(db, _strToPk(t, m1Pub))
   523  			require.NoError(err)
   524  			require.Equal(len(m1Follows), len(followPks))
   525  			for i := 0; i < len(followPks); i++ {
   526  				require.Contains(m1Follows, followPks[i])
   527  			}
   528  		}
   529  
   530  		// Verify m2's follows.
   531  		{
   532  			followPks, err := DbGetPubKeysYouFollow(db, _strToPk(t, m2Pub))
   533  			require.NoError(err)
   534  			require.Equal(len(m2Follows), len(followPks))
   535  			for i := 0; i < len(followPks); i++ {
   536  				require.Contains(m2Follows, followPks[i])
   537  			}
   538  		}
   539  
   540  		// Verify m3's follows.
   541  		{
   542  			followPks, err := DbGetPubKeysYouFollow(db, _strToPk(t, m3Pub))
   543  			require.NoError(err)
   544  			require.Equal(len(m3Follows), len(followPks))
   545  			for i := 0; i < len(followPks); i++ {
   546  				require.Contains(m3Follows, followPks[i])
   547  			}
   548  		}
   549  	}
   550  	testConnectedState()
   551  
   552  	// Roll back all of the above using the utxoOps from each.
   553  	for ii := 0; ii < len(txnOps); ii++ {
   554  		backwardIter := len(txnOps) - 1 - ii
   555  		currentOps := txnOps[backwardIter]
   556  		currentTxn := txns[backwardIter]
   557  		fmt.Printf("Disconnecting transaction with type %v index %d (going backwards)\n", currentTxn.TxnMeta.GetTxnType(), backwardIter)
   558  
   559  		utxoView, err := NewUtxoView(db, params, nil)
   560  		require.NoError(err)
   561  
   562  		currentHash := currentTxn.Hash()
   563  		err = utxoView.DisconnectTransaction(currentTxn, currentHash, currentOps, savedHeight)
   564  		require.NoError(err)
   565  
   566  		require.NoError(utxoView.FlushToDb())
   567  
   568  		// After disconnecting, the balances should be restored to what they
   569  		// were before this transaction was applied.
   570  		require.Equal(int64(expectedSenderBalances[backwardIter]), int64(_getBalance(t, chain, nil, senderPkString)))
   571  		require.Equal(expectedRecipientBalances[backwardIter], _getBalance(t, chain, nil, recipientPkString))
   572  	}
   573  
   574  	// This function is used to test the state after all ops are rolled back.
   575  	testDisconnectedState := func() {
   576  		// Verify that all the pks following you have been deleted.
   577  		{
   578  			followPks, err := DbGetPubKeysFollowingYou(db, _strToPk(t, m0Pub))
   579  			require.NoError(err)
   580  			require.Equal(0, len(followPks))
   581  		}
   582  		{
   583  			followPks, err := DbGetPubKeysFollowingYou(db, _strToPk(t, m1Pub))
   584  			require.NoError(err)
   585  			require.Equal(0, len(followPks))
   586  		}
   587  		{
   588  			followPks, err := DbGetPubKeysFollowingYou(db, _strToPk(t, m2Pub))
   589  			require.NoError(err)
   590  			require.Equal(0, len(followPks))
   591  		}
   592  		{
   593  			followPks, err := DbGetPubKeysFollowingYou(db, _strToPk(t, m3Pub))
   594  			require.NoError(err)
   595  			require.Equal(0, len(followPks))
   596  		}
   597  
   598  		// Verify that all the keys you followed have been deleted.
   599  		{
   600  			followPks, err := DbGetPubKeysYouFollow(db, _strToPk(t, m0Pub))
   601  			require.NoError(err)
   602  			require.Equal(0, len(followPks))
   603  		}
   604  		{
   605  			followPks, err := DbGetPubKeysYouFollow(db, _strToPk(t, m1Pub))
   606  			require.NoError(err)
   607  			require.Equal(0, len(followPks))
   608  		}
   609  		{
   610  			followPks, err := DbGetPubKeysYouFollow(db, _strToPk(t, m2Pub))
   611  			require.NoError(err)
   612  			require.Equal(0, len(followPks))
   613  		}
   614  		{
   615  			followPks, err := DbGetPubKeysYouFollow(db, _strToPk(t, m3Pub))
   616  			require.NoError(err)
   617  			require.Equal(0, len(followPks))
   618  		}
   619  	}
   620  
   621  	testDisconnectedState()
   622  
   623  	// Apply all the transactions to a mempool object and make sure we don't get any
   624  	// errors. Verify the balances align as we go.
   625  	for ii, tx := range txns {
   626  		// See comment above on this transaction.
   627  		fmt.Printf("Adding txn %d of type %v to mempool\n", ii, tx.TxnMeta.GetTxnType())
   628  
   629  		require.Equal(expectedSenderBalances[ii], _getBalance(t, chain, mempool, senderPkString))
   630  		require.Equal(expectedRecipientBalances[ii], _getBalance(t, chain, mempool, recipientPkString))
   631  
   632  		_, err := mempool.ProcessTransaction(tx, false, false, 0, true)
   633  		require.NoError(err, "Problem adding transaction %d to mempool: %v", ii, tx)
   634  	}
   635  
   636  	// Apply all the transactions to a view and flush the view to the db.
   637  	utxoView, err := NewUtxoView(db, params, nil)
   638  	require.NoError(err)
   639  	for ii, txn := range txns {
   640  		fmt.Printf("Adding txn %v of type %v to UtxoView\n", ii, txn.TxnMeta.GetTxnType())
   641  
   642  		// Always use height+1 for validation since it's assumed the transaction will
   643  		// get mined into the next block.
   644  		txHash := txn.Hash()
   645  		blockHeight := chain.blockTip().Height + 1
   646  		_, _, _, _, err :=
   647  			utxoView.ConnectTransaction(txn, txHash, getTxnSize(*txn), blockHeight, true /*verifySignature*/, false /*ignoreUtxos*/)
   648  		require.NoError(err)
   649  	}
   650  	// Flush the utxoView after having added all the transactions.
   651  	require.NoError(utxoView.FlushToDb())
   652  	testConnectedState()
   653  
   654  	// Disconnect the transactions from a single view in the same way as above
   655  	// i.e. without flushing each time.
   656  	utxoView2, err := NewUtxoView(db, params, nil)
   657  	require.NoError(err)
   658  	for ii := 0; ii < len(txnOps); ii++ {
   659  		backwardIter := len(txnOps) - 1 - ii
   660  		fmt.Printf("Disconnecting transaction with index %d (going backwards)\n", backwardIter)
   661  		currentOps := txnOps[backwardIter]
   662  		currentTxn := txns[backwardIter]
   663  
   664  		currentHash := currentTxn.Hash()
   665  		err = utxoView2.DisconnectTransaction(currentTxn, currentHash, currentOps, savedHeight)
   666  		require.NoError(err)
   667  	}
   668  	require.NoError(utxoView2.FlushToDb())
   669  	require.Equal(expectedSenderBalances[0], _getBalance(t, chain, nil, senderPkString))
   670  	require.Equal(expectedRecipientBalances[0], _getBalance(t, chain, nil, recipientPkString))
   671  
   672  	testDisconnectedState()
   673  
   674  	// All the txns should be in the mempool already so mining a block should put
   675  	// all those transactions in it.
   676  	block, err := miner.MineAndProcessSingleBlock(0 /*threadIndex*/, mempool)
   677  	require.NoError(err)
   678  	// Add one for the block reward. Now we have a meaty block.
   679  	require.Equal(len(txnOps)+1, len(block.Txns))
   680  	// Estimate the transaction fees of the tip block in various ways.
   681  	{
   682  		// Threshold above what's in the block should return the default fee at all times.
   683  		require.Equal(int64(0), int64(chain.EstimateDefaultFeeRateNanosPerKB(.1, 0)))
   684  		require.Equal(int64(7), int64(chain.EstimateDefaultFeeRateNanosPerKB(.1, 7)))
   685  		// Threshold below what's in the block should return the max of the median
   686  		// and the minfee. This means with a low minfee the value returned should be
   687  		// higher. And with a high minfee the value returned should be equal to the
   688  		// fee.
   689  		require.Equal(int64(7), int64(chain.EstimateDefaultFeeRateNanosPerKB(0, 7)))
   690  		require.Equal(int64(4), int64(chain.EstimateDefaultFeeRateNanosPerKB(0, 0)))
   691  		require.Equal(int64(7), int64(chain.EstimateDefaultFeeRateNanosPerKB(.01, 7)))
   692  		require.Equal(int64(4), int64(chain.EstimateDefaultFeeRateNanosPerKB(.01, 1)))
   693  	}
   694  
   695  	testConnectedState()
   696  
   697  	// Roll back the block and make sure we don't hit any errors.
   698  	{
   699  		utxoView, err := NewUtxoView(db, params, nil)
   700  		require.NoError(err)
   701  
   702  		// Fetch the utxo operations for the block we're detaching. We need these
   703  		// in order to be able to detach the block.
   704  		hash, err := block.Header.Hash()
   705  		require.NoError(err)
   706  		utxoOps, err := GetUtxoOperationsForBlock(db, hash)
   707  		require.NoError(err)
   708  
   709  		// Compute the hashes for all the transactions.
   710  		txHashes, err := ComputeTransactionHashes(block.Txns)
   711  		require.NoError(err)
   712  		require.NoError(utxoView.DisconnectBlock(block, txHashes, utxoOps))
   713  
   714  		// Flushing the view after applying and rolling back should work.
   715  		require.NoError(utxoView.FlushToDb())
   716  	}
   717  
   718  	testDisconnectedState()
   719  }