// TestBlockUndoDataSerializing ensures serializing and deserializing the // block undo data works as expected. func TestBlockUndoDataSerializing(t *testing.T) { t.Parallel() tests := []struct { name string utds []UndoTicketData serialized []byte }{ { name: "two ticket datas", utds: []UndoTicketData{ UndoTicketData{ TicketHash: chainhash.HashFuncH([]byte{0x00}), TicketHeight: 123456, Missed: true, Revoked: false, Spent: false, Expired: true, }, UndoTicketData{ TicketHash: chainhash.HashFuncH([]byte{0x01}), TicketHeight: 122222, Missed: false, Revoked: true, Spent: true, Expired: false, }, }, serialized: hexToBytes("0ce8d4ef4dd7cd8d62dfded9d4edb0a774ae6a41929a74da23109e8f11139c8740e20100094a6c419a1e25c85327115c4ace586decddfe2990ed8f3d4d801871158338501d6edd010006"), }, } for i, test := range tests { // Ensure the state serializes to the expected value. gotBytes := serializeBlockUndoData(test.utds) if !bytes.Equal(gotBytes, test.serialized) { t.Errorf("serializeBlockUndoData #%d (%s): mismatched "+ "bytes - got %x, want %x", i, test.name, gotBytes, test.serialized) continue } // Ensure the serialized bytes are decoded back to the expected // state. utds, err := deserializeBlockUndoData(test.serialized) if err != nil { t.Errorf("deserializeBlockUndoData #%d (%s) "+ "unexpected error: %v", i, test.name, err) continue } if !reflect.DeepEqual(utds, test.utds) { t.Errorf("deserializeBlockUndoData #%d (%s) "+ "mismatched state - got %v, want %v", i, test.name, utds, test.utds) continue } } }
// TestBestChainStateSerialization ensures serializing and deserializing the // best chain state works as expected. func TestBestChainStateSerialization(t *testing.T) { t.Parallel() hash1 := chainhash.HashFuncH([]byte{0x00}) hash2 := chainhash.HashFuncH([]byte{0x01}) hash3 := chainhash.HashFuncH([]byte{0x02}) hash4 := chainhash.HashFuncH([]byte{0x03}) hash5 := chainhash.HashFuncH([]byte{0x04}) tests := []struct { name string state BestChainState serialized []byte }{ { name: "generic block", state: BestChainState{ Hash: *newShaHashFromStr("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"), Height: 12323, Live: 29399, Missed: 293929392, Revoked: 349839493, PerBlock: 5, NextWinners: []chainhash.Hash{hash1, hash2, hash3, hash4, hash5}, }, serialized: hexToBytes("6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d619000000000023300000d7720000b0018511000000008520da140000000005000ce8d4ef4dd7cd8d62dfded9d4edb0a774ae6a41929a74da23109e8f11139c874a6c419a1e25c85327115c4ace586decddfe2990ed8f3d4d801871158338501d49af37ab5270015fe25276ea5a3bb159d852943df23919522a202205fb7d175cb706d561742ad3671703c247eb927ee8a386369c79644131cdeb2c5c26bf6c5d4c6eb9e38415034f4c93d3304d10bef38bf0ad420eefd0f72f940f11c5857786"), }, } for i, test := range tests { // Ensure the state serializes to the expected value. gotBytes := serializeBestChainState(test.state) if !bytes.Equal(gotBytes, test.serialized) { t.Errorf("serializeBestChainState #%d (%s): mismatched "+ "bytes - got %x, want %x", i, test.name, gotBytes, test.serialized) continue } // Ensure the serialized bytes are decoded back to the expected // state. state, err := deserializeBestChainState(test.serialized) if err != nil { t.Errorf("deserializeBestChainState #%d (%s) "+ "unexpected error: %v", i, test.name, err) continue } if !reflect.DeepEqual(state, test.state) { t.Errorf("deserializeBestChainState #%d (%s) "+ "mismatched state - got %v, want %v", i, test.name, state, test.state) continue } } }
// TestTicketHashesSerializing ensures serializing and deserializing the // ticket hashes works as expected. func TestTicketHashesSerializing(t *testing.T) { t.Parallel() hash1 := chainhash.HashFuncH([]byte{0x00}) hash2 := chainhash.HashFuncH([]byte{0x01}) tests := []struct { name string ths TicketHashes serialized []byte }{ { name: "two ticket hashes", ths: TicketHashes{ hash1, hash2, }, serialized: hexToBytes("0ce8d4ef4dd7cd8d62dfded9d4edb0a774ae6a41929a74da23109e8f11139c874a6c419a1e25c85327115c4ace586decddfe2990ed8f3d4d801871158338501d"), }, } for i, test := range tests { // Ensure the state serializes to the expected value. gotBytes := serializeTicketHashes(test.ths) if !bytes.Equal(gotBytes, test.serialized) { t.Errorf("serializeBlockUndoData #%d (%s): mismatched "+ "bytes - got %x, want %x", i, test.name, gotBytes, test.serialized) continue } // Ensure the serialized bytes are decoded back to the expected // state. ths, err := deserializeTicketHashes(test.serialized) if err != nil { t.Errorf("deserializeBlockUndoData #%d (%s) "+ "unexpected error: %v", i, test.name, err) continue } if !reflect.DeepEqual(ths, test.ths) { t.Errorf("deserializeBlockUndoData #%d (%s) "+ "mismatched state - got %v, want %v", i, test.name, ths, test.ths) continue } } }
// HashMerkleBranches takes two hashes, treated as the left and right tree // nodes, and returns the hash of their concatenation. This is a helper // function used to aid in the generation of a merkle tree. func HashMerkleBranches(left *chainhash.Hash, right *chainhash.Hash) *chainhash.Hash { // Concatenate the left and right nodes. var sha [chainhash.HashSize * 2]byte copy(sha[:chainhash.HashSize], left[:]) copy(sha[chainhash.HashSize:], right[:]) newSha := chainhash.HashFuncH(sha[:]) return &newSha }
func (b Block) Verify() bool { databytes, err := hex.DecodeString(b.header[0:360]) if err != nil { return false } hash := chainhash.HashFuncH(databytes) hashNum := blockchain.ShaHashToBig(&hash) return hashNum.Cmp(b.difficulty) <= 0 }
// BlockSha computes the block identifier hash for the given block header. func (h *BlockHeader) BlockSha() chainhash.Hash { // Encode the header and hash256 everything prior to the number of // transactions. Ignore the error returns since there is no way the // encode could fail except being out of memory which would cause a // run-time panic. var buf bytes.Buffer _ = writeBlockHeader(&buf, 0, h) return chainhash.HashFuncH(buf.Bytes()) }
// StateHash returns a hash referencing the current state the deterministic PRNG. func (hp *Hash256PRNG) StateHash() chainhash.Hash { fHash := hp.lastHash fIdx := hp.idx fHashIdx := hp.hashIdx finalState := make([]byte, len(fHash)+4+1) copy(finalState, fHash[:]) binary.BigEndian.PutUint32(finalState[len(fHash):], uint32(fIdx)) finalState[len(fHash)+4] = byte(fHashIdx) return chainhash.HashFuncH(finalState) }
// BenchmarkHashFuncH performs a benchmark on how long it takes to perform // a hash returning a Hash. func BenchmarkHashFuncH(b *testing.B) { var buf bytes.Buffer if err := genesisCoinbaseTx.Serialize(&buf); err != nil { b.Errorf("Serialize: unexpected error: %v", err) return } txBytes := buf.Bytes() b.ResetTimer() for i := 0; i < b.N; i++ { _ = chainhash.HashFuncH(txBytes) } }
// Hash256Rand returns a uint32 random number using the pseudorandom number // generator and updates the state. func (hp *Hash256PRNG) Hash256Rand() uint32 { r := binary.BigEndian.Uint32(hp.lastHash[hp.hashIdx*4 : hp.hashIdx*4+4]) hp.hashIdx++ // 'roll over' the hash index to use and store it. if hp.hashIdx > 7 { idxB := make([]byte, 4, 4) binary.BigEndian.PutUint32(idxB, uint32(hp.idx)) hp.lastHash = chainhash.HashFuncH(append(hp.seed[:], idxB...)) hp.idx++ hp.hashIdx = 0 } // 'roll over' the PRNG by re-hashing the seed when // we overflow idx. if hp.idx > 0xFFFFFFFF { hp.seed = chainhash.HashFuncH(hp.seed[:]) hp.lastHash = hp.seed hp.idx = 0 } return r }
// NewHash256PRNG creates a pointer to a newly created hash256PRNG. func NewHash256PRNG(seed []byte) *Hash256PRNG { // idx and lastHash are automatically initialized // as 0. We initialize the seed by appending a constant // to it and hashing to give 32 bytes. This ensures // that regardless of the input, the PRNG is always // doing a short number of rounds because it only // has to hash < 64 byte messages. The constant is // derived from the hexadecimal representation of // pi. hp := new(Hash256PRNG) hp.seed = chainhash.HashFuncH(append(seed, seedConst[:]...)) hp.lastHash = hp.seed hp.idx = 0 return hp }
func TestFetchWinnersErrors(t *testing.T) { treap := new(tickettreap.Immutable) for i := 0; i < 0xff; i++ { h := chainhash.HashFuncH([]byte{byte(i)}) v := &tickettreap.Value{ Height: uint32(i), Missed: i%2 == 0, Revoked: i%2 != 0, Spent: i%2 == 0, Expired: i%2 != 0, } treap = treap.Put(tickettreap.Key(h), v) } // No indexes. _, err := fetchWinners(nil, treap) if err == nil { t.Errorf("Expected nil slice error") } // No treap. _, err = fetchWinners([]int{1, 2, 3, 4, -1}, nil) if err == nil { t.Errorf("Expected nil treap error") } // Bad index too small. _, err = fetchWinners([]int{1, 2, 3, 4, -1}, treap) if err == nil { t.Errorf("Expected index too small error") } // Bad index too big. _, err = fetchWinners([]int{1, 2, 3, 4, 256}, treap) if err == nil { t.Errorf("Expected index too big error") } }
func TestTicketSorting(t *testing.T) { ticketsPerBlock := 5 ticketPoolSize := uint16(8192) totalTickets := uint32(ticketPoolSize) * uint32(5) bucketsSize := 256 randomGen := rand.New(rand.NewSource(12345)) ticketMap := make([]SStxMemMap, int(bucketsSize), int(bucketsSize)) for i := 0; i < bucketsSize; i++ { ticketMap[i] = make(SStxMemMap) } toMake := int(ticketPoolSize) * ticketsPerBlock for i := 0; i < toMake; i++ { td := new(TicketData) rint64 := randomGen.Int63n(1 << 62) randBytes := make([]byte, 8, 8) binary.LittleEndian.PutUint64(randBytes, uint64(rint64)) h := chainhash.HashFuncH(randBytes) td.SStxHash = h prefix := byte(h[0]) ticketMap[prefix][h] = td } // Pre-sort with buckets (faster). sortedSlice := make([]*TicketData, 0, totalTickets) for i := 0; i < bucketsSize; i++ { tempTdSlice := NewTicketDataSlice(len(ticketMap[i])) itr := 0 // Iterator for _, td := range ticketMap[i] { tempTdSlice[itr] = td itr++ } sort.Sort(tempTdSlice) sortedSlice = append(sortedSlice, tempTdSlice...) } sortedSlice1 := sortedSlice // However, it should be the same as a sort without the buckets. toSortSlice := make([]*TicketData, 0, totalTickets) for i := 0; i < bucketsSize; i++ { tempTdSlice := make([]*TicketData, len(ticketMap[i]), len(ticketMap[i])) itr := 0 // Iterator for _, td := range ticketMap[i] { tempTdSlice[itr] = td itr++ } toSortSlice = append(toSortSlice, tempTdSlice...) } sortedSlice = NewTicketDataSlice(int(totalTickets)) copy(sortedSlice, toSortSlice) sort.Sort(TicketDataSlice(sortedSlice)) sortedSlice2 := sortedSlice if !reflect.DeepEqual(sortedSlice1, sortedSlice2) { t.Errorf("bucket sort failed to sort to the same slice as global sort") } }
// TestLiveDatabase tests various functions that require a live database. func TestLiveDatabase(t *testing.T) { // Create a new database to store the accepted stake node data into. dbName := "ffldb_ticketdb_test" dbPath := filepath.Join(testDbRoot, dbName) _ = os.RemoveAll(dbPath) testDb, err := database.Create(testDbType, dbPath, chaincfg.SimNetParams.Net) if err != nil { t.Fatalf("error creating db: %v", err) } // Setup a teardown. defer os.RemoveAll(dbPath) defer os.RemoveAll(testDbRoot) defer testDb.Close() // Initialize the database, then try to read the version. err = testDb.Update(func(dbTx database.Tx) error { return DbCreate(dbTx) }) if err != nil { t.Fatalf("%v", err.Error()) } var dbi *DatabaseInfo err = testDb.View(func(dbTx database.Tx) error { dbi, err = DbFetchDatabaseInfo(dbTx) if err != nil { return err } return nil }) if err != nil { t.Fatalf("%v", err.Error()) } if dbi.Version != currentDatabaseVersion { t.Fatalf("bad version after reading from DB; want %v, got %v", currentDatabaseVersion, dbi.Version) } // Test storing arbitrary ticket treaps. ticketMap := make(map[tickettreap.Key]*tickettreap.Value) tickets := make([]chainhash.Hash, 5) for i := 0; i < 4; i++ { h := chainhash.HashFuncH(bytes.Repeat([]byte{0x01}, i)) ticketMap[tickettreap.Key(h)] = &tickettreap.Value{ Height: 12345 + uint32(i), Missed: i%2 == 0, Revoked: i%2 != 0, Spent: i%2 == 0, Expired: i%2 != 0, } tickets[i] = h } err = testDb.Update(func(dbTx database.Tx) error { for k, v := range ticketMap { h := chainhash.Hash(k) err = DbPutTicket(dbTx, dbnamespace.LiveTicketsBucketName, &h, v.Height, v.Missed, v.Revoked, v.Spent, v.Expired) if err != nil { return err } } return nil }) if err != nil { t.Fatalf("%v", err.Error()) } var treap *tickettreap.Immutable ticketMap2 := make(map[tickettreap.Key]*tickettreap.Value) err = testDb.View(func(dbTx database.Tx) error { treap, err = DbLoadAllTickets(dbTx, dbnamespace.LiveTicketsBucketName) if err != nil { return err } return nil }) if err != nil { t.Fatalf("%v", err.Error()) } treap.ForEach(func(k tickettreap.Key, v *tickettreap.Value) bool { ticketMap2[k] = v return true }) if !reflect.DeepEqual(ticketMap, ticketMap2) { t.Fatalf("not same ticket maps") } }
// estimateNextStakeDifficulty returns a user-specified estimate for the next // stake difficulty, with the passed ticketsInWindow indicating the number of // fresh stake to pretend exists within this window. Optionally the user can // also override this variable with useMaxTickets, which simply plugs in the // maximum number of tickets the user can try. func (b *BlockChain) estimateNextStakeDifficulty(curNode *blockNode, ticketsInWindow int64, useMaxTickets bool) (int64, error) { alpha := b.chainParams.StakeDiffAlpha stakeDiffStartHeight := int64(b.chainParams.CoinbaseMaturity) + 1 maxRetarget := int64(b.chainParams.RetargetAdjustmentFactor) TicketPoolWeight := int64(b.chainParams.TicketPoolSizeWeight) // Number of nodes to traverse while calculating difficulty. nodesToTraverse := (b.chainParams.StakeDiffWindowSize * b.chainParams.StakeDiffWindows) // Genesis block. Block at height 1 has these parameters. if curNode == nil || curNode.height < stakeDiffStartHeight { return b.chainParams.MinimumStakeDiff, nil } // Create a fake blockchain on top of the current best node with // the number of freshly purchased tickets as indicated by the // user. oldDiff := curNode.header.SBits topNode := curNode if (curNode.height+1)%b.chainParams.StakeDiffWindowSize != 0 { nextAdjHeight := ((curNode.height / b.chainParams.StakeDiffWindowSize) + 1) * b.chainParams.StakeDiffWindowSize maxTickets := (nextAdjHeight - curNode.height) * int64(b.chainParams.MaxFreshStakePerBlock) // If the user has indicated that the automatically // calculated maximum amount of tickets should be // used, plug that in here. if useMaxTickets { ticketsInWindow = maxTickets } // Double check to make sure there isn't too much. if ticketsInWindow > maxTickets { return 0, fmt.Errorf("too much fresh stake to be used "+ "in evaluation requested; max %v, got %v", maxTickets, ticketsInWindow) } // Insert all the tickets into bogus nodes that will be // used to calculate the next difficulty below. ticketsToInsert := ticketsInWindow for i := curNode.height + 1; i < nextAdjHeight; i++ { emptyHeader := new(wire.BlockHeader) emptyHeader.Height = uint32(i) // User a constant pool size for estimate, since // this has much less fluctuation than freshStake. // TODO Use a better pool size estimate? emptyHeader.PoolSize = curNode.header.PoolSize // Insert the fake fresh stake into each block, // decrementing the amount we need to use each // time until we hit 0. freshStake := b.chainParams.MaxFreshStakePerBlock if int64(freshStake) > ticketsToInsert { freshStake = uint8(ticketsToInsert) ticketsToInsert -= ticketsToInsert } else { ticketsToInsert -= int64(b.chainParams.MaxFreshStakePerBlock) } emptyHeader.FreshStake = freshStake // Connect the header. emptyHeader.PrevBlock = topNode.hash // Make up a node hash. hB, err := emptyHeader.Bytes() if err != nil { return 0, err } emptyHeaderHash := chainhash.HashFuncH(hB) thisNode := new(blockNode) thisNode.header = *emptyHeader thisNode.hash = emptyHeaderHash thisNode.height = i thisNode.parent = topNode topNode = thisNode } } // The target size of the ticketPool in live tickets. Recast these as int64 // to avoid possible overflows for large sizes of either variable in // params. targetForTicketPool := int64(b.chainParams.TicketsPerBlock) * int64(b.chainParams.TicketPoolSize) // Initialize bigInt slice for the percentage changes for each window period // above or below the target. windowChanges := make([]*big.Int, b.chainParams.StakeDiffWindows) // Regress through all of the previous blocks and store the percent changes // per window period; use bigInts to emulate 64.32 bit fixed point. oldNode := topNode windowPeriod := int64(0) weights := uint64(0) for i := int64(0); ; i++ { // Store and reset after reaching the end of every window period. if (i+1)%b.chainParams.StakeDiffWindowSize == 0 { // First adjust based on ticketPoolSize. Skew the difference // in ticketPoolSize by max adjustment factor to help // weight ticket pool size versus tickets per block. poolSizeSkew := (int64(oldNode.header.PoolSize)- targetForTicketPool)*TicketPoolWeight + targetForTicketPool // Don't let this be negative or zero. if poolSizeSkew <= 0 { poolSizeSkew = 1 } curPoolSizeTemp := big.NewInt(poolSizeSkew) curPoolSizeTemp.Lsh(curPoolSizeTemp, 32) // Add padding targetTemp := big.NewInt(targetForTicketPool) windowAdjusted := curPoolSizeTemp.Div(curPoolSizeTemp, targetTemp) // Weight it exponentially. Be aware that this could at some point // overflow if alpha or the number of blocks used is really large. windowAdjusted = windowAdjusted.Lsh(windowAdjusted, uint((b.chainParams.StakeDiffWindows-windowPeriod)*alpha)) // Sum up all the different weights incrementally. weights += 1 << uint64((b.chainParams.StakeDiffWindows-windowPeriod)* alpha) // Store it in the slice. windowChanges[windowPeriod] = windowAdjusted // windowFreshStake = 0 windowPeriod++ } if (i + 1) == nodesToTraverse { break // Exit for loop when we hit the end. } // Get the previous block node. var err error tempNode := oldNode oldNode, err = b.getPrevNodeFromNode(oldNode) if err != nil { return 0, err } // If we're at the genesis block, reset the oldNode // so that it stays at the genesis block. if oldNode == nil { oldNode = tempNode } } // Sum up the weighted window periods. weightedSum := big.NewInt(0) for i := int64(0); i < b.chainParams.StakeDiffWindows; i++ { weightedSum.Add(weightedSum, windowChanges[i]) } // Divide by the sum of all weights. weightsBig := big.NewInt(int64(weights)) weightedSumDiv := weightedSum.Div(weightedSum, weightsBig) // Multiply by the old stake diff. oldDiffBig := big.NewInt(oldDiff) nextDiffBig := weightedSumDiv.Mul(weightedSumDiv, oldDiffBig) // Right shift to restore the original padding (restore non-fixed point). nextDiffBig = nextDiffBig.Rsh(nextDiffBig, 32) nextDiffTicketPool := nextDiffBig.Int64() // Check to see if we're over the limits for the maximum allowable retarget; // if we are, return the maximum or minimum except in the case that oldDiff // is zero. if oldDiff == 0 { // This should never really happen, but in case it does... return nextDiffTicketPool, nil } else if nextDiffTicketPool == 0 { nextDiffTicketPool = oldDiff / maxRetarget } else if (nextDiffTicketPool / oldDiff) > (maxRetarget - 1) { nextDiffTicketPool = oldDiff * maxRetarget } else if (oldDiff / nextDiffTicketPool) > (maxRetarget - 1) { nextDiffTicketPool = oldDiff / maxRetarget } // The target number of new SStx per block for any given window period. targetForWindow := b.chainParams.StakeDiffWindowSize * int64(b.chainParams.TicketsPerBlock) // Regress through all of the previous blocks and store the percent changes // per window period; use bigInts to emulate 64.32 bit fixed point. oldNode = topNode windowFreshStake := int64(0) windowPeriod = int64(0) weights = uint64(0) for i := int64(0); ; i++ { // Add the fresh stake into the store for this window period. windowFreshStake += int64(oldNode.header.FreshStake) // Store and reset after reaching the end of every window period. if (i+1)%b.chainParams.StakeDiffWindowSize == 0 { // Don't let fresh stake be zero. if windowFreshStake <= 0 { windowFreshStake = 1 } freshTemp := big.NewInt(windowFreshStake) freshTemp.Lsh(freshTemp, 32) // Add padding targetTemp := big.NewInt(targetForWindow) // Get the percentage change. windowAdjusted := freshTemp.Div(freshTemp, targetTemp) // Weight it exponentially. Be aware that this could at some point // overflow if alpha or the number of blocks used is really large. windowAdjusted = windowAdjusted.Lsh(windowAdjusted, uint((b.chainParams.StakeDiffWindows-windowPeriod)*alpha)) // Sum up all the different weights incrementally. weights += 1 << uint64((b.chainParams.StakeDiffWindows-windowPeriod)*alpha) // Store it in the slice. windowChanges[windowPeriod] = windowAdjusted windowFreshStake = 0 windowPeriod++ } if (i + 1) == nodesToTraverse { break // Exit for loop when we hit the end. } // Get the previous block node. var err error tempNode := oldNode oldNode, err = b.getPrevNodeFromNode(oldNode) if err != nil { return 0, err } // If we're at the genesis block, reset the oldNode // so that it stays at the genesis block. if oldNode == nil { oldNode = tempNode } } // Sum up the weighted window periods. weightedSum = big.NewInt(0) for i := int64(0); i < b.chainParams.StakeDiffWindows; i++ { weightedSum.Add(weightedSum, windowChanges[i]) } // Divide by the sum of all weights. weightsBig = big.NewInt(int64(weights)) weightedSumDiv = weightedSum.Div(weightedSum, weightsBig) // Multiply by the old stake diff. oldDiffBig = big.NewInt(oldDiff) nextDiffBig = weightedSumDiv.Mul(weightedSumDiv, oldDiffBig) // Right shift to restore the original padding (restore non-fixed point). nextDiffBig = nextDiffBig.Rsh(nextDiffBig, 32) nextDiffFreshStake := nextDiffBig.Int64() // Check to see if we're over the limits for the maximum allowable retarget; // if we are, return the maximum or minimum except in the case that oldDiff // is zero. if oldDiff == 0 { // This should never really happen, but in case it does... return nextDiffFreshStake, nil } else if nextDiffFreshStake == 0 { nextDiffFreshStake = oldDiff / maxRetarget } else if (nextDiffFreshStake / oldDiff) > (maxRetarget - 1) { nextDiffFreshStake = oldDiff * maxRetarget } else if (oldDiff / nextDiffFreshStake) > (maxRetarget - 1) { nextDiffFreshStake = oldDiff / maxRetarget } // Average the two differences using scaled multiplication. nextDiff := mergeDifficulty(oldDiff, nextDiffTicketPool, nextDiffFreshStake) // Check to see if we're over the limits for the maximum allowable retarget; // if we are, return the maximum or minimum except in the case that oldDiff // is zero. if oldDiff == 0 { // This should never really happen, but in case it does... return oldDiff, nil } else if nextDiff == 0 { nextDiff = oldDiff / maxRetarget } else if (nextDiff / oldDiff) > (maxRetarget - 1) { nextDiff = oldDiff * maxRetarget } else if (oldDiff / nextDiff) > (maxRetarget - 1) { nextDiff = oldDiff / maxRetarget } // If the next diff is below the network minimum, set the required stake // difficulty to the minimum. if nextDiff < b.chainParams.MinimumStakeDiff { return b.chainParams.MinimumStakeDiff, nil } return nextDiff, nil }
// TestCalcSignatureHash does some rudimentary testing of msg hash calculation. func TestCalcSignatureHash(t *testing.T) { tx := new(wire.MsgTx) for i := 0; i < 3; i++ { txIn := new(wire.TxIn) txIn.Sequence = 0xFFFFFFFF txIn.PreviousOutPoint.Hash = chainhash.HashFuncH([]byte{byte(i)}) txIn.PreviousOutPoint.Index = uint32(i) txIn.PreviousOutPoint.Tree = int8(0) tx.AddTxIn(txIn) } for i := 0; i < 2; i++ { txOut := new(wire.TxOut) txOut.PkScript = []byte{0x01, 0x01, 0x02, 0x03} txOut.Value = 0x0000FF00FF00FF00 tx.AddTxOut(txOut) } want, _ := hex.DecodeString("d09285b6f60c71329323bc2e76c48" + "a462cde4e1032aa8f59c55823f1722c7f4a") pops, _ := txscript.TstParseScript([]byte{0x01, 0x01, 0x02, 0x03}) // Test prefix caching. msg1, err := txscript.CalcSignatureHash(pops, txscript.SigHashAll, tx, 0, nil) if err != nil { t.Fatalf("unexpected error %v", err.Error()) } prefixHash := tx.TxSha() msg2, err := txscript.CalcSignatureHash(pops, txscript.SigHashAll, tx, 0, &prefixHash) if err != nil { t.Fatalf("unexpected error %v", err.Error()) } if !bytes.Equal(msg1, want) { t.Errorf("for sighash all sig noncached wrong msg %x given, want %x", msg1, want) } if !bytes.Equal(msg2, want) { t.Errorf("for sighash all sig cached wrong msg %x given, want %x", msg1, want) } if !bytes.Equal(msg1, msg2) { t.Errorf("for sighash all sig non-equivalent msgs %x and %x were "+ "returned when using a cached prefix", msg1, msg2) } // Move the index and make sure that we get a whole new hash, despite // using the same TxOuts. msg3, err := txscript.CalcSignatureHash(pops, txscript.SigHashAll, tx, 1, &prefixHash) if err != nil { t.Fatalf("unexpected error %v", err.Error()) } if bytes.Equal(msg1, msg3) { t.Errorf("for sighash all sig equivalent msgs %x and %x were "+ "returned when using a cached prefix but different indices", msg1, msg3) } }
func TestTicketDBGeneral(t *testing.T) { // Declare some useful variables. testBCHeight := int64(168) filename := filepath.Join("..", "/../blockchain/testdata", "blocks0to168.bz2") fi, err := os.Open(filename) bcStream := bzip2.NewReader(fi) defer fi.Close() // Create a buffer of the read file. bcBuf := new(bytes.Buffer) bcBuf.ReadFrom(bcStream) // Create decoder from the buffer and a map to store the data. bcDecoder := gob.NewDecoder(bcBuf) testBlockchainBytes := make(map[int64][]byte) // Decode the blockchain into the map. if err := bcDecoder.Decode(&testBlockchainBytes); err != nil { t.Errorf("error decoding test blockchain") } testBlockchain := make(map[int64]*dcrutil.Block, len(testBlockchainBytes)) for k, v := range testBlockchainBytes { bl, err := dcrutil.NewBlockFromBytes(v) if err != nil { t.Fatalf("couldn't decode block") } testBlockchain[k] = bl } // Create a new database to store the accepted stake node data into. dbName := "ffldb_staketest" dbPath := filepath.Join(testDbRoot, dbName) _ = os.RemoveAll(dbPath) testDb, err := database.Create(testDbType, dbPath, simNetParams.Net) if err != nil { t.Fatalf("error creating db: %v", err) } // Setup a teardown. defer os.RemoveAll(dbPath) defer os.RemoveAll(testDbRoot) defer testDb.Close() // Load the genesis block and begin testing exported functions. var bestNode *Node err = testDb.Update(func(dbTx database.Tx) error { var errLocal error bestNode, errLocal = InitDatabaseState(dbTx, simNetParams) if errLocal != nil { return errLocal } return nil }) if err != nil { t.Fatalf(err.Error()) } // Cache all of our nodes so that we can check them when we start // disconnecting and going backwards through the blockchain. nodesForward := make([]*Node, testBCHeight+1) loadedNodesForward := make([]*Node, testBCHeight+1) nodesForward[0] = bestNode loadedNodesForward[0] = bestNode err = testDb.Update(func(dbTx database.Tx) error { for i := int64(1); i <= testBCHeight; i++ { block := testBlockchain[i] ticketsToAdd := make([]chainhash.Hash, 0) if i >= simNetParams.StakeEnabledHeight { matureHeight := (i - int64(simNetParams.TicketMaturity)) ticketsToAdd = ticketsInBlock(testBlockchain[matureHeight]) } header := block.MsgBlock().Header if int(header.PoolSize) != len(bestNode.LiveTickets()) { t.Errorf("bad number of live tickets: want %v, got %v", header.PoolSize, len(bestNode.LiveTickets())) } if header.FinalState != bestNode.FinalState() { t.Errorf("bad final state: want %x, got %x", header.FinalState, bestNode.FinalState()) } // In memory addition test. bestNode, err = bestNode.ConnectNode(header, ticketsSpentInBlock(block), revokedTicketsInBlock(block), ticketsToAdd) if err != nil { return fmt.Errorf("couldn't connect node: %v", err.Error()) } // Write the new node to db. nodesForward[i] = bestNode blockSha := block.Sha() err := WriteConnectedBestNode(dbTx, bestNode, *blockSha) if err != nil { return fmt.Errorf("failure writing the best node: %v", err.Error()) } // Reload the node from DB and make sure it's the same. blockHash := block.Sha() loadedNode, err := LoadBestNode(dbTx, bestNode.Height(), *blockHash, header, simNetParams) if err != nil { return fmt.Errorf("failed to load the best node: %v", err.Error()) } err = nodesEqual(loadedNode, bestNode) if err != nil { return fmt.Errorf("loaded best node was not same as "+ "in memory best node: %v", err.Error()) } loadedNodesForward[i] = loadedNode } return nil }) if err != nil { t.Fatalf(err.Error()) } nodesBackward := make([]*Node, testBCHeight+1) nodesBackward[testBCHeight] = bestNode for i := testBCHeight; i >= int64(1); i-- { parentBlock := testBlockchain[i-1] ticketsToAdd := make([]chainhash.Hash, 0) if i >= simNetParams.StakeEnabledHeight { matureHeight := (i - 1 - int64(simNetParams.TicketMaturity)) ticketsToAdd = ticketsInBlock(testBlockchain[matureHeight]) } header := parentBlock.MsgBlock().Header blockUndoData := nodesForward[i-1].UndoData() formerBestNode := bestNode // In memory disconnection test. bestNode, err = bestNode.DisconnectNode(header, blockUndoData, ticketsToAdd, nil) if err != nil { t.Fatalf(err.Error()) } err = nodesEqual(bestNode, nodesForward[i-1]) if err != nil { t.Errorf("non-equiv stake nodes at height %v: %v", i-1, err.Error()) } // Try again using the database instead of the in memory // data to disconnect the node, too. var bestNodeUsingDB *Node err = testDb.View(func(dbTx database.Tx) error { // Negative test. bestNodeUsingDB, err = formerBestNode.DisconnectNode(header, nil, nil, nil) if err == nil && formerBestNode.height > 1 { return fmt.Errorf("expected error when no in memory data " + "or dbtx is passed") } bestNodeUsingDB, err = formerBestNode.DisconnectNode(header, nil, nil, dbTx) if err != nil { return err } return nil }) if err != nil { t.Errorf("couldn't disconnect using the database: %v", err.Error()) } err = nodesEqual(bestNode, bestNodeUsingDB) if err != nil { t.Errorf("non-equiv stake nodes using db when disconnecting: %v", err.Error()) } // Write the new best node to the database. nodesBackward[i-1] = bestNode err = testDb.Update(func(dbTx database.Tx) error { nodesForward[i] = bestNode parentBlockSha := parentBlock.Sha() err := WriteDisconnectedBestNode(dbTx, bestNode, *parentBlockSha, formerBestNode.UndoData()) if err != nil { return fmt.Errorf("failure writing the best node: %v", err.Error()) } return nil }) if err != nil { t.Errorf("%s", err.Error()) } // Check the best node against the loaded best node from // the database after. err = testDb.View(func(dbTx database.Tx) error { parentBlockHash := parentBlock.Sha() loadedNode, err := LoadBestNode(dbTx, bestNode.Height(), *parentBlockHash, header, simNetParams) if err != nil { return fmt.Errorf("failed to load the best node: %v", err.Error()) } err = nodesEqual(loadedNode, bestNode) if err != nil { return fmt.Errorf("loaded best node was not same as "+ "in memory best node: %v", err.Error()) } err = nodesEqual(loadedNode, loadedNodesForward[i-1]) if err != nil { return fmt.Errorf("loaded best node was not same as "+ "previously cached node: %v", err.Error()) } return nil }) if err != nil { t.Errorf("%s", err.Error()) } } // Unit testing the in-memory implementation negatively. b161 := testBlockchain[161] b162 := testBlockchain[162] n162Test := copyNode(nodesForward[162]) // No node. _, err = connectNode(nil, b162.MsgBlock().Header, n162Test.SpentByBlock(), revokedTicketsInBlock(b162), n162Test.NewTickets()) if err == nil { t.Errorf("expect error for no node") } // Best node missing ticket in live ticket bucket to spend. n161Copy := copyNode(nodesForward[161]) n161Copy.liveTickets.Delete(tickettreap.Key(n162Test.SpentByBlock()[0])) _, err = n161Copy.ConnectNode(b162.MsgBlock().Header, n162Test.SpentByBlock(), revokedTicketsInBlock(b162), n162Test.NewTickets()) if err == nil || err.(RuleError).GetCode() != ErrMissingTicket { t.Errorf("unexpected wrong or no error for "+ "Best node missing ticket in live ticket bucket to spend: %v", err) } // Duplicate best winners. n161Copy = copyNode(nodesForward[161]) n162Copy := copyNode(nodesForward[162]) n161Copy.nextWinners[0] = n161Copy.nextWinners[1] spentInBlock := n162Copy.SpentByBlock() spentInBlock[0] = spentInBlock[1] _, err = n161Copy.ConnectNode(b162.MsgBlock().Header, spentInBlock, revokedTicketsInBlock(b162), n162Test.NewTickets()) if err == nil || err.(RuleError).GetCode() != ErrMissingTicket { t.Errorf("unexpected wrong or no error for "+ "Best node missing ticket in live ticket bucket to spend: %v", err) } // Test for corrupted spentInBlock. someHash := chainhash.HashFuncH([]byte{0x00}) spentInBlock = n162Test.SpentByBlock() spentInBlock[4] = someHash _, err = nodesForward[161].ConnectNode(b162.MsgBlock().Header, spentInBlock, revokedTicketsInBlock(b162), n162Test.NewTickets()) if err == nil || err.(RuleError).GetCode() != ErrUnknownTicketSpent { t.Errorf("unexpected wrong or no error for "+ "Test for corrupted spentInBlock: %v", err) } // Corrupt winners. n161Copy = copyNode(nodesForward[161]) n161Copy.nextWinners[4] = someHash _, err = n161Copy.ConnectNode(b162.MsgBlock().Header, spentInBlock, revokedTicketsInBlock(b162), n162Test.NewTickets()) if err == nil || err.(RuleError).GetCode() != ErrMissingTicket { t.Errorf("unexpected wrong or no error for "+ "Corrupt winners: %v", err) } // Unknown missed ticket. n162Copy = copyNode(nodesForward[162]) spentInBlock = n162Copy.SpentByBlock() _, err = nodesForward[161].ConnectNode(b162.MsgBlock().Header, spentInBlock, append(revokedTicketsInBlock(b162), someHash), n162Copy.NewTickets()) if err == nil || err.(RuleError).GetCode() != ErrMissingTicket { t.Errorf("unexpected wrong or no error for "+ "Unknown missed ticket: %v", err) } // Insert a duplicate new ticket. spentInBlock = n162Test.SpentByBlock() newTicketsDup := []chainhash.Hash{someHash, someHash} _, err = nodesForward[161].ConnectNode(b162.MsgBlock().Header, spentInBlock, revokedTicketsInBlock(b162), newTicketsDup) if err == nil || err.(RuleError).GetCode() != ErrDuplicateTicket { t.Errorf("unexpected wrong or no error for "+ "Insert a duplicate new ticket: %v", err) } // Impossible undo data for disconnecting. n161Copy = copyNode(nodesForward[161]) n162Copy = copyNode(nodesForward[162]) n162Copy.databaseUndoUpdate[0].Expired = false n162Copy.databaseUndoUpdate[0].Missed = false n162Copy.databaseUndoUpdate[0].Spent = false n162Copy.databaseUndoUpdate[0].Revoked = true _, err = n162Copy.DisconnectNode(b161.MsgBlock().Header, n161Copy.UndoData(), n161Copy.NewTickets(), nil) if err == nil { t.Errorf("unexpected wrong or no error for "+ "Impossible undo data for disconnecting: %v", err) } // Missing undo data for disconnecting. n161Copy = copyNode(nodesForward[161]) n162Copy = copyNode(nodesForward[162]) n162Copy.databaseUndoUpdate = n162Copy.databaseUndoUpdate[0:3] _, err = n162Copy.DisconnectNode(b161.MsgBlock().Header, n161Copy.UndoData(), n161Copy.NewTickets(), nil) if err == nil { t.Errorf("unexpected wrong or no error for "+ "Missing undo data for disconnecting: %v", err) } // Unknown undo data hash when disconnecting (missing). n161Copy = copyNode(nodesForward[161]) n162Copy = copyNode(nodesForward[162]) n162Copy.databaseUndoUpdate[0].TicketHash = someHash n162Copy.databaseUndoUpdate[0].Expired = false n162Copy.databaseUndoUpdate[0].Missed = true n162Copy.databaseUndoUpdate[0].Spent = false n162Copy.databaseUndoUpdate[0].Revoked = false _, err = n162Copy.DisconnectNode(b161.MsgBlock().Header, n161Copy.UndoData(), n161Copy.NewTickets(), nil) if err == nil || err.(RuleError).GetCode() != ErrMissingTicket { t.Errorf("unexpected wrong or no error for "+ "Unknown undo data for disconnecting (missing): %v", err) } // Unknown undo data hash when disconnecting (revoked). n161Copy = copyNode(nodesForward[161]) n162Copy = copyNode(nodesForward[162]) n162Copy.databaseUndoUpdate[0].TicketHash = someHash n162Copy.databaseUndoUpdate[0].Expired = false n162Copy.databaseUndoUpdate[0].Missed = true n162Copy.databaseUndoUpdate[0].Spent = false n162Copy.databaseUndoUpdate[0].Revoked = true _, err = n162Copy.DisconnectNode(b161.MsgBlock().Header, n161Copy.UndoData(), n161Copy.NewTickets(), nil) if err == nil || err.(RuleError).GetCode() != ErrMissingTicket { t.Errorf("unexpected wrong or no error for "+ "Unknown undo data for disconnecting (revoked): %v", err) } }