Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

blockchain: move block validation rule tests into fullblocktests (1 of x). #1060

Merged
merged 1 commit into from
Feb 25, 2018

Conversation

dnldd
Copy link
Member

@dnldd dnldd commented Feb 20, 2018

This PR addsAssertTipBlockStakeRoot helper and moves the following block rejection cases into fullblocktests.

  • ErrVotesOnWrongBlock
    • Attempt to add block with tickets voting on wrong block
  • ErrIncongruentVotebit
    • Attempt to add block with incorrect votebits set
      • Everyone votes yea, but block header says nay
      • Everyone votes nay, but block header says yea
      • 3x nay 2x yea, but block header says yea
      • 2x nay 3x yea, but block header says nay
  • ErrBadMerkleRoot
    • Create block with an invalid stake root
  • ErrWrongBlockSize
    • Create block with an invalid block size
  • ErrBadCoinbaseAmountIn
    • Create block with an invalid subsidy for coinbase output
  • ErrBadStakebaseAmountIn
    • Create block with an invalid subsidy for a stakebase input
  • ErrRevocationsMismatch
    • Create block with a revocations count mismatch

Work towards #1030.

@dnldd dnldd force-pushed the update_fullblocktests_one_of_x branch 2 times, most recently from df11353 to 68a4456 Compare February 20, 2018 21:04
Copy link
Member

@davecgh davecgh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before I dig into the particulars, I noticed the copyright dates on the touched files need to be updated for 2018.

Copy link
Member

@davecgh davecgh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still have to go through each condition more thoroughly, but here is some initial feedback for the first couple of tests.

