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

     1  package lib
     2  
     3  import (
     4  	"encoding/hex"
     5  	"encoding/json"
     6  	"fmt"
     7  	"github.com/dgraph-io/badger/v3"
     8  	"github.com/pkg/errors"
     9  	"github.com/stretchr/testify/assert"
    10  	"github.com/stretchr/testify/require"
    11  	"reflect"
    12  	"sort"
    13  	"testing"
    14  	"time"
    15  )
    16  
    17  func _submitPost(t *testing.T, chain *Blockchain, db *badger.DB,
    18  	params *DeSoParams, feeRateNanosPerKB uint64, updaterPkBase58Check string,
    19  	updaterPrivBase58Check string, postHashToModify []byte,
    20  	parentStakeID []byte,
    21  	bodyObj *DeSoBodySchema,
    22  	repostedPostHash []byte,
    23  	tstampNanos uint64,
    24  	isHidden bool) (
    25  	_utxoOps []*UtxoOperation, _txn *MsgDeSoTxn, _height uint32, _err error) {
    26  
    27  	assert := assert.New(t)
    28  	require := require.New(t)
    29  	_ = assert
    30  	_ = require
    31  
    32  	updaterPkBytes, _, err := Base58CheckDecode(updaterPkBase58Check)
    33  	require.NoError(err)
    34  
    35  	utxoView, err := NewUtxoView(db, params, nil)
    36  	require.NoError(err)
    37  
    38  	body, err := json.Marshal(bodyObj)
    39  	require.NoError(err)
    40  
    41  	isQuotedRepost := false
    42  	if len(repostedPostHash) > 0 && (bodyObj.Body != "" || len(bodyObj.ImageURLs) > 0 || len(bodyObj.VideoURLs) > 0) {
    43  		isQuotedRepost = true
    44  	}
    45  	postExtraData := make(map[string][]byte)
    46  	txn, totalInputMake, changeAmountMake, feesMake, err := chain.CreateSubmitPostTxn(
    47  		updaterPkBytes,
    48  		postHashToModify,
    49  		parentStakeID,
    50  		body,
    51  		repostedPostHash,
    52  		isQuotedRepost,
    53  		tstampNanos,
    54  		postExtraData,
    55  		isHidden,
    56  		feeRateNanosPerKB,
    57  		nil,
    58  		[]*DeSoOutput{})
    59  	if err != nil {
    60  		return nil, nil, 0, err
    61  	}
    62  
    63  	require.Equal(totalInputMake, changeAmountMake+feesMake)
    64  
    65  	// Sign the transaction now that its inputs are set up.
    66  	_signTxn(t, txn, updaterPrivBase58Check)
    67  
    68  	txHash := txn.Hash()
    69  	// Always use height+1 for validation since it's assumed the transaction will
    70  	// get mined into the next block.
    71  	blockHeight := chain.blockTip().Height + 1
    72  	utxoOps, totalInput, totalOutput, fees, err :=
    73  		utxoView.ConnectTransaction(txn, txHash, getTxnSize(*txn), blockHeight, true /*verifySignature*/, false /*ignoreUtxos*/)
    74  	// ConnectTransaction should treat the amount locked as contributing to the
    75  	// output.
    76  	if err != nil {
    77  		return nil, nil, 0, err
    78  	}
    79  	require.Equal(totalInput, totalOutput+fees)
    80  	require.Equal(totalInput, totalInputMake)
    81  
    82  	// We should have one SPEND UtxoOperation for each input, one ADD operation
    83  	// for each output, and one OperationTypePrivateMessage operation at the end.
    84  	require.Equal(len(txn.TxInputs)+len(txn.TxOutputs)+1, len(utxoOps))
    85  	for ii := 0; ii < len(txn.TxInputs); ii++ {
    86  		require.Equal(OperationTypeSpendUtxo, utxoOps[ii].Type)
    87  	}
    88  	require.Equal(OperationTypeSubmitPost, utxoOps[len(utxoOps)-1].Type)
    89  
    90  	require.NoError(utxoView.FlushToDb())
    91  
    92  	return utxoOps, txn, blockHeight, nil
    93  }
    94  
    95  func _submitPostWithTestMeta(
    96  	testMeta *TestMeta,
    97  	feeRateNanosPerKB uint64,
    98  	updaterPkBase58Check string,
    99  	updaterPrivBase58Check string,
   100  	postHashToModify []byte,
   101  	parentStakeID []byte,
   102  	body *DeSoBodySchema,
   103  	repostedPostHash []byte,
   104  	tstampNanos uint64,
   105  	isHidden bool) {
   106  
   107  	testMeta.expectedSenderBalances = append(
   108  		testMeta.expectedSenderBalances, _getBalance(testMeta.t, testMeta.chain, nil, updaterPkBase58Check))
   109  
   110  	currentOps, currentTxn, _, err := _submitPost(
   111  		testMeta.t, testMeta.chain, testMeta.db, testMeta.params, feeRateNanosPerKB,
   112  		updaterPkBase58Check,
   113  		updaterPrivBase58Check,
   114  		postHashToModify,
   115  		parentStakeID,
   116  		body,
   117  		repostedPostHash,
   118  		tstampNanos,
   119  		isHidden)
   120  
   121  	require.NoError(testMeta.t, err)
   122  
   123  	testMeta.txnOps = append(testMeta.txnOps, currentOps)
   124  	testMeta.txns = append(testMeta.txns, currentTxn)
   125  }
   126  
   127  func _giveDeSoDiamonds(t *testing.T, chain *Blockchain, db *badger.DB, params *DeSoParams,
   128  	feeRateNanosPerKB uint64, senderPkBase58Check string, senderPrivBase58Check string,
   129  	diamondPostHash *BlockHash, diamondLevel int64, deleteDiamondLevel bool,
   130  ) (_utxoOps []*UtxoOperation, _txn *MsgDeSoTxn, _height uint32, _err error) {
   131  
   132  	senderPkBytes, _, err := Base58CheckDecode(senderPkBase58Check)
   133  	require.NoError(t, err)
   134  
   135  	utxoView, err := NewUtxoView(db, params, nil)
   136  	require.NoError(t, err)
   137  
   138  	txn, totalInputMake, spendAmount, changeAmountMake, feesMake, err := chain.CreateBasicTransferTxnWithDiamonds(
   139  		senderPkBytes,
   140  		diamondPostHash,
   141  		diamondLevel,
   142  		feeRateNanosPerKB,
   143  		nil,
   144  		[]*DeSoOutput{})
   145  	if err != nil {
   146  		return nil, nil, 0, err
   147  	}
   148  
   149  	require.Equal(t, totalInputMake, spendAmount+changeAmountMake+feesMake)
   150  
   151  	// For testing purposes.
   152  	if deleteDiamondLevel {
   153  		delete(txn.ExtraData, DiamondLevelKey)
   154  	}
   155  
   156  	// Sign the transaction now that its inputs are set up.
   157  	_signTxn(t, txn, senderPrivBase58Check)
   158  
   159  	txHash := txn.Hash()
   160  	// Always use height+1 for validation since it's assumed the transaction will
   161  	// get mined into the next block.
   162  	blockHeight := chain.blockTip().Height + 1
   163  	utxoOps, totalInput, totalOutput, fees, err :=
   164  		utxoView.ConnectTransaction(txn, txHash, getTxnSize(*txn), blockHeight, true /*verifySignature*/, false /*ignoreUtxos*/)
   165  	if err != nil {
   166  		return nil, nil, 0, err
   167  	}
   168  	require.Equal(t, totalInput, totalOutput+fees)
   169  	require.Equal(t, totalInput, totalInputMake)
   170  
   171  	// We should have one SPEND UtxoOperation for each input, one ADD operation
   172  	// for each output, and one OperationTypeDeSoDiamond operation at the end.
   173  	require.Equal(t, len(txn.TxInputs)+len(txn.TxOutputs)+1, len(utxoOps))
   174  	for ii := 0; ii < len(txn.TxInputs); ii++ {
   175  		require.Equal(t, OperationTypeSpendUtxo, utxoOps[ii].Type)
   176  	}
   177  	require.Equal(t, OperationTypeDeSoDiamond, utxoOps[len(utxoOps)-1].Type)
   178  
   179  	require.NoError(t, utxoView.FlushToDb())
   180  
   181  	return utxoOps, txn, blockHeight, nil
   182  }
   183  
   184  func _giveDeSoDiamondsWithTestMeta(
   185  	testMeta *TestMeta,
   186  	feeRateNanosPerKB uint64,
   187  	senderPkBase58Check string,
   188  	senderPrivBase58Check string,
   189  	postHashToModify *BlockHash,
   190  	diamondLevel int64,
   191  ) {
   192  
   193  	testMeta.expectedSenderBalances = append(
   194  		testMeta.expectedSenderBalances, _getBalance(testMeta.t, testMeta.chain, nil, senderPkBase58Check))
   195  	currentOps, currentTxn, _, err := _giveDeSoDiamonds(
   196  		testMeta.t, testMeta.chain, testMeta.db, testMeta.params, feeRateNanosPerKB,
   197  		senderPkBase58Check,
   198  		senderPrivBase58Check,
   199  		postHashToModify,
   200  		diamondLevel,
   201  		false,
   202  	)
   203  	require.NoError(testMeta.t, err)
   204  
   205  	testMeta.txnOps = append(testMeta.txnOps, currentOps)
   206  	testMeta.txns = append(testMeta.txns, currentTxn)
   207  }
   208  
   209  func _doSubmitPostTxn(t *testing.T, chain *Blockchain, db *badger.DB,
   210  	params *DeSoParams, feeRateNanosPerKB uint64,
   211  	UpdaterPublicKeyBase58Check string, UpdaterPrivateKeyBase58Check string,
   212  	postHashToModify []byte,
   213  	parentPostHashBytes []byte,
   214  	body string,
   215  	extraData map[string][]byte,
   216  	isHidden bool) (
   217  	_utxoOps []*UtxoOperation, _txn *MsgDeSoTxn, _height uint32, _err error) {
   218  
   219  	assert := assert.New(t)
   220  	require := require.New(t)
   221  	_ = assert
   222  	_ = require
   223  
   224  	updaterPkBytes, _, err := Base58CheckDecode(UpdaterPublicKeyBase58Check)
   225  	require.NoError(err)
   226  
   227  	utxoView, err := NewUtxoView(db, params, nil)
   228  	require.NoError(err)
   229  
   230  	txn, totalInputMake, _, _, err := chain.CreateSubmitPostTxn(
   231  		updaterPkBytes,
   232  		postHashToModify,
   233  		parentPostHashBytes,
   234  		[]byte(body),
   235  		nil,
   236  		false,
   237  		uint64(time.Now().UnixNano()),
   238  		extraData,
   239  		isHidden,
   240  		feeRateNanosPerKB,
   241  		nil, /*mempool*/
   242  		[]*DeSoOutput{})
   243  	if err != nil {
   244  		return nil, nil, 0, err
   245  	}
   246  
   247  	// Sign the transaction now that its inputs are set up.
   248  	_signTxn(t, txn, UpdaterPrivateKeyBase58Check)
   249  
   250  	txHash := txn.Hash()
   251  	// Always use height+1 for validation since it's assumed the transaction will
   252  	// get mined into the next block.
   253  	blockHeight := chain.blockTip().Height + 1
   254  	utxoOps, totalInput, totalOutput, fees, err :=
   255  		utxoView.ConnectTransaction(txn, txHash, getTxnSize(*txn), blockHeight, true /*verifySignature*/, false /*ignoreUtxos*/)
   256  	// ConnectTransaction should treat the amount locked as contributing to the
   257  	// output.
   258  	if err != nil {
   259  		return nil, nil, 0, err
   260  	}
   261  	require.Equal(totalInput, totalOutput+fees)
   262  	require.GreaterOrEqual(totalInput, totalInputMake)
   263  
   264  	// We should have one SPEND UtxoOperation for each input, one ADD operation
   265  	// for each output, and one OperationTypeSubmitPost operation at the end.
   266  	require.Equal(len(txn.TxInputs)+len(txn.TxOutputs)+1, len(utxoOps))
   267  	for ii := 0; ii < len(txn.TxInputs); ii++ {
   268  		require.Equal(OperationTypeSpendUtxo, utxoOps[ii].Type)
   269  	}
   270  	require.Equal(OperationTypeSubmitPost, utxoOps[len(utxoOps)-1].Type)
   271  
   272  	require.NoError(utxoView.FlushToDb())
   273  
   274  	return utxoOps, txn, blockHeight, nil
   275  }
   276  
   277  func TestSubmitPost(t *testing.T) {
   278  	assert := assert.New(t)
   279  	require := require.New(t)
   280  	_ = assert
   281  	_ = require
   282  
   283  	chain, params, db := NewLowDifficultyBlockchain()
   284  	mempool, miner := NewTestMiner(t, chain, params, true /*isSender*/)
   285  	// Make m3 a paramUpdater for this test
   286  	params.ParamUpdaterPublicKeys[MakePkMapKey(m3PkBytes)] = true
   287  
   288  	// Mine a few blocks to give the senderPkString some money.
   289  	_, err := miner.MineAndProcessSingleBlock(0 /*threadIndex*/, mempool)
   290  	require.NoError(err)
   291  	_, err = miner.MineAndProcessSingleBlock(0 /*threadIndex*/, mempool)
   292  	require.NoError(err)
   293  	_, err = miner.MineAndProcessSingleBlock(0 /*threadIndex*/, mempool)
   294  	require.NoError(err)
   295  	_, err = miner.MineAndProcessSingleBlock(0 /*threadIndex*/, mempool)
   296  	require.NoError(err)
   297  
   298  	// Setup some convenience functions for the test.
   299  	txnOps := [][]*UtxoOperation{}
   300  	txns := []*MsgDeSoTxn{}
   301  	expectedSenderBalances := []uint64{}
   302  
   303  	// We take the block tip to be the blockchain height rather than the
   304  	// header chain height.
   305  	savedHeight := chain.blockTip().Height + 1
   306  	registerOrTransfer := func(username string,
   307  		senderPk string, recipientPk string, senderPriv string) {
   308  
   309  		expectedSenderBalances = append(expectedSenderBalances, _getBalance(t, chain, nil, senderPk))
   310  
   311  		currentOps, currentTxn, _ := _doBasicTransferWithViewFlush(
   312  			t, chain, db, params, senderPk, recipientPk,
   313  			senderPriv, 70 /*amount to send*/, 11 /*feerate*/)
   314  
   315  		txnOps = append(txnOps, currentOps)
   316  		txns = append(txns, currentTxn)
   317  	}
   318  
   319  	// Fund all the keys.
   320  	registerOrTransfer("", senderPkString, m0Pub, senderPrivString)
   321  	registerOrTransfer("", senderPkString, m1Pub, senderPrivString)
   322  	registerOrTransfer("", senderPkString, m1Pub, senderPrivString)
   323  	registerOrTransfer("", senderPkString, m1Pub, senderPrivString)
   324  	registerOrTransfer("", senderPkString, m1Pub, senderPrivString)
   325  	registerOrTransfer("", senderPkString, m1Pub, senderPrivString)
   326  	registerOrTransfer("", senderPkString, m1Pub, senderPrivString)
   327  	registerOrTransfer("", senderPkString, m2Pub, senderPrivString)
   328  	registerOrTransfer("", senderPkString, m2Pub, senderPrivString)
   329  	registerOrTransfer("", senderPkString, m3Pub, senderPrivString)
   330  	registerOrTransfer("", senderPkString, m3Pub, senderPrivString)
   331  	registerOrTransfer("", senderPkString, m3Pub, senderPrivString)
   332  
   333  	checkPostsDeleted := func() {
   334  		utxoView, err := NewUtxoView(db, params, nil)
   335  		require.NoError(err)
   336  		corePosts, commentsByPostHash, err := utxoView.GetAllPosts()
   337  		require.NoError(err)
   338  		require.Equal(4, len(corePosts))
   339  		totalComments := 0
   340  		for _, currentComment := range commentsByPostHash {
   341  			totalComments += len(currentComment)
   342  		}
   343  		// 3 comments from seed txns
   344  		require.Equal(3, totalComments)
   345  
   346  		require.Equal(0, len(utxoView.RepostKeyToRepostEntry))
   347  
   348  		// TODO: add checks that repost entries are deleted
   349  	}
   350  	checkPostsDeleted()
   351  
   352  	updateProfile := func(
   353  		feeRateNanosPerKB uint64, updaterPkBase58Check string,
   354  		updaterPrivBase58Check string, profilePubKey []byte, newUsername string,
   355  		newDescription string, newProfilePic string, newCreatorBasisPoints uint64,
   356  		newStakeMultipleBasisPoints uint64, isHidden bool) {
   357  
   358  		expectedSenderBalances = append(expectedSenderBalances, _getBalance(t, chain, nil, updaterPkBase58Check))
   359  
   360  		currentOps, currentTxn, _, err := _updateProfile(
   361  			t, chain, db, params,
   362  			feeRateNanosPerKB, updaterPkBase58Check,
   363  			updaterPrivBase58Check, profilePubKey, newUsername,
   364  			newDescription, newProfilePic, newCreatorBasisPoints,
   365  			newStakeMultipleBasisPoints, isHidden)
   366  
   367  		require.NoError(err)
   368  
   369  		txnOps = append(txnOps, currentOps)
   370  		txns = append(txns, currentTxn)
   371  	}
   372  	_, _, _ = m2Priv, m3Priv, updateProfile
   373  
   374  	submitPost := func(
   375  		feeRateNanosPerKB uint64, updaterPkBase58Check string,
   376  		updaterPrivBase58Check string,
   377  		postHashToModify []byte,
   378  		parentStakeID []byte,
   379  		body *DeSoBodySchema,
   380  		repostedPostHash []byte,
   381  		tstampNanos uint64,
   382  		isHidden bool) {
   383  
   384  		expectedSenderBalances = append(expectedSenderBalances, _getBalance(t, chain, nil, updaterPkBase58Check))
   385  
   386  		currentOps, currentTxn, _, err := _submitPost(
   387  			t, chain, db, params, feeRateNanosPerKB,
   388  			updaterPkBase58Check,
   389  			updaterPrivBase58Check,
   390  			postHashToModify,
   391  			parentStakeID,
   392  			body,
   393  			repostedPostHash,
   394  			tstampNanos,
   395  			isHidden)
   396  
   397  		require.NoError(err)
   398  
   399  		txnOps = append(txnOps, currentOps)
   400  		txns = append(txns, currentTxn)
   401  	}
   402  	_ = submitPost
   403  
   404  	swapIdentity := func(
   405  		feeRateNanosPerKB uint64, updaterPkBase58Check string,
   406  		updaterPrivBase58Check string,
   407  		fromPkBytes []byte,
   408  		toPkBytes []byte) {
   409  
   410  		expectedSenderBalances = append(expectedSenderBalances, _getBalance(t, chain, nil, updaterPkBase58Check))
   411  
   412  		currentOps, currentTxn, _, err := _swapIdentity(
   413  			t, chain, db, params, feeRateNanosPerKB,
   414  			updaterPkBase58Check,
   415  			updaterPrivBase58Check,
   416  			fromPkBytes, toPkBytes)
   417  
   418  		require.NoError(err)
   419  
   420  		txnOps = append(txnOps, currentOps)
   421  		txns = append(txns, currentTxn)
   422  	}
   423  
   424  	// Creating a post from an unregistered profile should succeed
   425  	{
   426  		submitPost(
   427  			10,       /*feeRateNanosPerKB*/
   428  			m0Pub,    /*updaterPkBase58Check*/
   429  			m0Priv,   /*updaterPrivBase58Check*/
   430  			[]byte{}, /*postHashToModify*/
   431  			[]byte{}, /*parentStakeID*/
   432  			&DeSoBodySchema{Body: "m0 post body 1 no profile"}, /*body*/
   433  			[]byte{},
   434  			1502947011*1e9, /*tstampNanos*/
   435  			false /*isHidden*/)
   436  	}
   437  	post1Txn := txns[len(txns)-1]
   438  	post1Hash := post1Txn.Hash()
   439  	_, _ = post1Txn, post1Hash
   440  
   441  	{
   442  		submitPost(
   443  			10,       /*feeRateNanosPerKB*/
   444  			m0Pub,    /*updaterPkBase58Check*/
   445  			m0Priv,   /*updaterPrivBase58Check*/
   446  			[]byte{}, /*postHashToModify*/
   447  			[]byte{}, /*parentStakeID*/
   448  			&DeSoBodySchema{Body: "m0 post body 2 no profile"}, /*body*/
   449  			[]byte{},
   450  			1502947012*1e9, /*tstampNanos*/
   451  			false /*isHidden*/)
   452  	}
   453  	post2Txn := txns[len(txns)-1]
   454  	post2Hash := post2Txn.Hash()
   455  	_, _ = post2Txn, post2Hash
   456  
   457  	{
   458  		submitPost(
   459  			10,       /*feeRateNanosPerKB*/
   460  			m1Pub,    /*updaterPkBase58Check*/
   461  			m1Priv,   /*updaterPrivBase58Check*/
   462  			[]byte{}, /*postHashToModify*/
   463  			[]byte{}, /*parentStakeID*/
   464  			&DeSoBodySchema{Body: "m1 post body 1 no profile"}, /*body*/
   465  			[]byte{},
   466  			1502947013*1e9, /*tstampNanos*/
   467  			false /*isHidden*/)
   468  	}
   469  	post3Txn := txns[len(txns)-1]
   470  	post3Hash := post3Txn.Hash()
   471  	_, _ = post3Txn, post3Hash
   472  
   473  	// Creating a post from a registered profile should succeed
   474  	{
   475  		updateProfile(
   476  			1,             /*feeRateNanosPerKB*/
   477  			m2Pub,         /*updaterPkBase58Check*/
   478  			m2Priv,        /*updaterPrivBase58Check*/
   479  			[]byte{},      /*profilePubKey*/
   480  			"m2",          /*newUsername*/
   481  			"i am the m2", /*newDescription*/
   482  			shortPic,      /*newProfilePic*/
   483  			10*100,        /*newCreatorBasisPoints*/
   484  			1.25*100*100,  /*newStakeMultipleBasisPoints*/
   485  			false /*isHidden*/)
   486  
   487  		submitPost(
   488  			10,       /*feeRateNanosPerKB*/
   489  			m2Pub,    /*updaterPkBase58Check*/
   490  			m2Priv,   /*updaterPrivBase58Check*/
   491  			[]byte{}, /*postHashToModify*/
   492  			[]byte{}, /*parentStakeID*/
   493  			&DeSoBodySchema{Body: "m2 post body 1 WITH profile"}, /*body*/
   494  			[]byte{},
   495  			1502947014*1e9, /*tstampNanos*/
   496  			false /*isHidden*/)
   497  	}
   498  	post4Txn := txns[len(txns)-1]
   499  	post4Hash := post4Txn.Hash()
   500  	_, _ = post4Txn, post4Hash
   501  
   502  	{
   503  		updateProfile(
   504  			1,             /*feeRateNanosPerKB*/
   505  			m3Pub,         /*updaterPkBase58Check*/
   506  			m3Priv,        /*updaterPrivBase58Check*/
   507  			[]byte{},      /*profilePubKey*/
   508  			"m3",          /*newUsername*/
   509  			"i am the m3", /*newDescription*/
   510  			shortPic,      /*newProfilePic*/
   511  			10*100,        /*newCreatorBasisPoints*/
   512  			1.25*100*100,  /*newStakeMultipleBasisPoints*/
   513  			false /*isHidden*/)
   514  
   515  		submitPost(
   516  			10,       /*feeRateNanosPerKB*/
   517  			m3Pub,    /*updaterPkBase58Check*/
   518  			m3Priv,   /*updaterPrivBase58Check*/
   519  			[]byte{}, /*postHashToModify*/
   520  			[]byte{}, /*parentStakeID*/
   521  			&DeSoBodySchema{Body: "m3 post body 1 WITH profile"}, /*body*/
   522  			[]byte{},
   523  			1502947015*1e9, /*tstampNanos*/
   524  			false /*isHidden*/)
   525  	}
   526  	post5Txn := txns[len(txns)-1]
   527  	post5Hash := post5Txn.Hash()
   528  	_, _ = post5Txn, post5Hash
   529  
   530  	// Create another post for m2
   531  	{
   532  		submitPost(
   533  			10,       /*feeRateNanosPerKB*/
   534  			m2Pub,    /*updaterPkBase58Check*/
   535  			m2Priv,   /*updaterPrivBase58Check*/
   536  			[]byte{}, /*postHashToModify*/
   537  			[]byte{}, /*parentStakeID*/
   538  			&DeSoBodySchema{Body: "m2 post body 2 WITH profile"}, /*body*/
   539  			[]byte{},
   540  			1502947016*1e9, /*tstampNanos*/
   541  			false /*isHidden*/)
   542  	}
   543  	post6Txn := txns[len(txns)-1]
   544  	post6Hash := post6Txn.Hash()
   545  	_, _ = post6Txn, post6Hash
   546  
   547  	// A zero input post should fail
   548  	{
   549  		_, _, _, err := _submitPost(
   550  			t, chain, db, params,
   551  			0,        /*feeRateNanosPerKB*/
   552  			m0Pub,    /*updaterPkBase58Check*/
   553  			m0Priv,   /*updaterPrivBase58Check*/
   554  			[]byte{}, /*postHashToModify*/
   555  			[]byte{}, /*parentStakeID*/
   556  			&DeSoBodySchema{Body: "this is a post body"}, /*body*/
   557  			[]byte{},
   558  			1502947011*1e9, /*tstampNanos*/
   559  			false /*isHidden*/)
   560  		require.Error(err)
   561  		require.Contains(err.Error(), RuleErrorTxnMustHaveAtLeastOneInput)
   562  	}
   563  
   564  	// PostHashToModify with bad length
   565  	{
   566  		_, _, _, err := _submitPost(
   567  			t, chain, db, params,
   568  			10,                           /*feeRateNanosPerKB*/
   569  			m0Pub,                        /*updaterPkBase58Check*/
   570  			m0Priv,                       /*updaterPrivBase58Check*/
   571  			RandomBytes(HashSizeBytes-1), /*postHashToModify*/
   572  			[]byte{},                     /*parentStakeID*/
   573  			&DeSoBodySchema{Body: "this is a post body"}, /*body*/
   574  			[]byte{},
   575  			1502947048*1e9, /*tstampNanos*/
   576  			false /*isHidden*/)
   577  		require.Error(err)
   578  		require.Contains(err.Error(), RuleErrorSubmitPostInvalidPostHashToModify)
   579  	}
   580  
   581  	// Setting PostHashToModify should fail for a non-existent post
   582  	{
   583  		_, _, _, err := _submitPost(
   584  			t, chain, db, params,
   585  			10,                         /*feeRateNanosPerKB*/
   586  			m0Pub,                      /*updaterPkBase58Check*/
   587  			m0Priv,                     /*updaterPrivBase58Check*/
   588  			RandomBytes(HashSizeBytes), /*postHashToModify*/
   589  			[]byte{},                   /*parentStakeID*/
   590  			&DeSoBodySchema{Body: "this is a post body"}, /*body*/
   591  			[]byte{},
   592  			1502947048*1e9, /*tstampNanos*/
   593  			false /*isHidden*/)
   594  		require.Error(err)
   595  		require.Contains(err.Error(), RuleErrorSubmitPostModifyingNonexistentPost)
   596  	}
   597  
   598  	// Bad length for parent stake id should fail
   599  	{
   600  		_, _, _, err := _submitPost(
   601  			t, chain, db, params,
   602  			10,                           /*feeRateNanosPerKB*/
   603  			m0Pub,                        /*updaterPkBase58Check*/
   604  			m0Priv,                       /*updaterPrivBase58Check*/
   605  			[]byte{},                     /*postHashToModify*/
   606  			RandomBytes(HashSizeBytes-1), /*parentStakeID*/
   607  			&DeSoBodySchema{Body: "this is a post body"}, /*body*/
   608  			[]byte{},
   609  			1502947048*1e9, /*tstampNanos*/
   610  			false /*isHidden*/)
   611  		require.Error(err)
   612  		require.Contains(err.Error(), RuleErrorSubmitPostInvalidParentStakeIDLength)
   613  	}
   614  
   615  	// Non-owner modifying post should fail
   616  	{
   617  		_, _, _, err := _submitPost(
   618  			t, chain, db, params,
   619  			10,           /*feeRateNanosPerKB*/
   620  			m1Pub,        /*updaterPkBase58Check*/
   621  			m1Priv,       /*updaterPrivBase58Check*/
   622  			post1Hash[:], /*postHashToModify*/
   623  			[]byte{},     /*parentStakeID*/
   624  			&DeSoBodySchema{Body: "this is a post body"}, /*body*/
   625  			[]byte{},
   626  			1502947048*1e9, /*tstampNanos*/
   627  			false /*isHidden*/)
   628  		require.Error(err)
   629  		require.Contains(err.Error(), RuleErrorSubmitPostPostModificationNotAuthorized)
   630  	}
   631  
   632  	// Zero timestamp should fail
   633  	{
   634  		_, _, _, err = _submitPost(
   635  			t, chain, db, params,
   636  			10,       /*feeRateNanosPerKB*/
   637  			m1Pub,    /*updaterPkBase58Check*/
   638  			m1Priv,   /*updaterPrivBase58Check*/
   639  			[]byte{}, /*postHashToModify*/
   640  			[]byte{}, /*parentStakeID*/
   641  			&DeSoBodySchema{Body: "this is a post body"}, /*body*/
   642  			[]byte{},
   643  			0, /*tstampNanos*/
   644  			false /*isHidden*/)
   645  		require.Error(err)
   646  		require.Contains(err.Error(), RuleErrorSubmitPostTimestampIsZero)
   647  	}
   648  
   649  	// User without profile modifying another user without profile's post
   650  	// should fail
   651  	{
   652  		_, _, _, err = _submitPost(
   653  			t, chain, db, params,
   654  			10,     /*feeRateNanosPerKB*/
   655  			m0Pub,  /*updaterPkBase58Check*/
   656  			m0Priv, /*updaterPrivBase58Check*/
   657  			// this belongs to m1 who doesn't have a profile.
   658  			post3Hash[:],                            /*postHashToModify*/
   659  			RandomBytes(HashSizeBytes),              /*parentStakeID*/
   660  			&DeSoBodySchema{Body: "m0 post body 2"}, /*body*/
   661  			[]byte{},
   662  			1502947049*1e9, /*tstampNanos*/
   663  			false /*isHidden*/)
   664  		require.Error(err)
   665  		require.Contains(err.Error(), RuleErrorSubmitPostPostModificationNotAuthorized)
   666  	}
   667  
   668  	// User WITH profile modifying another user without profile's post
   669  	// should fail
   670  	{
   671  		_, _, _, err = _submitPost(
   672  			t, chain, db, params,
   673  			10,     /*feeRateNanosPerKB*/
   674  			m2Pub,  /*updaterPkBase58Check*/
   675  			m2Priv, /*updaterPrivBase58Check*/
   676  			// this belongs to m1 who doesn't have a profile.
   677  			post1Hash[:],                            /*postHashToModify*/
   678  			RandomBytes(HashSizeBytes),              /*parentStakeID*/
   679  			&DeSoBodySchema{Body: "m0 post body 2"}, /*body*/
   680  			[]byte{},
   681  			1502947049*1e9, /*tstampNanos*/
   682  			false /*isHidden*/)
   683  		require.Error(err)
   684  		require.Contains(err.Error(), RuleErrorSubmitPostPostModificationNotAuthorized)
   685  	}
   686  
   687  	// User without profile modifying another user WITH profile's post
   688  	// should fail
   689  	{
   690  		_, _, _, err = _submitPost(
   691  			t, chain, db, params,
   692  			10,     /*feeRateNanosPerKB*/
   693  			m0Pub,  /*updaterPkBase58Check*/
   694  			m0Priv, /*updaterPrivBase58Check*/
   695  			// this belongs to m1 who doesn't have a profile.
   696  			post4Hash[:],                            /*postHashToModify*/
   697  			RandomBytes(HashSizeBytes),              /*parentStakeID*/
   698  			&DeSoBodySchema{Body: "m0 post body 2"}, /*body*/
   699  			[]byte{},
   700  			1502947049*1e9, /*tstampNanos*/
   701  			false /*isHidden*/)
   702  		require.Error(err)
   703  		require.Contains(err.Error(), RuleErrorSubmitPostPostModificationNotAuthorized)
   704  	}
   705  
   706  	// User WITH profile modifying another user WITH profile's post
   707  	// should fail
   708  	{
   709  		_, _, _, err = _submitPost(
   710  			t, chain, db, params,
   711  			10,     /*feeRateNanosPerKB*/
   712  			m2Pub,  /*updaterPkBase58Check*/
   713  			m2Priv, /*updaterPrivBase58Check*/
   714  			// this belongs to m1 who doesn't have a profile.
   715  			post5Hash[:],                            /*postHashToModify*/
   716  			RandomBytes(HashSizeBytes),              /*parentStakeID*/
   717  			&DeSoBodySchema{Body: "m0 post body 2"}, /*body*/
   718  			[]byte{},
   719  			1502947049*1e9, /*tstampNanos*/
   720  			false /*isHidden*/)
   721  		require.Error(err)
   722  		require.Contains(err.Error(), RuleErrorSubmitPostPostModificationNotAuthorized)
   723  	}
   724  
   725  	// Owner without profile modifying post should succeed but all the non-body fields
   726  	// should be ignored.
   727  	{
   728  		submitPost(
   729  			10,                         /*feeRateNanosPerKB*/
   730  			m0Pub,                      /*updaterPkBase58Check*/
   731  			m0Priv,                     /*updaterPrivBase58Check*/
   732  			post1Hash[:],               /*postHashToModify*/
   733  			RandomBytes(HashSizeBytes), /*parentStakeID*/
   734  			&DeSoBodySchema{Body: "m0 post body MODIFIED"}, /*body*/
   735  			[]byte{},
   736  			1502947017*1e9, /*tstampNanos*/
   737  			true /*isHidden*/)
   738  	}
   739  
   740  	// Owner with profile modifying one of their posts should succeed but
   741  	// all non-body posts should be unchanged.
   742  	{
   743  		submitPost(
   744  			10,                         /*feeRateNanosPerKB*/
   745  			m2Pub,                      /*updaterPkBase58Check*/
   746  			m2Priv,                     /*updaterPrivBase58Check*/
   747  			post4Hash[:],               /*postHashToModify*/
   748  			RandomBytes(HashSizeBytes), /*parentStakeID*/
   749  			&DeSoBodySchema{Body: "m2 post body MODIFIED"}, /*body*/
   750  			[]byte{},
   751  			1502947018*1e9, /*tstampNanos*/
   752  			true /*isHidden*/)
   753  	}
   754  
   755  	// ParamUpdater modifying their own post should succeed
   756  	{
   757  		submitPost(
   758  			10,                         /*feeRateNanosPerKB*/
   759  			m3Pub,                      /*updaterPkBase58Check*/
   760  			m3Priv,                     /*updaterPrivBase58Check*/
   761  			post5Hash[:],               /*postHashToModify*/
   762  			RandomBytes(HashSizeBytes), /*parentStakeID*/
   763  			&DeSoBodySchema{Body: "paramUpdater post body MODIFIED"}, /*body*/
   764  			[]byte{},
   765  			1502947019*1e9, /*tstampNanos*/
   766  			true /*isHidden*/)
   767  	}
   768  
   769  	// Modifying a post and then modifying it back should work.
   770  	{
   771  		submitPost(
   772  			10,                         /*feeRateNanosPerKB*/
   773  			m1Pub,                      /*updaterPkBase58Check*/
   774  			m1Priv,                     /*updaterPrivBase58Check*/
   775  			post3Hash[:],               /*postHashToModify*/
   776  			RandomBytes(HashSizeBytes), /*parentStakeID*/
   777  			&DeSoBodySchema{Body: "sldkfjlskdfjlajflkasjdflkasjdf"}, /*body*/
   778  			[]byte{},
   779  			1502947022*1e9, /*tstampNanos*/
   780  			true /*isHidden*/)
   781  		submitPost(
   782  			10,                         /*feeRateNanosPerKB*/
   783  			m1Pub,                      /*updaterPkBase58Check*/
   784  			m1Priv,                     /*updaterPrivBase58Check*/
   785  			post3Hash[:],               /*postHashToModify*/
   786  			RandomBytes(HashSizeBytes), /*parentStakeID*/
   787  			&DeSoBodySchema{Body: "m1 post body 1 no profile modified back"}, /*body*/
   788  			[]byte{},
   789  			1502947049*1e9, /*tstampNanos*/
   790  			false /*isHidden*/)
   791  	}
   792  
   793  	// Comment on a post with an anonymous public key
   794  	{
   795  		submitPost(
   796  			10,           /*feeRateNanosPerKB*/
   797  			m0Pub,        /*updaterPkBase58Check*/
   798  			m0Priv,       /*updaterPrivBase58Check*/
   799  			[]byte{},     /*postHashToModify*/
   800  			post3Hash[:], /*parentStakeID*/
   801  			&DeSoBodySchema{Body: "comment 1 from m0 on post3"}, /*body*/
   802  			[]byte{},
   803  			1502947001*1e9, /*tstampNanos*/
   804  			false /*isHidden*/)
   805  	}
   806  	comment1Txn := txns[len(txns)-1]
   807  	comment1Hash := comment1Txn.Hash()
   808  
   809  	// Make a few more comments.
   810  	{
   811  		submitPost(
   812  			10,           /*feeRateNanosPerKB*/
   813  			m0Pub,        /*updaterPkBase58Check*/
   814  			m0Priv,       /*updaterPrivBase58Check*/
   815  			[]byte{},     /*postHashToModify*/
   816  			post6Hash[:], /*parentStakeID*/
   817  			&DeSoBodySchema{Body: "comment 2 from m0 on post3"}, /*body*/
   818  			[]byte{},
   819  			1502947002*1e9, /*tstampNanos*/
   820  			false /*isHidden*/)
   821  	}
   822  	comment2CreatedTxnIndex := len(txns) - 1
   823  	comment2Txn := txns[comment2CreatedTxnIndex]
   824  	comment2Hash := comment2Txn.Hash()
   825  
   826  	{
   827  		submitPost(
   828  			10,           /*feeRateNanosPerKB*/
   829  			m2Pub,        /*updaterPkBase58Check*/
   830  			m2Priv,       /*updaterPrivBase58Check*/
   831  			[]byte{},     /*postHashToModify*/
   832  			post6Hash[:], /*parentStakeID*/
   833  			&DeSoBodySchema{Body: "comment 1 from m2 on post6"}, /*body*/
   834  			[]byte{},
   835  			1502947003*1e9, /*tstampNanos*/
   836  			false /*isHidden*/)
   837  	}
   838  	comment3CreatedTxnIndex := len(txns) - 1
   839  	comment3Txn := txns[comment3CreatedTxnIndex]
   840  	comment3Hash := comment3Txn.Hash()
   841  
   842  	{
   843  		submitPost(
   844  			10,           /*feeRateNanosPerKB*/
   845  			m3Pub,        /*updaterPkBase58Check*/
   846  			m3Priv,       /*updaterPrivBase58Check*/
   847  			[]byte{},     /*postHashToModify*/
   848  			post6Hash[:], /*parentStakeID*/
   849  			&DeSoBodySchema{Body: "comment 1 from m3 on post6"}, /*body*/
   850  			[]byte{},
   851  			1502947004*1e9, /*tstampNanos*/
   852  			false /*isHidden*/)
   853  	}
   854  	comment4CreatedTxnIndex := len(txns) - 1
   855  	comment4Txn := txns[comment4CreatedTxnIndex]
   856  	comment4Hash := comment4Txn.Hash()
   857  
   858  	// Modify some comments
   859  	var comment3HiddenTxnIndex int
   860  	{
   861  		// Modifying the comment with the wrong pub should fail.
   862  		_, _, _, err = _submitPost(
   863  			t, chain, db, params,
   864  			10,              /*feeRateNanosPerKB*/
   865  			m1Pub,           /*updaterPkBase58Check*/
   866  			m1Priv,          /*updaterPrivBase58Check*/
   867  			comment1Hash[:], /*postHashToModify*/
   868  			[]byte{},        /*parentStakeID*/
   869  			&DeSoBodySchema{Body: "modifying comment 1 by m1 should fail"}, /*body*/
   870  			[]byte{},
   871  			1502947049*1e9, /*tstampNanos*/
   872  			false /*isHidden*/)
   873  		require.Error(err)
   874  		require.Contains(err.Error(), RuleErrorSubmitPostPostModificationNotAuthorized)
   875  
   876  		// Modifying the comment with the proper key should work.
   877  		submitPost(
   878  			10,              /*feeRateNanosPerKB*/
   879  			m0Pub,           /*updaterPkBase58Check*/
   880  			m0Priv,          /*updaterPrivBase58Check*/
   881  			comment1Hash[:], /*postHashToModify*/
   882  			[]byte{},        /*parentStakeID*/
   883  			&DeSoBodySchema{Body: "comment from m0 on post3 MODIFIED"}, /*body*/
   884  			[]byte{},
   885  			1502947049*1e9, /*tstampNanos*/
   886  			false /*isHidden*/)
   887  
   888  		// Modifying the comment with the proper key should work.
   889  		submitPost(
   890  			10,              /*feeRateNanosPerKB*/
   891  			m2Pub,           /*updaterPkBase58Check*/
   892  			m2Priv,          /*updaterPrivBase58Check*/
   893  			comment3Hash[:], /*postHashToModify*/
   894  			[]byte{},        /*parentStakeID*/
   895  			&DeSoBodySchema{Body: "comment from m2 on post6 MODIFIED"}, /*body*/
   896  			[]byte{},
   897  			1502947049*1e9, /*tstampNanos*/
   898  			true /*isHidden*/)
   899  		comment3HiddenTxnIndex = len(txns) - 1
   900  
   901  		// Modify a comment and modify it back.
   902  		submitPost(
   903  			10,              /*feeRateNanosPerKB*/
   904  			m0Pub,           /*updaterPkBase58Check*/
   905  			m0Priv,          /*updaterPrivBase58Check*/
   906  			comment2Hash[:], /*postHashToModify*/
   907  			[]byte{},        /*parentStakeID*/
   908  			&DeSoBodySchema{Body: "comment from m0 on post3 MODIFIED"}, /*body*/
   909  			[]byte{},
   910  			1502947049*1e9, /*tstampNanos*/
   911  			true /*isHidden*/)
   912  		submitPost(
   913  			10,              /*feeRateNanosPerKB*/
   914  			m0Pub,           /*updaterPkBase58Check*/
   915  			m0Priv,          /*updaterPrivBase58Check*/
   916  			comment2Hash[:], /*postHashToModify*/
   917  			[]byte{},        /*parentStakeID*/
   918  			&DeSoBodySchema{Body: "comment 2 from m0 on post3"}, /*body*/
   919  			[]byte{},
   920  			1502947049*1e9, /*tstampNanos*/
   921  			false /*isHidden*/)
   922  	}
   923  
   924  	// Commenting on a public key should work regardless of whether
   925  	// a profile actually exists for that stake ID.
   926  	{
   927  		submitPost(
   928  			10,        /*feeRateNanosPerKB*/
   929  			m0Pub,     /*updaterPkBase58Check*/
   930  			m0Priv,    /*updaterPrivBase58Check*/
   931  			[]byte{},  /*postHashToModify*/
   932  			m1PkBytes, /*parentStakeID*/
   933  			&DeSoBodySchema{Body: "comment m0 on profile m1 [1]"}, /*body*/
   934  			[]byte{},
   935  			1502947005*1e9, /*tstampNanos*/
   936  			false /*isHidden*/)
   937  	}
   938  	comment5Txn := txns[len(txns)-1]
   939  	comment5Hash := comment5Txn.Hash()
   940  	{
   941  		submitPost(
   942  			10,        /*feeRateNanosPerKB*/
   943  			m1Pub,     /*updaterPkBase58Check*/
   944  			m1Priv,    /*updaterPrivBase58Check*/
   945  			[]byte{},  /*postHashToModify*/
   946  			m2PkBytes, /*parentStakeID*/
   947  			&DeSoBodySchema{Body: "comment m1 on profile m2 [1]"}, /*body*/
   948  			[]byte{},
   949  			1502947006*1e9, /*tstampNanos*/
   950  			false /*isHidden*/)
   951  	}
   952  	comment6Txn := txns[len(txns)-1]
   953  	comment6Hash := comment6Txn.Hash()
   954  	{
   955  		submitPost(
   956  			10,        /*feeRateNanosPerKB*/
   957  			m3Pub,     /*updaterPkBase58Check*/
   958  			m3Priv,    /*updaterPrivBase58Check*/
   959  			[]byte{},  /*postHashToModify*/
   960  			m3PkBytes, /*parentStakeID*/
   961  			&DeSoBodySchema{Body: "comment m3 on profile m3 [1]"}, /*body*/
   962  			[]byte{},
   963  			1502947007*1e9, /*tstampNanos*/
   964  			false /*isHidden*/)
   965  	}
   966  	comment7Txn := txns[len(txns)-1]
   967  	comment7Hash := comment7Txn.Hash()
   968  	{
   969  		submitPost(
   970  			10,        /*feeRateNanosPerKB*/
   971  			m0Pub,     /*updaterPkBase58Check*/
   972  			m0Priv,    /*updaterPrivBase58Check*/
   973  			[]byte{},  /*postHashToModify*/
   974  			m3PkBytes, /*parentStakeID*/
   975  			&DeSoBodySchema{Body: "comment m0 on profile m3 [2]"}, /*body*/
   976  			[]byte{},
   977  			1502947008*1e9, /*tstampNanos*/
   978  			false /*isHidden*/)
   979  	}
   980  	comment8Txn := txns[len(txns)-1]
   981  	comment8Hash := comment8Txn.Hash()
   982  
   983  	// Modifying the profile comments should work when the key is authorized
   984  	// and fail when it's not.
   985  	{
   986  		submitPost(
   987  			10,              /*feeRateNanosPerKB*/
   988  			m0Pub,           /*updaterPkBase58Check*/
   989  			m0Priv,          /*updaterPrivBase58Check*/
   990  			comment5Hash[:], /*postHashToModify*/
   991  			[]byte{},        /*parentStakeID*/
   992  			&DeSoBodySchema{Body: "comment 1 from m0 on post3 MODIFIED"}, /*body*/
   993  			[]byte{},
   994  			1502947049*1e9, /*tstampNanos*/
   995  			true /*isHidden*/)
   996  
   997  		_, _, _, err = _submitPost(
   998  			t, chain, db, params,
   999  			10,              /*feeRateNanosPerKB*/
  1000  			m1Pub,           /*updaterPkBase58Check*/
  1001  			m1Priv,          /*updaterPrivBase58Check*/
  1002  			comment5Hash[:], /*postHashToModify*/
  1003  			[]byte{},        /*parentStakeID*/
  1004  			&DeSoBodySchema{Body: "modifying comment 1 by m1 should fail"}, /*body*/
  1005  			[]byte{},
  1006  			1502947049*1e9, /*tstampNanos*/
  1007  			false /*isHidden*/)
  1008  		require.Error(err)
  1009  		require.Contains(err.Error(), RuleErrorSubmitPostPostModificationNotAuthorized)
  1010  
  1011  		// Modify a profile comment then modify it back.
  1012  		submitPost(
  1013  			10,              /*feeRateNanosPerKB*/
  1014  			m1Pub,           /*updaterPkBase58Check*/
  1015  			m1Priv,          /*updaterPrivBase58Check*/
  1016  			comment6Hash[:], /*postHashToModify*/
  1017  			[]byte{},        /*parentStakeID*/
  1018  			&DeSoBodySchema{Body: "comment m1 on profile m2 [1] MODIFIED"}, /*body*/
  1019  			[]byte{},
  1020  			1502947049*1e9, /*tstampNanos*/
  1021  			true /*isHidden*/)
  1022  		submitPost(
  1023  			10,              /*feeRateNanosPerKB*/
  1024  			m1Pub,           /*updaterPkBase58Check*/
  1025  			m1Priv,          /*updaterPrivBase58Check*/
  1026  			comment6Hash[:], /*postHashToModify*/
  1027  			[]byte{},        /*parentStakeID*/
  1028  			&DeSoBodySchema{Body: "comment m1 on profile m2 [1]"}, /*body*/
  1029  			[]byte{},
  1030  			1502947049*1e9, /*tstampNanos*/
  1031  			false /*isHidden*/)
  1032  	}
  1033  
  1034  	// Reposting tests
  1035  	// repost 1 - vanilla repost
  1036  	{
  1037  		submitPost(
  1038  			10,       /*feeRateNanosPerKB*/
  1039  			m1Pub,    /*updaterPkBase58Check*/
  1040  			m1Priv,   /*updaterPrivBase58Check*/
  1041  			[]byte{}, /*postHashToModify*/
  1042  			[]byte{}, /*parentStakeID*/
  1043  			&DeSoBodySchema{},
  1044  			post3Hash[:],
  1045  			15029557050*1e9, /*tstampNanos*/
  1046  			false /*isHidden*/)
  1047  
  1048  	}
  1049  	repost1Txn := txns[len(txns)-1]
  1050  	repost1Hash := repost1Txn.Hash()
  1051  	_, _ = repost1Txn, repost1Hash
  1052  	// repost 2 - vanilla repost + hide
  1053  	{
  1054  		submitPost(
  1055  			10,       /*feeRateNanosPerKB*/
  1056  			m1Pub,    /*updaterPkBase58Check*/
  1057  			m1Priv,   /*updaterPrivBase58Check*/
  1058  			[]byte{}, /*postHashToModify*/
  1059  			[]byte{}, /*parentStakeID*/
  1060  			&DeSoBodySchema{},
  1061  			post4Hash[:],
  1062  			15029557051*1e9, /*tstampNanos*/
  1063  			false /*isHidden*/)
  1064  		repost2Txn := txns[len(txns)-1]
  1065  		repost2Hash := repost2Txn.Hash()
  1066  		submitPost(
  1067  			10,             /*feeRateNanosPerKB*/
  1068  			m1Pub,          /*updaterPkBase58Check*/
  1069  			m1Priv,         /*updaterPrivBase58Check*/
  1070  			repost2Hash[:], /*postHashToModify*/
  1071  			[]byte{},       /*parentStakeID*/
  1072  			&DeSoBodySchema{},
  1073  			post4Hash[:],
  1074  			15029557052*1e9, /*tstampNanos*/
  1075  			true /*isHidden*/)
  1076  	}
  1077  	// repost 3 - Quote Repost
  1078  	{
  1079  		submitPost(
  1080  			10,       /*feeRateNanosPerKB*/
  1081  			m1Pub,    /*updaterPkBase58Check*/
  1082  			m1Priv,   /*updaterPrivBase58Check*/
  1083  			[]byte{}, /*postHashToModify*/
  1084  			[]byte{}, /*parentStakeID*/
  1085  			&DeSoBodySchema{Body: "quote-post"},
  1086  			post5Hash[:],
  1087  			15029557053*1e9, /*tstampNanos*/
  1088  			false /*isHidden*/)
  1089  	}
  1090  	// repost 4 - Quote Repost + hide
  1091  	{
  1092  		submitPost(
  1093  			10,       /*feeRateNanosPerKB*/
  1094  			m1Pub,    /*updaterPkBase58Check*/
  1095  			m1Priv,   /*updaterPrivBase58Check*/
  1096  			[]byte{}, /*postHashToModify*/
  1097  			[]byte{}, /*parentStakeID*/
  1098  			&DeSoBodySchema{Body: "quote-post-hide-me"},
  1099  			post6Hash[:],
  1100  			15029557054*1e9, /*tstampNanos*/
  1101  			false /*isHidden*/)
  1102  		repost4Txn := txns[len(txns)-1]
  1103  		repost4hash := repost4Txn.Hash()
  1104  		submitPost(
  1105  			10,             /*feeRateNanosPerKB*/
  1106  			m1Pub,          /*updaterPkBase58Check*/
  1107  			m1Priv,         /*updaterPrivBase58Check*/
  1108  			repost4hash[:], /*postHashToModify*/
  1109  			[]byte{},       /*parentStakeID*/
  1110  			&DeSoBodySchema{Body: "quote-post-hide-me"},
  1111  			post6Hash[:],
  1112  			15029557054*1e9, /*tstampNanos*/
  1113  			true /*isHidden*/)
  1114  	}
  1115  	// repost -- test exceptions
  1116  	{
  1117  		{
  1118  			// Reposting a post that doesn't exist will raise an error.
  1119  			_, _, _, err = _submitPost(t, chain, db, params,
  1120  				10,
  1121  				m1Pub,
  1122  				m1Priv,
  1123  				[]byte{},
  1124  				[]byte{},
  1125  				&DeSoBodySchema{},
  1126  				[]byte{1, 2, 3},
  1127  				15029557055,
  1128  				false,
  1129  			)
  1130  			require.Error(err)
  1131  			require.Contains(err.Error(), RuleErrorSubmitPostRepostPostNotFound)
  1132  		}
  1133  		{
  1134  			// Cannot repost a vanilla repost
  1135  			_, _, _, err = _submitPost(t, chain, db, params,
  1136  				10,
  1137  				m1Pub,
  1138  				m1Priv,
  1139  				[]byte{},
  1140  				[]byte{},
  1141  				&DeSoBodySchema{},
  1142  				repost1Hash[:],
  1143  				15029557055,
  1144  				false,
  1145  			)
  1146  			require.Error(err)
  1147  			require.Contains(err.Error(), RuleErrorSubmitPostRepostOfRepost)
  1148  		}
  1149  		{
  1150  			// Cannot update the repostedPostHashHex
  1151  			_, _, _, err = _submitPost(t, chain, db, params,
  1152  				10,
  1153  				m1Pub,
  1154  				m1Priv,
  1155  				repost1Hash[:],
  1156  				[]byte{},
  1157  				&DeSoBodySchema{},
  1158  				post4Hash[:],
  1159  				15029557055,
  1160  				false,
  1161  			)
  1162  			require.Error(err)
  1163  			require.Contains(err.Error(), RuleErrorSubmitPostUpdateRepostHash)
  1164  		}
  1165  
  1166  	}
  1167  
  1168  	// Swapping the identity of m0 and m1 should not result in any issues.
  1169  	// TODO: This will no longer be the case once posts are a part of the PKID
  1170  	// infrastructure.
  1171  	swapIdentity(
  1172  		10,    /*feeRateNanosPerKB*/
  1173  		m3Pub, // m3 is paramUpdater for this test.
  1174  		m3Priv,
  1175  		m0PkBytes,
  1176  		m1PkBytes)
  1177  
  1178  	// post1: m0 post body MODIFIED
  1179  	// post2: paramUpdater post body MODIFIED
  1180  	// post3: m1 post body 1 no profile modified back (isHidden = false)
  1181  	// post4: m2 post body MODIFIED
  1182  	// post5: paramUpdater post body MODIFIED
  1183  	// post6: paramUpdater m2 post body MODIFIED
  1184  	// comment1: m0 comment from m0 on post3 MODIFIED
  1185  	// comment2: m0 comment 2 from m0 on post3
  1186  	// comment3: comment from m2 on post6 MODIFIED (isHidden = true)
  1187  	// comment4: m3 comment 1 from m3 on post6
  1188  	// comment5: comment 1 from m0 on post3 MODIFIED
  1189  	// comment6: m1 comment m1 on profile m2 [1]
  1190  	// comment7: m3 comment m3 on profile m3 [1]
  1191  	// comment8: m0 comment m0 on profile m3 [2]
  1192  	// Comments for post3
  1193  	// - comment1
  1194  	// Comments for post6
  1195  	// - comment2, comment3, comment4
  1196  	// Coomments for m1
  1197  	// - comment5
  1198  	// Comments profile m2
  1199  	// - comment6
  1200  	// Comments profile m3
  1201  	// - comment7, comment8
  1202  	// - repost1
  1203  	// Reposts post3
  1204  	// - repost 2
  1205  	// reposts post4 and then hides itself -- test RepostCount
  1206  	// - repost 3
  1207  	// quote repost post 5
  1208  	// - repost 4
  1209  	// quote repost post 6 and then hides itself
  1210  
  1211  	comparePostBody := func(postEntry *PostEntry, message string, repostPostHash *BlockHash) {
  1212  		bodyJSONObj := &DeSoBodySchema{}
  1213  		err := json.Unmarshal(postEntry.Body, bodyJSONObj)
  1214  		require.NoError(err)
  1215  		require.Equal(message, bodyJSONObj.Body)
  1216  		if repostPostHash != nil {
  1217  			require.Equal(repostPostHash, postEntry.RepostedPostHash)
  1218  		}
  1219  	}
  1220  
  1221  	checkPostsExist := func() {
  1222  		utxoView, err := NewUtxoView(db, params, nil)
  1223  		require.NoError(err)
  1224  		corePosts, commentsByPostHash, err := utxoView.GetAllPosts()
  1225  		require.NoError(err)
  1226  		// 4 posts from seed txns
  1227  		require.Equal(14, len(corePosts))
  1228  
  1229  		totalComments := 0
  1230  		for _, currentComment := range commentsByPostHash {
  1231  			totalComments += len(currentComment)
  1232  		}
  1233  		// 3 comments from seed txns
  1234  		require.Equal(11, totalComments)
  1235  
  1236  		// post3 should have 1 comment
  1237  		{
  1238  			commentsForPost, exists := commentsByPostHash[*post3Hash]
  1239  			require.True(exists)
  1240  			require.Equal(1, len(commentsForPost))
  1241  
  1242  			require.Equal(m0PkBytes, commentsForPost[0].PosterPublicKey)
  1243  			require.Equal(post3Hash[:], commentsForPost[0].ParentStakeID)
  1244  			require.Equal(*comment1Hash, *commentsForPost[0].PostHash)
  1245  			comparePostBody(commentsForPost[0], "comment from m0 on post3 MODIFIED", nil)
  1246  			require.Equal(false, commentsForPost[0].IsHidden)
  1247  
  1248  			post3 := findPostByPostHash(corePosts, post3Hash)
  1249  			require.Equal(uint64(1), post3.CommentCount)
  1250  		}
  1251  		// post6 should have 3 comments
  1252  		{
  1253  			commentsForPost, err := commentsByPostHash[*post6Hash]
  1254  			require.True(err)
  1255  			require.Equal(3, len(commentsForPost))
  1256  
  1257  			require.Equal(m0PkBytes, commentsForPost[0].PosterPublicKey)
  1258  			require.Equal(post6Hash[:], commentsForPost[0].ParentStakeID)
  1259  			require.Equal(*comment2Hash, *commentsForPost[0].PostHash)
  1260  			comparePostBody(commentsForPost[0], "comment 2 from m0 on post3", nil)
  1261  			require.Equal(false, commentsForPost[0].IsHidden)
  1262  
  1263  			require.Equal(m2PkBytes, commentsForPost[1].PosterPublicKey)
  1264  			require.Equal(post6Hash[:], commentsForPost[1].ParentStakeID)
  1265  			require.Equal(*comment3Hash, *commentsForPost[1].PostHash)
  1266  			comparePostBody(commentsForPost[1], "comment from m2 on post6 MODIFIED", nil)
  1267  			require.Equal(true, commentsForPost[1].IsHidden)
  1268  
  1269  			require.Equal(m3PkBytes, commentsForPost[2].PosterPublicKey)
  1270  			require.Equal(post6Hash[:], commentsForPost[2].ParentStakeID)
  1271  			require.Equal(*comment4Hash, *commentsForPost[2].PostHash)
  1272  			comparePostBody(commentsForPost[2], "comment 1 from m3 on post6", nil)
  1273  			require.Equal(false, commentsForPost[2].IsHidden)
  1274  
  1275  			// Two comments are not hidden, so commentCount should be 2
  1276  			post6 := findPostByPostHash(corePosts, post6Hash)
  1277  			require.Equal(uint64(2), post6.CommentCount)
  1278  		}
  1279  		// m1 should have 1 comment
  1280  		{
  1281  			commentsForPost, err := commentsByPostHash[*NewBlockHash(m1PkBytes)]
  1282  			require.True(err)
  1283  			require.Equal(1, len(commentsForPost))
  1284  
  1285  			require.Equal(m0PkBytes, commentsForPost[0].PosterPublicKey)
  1286  			require.Equal(m1PkBytes[:], commentsForPost[0].ParentStakeID)
  1287  			require.Equal(*comment5Hash, *commentsForPost[0].PostHash)
  1288  			comparePostBody(commentsForPost[0], "comment 1 from m0 on post3 MODIFIED", nil)
  1289  			require.Equal(true, commentsForPost[0].IsHidden)
  1290  		}
  1291  		// m2 should have 1 comment
  1292  		{
  1293  			commentsForPost, err := commentsByPostHash[*NewBlockHash(m2PkBytes)]
  1294  			require.True(err)
  1295  			require.Equal(1, len(commentsForPost))
  1296  
  1297  			require.Equal(m1PkBytes, commentsForPost[0].PosterPublicKey)
  1298  			require.Equal(m2PkBytes[:], commentsForPost[0].ParentStakeID)
  1299  			require.Equal(*comment6Hash, *commentsForPost[0].PostHash)
  1300  			comparePostBody(commentsForPost[0], "comment m1 on profile m2 [1]", nil)
  1301  			require.Equal(false, commentsForPost[0].IsHidden)
  1302  		}
  1303  		// m3 should have 2 comments
  1304  		{
  1305  			commentsForPost, err := commentsByPostHash[*NewBlockHash(m3PkBytes)]
  1306  			require.True(err)
  1307  			require.Equal(2, len(commentsForPost))
  1308  
  1309  			require.Equal(m3PkBytes, commentsForPost[0].PosterPublicKey)
  1310  			require.Equal(m3PkBytes[:], commentsForPost[0].ParentStakeID)
  1311  			require.Equal(*comment7Hash, *commentsForPost[0].PostHash)
  1312  			comparePostBody(commentsForPost[0], "comment m3 on profile m3 [1]", nil)
  1313  			require.Equal(false, commentsForPost[0].IsHidden)
  1314  
  1315  			require.Equal(m0PkBytes, commentsForPost[1].PosterPublicKey)
  1316  			require.Equal(m3PkBytes[:], commentsForPost[1].ParentStakeID)
  1317  			require.Equal(*comment8Hash, *commentsForPost[1].PostHash)
  1318  			comparePostBody(commentsForPost[1], "comment m0 on profile m3 [2]", nil)
  1319  			require.Equal(false, commentsForPost[1].IsHidden)
  1320  		}
  1321  
  1322  		sort.Slice(corePosts, func(ii, jj int) bool {
  1323  			return corePosts[ii].TimestampNanos < corePosts[jj].TimestampNanos
  1324  		})
  1325  
  1326  		{
  1327  			require.Equal(m0PkBytes, corePosts[0].PosterPublicKey)
  1328  			comparePostBody(corePosts[0], "m0 post body MODIFIED", nil)
  1329  			require.Equal(true, corePosts[0].IsHidden)
  1330  			require.Equal(int64(10*100), int64(corePosts[0].CreatorBasisPoints))
  1331  			require.Equal(int64(1.25*100*100), int64(corePosts[0].StakeMultipleBasisPoints))
  1332  		}
  1333  		{
  1334  			require.Equal(m0PkBytes, corePosts[1].PosterPublicKey)
  1335  			comparePostBody(corePosts[1], "m0 post body 2 no profile", nil)
  1336  			require.Equal(false, corePosts[1].IsHidden)
  1337  			require.Equal(int64(10*100), int64(corePosts[1].CreatorBasisPoints))
  1338  			require.Equal(int64(1.25*100*100), int64(corePosts[1].StakeMultipleBasisPoints))
  1339  		}
  1340  		{
  1341  			require.Equal(m1PkBytes, corePosts[2].PosterPublicKey)
  1342  			comparePostBody(corePosts[2], "m1 post body 1 no profile modified back", nil)
  1343  			require.Equal(false, corePosts[2].IsHidden)
  1344  			require.Equal(int64(10*100), int64(corePosts[2].CreatorBasisPoints))
  1345  			require.Equal(int64(1.25*100*100), int64(corePosts[2].StakeMultipleBasisPoints))
  1346  			require.Equal(int64(1), int64(corePosts[2].RepostCount))
  1347  		}
  1348  		{
  1349  			require.Equal(m2PkBytes, corePosts[3].PosterPublicKey)
  1350  			comparePostBody(corePosts[3], "m2 post body MODIFIED", nil)
  1351  			require.Equal(true, corePosts[3].IsHidden)
  1352  			require.Equal(int64(10*100), int64(corePosts[3].CreatorBasisPoints))
  1353  			require.Equal(int64(1.25*100*100), int64(corePosts[3].StakeMultipleBasisPoints))
  1354  			require.Equal(int64(0), int64(corePosts[3].RepostCount))
  1355  		}
  1356  		{
  1357  			require.Equal(m3PkBytes, corePosts[4].PosterPublicKey)
  1358  			comparePostBody(corePosts[4], "paramUpdater post body MODIFIED", nil)
  1359  			require.Equal(true, corePosts[4].IsHidden)
  1360  			require.Equal(int64(10*100), int64(corePosts[4].CreatorBasisPoints))
  1361  			require.Equal(int64(1.25*100*100), int64(corePosts[4].StakeMultipleBasisPoints))
  1362  			// Quote desos do not count towards repost count
  1363  			require.Equal(int64(0), int64(corePosts[4].RepostCount))
  1364  		}
  1365  		{
  1366  			require.Equal(m2PkBytes, corePosts[5].PosterPublicKey)
  1367  			comparePostBody(corePosts[5], "m2 post body 2 WITH profile", nil)
  1368  			require.Equal(false, corePosts[5].IsHidden)
  1369  			require.Equal(int64(10*100), int64(corePosts[5].CreatorBasisPoints))
  1370  			require.Equal(int64(1.25*100*100), int64(corePosts[5].StakeMultipleBasisPoints))
  1371  			// Quote desos do not count towards repost count
  1372  			require.Equal(int64(0), int64(corePosts[5].RepostCount))
  1373  		}
  1374  		{
  1375  			require.Equal(m1PkBytes, corePosts[10].PosterPublicKey)
  1376  			comparePostBody(corePosts[10], "", corePosts[2].PostHash)
  1377  			require.Equal(false, corePosts[10].IsHidden)
  1378  			m1Post2ReaderState := utxoView.GetPostEntryReaderState(m1PkBytes, corePosts[2])
  1379  			require.Equal(m1Post2ReaderState.RepostedByReader, true)
  1380  			require.Equal(m1Post2ReaderState.RepostPostHashHex, hex.EncodeToString(corePosts[10].PostHash[:]))
  1381  			// Make sure the utxoView has the correct repost entry mapping
  1382  			require.Equal(utxoView.RepostKeyToRepostEntry[MakeRepostKey(m1PkBytes, *corePosts[2].PostHash)],
  1383  				&RepostEntry{
  1384  					ReposterPubKey:   m1PkBytes,
  1385  					RepostedPostHash: corePosts[2].PostHash,
  1386  					RepostPostHash:   corePosts[10].PostHash,
  1387  				})
  1388  		}
  1389  		{
  1390  			require.Equal(m1PkBytes, corePosts[11].PosterPublicKey)
  1391  			comparePostBody(corePosts[11], "", corePosts[3].PostHash)
  1392  			require.Equal(true, corePosts[11].IsHidden)
  1393  			m1Post3ReaderState := utxoView.GetPostEntryReaderState(m1PkBytes, corePosts[3])
  1394  			// If we hide the repost, we expect RepostedByReader to be false, but RepostPostHashHex to still be set.
  1395  			require.Equal(m1Post3ReaderState.RepostedByReader, false)
  1396  			require.Equal(m1Post3ReaderState.RepostPostHashHex, hex.EncodeToString(corePosts[11].PostHash[:]))
  1397  			// Make sure the utxoView has the correct repost entry mapping
  1398  			require.Equal(utxoView.RepostKeyToRepostEntry[MakeRepostKey(m1PkBytes, *corePosts[3].PostHash)],
  1399  				&RepostEntry{
  1400  					ReposterPubKey:   m1PkBytes,
  1401  					RepostedPostHash: corePosts[3].PostHash,
  1402  					RepostPostHash:   corePosts[11].PostHash,
  1403  				})
  1404  		}
  1405  		{
  1406  			require.Equal(m1PkBytes, corePosts[12].PosterPublicKey)
  1407  			comparePostBody(corePosts[12], "quote-post", corePosts[4].PostHash)
  1408  			require.Equal(false, corePosts[12].IsHidden)
  1409  			m1Post4ReaderState := utxoView.GetPostEntryReaderState(m1PkBytes, corePosts[4])
  1410  			// Quote reposts do not impact PostEntryReaderState
  1411  			require.Equal(m1Post4ReaderState.RepostedByReader, false)
  1412  			require.Equal(m1Post4ReaderState.RepostPostHashHex, "")
  1413  			// Quote reposts do not make repost entry mappings
  1414  			_, repostEntryExists := utxoView.RepostKeyToRepostEntry[MakeRepostKey(m1PkBytes, *corePosts[4].PostHash)]
  1415  			require.False(repostEntryExists)
  1416  		}
  1417  		{
  1418  			require.Equal(m1PkBytes, corePosts[13].PosterPublicKey)
  1419  			comparePostBody(corePosts[13], "quote-post-hide-me", corePosts[5].PostHash)
  1420  			require.Equal(true, corePosts[13].IsHidden)
  1421  			m1Post5ReaderState := utxoView.GetPostEntryReaderState(m1PkBytes, corePosts[5])
  1422  			// Quote reposts do not impact PostEntryReaderState
  1423  			require.Equal(m1Post5ReaderState.RepostedByReader, false)
  1424  			require.Equal(m1Post5ReaderState.RepostPostHashHex, "")
  1425  			// Quote reposts do not make repost entry mappings
  1426  			_, repostEntryExists := utxoView.RepostKeyToRepostEntry[MakeRepostKey(m1PkBytes, *corePosts[5].PostHash)]
  1427  			require.False(repostEntryExists)
  1428  		}
  1429  	}
  1430  	checkPostsExist()
  1431  
  1432  	// ===================================================================================
  1433  	// Finish it off with some transactions
  1434  	// ===================================================================================
  1435  	registerOrTransfer("", senderPkString, m0Pub, senderPrivString)
  1436  	registerOrTransfer("", senderPkString, m0Pub, senderPrivString)
  1437  	registerOrTransfer("", senderPkString, m0Pub, senderPrivString)
  1438  	registerOrTransfer("", senderPkString, m0Pub, senderPrivString)
  1439  	registerOrTransfer("", senderPkString, m0Pub, senderPrivString)
  1440  	registerOrTransfer("", senderPkString, m0Pub, senderPrivString)
  1441  	registerOrTransfer("", senderPkString, m1Pub, senderPrivString)
  1442  	registerOrTransfer("", senderPkString, m1Pub, senderPrivString)
  1443  	registerOrTransfer("", senderPkString, m1Pub, senderPrivString)
  1444  	registerOrTransfer("", senderPkString, m1Pub, senderPrivString)
  1445  	registerOrTransfer("", senderPkString, m1Pub, senderPrivString)
  1446  	registerOrTransfer("", senderPkString, m1Pub, senderPrivString)
  1447  	registerOrTransfer("", m0Pub, m1Pub, m0Priv)
  1448  	registerOrTransfer("", m1Pub, m0Pub, m1Priv)
  1449  	registerOrTransfer("", m1Pub, m0Pub, m1Priv)
  1450  	registerOrTransfer("", m0Pub, m1Pub, m0Priv)
  1451  	registerOrTransfer("", m1Pub, m0Pub, m1Priv)
  1452  	registerOrTransfer("", m0Pub, m1Pub, m0Priv)
  1453  	registerOrTransfer("", m1Pub, m0Pub, m1Priv)
  1454  	registerOrTransfer("", m0Pub, m1Pub, m0Priv)
  1455  	registerOrTransfer("", m1Pub, m0Pub, m1Priv)
  1456  	registerOrTransfer("", m1Pub, m0Pub, m1Priv)
  1457  	registerOrTransfer("", m0Pub, m1Pub, m0Priv)
  1458  	registerOrTransfer("", m0Pub, m1Pub, m0Priv)
  1459  	registerOrTransfer("", m0Pub, m1Pub, m0Priv)
  1460  
  1461  	// Roll back all of the above using the utxoOps from each.
  1462  	for ii := 0; ii < len(txnOps); ii++ {
  1463  		backwardIter := len(txnOps) - 1 - ii
  1464  		currentOps := txnOps[backwardIter]
  1465  		currentTxn := txns[backwardIter]
  1466  		fmt.Printf("Disconnecting transaction with type %v index %d (going backwards)\n", currentTxn.TxnMeta.GetTxnType(), backwardIter)
  1467  
  1468  		utxoView, err := NewUtxoView(db, params, nil)
  1469  		require.NoError(err)
  1470  
  1471  		currentHash := currentTxn.Hash()
  1472  		err = utxoView.DisconnectTransaction(currentTxn, currentHash, currentOps, savedHeight)
  1473  		require.NoError(err)
  1474  
  1475  		require.NoError(utxoView.FlushToDb())
  1476  
  1477  		// After disconnecting, the balances should be restored to what they
  1478  		// were before this transaction was applied.
  1479  		require.Equal(expectedSenderBalances[backwardIter], _getBalance(t, chain, nil, PkToStringTestnet(currentTxn.PublicKey)))
  1480  	}
  1481  
  1482  	// Verify that all the profiles have been deleted.
  1483  	checkPostsDeleted()
  1484  
  1485  	// Apply all the transactions to a mempool object and make sure we don't get any
  1486  	// errors. Verify the balances align as we go.
  1487  	for ii, tx := range txns {
  1488  		// See comment above on this transaction.
  1489  		fmt.Printf("Adding txn %d of type %v to mempool\n", ii, tx.TxnMeta.GetTxnType())
  1490  
  1491  		require.Equal(expectedSenderBalances[ii], _getBalance(t, chain, mempool, PkToStringTestnet(tx.PublicKey)))
  1492  
  1493  		_, err := mempool.ProcessTransaction(tx, false, false, 0, true)
  1494  		require.NoError(err, "Problem adding transaction %d to mempool: %v", ii, tx)
  1495  	}
  1496  
  1497  	// Apply all the transactions to a view and flush the view to the db.
  1498  	utxoView, err := NewUtxoView(db, params, nil)
  1499  	require.NoError(err)
  1500  	for ii, txn := range txns {
  1501  		fmt.Printf("Adding txn %v of type %v to UtxoView\n", ii, txn.TxnMeta.GetTxnType())
  1502  
  1503  		// Assert "before" comment counts are correct at a few different spots
  1504  		if ii == comment2CreatedTxnIndex {
  1505  			assertCommentCount(utxoView, require, post6Hash, 0)
  1506  		}
  1507  		if ii == comment3CreatedTxnIndex {
  1508  			assertCommentCount(utxoView, require, post6Hash, 1)
  1509  		}
  1510  		if ii == comment4CreatedTxnIndex {
  1511  			assertCommentCount(utxoView, require, post6Hash, 2)
  1512  		}
  1513  		if ii == comment3HiddenTxnIndex {
  1514  			assertCommentCount(utxoView, require, post6Hash, 3)
  1515  		}
  1516  
  1517  		// Always use height+1 for validation since it's assumed the transaction will
  1518  		// get mined into the next block.
  1519  		txHash := txn.Hash()
  1520  		blockHeight := chain.blockTip().Height + 1
  1521  		_, _, _, _, err =
  1522  			utxoView.ConnectTransaction(txn, txHash, getTxnSize(*txn), blockHeight, true /*verifySignature*/, false /*ignoreUtxos*/)
  1523  		require.NoError(err)
  1524  
  1525  		// Assert "after" comment counts are correct at a few different spots
  1526  		if ii == comment2CreatedTxnIndex {
  1527  			assertCommentCount(utxoView, require, post6Hash, 1)
  1528  		}
  1529  		if ii == comment3CreatedTxnIndex {
  1530  			assertCommentCount(utxoView, require, post6Hash, 2)
  1531  		}
  1532  		if ii == comment4CreatedTxnIndex {
  1533  			assertCommentCount(utxoView, require, post6Hash, 3)
  1534  		}
  1535  		if ii == comment3HiddenTxnIndex {
  1536  			assertCommentCount(utxoView, require, post6Hash, 2)
  1537  		}
  1538  	}
  1539  	// Flush the utxoView after having added all the transactions.
  1540  	require.NoError(utxoView.FlushToDb())
  1541  
  1542  	// Verify the profiles exist.
  1543  	checkPostsExist()
  1544  
  1545  	// Disonnect the transactions from a single view in the same way as above
  1546  	// i.e. without flushing each time.
  1547  	utxoView2, err := NewUtxoView(db, params, nil)
  1548  	require.NoError(err)
  1549  	for ii := 0; ii < len(txnOps); ii++ {
  1550  		backwardIter := len(txnOps) - 1 - ii
  1551  		fmt.Printf("Disconnecting transaction with index %d (going backwards)\n", backwardIter)
  1552  		currentOps := txnOps[backwardIter]
  1553  		currentTxn := txns[backwardIter]
  1554  
  1555  		// Assert "before" comment counts are correct at a few different spots
  1556  		if backwardIter == comment3HiddenTxnIndex {
  1557  			assertCommentCount(utxoView2, require, post6Hash, 2)
  1558  		}
  1559  		if backwardIter == comment4CreatedTxnIndex {
  1560  			assertCommentCount(utxoView2, require, post6Hash, 3)
  1561  		}
  1562  		if backwardIter == comment3CreatedTxnIndex {
  1563  			assertCommentCount(utxoView2, require, post6Hash, 2)
  1564  		}
  1565  		if backwardIter == comment2CreatedTxnIndex {
  1566  			assertCommentCount(utxoView2, require, post6Hash, 1)
  1567  		}
  1568  
  1569  		currentHash := currentTxn.Hash()
  1570  		err = utxoView2.DisconnectTransaction(currentTxn, currentHash, currentOps, savedHeight)
  1571  		require.NoError(err)
  1572  
  1573  		// Assert "after" comment counts are correct at a few different spots
  1574  		if backwardIter == comment3HiddenTxnIndex {
  1575  			assertCommentCount(utxoView2, require, post6Hash, 3)
  1576  		}
  1577  		if backwardIter == comment4CreatedTxnIndex {
  1578  			assertCommentCount(utxoView2, require, post6Hash, 2)
  1579  		}
  1580  		if backwardIter == comment3CreatedTxnIndex {
  1581  			assertCommentCount(utxoView2, require, post6Hash, 1)
  1582  		}
  1583  		if backwardIter == comment2CreatedTxnIndex {
  1584  			assertCommentCount(utxoView2, require, post6Hash, 0)
  1585  		}
  1586  	}
  1587  	require.NoError(utxoView2.FlushToDb())
  1588  	require.Equal(expectedSenderBalances[0], _getBalance(t, chain, nil, senderPkString))
  1589  
  1590  	// Verify that all the profiles have been deleted.
  1591  	checkPostsDeleted()
  1592  
  1593  	// All the txns should be in the mempool already so mining a block should put
  1594  	// all those transactions in it.
  1595  	block, err := miner.MineAndProcessSingleBlock(0 /*threadIndex*/, mempool)
  1596  	require.NoError(err)
  1597  	// Add one for the block reward. Now we have a meaty block.
  1598  	require.Equal(len(txnOps)+1, len(block.Txns))
  1599  
  1600  	// Roll back the block and make sure we don't hit any errors.
  1601  	{
  1602  		utxoView, err := NewUtxoView(db, params, nil)
  1603  		require.NoError(err)
  1604  
  1605  		// Fetch the utxo operations for the block we're detaching. We need these
  1606  		// in order to be able to detach the block.
  1607  		hash, err := block.Header.Hash()
  1608  		require.NoError(err)
  1609  		utxoOps, err := GetUtxoOperationsForBlock(db, hash)
  1610  		require.NoError(err)
  1611  
  1612  		// Compute the hashes for all the transactions.
  1613  		txHashes, err := ComputeTransactionHashes(block.Txns)
  1614  		require.NoError(err)
  1615  		require.NoError(utxoView.DisconnectBlock(block, txHashes, utxoOps))
  1616  
  1617  		// Flushing the view after applying and rolling back should work.
  1618  		require.NoError(utxoView.FlushToDb())
  1619  	}
  1620  
  1621  	// Verify that all the profiles have been deleted.
  1622  	checkPostsDeleted()
  1623  }
  1624  
  1625  func assertCommentCount(utxoView *UtxoView, require *require.Assertions, postHash *BlockHash,
  1626  	expectedCommentCount int) {
  1627  	corePosts, _, err := utxoView.GetAllPosts()
  1628  	require.NoError(err)
  1629  
  1630  	post := findPostByPostHash(corePosts, postHash)
  1631  	require.Equal(uint64(expectedCommentCount), post.CommentCount)
  1632  }
  1633  
  1634  func findPostByPostHash(posts []*PostEntry, targetPostHash *BlockHash) (_targetPost *PostEntry) {
  1635  	var targetPost *PostEntry
  1636  	for _, post := range posts {
  1637  		if reflect.DeepEqual(post.PostHash, targetPostHash) {
  1638  			targetPost = post
  1639  			break
  1640  		}
  1641  	}
  1642  	return targetPost
  1643  }
  1644  
  1645  func TestDeSoDiamonds(t *testing.T) {
  1646  	DeSoDiamondsBlockHeight = 0
  1647  	diamondValueMap := GetDeSoNanosDiamondLevelMapAtBlockHeight(0)
  1648  
  1649  	assert := assert.New(t)
  1650  	require := require.New(t)
  1651  	_ = assert
  1652  	_ = require
  1653  
  1654  	chain, params, db := NewLowDifficultyBlockchain()
  1655  	mempool, miner := NewTestMiner(t, chain, params, true /*isSender*/)
  1656  	// Make m3, m4 a paramUpdater for this test
  1657  	params.ParamUpdaterPublicKeys[MakePkMapKey(m3PkBytes)] = true
  1658  	params.ParamUpdaterPublicKeys[MakePkMapKey(m4PkBytes)] = true
  1659  
  1660  	// Mine a few blocks to give the senderPkString some money.
  1661  	_, err := miner.MineAndProcessSingleBlock(0 /*threadIndex*/, mempool)
  1662  	require.NoError(err)
  1663  	_, err = miner.MineAndProcessSingleBlock(0 /*threadIndex*/, mempool)
  1664  	require.NoError(err)
  1665  	_, err = miner.MineAndProcessSingleBlock(0 /*threadIndex*/, mempool)
  1666  	require.NoError(err)
  1667  	_, err = miner.MineAndProcessSingleBlock(0 /*threadIndex*/, mempool)
  1668  	require.NoError(err)
  1669  
  1670  	// We build the testMeta obj after mining blocks so that we save the correct block height.
  1671  	testMeta := &TestMeta{
  1672  		t:           t,
  1673  		chain:       chain,
  1674  		params:      params,
  1675  		db:          db,
  1676  		mempool:     mempool,
  1677  		miner:       miner,
  1678  		savedHeight: chain.blockTip().Height + 1,
  1679  	}
  1680  
  1681  	// Fund all the keys.
  1682  	_registerOrTransferWithTestMeta(testMeta, "", senderPkString, m0Pub, senderPrivString, 1000)
  1683  	_registerOrTransferWithTestMeta(testMeta, "", senderPkString, m1Pub, senderPrivString, 1000000000)
  1684  	_registerOrTransferWithTestMeta(testMeta, "", senderPkString, m2Pub, senderPrivString, 1000000000)
  1685  
  1686  	// Get PKIDs for looking up diamond entries.
  1687  	m0PkBytes, _, err := Base58CheckDecode(m0Pub)
  1688  	require.NoError(err)
  1689  	m0PKID := DBGetPKIDEntryForPublicKey(db, m0PkBytes)
  1690  
  1691  	m1PkBytes, _, err := Base58CheckDecode(m1Pub)
  1692  	require.NoError(err)
  1693  	m1PKID := DBGetPKIDEntryForPublicKey(db, m1PkBytes)
  1694  
  1695  	m2PkBytes, _, err := Base58CheckDecode(m2Pub)
  1696  	require.NoError(err)
  1697  	m2PKID := DBGetPKIDEntryForPublicKey(db, m2PkBytes)
  1698  	_ = m2PKID
  1699  
  1700  	validateDiamondEntry := func(
  1701  		senderPKID *PKID, receiverPKID *PKID, diamondPostHash *BlockHash, diamondLevel int64) {
  1702  
  1703  		diamondEntry := DbGetDiamondMappings(db, receiverPKID, senderPKID, diamondPostHash)
  1704  
  1705  		if diamondEntry == nil && diamondLevel > 0 {
  1706  			t.Errorf("validateDiamondEntry: couldn't find diamond entry for diamondLevel %d", diamondLevel)
  1707  		} else if diamondEntry == nil && diamondLevel == 0 {
  1708  			// If diamondLevel is set to zero, we are checking that diamondEntry is nil.
  1709  			return
  1710  		}
  1711  
  1712  		require.Equal(diamondEntry.DiamondLevel, diamondLevel)
  1713  	}
  1714  
  1715  	// Create a post for testing.
  1716  	{
  1717  		_submitPostWithTestMeta(
  1718  			testMeta,
  1719  			10,                                 /*feeRateNanosPerKB*/
  1720  			m0Pub,                              /*updaterPkBase58Check*/
  1721  			m0Priv,                             /*updaterPrivBase58Check*/
  1722  			[]byte{},                           /*postHashToModify*/
  1723  			[]byte{},                           /*parentStakeID*/
  1724  			&DeSoBodySchema{Body: "m0 post 1"}, /*body*/
  1725  			[]byte{},
  1726  			1502947011*1e9, /*tstampNanos*/
  1727  			false /*isHidden*/)
  1728  	}
  1729  	post1Hash := testMeta.txns[len(testMeta.txns)-1].Hash()
  1730  	_ = post1Hash
  1731  
  1732  	// Have m1 give the post a diamond.
  1733  	{
  1734  		// Balances before.
  1735  		m0BalBeforeNFT := _getBalance(t, chain, nil, m0Pub)
  1736  		require.Equal(uint64(999), m0BalBeforeNFT)
  1737  		m1BalBeforeNFT := _getBalance(t, chain, nil, m1Pub)
  1738  		require.Equal(uint64(1e9), m1BalBeforeNFT)
  1739  
  1740  		validateDiamondEntry(m1PKID.PKID, m0PKID.PKID, post1Hash, 0)
  1741  		_giveDeSoDiamondsWithTestMeta(testMeta, 10, m1Pub, m1Priv, post1Hash, 1)
  1742  		validateDiamondEntry(m1PKID.PKID, m0PKID.PKID, post1Hash, 1)
  1743  
  1744  		// Balances after.
  1745  		m0BalAfterNFT := _getBalance(t, chain, nil, m0Pub)
  1746  		require.Equal(uint64(999+diamondValueMap[1]), m0BalAfterNFT)
  1747  		m1BalAfterNFT := _getBalance(t, chain, nil, m1Pub)
  1748  		require.Equal(uint64(1e9-diamondValueMap[1]-2), m1BalAfterNFT)
  1749  	}
  1750  
  1751  	// Upgrade the post from 1 -> 2 diamonds.
  1752  	{
  1753  		// Balances before.
  1754  		m0BalBeforeNFT := _getBalance(t, chain, nil, m0Pub)
  1755  		require.Equal(uint64(999+diamondValueMap[1]), m0BalBeforeNFT)
  1756  		m1BalBeforeNFT := _getBalance(t, chain, nil, m1Pub)
  1757  		require.Equal(uint64(1e9-diamondValueMap[1]-2), m1BalBeforeNFT)
  1758  
  1759  		validateDiamondEntry(m1PKID.PKID, m0PKID.PKID, post1Hash, 1)
  1760  		_giveDeSoDiamondsWithTestMeta(testMeta, 10, m1Pub, m1Priv, post1Hash, 2)
  1761  		validateDiamondEntry(m1PKID.PKID, m0PKID.PKID, post1Hash, 2)
  1762  
  1763  		// Balances after.
  1764  		m0BalAfterNFT := _getBalance(t, chain, nil, m0Pub)
  1765  		require.Equal(uint64(999+diamondValueMap[2]), m0BalAfterNFT)
  1766  		m1BalAfterNFT := _getBalance(t, chain, nil, m1Pub)
  1767  		require.Equal(uint64(1e9-diamondValueMap[2]-4), m1BalAfterNFT)
  1768  	}
  1769  
  1770  	// Upgrade the post from 2 -> 3 diamonds.
  1771  	{
  1772  		// Balances before.
  1773  		m0BalBeforeNFT := _getBalance(t, chain, nil, m0Pub)
  1774  		require.Equal(uint64(999+diamondValueMap[2]), m0BalBeforeNFT)
  1775  		m1BalBeforeNFT := _getBalance(t, chain, nil, m1Pub)
  1776  		require.Equal(uint64(1e9-diamondValueMap[2]-4), m1BalBeforeNFT)
  1777  
  1778  		validateDiamondEntry(m1PKID.PKID, m0PKID.PKID, post1Hash, 2)
  1779  		_giveDeSoDiamondsWithTestMeta(testMeta, 10, m1Pub, m1Priv, post1Hash, 3)
  1780  		validateDiamondEntry(m1PKID.PKID, m0PKID.PKID, post1Hash, 3)
  1781  
  1782  		// Balances after.
  1783  		m0BalAfterNFT := _getBalance(t, chain, nil, m0Pub)
  1784  		require.Equal(uint64(999+diamondValueMap[3]), m0BalAfterNFT)
  1785  		m1BalAfterNFT := _getBalance(t, chain, nil, m1Pub)
  1786  		require.Equal(uint64(1e9-diamondValueMap[3]-6), m1BalAfterNFT)
  1787  	}
  1788  
  1789  	// Upgrade the post from 3 -> 4 diamonds.
  1790  	{
  1791  		// Balances before.
  1792  		m0BalBeforeNFT := _getBalance(t, chain, nil, m0Pub)
  1793  		require.Equal(uint64(999+diamondValueMap[3]), m0BalBeforeNFT)
  1794  		m1BalBeforeNFT := _getBalance(t, chain, nil, m1Pub)
  1795  		require.Equal(uint64(1e9-diamondValueMap[3]-6), m1BalBeforeNFT)
  1796  
  1797  		validateDiamondEntry(m1PKID.PKID, m0PKID.PKID, post1Hash, 3)
  1798  		_giveDeSoDiamondsWithTestMeta(testMeta, 10, m1Pub, m1Priv, post1Hash, 4)
  1799  		validateDiamondEntry(m1PKID.PKID, m0PKID.PKID, post1Hash, 4)
  1800  
  1801  		// Balances after.
  1802  		m0BalAfterNFT := _getBalance(t, chain, nil, m0Pub)
  1803  		require.Equal(uint64(999+diamondValueMap[4]), m0BalAfterNFT)
  1804  		m1BalAfterNFT := _getBalance(t, chain, nil, m1Pub)
  1805  		require.Equal(uint64(1e9-diamondValueMap[4]-8), m1BalAfterNFT)
  1806  	}
  1807  
  1808  	// Upgrade the post from 4 -> 5 diamonds.
  1809  	{
  1810  		// Balances before.
  1811  		m0BalBeforeNFT := _getBalance(t, chain, nil, m0Pub)
  1812  		require.Equal(uint64(999+diamondValueMap[4]), m0BalBeforeNFT)
  1813  		m1BalBeforeNFT := _getBalance(t, chain, nil, m1Pub)
  1814  		require.Equal(uint64(1e9-diamondValueMap[4]-8), m1BalBeforeNFT)
  1815  
  1816  		validateDiamondEntry(m1PKID.PKID, m0PKID.PKID, post1Hash, 4)
  1817  		_giveDeSoDiamondsWithTestMeta(testMeta, 10, m1Pub, m1Priv, post1Hash, 5)
  1818  		validateDiamondEntry(m1PKID.PKID, m0PKID.PKID, post1Hash, 5)
  1819  
  1820  		// Balances after.
  1821  		m0BalAfterNFT := _getBalance(t, chain, nil, m0Pub)
  1822  		require.Equal(uint64(999+diamondValueMap[5]), m0BalAfterNFT)
  1823  		m1BalAfterNFT := _getBalance(t, chain, nil, m1Pub)
  1824  		require.Equal(uint64(1e9-diamondValueMap[5]-10), m1BalAfterNFT)
  1825  	}
  1826  
  1827  	// Have m2 give the post 5 diamonds right off the bat.
  1828  	{
  1829  		// Balances before.
  1830  		m0BalBeforeNFT := _getBalance(t, chain, nil, m0Pub)
  1831  		require.Equal(uint64(999+diamondValueMap[5]), m0BalBeforeNFT)
  1832  		m2BalBeforeNFT := _getBalance(t, chain, nil, m2Pub)
  1833  		require.Equal(uint64(1e9), m2BalBeforeNFT)
  1834  
  1835  		validateDiamondEntry(m2PKID.PKID, m0PKID.PKID, post1Hash, 0)
  1836  		_giveDeSoDiamondsWithTestMeta(testMeta, 10, m2Pub, m2Priv, post1Hash, 5)
  1837  		validateDiamondEntry(m2PKID.PKID, m0PKID.PKID, post1Hash, 5)
  1838  
  1839  		// Balances after.
  1840  		m0BalAfterNFT := _getBalance(t, chain, nil, m0Pub)
  1841  		require.Equal(uint64(999+diamondValueMap[5]+diamondValueMap[5]), m0BalAfterNFT)
  1842  		m2BalAfterNFT := _getBalance(t, chain, nil, m2Pub)
  1843  		require.Equal(uint64(1e9-diamondValueMap[5]-2), m2BalAfterNFT)
  1844  	}
  1845  
  1846  	// Roll all successful txns through connect and disconnect loops to make sure nothing breaks.
  1847  	_rollBackTestMetaTxnsAndFlush(testMeta)
  1848  	_applyTestMetaTxnsToMempool(testMeta)
  1849  	_applyTestMetaTxnsToViewAndFlush(testMeta)
  1850  	_disconnectTestMetaTxnsFromViewAndFlush(testMeta)
  1851  	_connectBlockThenDisconnectBlockAndFlush(testMeta)
  1852  }
  1853  
  1854  func TestDeSoDiamondErrorCases(t *testing.T) {
  1855  	DeSoDiamondsBlockHeight = 0
  1856  	diamondValueMap := GetDeSoNanosDiamondLevelMapAtBlockHeight(0)
  1857  
  1858  	assert := assert.New(t)
  1859  	require := require.New(t)
  1860  	_ = assert
  1861  	_ = require
  1862  
  1863  	chain, params, db := NewLowDifficultyBlockchain()
  1864  	mempool, miner := NewTestMiner(t, chain, params, true /*isSender*/)
  1865  	// Make m3, m4 a paramUpdater for this test
  1866  	params.ParamUpdaterPublicKeys[MakePkMapKey(m3PkBytes)] = true
  1867  	params.ParamUpdaterPublicKeys[MakePkMapKey(m4PkBytes)] = true
  1868  
  1869  	// Mine a few blocks to give the senderPkString some money.
  1870  	_, err := miner.MineAndProcessSingleBlock(0 /*threadIndex*/, mempool)
  1871  	require.NoError(err)
  1872  	_, err = miner.MineAndProcessSingleBlock(0 /*threadIndex*/, mempool)
  1873  	require.NoError(err)
  1874  	_, err = miner.MineAndProcessSingleBlock(0 /*threadIndex*/, mempool)
  1875  	require.NoError(err)
  1876  	_, err = miner.MineAndProcessSingleBlock(0 /*threadIndex*/, mempool)
  1877  	require.NoError(err)
  1878  
  1879  	// We build the testMeta obj after mining blocks so that we save the correct block height.
  1880  	testMeta := &TestMeta{
  1881  		t:           t,
  1882  		chain:       chain,
  1883  		params:      params,
  1884  		db:          db,
  1885  		mempool:     mempool,
  1886  		miner:       miner,
  1887  		savedHeight: chain.blockTip().Height + 1,
  1888  	}
  1889  
  1890  	// Fund all the keys.
  1891  	_registerOrTransferWithTestMeta(testMeta, "", senderPkString, m0Pub, senderPrivString, 1000000000)
  1892  	_registerOrTransferWithTestMeta(testMeta, "", senderPkString, m1Pub, senderPrivString, 1000000000)
  1893  
  1894  	// Since the "CreateBasicTransferTxnWithDiamonds()" function in blockchain.go won't let us
  1895  	// trigger most errors that we want to check, we create another version of the function here
  1896  	// that allows us to put together whatever type of broken txn we want.
  1897  	_giveCustomDeSoDiamondTxn := func(
  1898  		senderPkBase58Check string, senderPrivBase58Check string, receiverPkBase58Check string,
  1899  		diamondPostHashBytes []byte, diamondLevel int64, amountNanos uint64) (_err error) {
  1900  
  1901  		senderPkBytes, _, err := Base58CheckDecode(senderPkBase58Check)
  1902  		require.NoError(err)
  1903  
  1904  		receiverPkBytes, _, err := Base58CheckDecode(receiverPkBase58Check)
  1905  		require.NoError(err)
  1906  
  1907  		utxoView, err := NewUtxoView(db, params, nil)
  1908  		require.NoError(err)
  1909  
  1910  		// Build the basic transfer txn.
  1911  		txn := &MsgDeSoTxn{
  1912  			PublicKey: senderPkBytes,
  1913  			TxnMeta:   &BasicTransferMetadata{},
  1914  			TxOutputs: []*DeSoOutput{
  1915  				{
  1916  					PublicKey:   receiverPkBytes,
  1917  					AmountNanos: amountNanos,
  1918  				},
  1919  			},
  1920  			// TxInputs and TxOutputs will be set below.
  1921  			// This function does not compute a signature.
  1922  		}
  1923  
  1924  		// Make a map for the diamond extra data and add it.
  1925  		diamondsExtraData := make(map[string][]byte)
  1926  		diamondsExtraData[DiamondLevelKey] = IntToBuf(diamondLevel)
  1927  		diamondsExtraData[DiamondPostHashKey] = diamondPostHashBytes
  1928  		txn.ExtraData = diamondsExtraData
  1929  
  1930  		// We don't need to make any tweaks to the amount because it's basically
  1931  		// a standard "pay per kilobyte" transaction.
  1932  		totalInput, _, _, fees, err :=
  1933  			chain.AddInputsAndChangeToTransaction(txn, 10, mempool)
  1934  		if err != nil {
  1935  			return errors.Wrapf(
  1936  				err, "giveCustomDeSoDiamondTxn: Problem adding inputs: ")
  1937  		}
  1938  
  1939  		// We want our transaction to have at least one input, even if it all
  1940  		// goes to change. This ensures that the transaction will not be "replayable."
  1941  		if len(txn.TxInputs) == 0 {
  1942  			return fmt.Errorf(
  1943  				"giveCustomDeSoDiamondTxn: BasicTransfer txn must have at" +
  1944  					" least one input but had zero inputs instead. Try increasing the fee rate.")
  1945  		}
  1946  
  1947  		// Sign the transaction now that its inputs are set up.
  1948  		_signTxn(t, txn, senderPrivBase58Check)
  1949  
  1950  		txHash := txn.Hash()
  1951  		// Always use height+1 for validation since it's assumed the transaction will
  1952  		// get mined into the next block.
  1953  		blockHeight := chain.blockTip().Height + 1
  1954  		utxoOps, totalInput, totalOutput, fees, err :=
  1955  			utxoView.ConnectTransaction(
  1956  				txn, txHash, getTxnSize(*txn), blockHeight, true /*verifySignature*/, false /*ignoreUtxos*/)
  1957  		if err != nil {
  1958  			return err
  1959  		}
  1960  		require.Equal(t, totalInput, totalOutput+fees)
  1961  
  1962  		// We should have one SPEND UtxoOperation for each input, one ADD operation
  1963  		// for each output, and one OperationTypeDeSoDiamond operation at the end.
  1964  		require.Equal(t, len(txn.TxInputs)+len(txn.TxOutputs)+1, len(utxoOps))
  1965  		for ii := 0; ii < len(txn.TxInputs); ii++ {
  1966  			require.Equal(t, OperationTypeSpendUtxo, utxoOps[ii].Type)
  1967  		}
  1968  		require.Equal(OperationTypeDeSoDiamond, utxoOps[len(utxoOps)-1].Type)
  1969  
  1970  		require.NoError(utxoView.FlushToDb())
  1971  
  1972  		return nil
  1973  	}
  1974  
  1975  	// Error case: PostHash with bad length.
  1976  	{
  1977  		err := _giveCustomDeSoDiamondTxn(
  1978  			m0Pub,
  1979  			m0Priv,
  1980  			m1Pub,
  1981  			RandomBytes(HashSizeBytes-1),
  1982  			1,
  1983  			diamondValueMap[1],
  1984  		)
  1985  		require.Error(err)
  1986  		require.Contains(err.Error(), RuleErrorBasicTransferDiamondInvalidLengthForPostHashBytes)
  1987  	}
  1988  
  1989  	// Error case: non-existent post.
  1990  	{
  1991  		err := _giveCustomDeSoDiamondTxn(
  1992  			m0Pub,
  1993  			m0Priv,
  1994  			m1Pub,
  1995  			RandomBytes(HashSizeBytes),
  1996  			1,
  1997  			diamondValueMap[1],
  1998  		)
  1999  		require.Error(err)
  2000  		require.Contains(err.Error(), RuleErrorBasicTransferDiamondPostEntryDoesNotExist)
  2001  	}
  2002  
  2003  	// Create a post for testing.
  2004  	{
  2005  		_submitPostWithTestMeta(
  2006  			testMeta,
  2007  			10,                                 /*feeRateNanosPerKB*/
  2008  			m0Pub,                              /*updaterPkBase58Check*/
  2009  			m0Priv,                             /*updaterPrivBase58Check*/
  2010  			[]byte{},                           /*postHashToModify*/
  2011  			[]byte{},                           /*parentStakeID*/
  2012  			&DeSoBodySchema{Body: "m0 post 1"}, /*body*/
  2013  			[]byte{},
  2014  			1502947011*1e9, /*tstampNanos*/
  2015  			false /*isHidden*/)
  2016  	}
  2017  	post1Hash := testMeta.txns[len(testMeta.txns)-1].Hash()
  2018  	_ = post1Hash
  2019  
  2020  	// Error case: cannot diamond yourself.
  2021  	{
  2022  		err := _giveCustomDeSoDiamondTxn(
  2023  			m0Pub,
  2024  			m0Priv,
  2025  			m1Pub,
  2026  			post1Hash[:],
  2027  			1,
  2028  			diamondValueMap[1],
  2029  		)
  2030  		require.Error(err)
  2031  		require.Contains(err.Error(), RuleErrorBasicTransferDiamondCannotTransferToSelf)
  2032  	}
  2033  
  2034  	// Error case: don't include diamond level.
  2035  	{
  2036  		_, _, _, err := _giveDeSoDiamonds(
  2037  			t, chain, db, params,
  2038  			10,
  2039  			m1Pub,
  2040  			m1Priv,
  2041  			post1Hash,
  2042  			1,
  2043  			true, /*deleteDiamondLevel*/
  2044  		)
  2045  		require.Error(err)
  2046  		require.Contains(err.Error(), RuleErrorBasicTransferHasDiamondPostHashWithoutDiamondLevel)
  2047  	}
  2048  
  2049  	// Error case: invalid diamond level.
  2050  	{
  2051  		err := _giveCustomDeSoDiamondTxn(
  2052  			m1Pub,
  2053  			m1Priv,
  2054  			m0Pub,
  2055  			post1Hash[:],
  2056  			-1,
  2057  			diamondValueMap[1],
  2058  		)
  2059  		require.Error(err)
  2060  		require.Contains(err.Error(), RuleErrorBasicTransferHasInvalidDiamondLevel)
  2061  	}
  2062  
  2063  	// Error case: insufficient deso.
  2064  	{
  2065  		err := _giveCustomDeSoDiamondTxn(
  2066  			m1Pub,
  2067  			m1Priv,
  2068  			m0Pub,
  2069  			post1Hash[:],
  2070  			2,
  2071  			diamondValueMap[1],
  2072  		)
  2073  		require.Error(err)
  2074  		require.Contains(err.Error(), RuleErrorBasicTransferInsufficientDeSoForDiamondLevel)
  2075  	}
  2076  }