blockchain: Vote commitments in block sanity.
This moves the checks for validating that votes commit to the parent of the block they are contained in and the vote bits specified by the header match the result of the actual votes included in the block to the checkBlockSanity function where they more naturally belong since they do not require access to any previous chain context.
This commit is contained in:
parent
a57df141f9
commit
79d3f08b2b
@ -574,8 +574,10 @@ func checkBlockSanity(block *dcrutil.Block, timeSource MedianTimeSource, flags B
|
||||
|
||||
// Do some preliminary checks on each stake transaction to ensure they
|
||||
// are sane while tallying each type before continuing.
|
||||
stakeValidationHeight := uint32(chainParams.StakeValidationHeight)
|
||||
var totalTickets, totalVotes, totalRevocations int64
|
||||
for i, stx := range msgBlock.STransactions {
|
||||
var totalYesVotes int64
|
||||
for txIdx, stx := range msgBlock.STransactions {
|
||||
err := CheckTransactionSanity(stx, chainParams)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -587,15 +589,40 @@ func checkBlockSanity(block *dcrutil.Block, timeSource MedianTimeSource, flags B
|
||||
if txType == stake.TxTypeRegular {
|
||||
errStr := fmt.Sprintf("block contains regular "+
|
||||
"transaction in stake transaction tree at "+
|
||||
"index %d", i)
|
||||
"index %d", txIdx)
|
||||
return ruleError(ErrRegTxInStakeTree, errStr)
|
||||
}
|
||||
|
||||
switch txType {
|
||||
case stake.TxTypeSStx:
|
||||
totalTickets++
|
||||
|
||||
case stake.TxTypeSSGen:
|
||||
totalVotes++
|
||||
|
||||
// All votes in a block must commit to the parent of the
|
||||
// block once stake validation height has been reached.
|
||||
if header.Height >= stakeValidationHeight {
|
||||
votedHash, votedHeight := stake.SSGenBlockVotedOn(stx)
|
||||
if (votedHash != header.PrevBlock) || (votedHeight !=
|
||||
header.Height-1) {
|
||||
|
||||
errStr := fmt.Sprintf("vote %s at index %d is "+
|
||||
"for parent block %s (height %d) versus "+
|
||||
"expected parent block %s (height %d)",
|
||||
stx.TxHash(), txIdx, votedHash,
|
||||
votedHeight, header.PrevBlock,
|
||||
header.Height-1)
|
||||
return ruleError(ErrVotesOnWrongBlock, errStr)
|
||||
}
|
||||
|
||||
// Tally how many votes approve the previous block for use
|
||||
// when validating the header commitment.
|
||||
if voteBitsApproveParent(stake.SSGenVoteBits(stx)) {
|
||||
totalYesVotes++
|
||||
}
|
||||
}
|
||||
|
||||
case stake.TxTypeSSRtx:
|
||||
totalRevocations++
|
||||
}
|
||||
@ -628,6 +655,21 @@ func checkBlockSanity(block *dcrutil.Block, timeSource MedianTimeSource, flags B
|
||||
return ruleError(ErrRevocationsMismatch, errStr)
|
||||
}
|
||||
|
||||
// A block header must commit to the same previous block acceptance
|
||||
// semantics expressed by the votes once stake validation height has
|
||||
// been reached.
|
||||
if header.Height >= stakeValidationHeight {
|
||||
totalNoVotes := totalVotes - totalYesVotes
|
||||
headerApproves := headerApprovesParent(header)
|
||||
votesApprove := totalYesVotes > totalNoVotes
|
||||
if headerApproves != votesApprove {
|
||||
errStr := fmt.Sprintf("block header commitment to previous "+
|
||||
"block approval does not match votes (header claims: %v, "+
|
||||
"votes: %v)", headerApproves, votesApprove)
|
||||
return ruleError(ErrIncongruentVotebit, errStr)
|
||||
}
|
||||
}
|
||||
|
||||
// A block must not contain anything other than ticket purchases prior to
|
||||
// stake validation height.
|
||||
//
|
||||
@ -637,7 +679,7 @@ func checkBlockSanity(block *dcrutil.Block, timeSource MedianTimeSource, flags B
|
||||
// type at the current time is ticket purchases, however, if another
|
||||
// stake type is ever added, consensus would break without this check.
|
||||
// It's better to be safe and it's a cheap check.
|
||||
if header.Height < uint32(chainParams.StakeValidationHeight) {
|
||||
if header.Height < stakeValidationHeight {
|
||||
if int64(len(msgBlock.STransactions)) != totalTickets {
|
||||
errStr := fmt.Sprintf("block contains stake "+
|
||||
"transactions other than ticket purchases before "+
|
||||
@ -1051,13 +1093,10 @@ func (b *BlockChain) CheckBlockStakeSanity(stakeValidationHeight int64, node *bl
|
||||
stakeTransactions := block.STransactions()
|
||||
msgBlock := block.MsgBlock()
|
||||
blockHash := block.Hash()
|
||||
prevBlockHash := &msgBlock.Header.PrevBlock
|
||||
finalState := msgBlock.Header.FinalState
|
||||
|
||||
ticketsPerBlock := int(b.chainParams.TicketsPerBlock)
|
||||
|
||||
txTreeRegularValid := headerApprovesParent(&msgBlock.Header)
|
||||
|
||||
parentStakeNode, err := b.fetchStakeNode(node.parent)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -1100,10 +1139,8 @@ func (b *BlockChain) CheckBlockStakeSanity(stakeValidationHeight int64, node *bl
|
||||
// block in the way of the majority of the voters.
|
||||
// 7. Check final state and ensure that it matches.
|
||||
|
||||
// Store the number of SSGen tx and votes to check later.
|
||||
// Store the number of SSGen tx to check later.
|
||||
numSSGenTx := 0
|
||||
voteYea := 0
|
||||
voteNay := 0
|
||||
|
||||
// 1. Retrieve an emulated ticket database of SStxMemMaps from both the
|
||||
// ticket database and the ticket store.
|
||||
@ -1127,13 +1164,6 @@ func (b *BlockChain) CheckBlockStakeSanity(stakeValidationHeight int64, node *bl
|
||||
if stake.IsSSGen(msgTx) {
|
||||
numSSGenTx++
|
||||
|
||||
// Check and store the vote for TxTreeRegular.
|
||||
if voteBitsApproveParent(stake.SSGenVoteBits(msgTx)) {
|
||||
voteYea++
|
||||
} else {
|
||||
voteNay++
|
||||
}
|
||||
|
||||
// Grab the input SStx hash from the inputs of the
|
||||
// transaction.
|
||||
sstxIn := msgTx.TxIn[1] // sstx input
|
||||
@ -1150,45 +1180,20 @@ func (b *BlockChain) CheckBlockStakeSanity(stakeValidationHeight int64, node *bl
|
||||
sstxHash, blockHash)
|
||||
return ruleError(ErrTicketUnavailable, errStr)
|
||||
}
|
||||
|
||||
// 3. Check to make sure that the SSGen tx votes on the
|
||||
// parent block of the block in which it is included.
|
||||
voteHash, voteHgt := stake.SSGenBlockVotedOn(msgTx)
|
||||
if !(voteHash.IsEqual(prevBlockHash)) ||
|
||||
(voteHgt != uint32(block.Height())-1) {
|
||||
txHash := msgTx.TxHash()
|
||||
errStr := fmt.Sprintf("Error in stake "+
|
||||
"consensus: SSGen %v voted on block "+
|
||||
"%v at height %v, however it was "+
|
||||
"found inside block %v at height %v!",
|
||||
txHash, voteHash, voteHgt,
|
||||
prevBlockHash, block.Height()-1)
|
||||
return ruleError(ErrVotesOnWrongBlock, errStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Check to make sure that the SSGen votes on the correct
|
||||
// block/height. Already checked in checkBlockSanity.
|
||||
|
||||
// 4. Check and make sure that we have the same number of SSGen tx as
|
||||
// we do votes. Already checked in checkBlockSanity.
|
||||
|
||||
// 5. Check for too many voters. Already checked in checkBlockSanity.
|
||||
|
||||
// 6. Determine if TxTreeRegular should be valid or not, and then check
|
||||
// it against what is provided in the block header.
|
||||
if (voteYea <= voteNay) && txTreeRegularValid {
|
||||
errStr := fmt.Sprintf("Error in stake consensus: the voters "+
|
||||
"voted against parent TxTreeRegular inclusion in "+
|
||||
"block %v, but the block header indicates it was "+
|
||||
"voted for", blockHash)
|
||||
return ruleError(ErrIncongruentVotebit, errStr)
|
||||
}
|
||||
if (voteYea > voteNay) && !txTreeRegularValid {
|
||||
errStr := fmt.Sprintf("Error in stake consensus: the voters "+
|
||||
"voted for parent TxTreeRegular inclusion in block %v,"+
|
||||
" but the block header indicates it was voted against",
|
||||
blockHash)
|
||||
return ruleError(ErrIncongruentVotebit, errStr)
|
||||
}
|
||||
// it against what is provided in the block header. Already checked
|
||||
// in checkBlockSanity.
|
||||
|
||||
// 7. Check the final state of the lottery PRNG and ensure that it
|
||||
// matches.
|
||||
|
||||
@ -8,6 +8,7 @@ package blockchain_test
|
||||
import (
|
||||
"bytes"
|
||||
"compress/bzip2"
|
||||
"encoding/binary"
|
||||
"encoding/gob"
|
||||
"encoding/hex"
|
||||
"os"
|
||||
@ -38,6 +39,23 @@ func recalculateMsgBlockMerkleRootsSize(msgBlock *wire.MsgBlock) {
|
||||
msgBlock.Header.Size = uint32(msgBlock.SerializeSize())
|
||||
}
|
||||
|
||||
// updateVoteCommitments updates all of the votes in the passed block to commit
|
||||
// to the previous block and height specified by the header.
|
||||
func updateVoteCommitments(msgBlock *wire.MsgBlock) {
|
||||
for _, stx := range msgBlock.STransactions {
|
||||
if !stake.IsSSGen(stx) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Generate and set the commitment.
|
||||
var commitment [36]byte
|
||||
copy(commitment[:], msgBlock.Header.PrevBlock[:])
|
||||
binary.LittleEndian.PutUint32(commitment[32:], msgBlock.Header.Height-1)
|
||||
pkScript, _ := txscript.GenerateProvablyPruneableOut(commitment[:])
|
||||
stx.TxOut[0].PkScript = pkScript
|
||||
}
|
||||
}
|
||||
|
||||
// TestBlockValidationRules unit tests various block validation rules.
|
||||
// It checks the following:
|
||||
// 1. ProcessBlock
|
||||
@ -366,6 +384,8 @@ func TestBlockValidationRules(t *testing.T) {
|
||||
missingParent153 := new(wire.MsgBlock)
|
||||
missingParent153.FromBytes(block153Bytes)
|
||||
missingParent153.Header.PrevBlock[8] ^= 0x01
|
||||
updateVoteCommitments(missingParent153)
|
||||
recalculateMsgBlockMerkleRootsSize(missingParent153)
|
||||
b153test = dcrutil.NewBlock(missingParent153)
|
||||
|
||||
err = blockchain.CheckWorklessBlockSanity(b153test, timeSource, params)
|
||||
@ -847,7 +867,7 @@ func TestBlockValidationRules(t *testing.T) {
|
||||
nonChosenTicket154 := new(wire.MsgBlock)
|
||||
nonChosenTicket154.FromBytes(block154Bytes)
|
||||
nonChosenTicket154.STransactions[4] = mtxFromB
|
||||
|
||||
updateVoteCommitments(nonChosenTicket154)
|
||||
recalculateMsgBlockMerkleRootsSize(nonChosenTicket154)
|
||||
b154test = dcrutil.NewBlock(nonChosenTicket154)
|
||||
|
||||
@ -878,8 +898,9 @@ func TestBlockValidationRules(t *testing.T) {
|
||||
b154test = dcrutil.NewBlock(wrongBlockVote154)
|
||||
|
||||
err = blockchain.CheckWorklessBlockSanity(b154test, timeSource, params)
|
||||
if err != nil {
|
||||
t.Errorf("got unexpected error for ErrVotesOnWrongBlock sanity check: %v",
|
||||
if err == nil || err.(blockchain.RuleError).ErrorCode !=
|
||||
blockchain.ErrVotesOnWrongBlock {
|
||||
t.Errorf("Unexpected no or wrong error for ErrVotesOnWrongBlock test: %v",
|
||||
err)
|
||||
}
|
||||
|
||||
@ -919,9 +940,10 @@ func TestBlockValidationRules(t *testing.T) {
|
||||
b154test = dcrutil.NewBlock(badVoteBit154)
|
||||
|
||||
err = blockchain.CheckWorklessBlockSanity(b154test, timeSource, params)
|
||||
if err != nil {
|
||||
t.Errorf("got unexpected error for ErrIncongruentVotebit 2 sanity "+
|
||||
"check: %v", err)
|
||||
if err == nil || err.(blockchain.RuleError).ErrorCode !=
|
||||
blockchain.ErrIncongruentVotebit {
|
||||
t.Errorf("Unexpected no or wrong error for ErrIncongruentVotebit "+
|
||||
"test 1: %v", err)
|
||||
}
|
||||
|
||||
// Fails and hits ErrIncongruentVotebit.
|
||||
@ -948,9 +970,10 @@ func TestBlockValidationRules(t *testing.T) {
|
||||
b154test = dcrutil.NewBlock(badVoteBit154)
|
||||
|
||||
err = blockchain.CheckWorklessBlockSanity(b154test, timeSource, params)
|
||||
if err != nil {
|
||||
t.Errorf("got unexpected error for ErrIncongruentVotebit 2 sanity "+
|
||||
"check: %v", err)
|
||||
if err == nil || err.(blockchain.RuleError).ErrorCode !=
|
||||
blockchain.ErrIncongruentVotebit {
|
||||
t.Errorf("Unexpected no or wrong error for ErrIncongruentVotebit "+
|
||||
"test 2: %v", err)
|
||||
}
|
||||
|
||||
// Fails and hits ErrIncongruentVotebit.
|
||||
@ -977,9 +1000,10 @@ func TestBlockValidationRules(t *testing.T) {
|
||||
b154test = dcrutil.NewBlock(badVoteBit154)
|
||||
|
||||
err = blockchain.CheckWorklessBlockSanity(b154test, timeSource, params)
|
||||
if err != nil {
|
||||
t.Errorf("got unexpected error for ErrIncongruentVotebit 3 sanity "+
|
||||
"check: %v", err)
|
||||
if err == nil || err.(blockchain.RuleError).ErrorCode !=
|
||||
blockchain.ErrIncongruentVotebit {
|
||||
t.Errorf("Unexpected no or wrong error for ErrIncongruentVotebit "+
|
||||
"test 3: %v", err)
|
||||
}
|
||||
|
||||
// Fails and hits ErrIncongruentVotebit.
|
||||
@ -1006,9 +1030,10 @@ func TestBlockValidationRules(t *testing.T) {
|
||||
b154test = dcrutil.NewBlock(badVoteBit154)
|
||||
|
||||
err = blockchain.CheckWorklessBlockSanity(b154test, timeSource, params)
|
||||
if err != nil {
|
||||
t.Errorf("got unexpected error for ErrIncongruentVotebit 4 sanity "+
|
||||
"check: %v", err)
|
||||
if err == nil || err.(blockchain.RuleError).ErrorCode !=
|
||||
blockchain.ErrIncongruentVotebit {
|
||||
t.Errorf("Unexpected no or wrong error for ErrIncongruentVotebit "+
|
||||
"test 4: %v", err)
|
||||
}
|
||||
|
||||
// Fails and hits ErrIncongruentVotebit.
|
||||
@ -1040,9 +1065,10 @@ func TestBlockValidationRules(t *testing.T) {
|
||||
b154test = dcrutil.NewBlock(badVoteBit154)
|
||||
|
||||
err = blockchain.CheckWorklessBlockSanity(b154test, timeSource, params)
|
||||
if err != nil {
|
||||
t.Errorf("got unexpected error for ErrIncongruentVotebit 5 sanity "+
|
||||
"check: %v", err)
|
||||
if err == nil || err.(blockchain.RuleError).ErrorCode !=
|
||||
blockchain.ErrIncongruentVotebit {
|
||||
t.Errorf("Unexpected no or wrong error for ErrIncongruentVotebit "+
|
||||
"test 5: %v", err)
|
||||
}
|
||||
|
||||
// Fails and hits ErrIncongruentVotebit.
|
||||
@ -1074,9 +1100,10 @@ func TestBlockValidationRules(t *testing.T) {
|
||||
b154test = dcrutil.NewBlock(badVoteBit154)
|
||||
|
||||
err = blockchain.CheckWorklessBlockSanity(b154test, timeSource, params)
|
||||
if err != nil {
|
||||
t.Errorf("got unexpected error for ErrIncongruentVotebit 6 sanity "+
|
||||
"check: %v", err)
|
||||
if err == nil || err.(blockchain.RuleError).ErrorCode !=
|
||||
blockchain.ErrIncongruentVotebit {
|
||||
t.Errorf("Unexpected no or wrong error for ErrIncongruentVotebit "+
|
||||
"test 6: %v", err)
|
||||
}
|
||||
|
||||
// Fails and hits ErrIncongruentVotebit.
|
||||
@ -1108,9 +1135,10 @@ func TestBlockValidationRules(t *testing.T) {
|
||||
b154test = dcrutil.NewBlock(badVoteBit154)
|
||||
|
||||
err = blockchain.CheckWorklessBlockSanity(b154test, timeSource, params)
|
||||
if err != nil {
|
||||
t.Errorf("got unexpected error for ErrIncongruentVotebit 7 sanity "+
|
||||
"check: %v", err)
|
||||
if err == nil || err.(blockchain.RuleError).ErrorCode !=
|
||||
blockchain.ErrIncongruentVotebit {
|
||||
t.Errorf("Unexpected no or wrong error for ErrIncongruentVotebit "+
|
||||
"test 7: %v", err)
|
||||
}
|
||||
|
||||
// Fails and hits ErrIncongruentVotebit.
|
||||
@ -1373,6 +1401,8 @@ func TestBlockValidationRules(t *testing.T) {
|
||||
badBlockHeight154 := new(wire.MsgBlock)
|
||||
badBlockHeight154.FromBytes(block154Bytes)
|
||||
badBlockHeight154.Header.Height++
|
||||
updateVoteCommitments(badBlockHeight154)
|
||||
recalculateMsgBlockMerkleRootsSize(badBlockHeight154)
|
||||
b154test = dcrutil.NewBlock(badBlockHeight154)
|
||||
|
||||
// Throws ProcessBlock error through checkBlockContext.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user