// \-> bv6(9)
g.SetTip("b36")
g.NextBlock("bv6", outs[9], ticketOuts[9], func(b *wire.MsgBlock) {
wrongBlockScript, _ := hex.DecodeString("6a24008e029f92ae880d45ae61a5366b" +
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please avoid hard coding things like this since it makes the tests more fragile in the face of code changes. It also would technically allow an extremely remote possibility that the generated block actually has the hard coded hash. Instead, it's better to flip a bit in the script to mirror what might happen with an alpha particle.

// \-> bv8(9)
g.SetTip("b36")
g.NextBlock("bv8", outs[9], ticketOuts[9], func(b *wire.MsgBlock) {
b.Header.VoteBits = 0x0001
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest using |= here to avoid changing any other bits that might later be used for other purposes and thus could invalidate the block for the wrong reason.

g.SetTip("b36")
g.NextBlock("bv8", outs[9], ticketOuts[9], func(b *wire.MsgBlock) {
b.Header.VoteBits = 0x0001
for i, stx := range b.STransactions {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than repeating the pattern over and over, please create a function similar to ReplaceVotes from chaingen which accepts a specific vote number, version, and bits. I would suggest the following:

// ReplaceVoteBitsN returns a function that itself takes a block and modifies
// it by replacing the vote bits of the vote located at the provided index.  It
// will panic if the stake transaction at the provided index is not already a
// vote.
//
// NOTE: This must only be used as a munger to the 'NextBlock' function or it
// will lead to an invalid live ticket pool.
func ReplaceVoteBitsN(voteNum int, voteBits uint16) func(*wire.MsgBlock) {
	return func(b *wire.MsgBlock) {
		// Attempt to prevent misuse of this function by ensuring the
		// provided stake transaction number is actually a vote.
		stx := b.STransactions[voteNum]
		if !isVoteTx(stx) {
			panic(fmt.Sprintf("attempt to replace non-vote "+
				"transaction #%d for for block %s", voteNum,
				b.BlockHash()))
		}

		// Extract the existing vote version.
		existingScript := stx.TxOut[1].PkScript
		var voteVersion uint32
		if len(existingScript) >= 8 {
			voteVersion = binary.LittleEndian.Uint32(existingScript[4:8])
		}

		stx.TxOut[1].PkScript = voteBitsScript(voteBits, voteVersion)
	}
}

That way it uses the existing infrastructure to properly recreate the entire vote script and will panic if it's used improperly to help ensure the tests stay valid.

Then this function will read more like:

b.Header.VoteBits |= 0x0001

const voteBitsNo = 0x000
for i := 0; i < 4; i++ {
	chaingen.ReplaceVoteBitsN(i, voteBitsNo)
}

g.SetTip("b36")
g.NextBlock("bv9", outs[9], ticketOuts[9], func(b *wire.MsgBlock) {
b.Header.VoteBits = 0x0001
for i, stx := range b.STransactions {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then the same deal here with reusing the function from above.

@dnldd dnldd force-pushed the update_fullblocktests_one_of_x branch 3 times, most recently from a4fafa1 to b1c604c Compare February 24, 2018 16:30
g.SetTip("b36")
g.NextBlock("bv6", outs[9], ticketOuts[9], func(b *wire.MsgBlock) {
// invalidate the vote by corrupting the output script
b.STransactions[0].TxOut[0].PkScript[2] &= 0x00
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not reliable because the block being voted on could already have a zero there in which case the test would fail with a false positive.

You need to ensure some bits are flipped. So, I would suggest using xor. You could do b.STransactions[0].TxOut[0].PkScript[2] ^= 0x55 to flip every other bit in the byte.

@@ -1089,6 +1092,95 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
})
rejected(blockchain.ErrFreshStakeMismatch)

// Attempt to add block with tickets voting on the wrong block
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Total nitpick, but please add periods to these so they're consistent with the rest of the comments.

// \-> bv7(9)
g.SetTip("b36")
g.NextBlock("bv7", outs[9], ticketOuts[9], func(b *wire.MsgBlock) {
// set the vote bit to Nay
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd personally leave these inner comments off. The intro comment already describes it. Also, the naming is inconsistent since it says "yea" while the variables are named yes. I prefer the yes/no naming.

g.SetTip("b46")
g.NextBlock("b56", outs[15], ticketOuts[15], func(b *wire.MsgBlock) {
// set an invalid revocations count
b.Header.Revocations = 2
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than setting a specific number, I would suggest doing b.Header.Revocations++ so that the test will remain valid even if the actual number of revocations somehow ends up being 2 which would create a false positive.

g.SetTip("b46")
g.NextBlock("b55", outs[15], ticketOuts[15], func(b *wire.MsgBlock) {
// set the stakebase subsidy to invalid value (greater than coin supply)
b.STransactions[0].TxIn[0].ValueIn = 1234567890123456
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than setting a specific number, I would suggest doing b.STransactions[0].TxIn[0].ValueIn++ so that the test can't possibly create any false positives if the actual value in somehow ultimately ends up being the magic number.

g.SetTip("b46")
g.NextBlock("b53", outs[15], ticketOuts[15], func(b *wire.MsgBlock) {
// set the block size an invalid value
b.Header.Size = 0x20ffff71
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than setting a specific number, I would suggest doing b.Header.Size++ so that the test can't possibly create any false positives if the actual value in somehow ultimately ends up being the magic number.

g.SetTip("b46")
g.NextBlock("b54", outs[15], ticketOuts[15], func(b *wire.MsgBlock) {
// set the coinbase subsidy to invalid value (greater than coin supply)
b.Transactions[0].TxIn[0].ValueIn = 1234567890123456
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than setting a specific number, I would suggest doing b.STransactions[0].TxIn[0].ValueIn++ so that the test can't possibly create any false positives if the actual value in somehow ultimately ends up being the magic number.

// ... -> b46(14)
// \-> b56(15)
g.SetTip("b46")
g.NextBlock("b56", outs[15], ticketOuts[15], func(b *wire.MsgBlock) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is already a block b56 below that this will collide with.

@@ -1462,17 +1554,75 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
// \-> b51(15)
g.SetTip("b46")
g.NextBlock("b51", outs[15], ticketOuts[15], func(b *wire.MsgBlock) {
// set the merkle root to an invalid hash
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The header comment already says this. Not needed.

b.Header.MerkleRoot = chainhash.Hash{}
})
g.AssertTipBlockMerkleRoot(chainhash.Hash{})
rejected(blockchain.ErrBadMerkleRoot)

// Create block with an invalid proof-of-work limit.
// Create block with an invalid stake root
//
// ... -> b46(14)
// \-> b52(15)
g.SetTip("b46")
g.NextBlock("b52", outs[15], ticketOuts[15], func(b *wire.MsgBlock) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

b52 can't be changed like this or it will cause the b52a test to not test the intended thing. More generally, you can't reuse any of the same names. I think I'll add an assertion to NextBlock to causes duplicates to fail.

@dnldd dnldd force-pushed the update_fullblocktests_one_of_x branch 5 times, most recently from 07f122a to 4b3abad Compare February 25, 2018 00:30
@davecgh
Copy link
Member

davecgh commented Feb 25, 2018

Some additional details:
List of tests removed from validate_test.go which were already covered by the existing full block tests:

- ErrBadMerkleRoot 1
- ErrUnexpectedDifficulty
- ErrHighHash
- ErrBadCoinbaseValue
- ErrFirstTxNotCoinbase
- ErrBadCoinbaseFraudProof    ***NOTE: Neither the removed nor existing covers the null block height case and this should be added.***
- ErrRegTxInStakeTree
- ErrStakeTxInRegularTree
- ErrBadStakebaseScriptLen
- ErrBadStakebaseScrVal

g.NextBlock("b4", outs[2], ticketOuts[2])
// ... -> bf1(0) -> bf2(1)
// \-> bf3(1) -> bf4(2)
g.NextBlock("bf4", outs[2], ticketOuts[2])
accepted()

// Extend b2 fork twice to make first chain longer and force reorg.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bf2 fork

// \-> bf7(2) -> bf8(4)
// \-> bf3(1) -> bf4(2)
g.SetTip("bf5")
g.NextBlock("bs7", outs[2], ticketOuts[3])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/bs/bf/

// \-> bf7(2) -> bf8(4)
// \-> bf3(1) -> bf4(2)
g.SetTip("bf5")
g.NextBlock("bs7", outs[2], ticketOuts[3])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/bs/bf/

})
rejected(blockchain.ErrBadStakebaseAmountIn)

// Create block with a revocations count mismatch.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/with a revocations count mismatch/with a header that commits to more revocations than the block actually contains./

g.SetTip("bsl5")
g.NextBlock("bv6", outs[9], ticketOuts[9], func(b *wire.MsgBlock) {
// invalidate the vote by corrupting the output script
b.STransactions[0].TxOut[0].PkScript[2] ^= 0x55
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works, but I think a better test would be to actually replace the entire script with a completely valid script that votes on bs14 (the block before the one it should be voting on which is bs14).

That way it will ensure it doesn't allow voting on the wrong block even when the block it's voting on is actually valid.

g.NextBlock("bv7", outs[9], ticketOuts[9], func(b *wire.MsgBlock) {
b.Header.VoteBits &^= voteBitYes

for i := 0; i < 5; i++ {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically there is no need to do this. Blocks coming from the generator already vote yes by default. However, I guess it can't hurt to be explicit.

g.NextBlock("bv9", outs[9], ticketOuts[9], func(b *wire.MsgBlock) {
b.Header.VoteBits |= voteBitYes

for i := 0; i < 5; i++ {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for i := 0; i < 3; i++ {
	g.ReplaceVoteBitsN(i, voteBitNo)(b)
}
for i := 3; i < 5; i++ {
	g.ReplaceVoteBitsN(i, voteBitYes)(b)
}

The second for loop isn't even needed since they're already yes.

g.NextBlock("bv10", outs[9], ticketOuts[9], func(b *wire.MsgBlock) {
b.Header.VoteBits &^= voteBitYes

for i := 0; i < 5; i++ {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for i := 0; i < 2; i++ {
	g.ReplaceVoteBitsN(i, voteBitNo)(b)
}
for i := 2; i < 5; i++ {
	g.ReplaceVoteBitsN(i, voteBitYes)(b)
}

The second for loop isn't even needed since they're already yes.

g.SetTip("b6")
g.NextBlock("b9", outs[4], ticketOuts[4], additionalCoinbasePoW(1))
// ... -> bf1(0) -> bf2(1) -> bf5(2) -> bf6(3)
// \-> bpw1(4)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The arrows on the next line should start after the parens like so:

//   ... -> bf1(0) -> bf2(1) -> bf5(2) -> bf6(3)
//                                              \-> bpw1(4)

The reason is because then it can be copied and extended like so with the arrows still lining up:

//   ... -> bf1(0) -> bf2(1) -> bf5(2) -> bf6(3) -> somethingelse....
//                                              \-> bpw1(4)

@dnldd dnldd force-pushed the update_fullblocktests_one_of_x branch from 4b3abad to 7fd2c6f Compare February 25, 2018 01:15
// ... -> bf1(0) -> bf2(1)
// \-> bf3(1)
g.SetTip("bf1")
g.NextBlock("bf3", outs[1], ticketOuts[1])
b3Tx1Out := chaingen.MakeSpendableOut(g.Tip(), 1, 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bf3Tx1Out

g.NextBlock("b4", outs[2], ticketOuts[2])
// ... -> bf1(0) -> bf2(1)
// \-> bf3(1) -> bf4(2)
g.NextBlock("bf4", outs[2], ticketOuts[2])
accepted()

// Extend b2 fork twice to make first chain longer and force reorg.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bf2

g.NextBlock("b10", outs[3], ticketOuts[3])
acceptedToSideChainWithExpectedTip("b6")
// ... -> bf1(0) -> bf2(1) -> bf5(2) -> bf6(3)
// \-> bpw1(3) -> bpw2(4)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bpw2 and bpw3

b14 := g.NextBlock("b14", outs[5], ticketOuts[5], additionalCoinbasePoW(1))
// ... -> bf1(0) -> bf2(1) -> bf5(2) -> bf6(3)
// | \-> bpw4(3) -> bpw5(4) -> bpw6(5)
// | (bpw4 added last)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

space just before (bpw4 added last)

g.NextBlock("b16", outs[4], ticketOuts[4], additionalCoinbaseDev(1))
acceptedToSideChainWithExpectedTip("b13")
// ... -> bf5(2) -> bpw4(3) -> bpw5(4)
// \ \-> bdc1(4) -> bdc3(5)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/bdc1/bdc2/

// ... -> b5(2) -> b12(3) -> b18(4) -> b19(5) -> b21(6)
// \-> b3(1) -> b4(2)
g.SetTip("b19")
// ... -> bf5(2) -> bpw4(3) -> bdc5(4) -> bdc6(5) -> bcs1(6)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/bdc5/bdc4/
s/bdc6/bdc5/

// ... -> b5(2) -> b12(3) -> b18(4) -> b19(5) -> b21(6)
// \ \-> b22(7)
// \-> b3(1) -> b4(2)
// ... -> bf5(2) -> bpw4(3) -> bdc5(4) -> bdc5(5) -> bcs1(6)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

first bdc5 should be bdc4

@dnldd dnldd force-pushed the update_fullblocktests_one_of_x branch 2 times, most recently from 12d9c29 to 550ebee Compare February 25, 2018 01:36
})
rejected(blockchain.ErrBadStakebaseAmountIn)

// Create block with a header that commits to more revocations than the block actually contains..
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exceeds 80.

	// Create block with a header that commits to more revocations than the
	// block actually contains..

// \-> bv6(9)
g.SetTip("bsl5")
g.NextBlock("bv6", outs[9], ticketOuts[9], func(b *wire.MsgBlock) {
b.STransactions[0].TxOut[0].PkScript = bsl4.STransactions[0].TxOut[0].PkScript
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't what I meant. It's a valid vote, but it's consuming a a ticket that is already spent and this is dependent on the order of the checks in the code. Go ahead and revert it to ^= 0x55 for now. I'll have to write some code to expose creating a vote script on an invalid block.

@dnldd dnldd force-pushed the update_fullblocktests_one_of_x branch from 550ebee to 6da8225 Compare February 25, 2018 02:04
@davecgh
Copy link
Member

davecgh commented Feb 25, 2018

Some additional details:
List of tests removed from validate_test.go which were not already covered by the existing full block tests and were ported:

- ErrBadMerkleRoot 2
- ErrWrongBlockSize
- ErrBadCoinbaseAmountIn
- ErrBadStakebaseAmountIn
- ErrRevocationsMismatch
- ErrVotesOnWrongBlock
- ErrIncongruentVotebit 1
- ErrIncongruentVotebit 2
- ErrIncongruentVotebit 3
- ErrIncongruentVotebit 4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants