Commit 279d9696 authored by David Vorick's avatar David Vorick

add test framework for forking

parent 8b45379b
package consensus
import (
"fmt"
"math/big"
)
......@@ -93,10 +94,24 @@ func (s *State) addBlockToTree(b Block) (err error) {
// allows them to cluster the processing. Utimately I think
// bandwidth will be the bigger issue, we'll reject bad orphans
// before we verify signatures.
_ = s.addBlockToTree(child)
// There's nothing we can really do about this error, because it
// doesn't reflect a problem with the current block. It means that
// someone managed to give us orphans with errors.
err = s.validHeader(child)
if err != nil {
// TODO: There's nothing we can really do about the error that gets
// returned, because it doesn't reflect a problem with the current
// block. It means that someone managed to give us orphans with
// errors.
fmt.Println(err)
continue
}
err = s.addBlockToTree(child)
if err != nil {
// TODO: There's nothing we can really do about the error that gets
// returned, because it doesn't reflect a problem with the current
// block. It means that someone managed to give us orphans with
// errors.
fmt.Println(err)
}
}
delete(s.missingParents, b.ID())
......
......@@ -25,10 +25,13 @@ func mineTestingBlock(parent BlockID, timestamp Timestamp, minerPayouts []Output
return
}
func nullMinerPayouts(s *State) []Output {
// nullMinerPayouts returns an []Output for the miner payouts field of a block
// so that the block can be valid. It assumes the block will be at whatever
// height you use as input.
func nullMinerPayouts(height BlockHeight) []Output {
return []Output{
Output{
Value: CalculateCoinbase(s.Height() + 1),
Value: CalculateCoinbase(height),
},
}
}
......@@ -36,7 +39,7 @@ func nullMinerPayouts(s *State) []Output {
// mineValidBlock picks valid/legal parameters for a block and then uses them
// to call mineTestingBlock.
func mineValidBlock(s *State) (b Block, err error) {
return mineTestingBlock(s.CurrentBlock().ID(), Timestamp(time.Now().Unix()), nullMinerPayouts(s), nil, s.CurrentTarget())
return mineTestingBlock(s.CurrentBlock().ID(), Timestamp(time.Now().Unix()), nullMinerPayouts(s.Height()+1), nil, s.CurrentTarget())
}
// testBlockTimestamps submits a block to the state with a timestamp that is
......@@ -44,7 +47,7 @@ func mineValidBlock(s *State) (b Block, err error) {
// rejected.
func testBlockTimestamps(t *testing.T, s *State) {
// Create a block with a timestamp that is too early.
b, err := mineTestingBlock(s.CurrentBlock().ID(), s.EarliestTimestamp()-1, nullMinerPayouts(s), nil, s.CurrentTarget())
b, err := mineTestingBlock(s.CurrentBlock().ID(), s.EarliestTimestamp()-1, nullMinerPayouts(s.Height()+1), nil, s.CurrentTarget())
if err != nil {
t.Fatal(err)
}
......@@ -54,7 +57,7 @@ func testBlockTimestamps(t *testing.T, s *State) {
}
// Create a block with a timestamp that is too late.
b, err = mineTestingBlock(s.CurrentBlock().ID(), Timestamp(time.Now().Unix())+10+FutureThreshold, nullMinerPayouts(s), nil, s.CurrentTarget())
b, err = mineTestingBlock(s.CurrentBlock().ID(), Timestamp(time.Now().Unix())+10+FutureThreshold, nullMinerPayouts(s.Height()+1), nil, s.CurrentTarget())
if err != nil {
t.Fatal(err)
}
......@@ -142,7 +145,7 @@ func testLargeBlock(t *testing.T, s *State) {
txns[0] = Transaction{
ArbitraryData: []string{bigData},
}
b, err := mineTestingBlock(s.CurrentBlock().ID(), Timestamp(time.Now().Unix()), nullMinerPayouts(s), txns, s.CurrentTarget())
b, err := mineTestingBlock(s.CurrentBlock().ID(), Timestamp(time.Now().Unix()), nullMinerPayouts(s.Height()+1), txns, s.CurrentTarget())
if err != nil {
t.Fatal(err)
}
......@@ -361,19 +364,19 @@ func testMultiOrphanBlock(t *testing.T, s *State) {
//
// The timestamp gets incremented each time so that we don't accidentally
// mine the same block twice or end up with a too early block.
orphanA, err := mineTestingBlock(parent.ID(), Timestamp(time.Now().Unix()), nullMinerPayouts(s), nil, orphanTarget)
orphanA, err := mineTestingBlock(parent.ID(), Timestamp(time.Now().Unix()), nullMinerPayouts(s.Height()+2), nil, orphanTarget)
if err != nil {
t.Fatal(err)
}
orphanB, err := mineTestingBlock(parent.ID(), Timestamp(time.Now().Unix()+1), nullMinerPayouts(s), nil, orphanTarget)
orphanB, err := mineTestingBlock(parent.ID(), Timestamp(time.Now().Unix()+1), nullMinerPayouts(s.Height()+2), nil, orphanTarget)
if err != nil {
t.Fatal(err)
}
orphanC, err := mineTestingBlock(parent.ID(), Timestamp(time.Now().Unix()+2), nullMinerPayouts(s), nil, orphanTarget)
orphanC, err := mineTestingBlock(parent.ID(), Timestamp(time.Now().Unix()+2), nullMinerPayouts(s.Height()+2), nil, orphanTarget)
if err != nil {
t.Fatal(err)
}
orphan2, err := mineTestingBlock(orphanA.ID(), Timestamp(time.Now().Unix()+3), nullMinerPayouts(s), nil, orphan2Target)
orphan2, err := mineTestingBlock(orphanB.ID(), Timestamp(time.Now().Unix()+3), nullMinerPayouts(s.Height()+3), nil, orphan2Target)
if err != nil {
t.Fatal(err)
}
......@@ -452,7 +455,7 @@ func testOrphanBlock(t *testing.T, s *State) {
parentTarget := s.CurrentTarget()
orphanRat := new(big.Rat).Mul(parentTarget.Rat(), MaxAdjustmentDown)
orphanTarget := RatToTarget(orphanRat)
orphan, err := mineTestingBlock(parent.ID(), Timestamp(time.Now().Unix()), nullMinerPayouts(s), nil, orphanTarget)
orphan, err := mineTestingBlock(parent.ID(), Timestamp(time.Now().Unix()), nullMinerPayouts(s.Height()+2), nil, orphanTarget)
if err != nil {
t.Fatal(err)
}
......@@ -566,9 +569,6 @@ func TestEmptyBlock(t *testing.T) {
// TestLargeBlock creates a new state and uses it to call testLargeBlock.
func TestLargeBlock(t *testing.T) {
if testing.Short() {
t.Skip()
}
s := CreateGenesisState()
testLargeBlock(t, s)
}
......@@ -588,14 +588,22 @@ func TestMissedTarget(t *testing.T) {
// TestDoubleOrphanBlock creates a new state and used it to call
// testDoubleOrphanBlock.
func TestMultiOrphanBlock(t *testing.T) {
if testing.Short() {
t.Skip()
}
s := CreateGenesisState()
testMultiOrphanBlock(t, s)
consistencyChecks(t, s)
}
// TestOrphanBlock creates a new state and uses it to call testOrphanBlock.
func TestOrphanBlock(t *testing.T) {
if testing.Short() {
t.Skip()
}
s := CreateGenesisState()
testOrphanBlock(t, s)
consistencyChecks(t, s)
}
// TestRepeatBlock creates a new state and uses it to call testRepeatBlock.
......@@ -613,11 +621,7 @@ func TestRepeatBlock(t *testing.T) {
// you want to be able to probe complex transactions that have lots of juicy
// stuff.
// TODO: Fork probing. Fork between complex sets of blocks and see that the
// state hashes always end up at the same point, make sure long forking works
// well and that reversing when a transaction fails also works well.
// TODO: Test the actual method which is used to calculated the earliest legal
// TODO: Test the actual method which is used to calculate the earliest legal
// timestamp for the next block. Like have some examples that should work out
// algebraically and make sure that earliest timestamp follows the rules layed
// out by the protocol. This should be done after we decide that the algorithm
......@@ -626,8 +630,3 @@ func TestRepeatBlock(t *testing.T) {
// TODO: Probe the target adjustments, make sure that they are happening
// according to specification, moving as much as they should and that the
// clamps are being effective.
// TODO: Write tests for CalculateCoinbase to make sure that it's behaving
// correctly. I guess this doesn't help us if the state stops using
// CalculateCoinbase, but I'm not sure what other good alternative ways there
// are for testing this.
package consensus
import (
"testing"
"time"
)
// TODO: Add a consistency check for the number of coins in the state, and the
// subsidies at each block.
// currentPathCheck looks at every block listed in currentPath and verifies
// that every block from current to genesis matches the block listed in
// currentPath.
func currentPathCheck(t *testing.T, s *State) {
currentNode := s.currentBlockNode()
for i := s.Height(); i != 0; i-- {
// Check that the CurrentPath entry exists.
id, exists := s.currentPath[i]
if !exists {
t.Error("current path is empty for a height with a known block.")
}
// Check that the CurrentPath entry contains the correct block id.
if currentNode.block.ID() != id {
t.Error("current path does not have correct id!")
}
// Check that each parent is one less in height than its child.
if currentNode.height != currentNode.parent.height+1 {
t.Error("heights are messed up")
}
currentNode = s.blockMap[currentNode.block.ParentID]
}
}
// rewindApplyCheck grabs the state hash and then rewinds to the genesis block.
// Then the state moves forwards to the initial starting place and verifies
// that the state hash is the same.
func rewindApplyCheck(t *testing.T, s *State) {
stateHash := s.stateHash()
rewoundNodes := s.rewindToNode(s.blockRoot)
for i := len(rewoundNodes) - 1; i >= 0; i-- {
s.applyBlockNode(rewoundNodes[i])
}
if stateHash != s.stateHash() {
t.Error("state hash is not consistent after rewinding and applying all the way through")
}
}
// consistencyChecks calls all of the consistency functions on each of the
// states.
func consistencyChecks(t *testing.T, states ...*State) {
for _, s := range states {
currentPathCheck(t, s)
rewindApplyCheck(t, s)
}
}
// orderedTestBattery calls all of the individual tests on each of the input
// states. The goal is to produce state with consistent but diverse sets of
// blocks to more effectively test things like diffs and forking.
func orderedTestBattery(t *testing.T, states ...*State) {
for _, s := range states {
testBlockTimestamps(t, s)
testEmptyBlock(t, s)
testLargeBlock(t, s)
testMinerPayouts(t, s)
testMissedTarget(t, s)
testMultiOrphanBlock(t, s)
testOrphanBlock(t, s)
testRepeatBlock(t, s)
}
}
// TestEverything creates a state and uses that one state to perform all of the
// individual tests, building a sizeable state with a lot of diverse
// transactions. Then it performs consistency checks and other stress testing.
func TestEverything(t *testing.T) {
if testing.Short() {
t.Skip()
}
// To help test the forking code, we're creating two states. We'll start
// each off on its own fork, and then test them together. They'll get the
// same set of tests and be in the same place, except the only shared block
// will be the genesis block. Then we'll mine blocks so that one is far
// ahead of the other. We'll show all of the blocks to the other state,
// which will cause it to fork and rewind the entire diverse set of blocks
// and then apply an entirely different diverse set of blocks.
s0 := CreateGenesisState()
s1 := CreateGenesisState()
// Verify that the genesis state creation is consistent.
if s0.stateHash() != s1.stateHash() {
t.Fatal("state hashes are different after calling CreateGenesisState")
}
// Get each on a separate fork.
b0, err := mineTestingBlock(s0.CurrentBlock().ID(), Timestamp(time.Now().Unix()-1), nullMinerPayouts(s0.Height()+1), nil, s0.CurrentTarget())
if err != nil {
t.Fatal(err)
}
err = s0.AcceptBlock(b0)
if err != nil {
t.Fatal(err)
}
b1, err := mineTestingBlock(s1.CurrentBlock().ID(), Timestamp(time.Now().Unix()), nullMinerPayouts(s1.Height()+1), nil, s1.CurrentTarget())
if err != nil {
t.Fatal(err)
}
err = s1.AcceptBlock(b1)
if err != nil {
t.Fatal(err)
}
// Verify that each state is on a separate fork.
if s0.stateHash() == s1.stateHash() {
t.Fatal("states have the same hash when they need to be in different places")
}
// Call orderedTestBattery on each state.
orderedTestBattery(t, s0, s1)
// Verify that each state is still on a separate fork.
if s0.stateHash() == s1.stateHash() {
t.Fatal("states have the same hash when they need to be in different places")
}
// Now perform consistency checks on each state.
consistencyChecks(t, s0, s1)
// Get s0 ahead of s1
for i := 0; i < 2; i++ {
b, err := mineValidBlock(s0)
if err != nil {
t.Fatal(err)
}
err = s0.AcceptBlock(b)
if err != nil {
t.Fatal(err)
}
}
// Show all s0 blocks to s1, which should trigger a fork in s1. Start from
// height 1 since the genesis block is shared.
for i := BlockHeight(1); i <= s0.Height(); i++ {
blockID := s0.currentPath[i]
err = s1.AcceptBlock(s0.blockMap[blockID].block)
if err != nil {
t.Error(i, "::", blockID, "::", err)
}
}
// Check that s0 and s1 now have the same state hash
if s0.stateHash() != s1.stateHash() {
t.Error("state hashes do not match even though a fork should have occured")
}
// Perform consistency checks on s1.
currentPathCheck(t, s1)
rewindApplyCheck(t, s1)
}
package consensus
// TODO: Move this to a different file, perhaps testing.
import (
"fmt"
)
// CurrentPathCheck looks at every block listed in CurrentPath and verifies
// that every block from current to genesis matches the block listed in
// CurrentPath.
func (s *State) currentPathCheck() {
currentNode := s.currentBlockNode()
for i := s.height(); ; i-- {
// Check that the CurrentPath entry exists.
id, exists := s.currentPath[i]
if !exists {
println(i)
panic("current path is empty for a height with a known block.")
}
// Check that the CurrentPath entry contains the correct block id.
if currentNode.block.ID() != id {
currentNodeID := currentNode.block.ID()
println(i)
fmt.Println(id[:])
fmt.Println(currentNodeID[:])
panic("current path does not have correct id!")
}
currentNode = s.blockMap[currentNode.block.ParentID]
// Have to do an awkward break beacuse i is unsigned.
if i == 0 {
break
}
}
}
......@@ -3,15 +3,13 @@ package consensus
// TODO: Swtich to 128 bit Currency, which is overflow-safe. Then update
// CalculateCoinbase.
// TODO: Enable siafund stuff
// TODO: Enforce the 100 block spending hold on certain types of outputs: Miner
// payouts, storage proof outputs, siafund claims.
// TODO: Enforce restrictions on CoveredFields
// TODO: Enforce restrictions on which storage proof transactions are legal
// TODO: Enforce siafund rules in consensus.
import (
"github.com/NebulousLabs/Sia/crypto"
"github.com/NebulousLabs/Sia/encoding"
......@@ -59,7 +57,7 @@ type Transaction struct {
// An Input contains the ID of the output it's trying to spend, and the spend
// conditions that unlock the output.
type Input struct {
OutputID OutputID // the source of coins for the input
OutputID OutputID
SpendConditions SpendConditions
}
......
......@@ -187,11 +187,13 @@ func (w *Wallet) SignTransaction(id string, wholeTransaction bool) (txn consensu
for i := range txn.StorageProofs {
coveredFields.StorageProofs = append(coveredFields.StorageProofs, uint64(i))
}
// TODO: Siafund stuff here.
for i := range txn.ArbitraryData {
coveredFields.ArbitraryData = append(coveredFields.ArbitraryData, uint64(i))
}
// TODO: Should we also sign all of the known signatures?
for i := range txn.Signatures {
coveredFields.Signatures = append(coveredFields.Signatures, uint64(i))
}
}
// For each input in the transaction that we added, provide a signature.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment