-
Notifications
You must be signed in to change notification settings - Fork 296
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
Conversation
df11353
to
68a4456
Compare
There was a problem hiding this 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.
There was a problem hiding this 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" + |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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.
a4fafa1
to
b1c604c
Compare
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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.
07f122a
to
4b3abad
Compare
Some additional details:
|
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. |
There was a problem hiding this comment.
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]) |
There was a problem hiding this comment.
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]) |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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++ { |
There was a problem hiding this comment.
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++ { |
There was a problem hiding this comment.
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++ { |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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)
4b3abad
to
7fd2c6f
Compare
// ... -> bf1(0) -> bf2(1) | ||
// \-> bf3(1) | ||
g.SetTip("bf1") | ||
g.NextBlock("bf3", outs[1], ticketOuts[1]) | ||
b3Tx1Out := chaingen.MakeSpendableOut(g.Tip(), 1, 0) |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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
12d9c29
to
550ebee
Compare
}) | ||
rejected(blockchain.ErrBadStakebaseAmountIn) | ||
|
||
// Create block with a header that commits to more revocations than the block actually contains.. |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
550ebee
to
6da8225
Compare
Some additional details:
|
This PR adds
AssertTipBlockStakeRoot
helper and moves the following block rejection cases into fullblocktests.ErrVotesOnWrongBlock
ErrIncongruentVotebit
ErrBadMerkleRoot
ErrWrongBlockSize
ErrBadCoinbaseAmountIn
ErrBadStakebaseAmountIn
ErrRevocationsMismatch
Work towards #1030.