// targetAdjustmentBase returns the magnitude that the target should be // adjusted by before a clamp is applied. func (cs *ConsensusSet) targetAdjustmentBase(blockMap *bolt.Bucket, pb *processedBlock) *big.Rat { // Grab the block that was generated 'TargetWindow' blocks prior to the // parent. If there are not 'TargetWindow' blocks yet, stop at the genesis // block. var windowSize types.BlockHeight parent := pb.Block.ParentID current := pb.Block.ID() for windowSize = 0; windowSize < types.TargetWindow && parent != (types.BlockID{}); windowSize++ { current = parent copy(parent[:], blockMap.Get(parent[:])[:32]) } timestamp := types.Timestamp(encoding.DecUint64(blockMap.Get(current[:])[40:48])) // The target of a child is determined by the amount of time that has // passed between the generation of its immediate parent and its // TargetWindow'th parent. The expected amount of seconds to have passed is // TargetWindow*BlockFrequency. The target is adjusted in proportion to how // time has passed vs. the expected amount of time to have passed. // // The target is converted to a big.Rat to provide infinite precision // during the calculation. The big.Rat is just the int representation of a // target. timePassed := pb.Block.Timestamp - timestamp expectedTimePassed := types.BlockFrequency * windowSize return big.NewRat(int64(timePassed), int64(expectedTimePassed)) }
// ReceiveUpdatedUnconfirmedTransactions updates the wallet's unconfirmed // transaction set. func (w *Wallet) ReceiveUpdatedUnconfirmedTransactions(txns []types.Transaction, _ modules.ConsensusChange) { // There are two different situations under which a subscribee calls // ProcessConsensusChange. The first is when w.subscribed is set to false // AND the mutex is already locked. The other situation is that subscribed // is set to true and is not going to be changed. Therefore there is no // race condition here. If w.subscribed is set to false, trying to grab the // lock would cause a deadlock. if w.subscribed { lockID := w.mu.Lock() defer w.mu.Unlock(lockID) } w.unconfirmedProcessedTransactions = nil for _, txn := range txns { // To save on code complexity, relveancy is determined while building // up the wallet transaction. relevant := false pt := modules.ProcessedTransaction{ Transaction: txn, TransactionID: txn.ID(), ConfirmationHeight: types.BlockHeight(math.MaxUint64), ConfirmationTimestamp: types.Timestamp(math.MaxUint64), } for _, sci := range txn.SiacoinInputs { _, exists := w.keys[sci.UnlockConditions.UnlockHash()] if exists { relevant = true } pt.Inputs = append(pt.Inputs, modules.ProcessedInput{ FundType: types.SpecifierSiacoinInput, WalletAddress: exists, RelatedAddress: sci.UnlockConditions.UnlockHash(), Value: w.historicOutputs[types.OutputID(sci.ParentID)], }) } for i, sco := range txn.SiacoinOutputs { _, exists := w.keys[sco.UnlockHash] if exists { relevant = true } pt.Outputs = append(pt.Outputs, modules.ProcessedOutput{ FundType: types.SpecifierSiacoinOutput, MaturityHeight: types.BlockHeight(math.MaxUint64), WalletAddress: exists, RelatedAddress: sco.UnlockHash, Value: sco.Value, }) w.historicOutputs[types.OutputID(txn.SiacoinOutputID(i))] = sco.Value } for _, fee := range txn.MinerFees { pt.Outputs = append(pt.Outputs, modules.ProcessedOutput{ FundType: types.SpecifierMinerFee, Value: fee, }) } if relevant { w.unconfirmedProcessedTransactions = append(w.unconfirmedProcessedTransactions, pt) } } }
// minimumValidChildTimestamp returns the earliest timestamp that a child node // can have while still being valid. See section 'Block Timestamps' in // Consensus.md. // // To boost performance, minimumValidChildTimestamp is passed a bucket that it // can use from inside of a boltdb transaction. func (rh stdBlockRuleHelper) minimumValidChildTimestamp(blockMap dbBucket, pb *processedBlock) types.Timestamp { // Get the previous MedianTimestampWindow timestamps. windowTimes := make(types.TimestampSlice, types.MedianTimestampWindow) windowTimes[0] = pb.Block.Timestamp parent := pb.Block.ParentID for i := uint64(1); i < types.MedianTimestampWindow; i++ { // If the genesis block is 'parent', use the genesis block timestamp // for all remaining times. if parent == (types.BlockID{}) { windowTimes[i] = windowTimes[i-1] continue } // Get the next parent's bytes. Because the ordering is specific, the // parent does not need to be decoded entirely to get the desired // information. This provides a performance boost. The id of the next // parent lies at the first 32 bytes, and the timestamp of the block // lies at bytes 40-48. parentBytes := blockMap.Get(parent[:]) copy(parent[:], parentBytes[:32]) windowTimes[i] = types.Timestamp(encoding.DecUint64(parentBytes[40:48])) } sort.Sort(windowTimes) // Return the median of the sorted timestamps. return windowTimes[len(windowTimes)/2] }
// testDependentUpdates adds a parent transaction and a dependent transaction // to the unconfirmed set. Then the parent transaction is added to the // confirmed set but the dependent is not. A check is made to see that the // dependent is still in the unconfirmed set. func (tpt *tpoolTester) testDependentUpdates() { // Put two transactions, a parent and a dependent, into the transaction // pool. Then create a transaction that is in conflict with the parent. parent := tpt.emptyUnlockTransaction() dependent := types.Transaction{ SiacoinInputs: []types.SiacoinInput{ types.SiacoinInput{ ParentID: parent.SiacoinOutputID(0), }, }, MinerFees: []types.Currency{ parent.SiacoinOutputs[0].Value, }, } err := tpt.tpool.AcceptTransaction(parent) if err != nil { tpt.t.Fatal(err) } tpt.tpUpdateWait() err = tpt.tpool.AcceptTransaction(dependent) if err != nil { tpt.t.Fatal(err) } tpt.tpUpdateWait() // Mine a block to put the parent into the confirmed set. tset := tpt.tpool.TransactionSet() tset = tset[:len(tset)-1] // strip 'dependent' target, exists := tpt.cs.ChildTarget(tpt.cs.CurrentBlock().ID()) if !exists { tpt.t.Fatal("unable to recover child target") } block := types.Block{ ParentID: tpt.cs.CurrentBlock().ID(), Timestamp: types.Timestamp(time.Now().Unix()), MinerPayouts: []types.SiacoinOutput{ types.SiacoinOutput{Value: types.CalculateCoinbase(tpt.cs.Height() + 1)}, }, Transactions: tset, } for { var found bool block, found = tpt.miner.SolveBlock(block, target) if found { err = tpt.cs.AcceptBlock(block) if err != nil { tpt.t.Fatal(err) } break } } tpt.csUpdateWait() // Check that 'parent' and 'dependent' have been removed from the // transaction set, since conflict has made the confirmed set. if len(tpt.tpool.TransactionSet()) != 1 { tpt.t.Error("dependent transaction does not remain unconfirmed after parent has been confirmed:", len(tset)) } }
// ReceiveUpdatedUnconfirmedTransactions updates the wallet's unconfirmed // transaction set. func (w *Wallet) ReceiveUpdatedUnconfirmedTransactions(txns []types.Transaction, _ modules.ConsensusChange) { if err := w.tg.Add(); err != nil { // Gracefully reject transactions if the wallet's Close method has // closed the wallet's ThreadGroup already. return } defer w.tg.Done() w.mu.Lock() defer w.mu.Unlock() w.unconfirmedProcessedTransactions = nil for _, txn := range txns { // To save on code complexity, relevancy is determined while building // up the wallet transaction. relevant := false pt := modules.ProcessedTransaction{ Transaction: txn, TransactionID: txn.ID(), ConfirmationHeight: types.BlockHeight(math.MaxUint64), ConfirmationTimestamp: types.Timestamp(math.MaxUint64), } for _, sci := range txn.SiacoinInputs { _, exists := w.keys[sci.UnlockConditions.UnlockHash()] if exists { relevant = true } pt.Inputs = append(pt.Inputs, modules.ProcessedInput{ FundType: types.SpecifierSiacoinInput, WalletAddress: exists, RelatedAddress: sci.UnlockConditions.UnlockHash(), Value: w.historicOutputs[types.OutputID(sci.ParentID)], }) } for i, sco := range txn.SiacoinOutputs { _, exists := w.keys[sco.UnlockHash] if exists { relevant = true } pt.Outputs = append(pt.Outputs, modules.ProcessedOutput{ FundType: types.SpecifierSiacoinOutput, MaturityHeight: types.BlockHeight(math.MaxUint64), WalletAddress: exists, RelatedAddress: sco.UnlockHash, Value: sco.Value, }) w.historicOutputs[types.OutputID(txn.SiacoinOutputID(uint64(i)))] = sco.Value } for _, fee := range txn.MinerFees { pt.Outputs = append(pt.Outputs, modules.ProcessedOutput{ FundType: types.SpecifierMinerFee, Value: fee, }) } if relevant { w.unconfirmedProcessedTransactions = append(w.unconfirmedProcessedTransactions, pt) } } }
// TestSetChildTarget probes the setChildTarget method of the block node type. func TestSetChildTarget(t *testing.T) { // Create a genesis node and a child that took 2x as long as expected. genesisNode := &blockNode{ block: types.Block{Timestamp: 10000}, } genesisNode.childTarget[0] = 64 doubleTimeNode := &blockNode{ block: types.Block{Timestamp: types.Timestamp(10000 + types.BlockFrequency*2)}, } doubleTimeNode.parent = genesisNode // Check the resulting childTarget of the new node and see that the clamp // was applied. doubleTimeNode.setChildTarget() if doubleTimeNode.childTarget.Cmp(genesisNode.childTarget) <= 0 { t.Error("double time node target did not increase") } fullAdjustment := genesisNode.childTarget.MulDifficulty(big.NewRat(1, 2)) if doubleTimeNode.childTarget.Cmp(fullAdjustment) >= 0 { t.Error("clamp was not applied when adjusting target") } }
// Returns many pieces of readily available information func (e *Explorer) ExplorerStatus() modules.ExplorerStatus { lockID := e.mu.RLock() defer e.mu.RUnlock(lockID) // No reason that consensus should broadcast a block that it // doesn't have information on var currentTarget types.Target if e.currentBlock.ID() == e.genesisBlockID { currentTarget = types.RootDepth } else { var exists bool currentTarget, exists = e.cs.ChildTarget(e.currentBlock.ParentID) if build.DEBUG { if !exists { panic("The state of the current block cannot be found") } } } // Find the seen time of the block 144 ago in the list matureBlockTime := e.seenTimes[(e.blockchainHeight-144)%types.BlockHeight(len(e.seenTimes))] return modules.ExplorerStatus{ Height: e.blockchainHeight, Block: e.currentBlock, Target: currentTarget, MatureTime: types.Timestamp(matureBlockTime.Unix()), TotalCurrency: totalCurrency(e.blockchainHeight), ActiveContractCount: e.activeContracts, ActiveContractCosts: e.activeContractCost, ActiveContractSize: e.activeContractSize, TotalContractCount: e.totalContracts, TotalContractCosts: e.totalContractCost, TotalContractSize: e.totalContractSize, } }
// TestNewChild probes the newChild method of the block node type. func TestNewChild(t *testing.T) { parent := &blockNode{ height: 12, } parent.depth[0] = 45 parent.block.Timestamp = 100 parent.childTarget[0] = 90 child := parent.newChild(types.Block{Timestamp: types.Timestamp(100 + types.BlockFrequency)}) if child.parent != parent { t.Error("parent-child relationship incorrect") } if child.height != 13 { t.Error("child height set incorrectly") } var expectedDepth types.Target expectedDepth[0] = 30 if child.depth.Cmp(expectedDepth) != 0 { t.Error("child depth did not adjust correctly") } if child.childTarget.Cmp(parent.childTarget) != 0 { t.Error("child childTarget not adjusted correctly") } }
// testBlockConflicts adds a transaction to the unconfirmed set, and then adds // a conflicting transaction to the confirmed set, checking that the conflict // is properly handled by the pool. func (tpt *tpoolTester) testBlockConflicts() { // Put two transactions, a parent and a dependent, into the transaction // pool. Then create a transaction that is in conflict with the parent. parent := tpt.emptyUnlockTransaction() dependent := types.Transaction{ SiacoinInputs: []types.SiacoinInput{ types.SiacoinInput{ ParentID: parent.SiacoinOutputID(0), }, }, MinerFees: []types.Currency{ parent.SiacoinOutputs[0].Value, }, } err := tpt.tpool.AcceptTransaction(parent) if err != nil { tpt.t.Fatal(err) } tpt.tpUpdateWait() err = tpt.tpool.AcceptTransaction(dependent) if err != nil { tpt.t.Fatal(err) } tpt.tpUpdateWait() // Create a transaction that is in conflict with the parent. parentValue := parent.SiacoinOutputSum() conflict := types.Transaction{ SiacoinInputs: parent.SiacoinInputs, MinerFees: []types.Currency{ parentValue, }, } // Mine a block to put the conflict into the confirmed set. 'parent' has // dependencies of it's own, and 'conflict' has the same dependencies as // 'parent'. So the block we mine needs to include all of the dependencies // without including 'parent' or 'dependent'. tset := tpt.tpool.TransactionSet() tset = tset[:len(tset)-2] // strip 'parent' and 'dependent' tset = append(tset, conflict) // add 'conflict' target, exists := tpt.cs.ChildTarget(tpt.cs.CurrentBlock().ID()) if !exists { tpt.t.Fatal("unable to recover child target") } block := types.Block{ ParentID: tpt.cs.CurrentBlock().ID(), Timestamp: types.Timestamp(time.Now().Unix()), MinerPayouts: []types.SiacoinOutput{ types.SiacoinOutput{Value: parentValue.Add(types.CalculateCoinbase(tpt.cs.Height() + 1))}, }, Transactions: tset, } for { block, found := tpt.miner.SolveBlock(block, target) if found { err = tpt.cs.AcceptBlock(block) if err != nil { tpt.t.Fatal(err) } break } } tpt.csUpdateWait() // Check that 'parent' and 'dependent' have been removed from the // transaction set, since conflict has made the confirmed set. if len(tpt.tpool.TransactionSet()) != 0 { tpt.t.Error("parent and dependent transaction are still in the pool after a conflict has been introduced, have", len(tset)) } }
// testRewinding adds transactions in a block, then removes the block and // verifies that the transaction pool adds the block transactions. func (tpt *tpoolTester) testRewinding() { // Put some transactions into the unconfirmed set. tpt.addSiacoinTransactionToPool() if len(tpt.tpool.TransactionSet()) == 0 { tpt.t.Fatal("transaction pool has no transactions") } // Prepare an empty block to cause a rewind (by forking). target, exists := tpt.cs.ChildTarget(tpt.cs.CurrentBlock().ID()) if !exists { tpt.t.Fatal("unable to recover child target") } forkStart := types.Block{ ParentID: tpt.cs.CurrentBlock().ID(), Timestamp: types.Timestamp(time.Now().Unix()), MinerPayouts: []types.SiacoinOutput{ types.SiacoinOutput{Value: types.CalculateCoinbase(tpt.cs.Height() + 1)}, }, } for { var found bool forkStart, found = tpt.miner.SolveBlock(forkStart, target) if found { break } } // Mine a block with the transaction. b, _ := tpt.miner.FindBlock() err := tpt.cs.AcceptBlock(b) if err != nil { tpt.t.Fatal(err) } tpt.csUpdateWait() if len(tpt.tpool.TransactionSet()) != 0 { tpt.t.Fatal("tset should be empty after FindBlock()") } // Fork around the block with the transaction. err = tpt.cs.AcceptBlock(forkStart) if err != nil && err != modules.ErrNonExtendingBlock { tpt.t.Fatal(err) } target, exists = tpt.cs.ChildTarget(tpt.cs.CurrentBlock().ID()) if !exists { tpt.t.Fatal("unable to recover child target") } forkCommit := types.Block{ ParentID: forkStart.ID(), Timestamp: types.Timestamp(time.Now().Unix()), MinerPayouts: []types.SiacoinOutput{ types.SiacoinOutput{Value: types.CalculateCoinbase(tpt.cs.Height() + 1)}, }, } for { var found bool forkCommit, found = tpt.miner.SolveBlock(forkCommit, target) if found { tpt.cs.AcceptBlock(forkCommit) break } } tpt.csUpdateWait() // Check that the transaction which was once confirmed but no longer is // confirmed is now unconfirmed. if len(tpt.tpool.TransactionSet()) == 0 { tpt.t.Error("tset should contain transactions that used to be confirmed but no longer are") } }
// Now returns mockClock's pre-defined Timestamp. func (c mockClock) Now() types.Timestamp { return c.now } var validateBlockTests = []struct { now types.Timestamp minTimestamp types.Timestamp blockTimestamp types.Timestamp blockSize uint64 errWant error msg string }{ { minTimestamp: types.Timestamp(5), blockTimestamp: types.Timestamp(4), errWant: errEarlyTimestamp, msg: "ValidateBlock should reject blocks with timestamps that are too early", }, { blockSize: types.BlockSizeLimit + 1, errWant: errLargeBlock, msg: "ValidateBlock should reject excessively large blocks", }, { now: types.Timestamp(50), blockTimestamp: types.Timestamp(50) + types.ExtremeFutureThreshold + 1, errWant: errExtremeFutureTimestamp, msg: "ValidateBlock should reject blocks timestamped in the extreme future", },
// TestTargetAdjustmentBase probes the targetAdjustmentBase method of the block // node type. func TestTargetAdjustmentBase(t *testing.T) { // Create a genesis node at timestamp 10,000 genesisNode := &blockNode{ block: types.Block{Timestamp: 10000}, } exactTimeNode := &blockNode{ block: types.Block{Timestamp: types.Timestamp(10000 + types.BlockFrequency)}, } exactTimeNode.parent = genesisNode // Base adjustment for the exactTimeNode should be 1. adjustment, exact := exactTimeNode.targetAdjustmentBase().Float64() if !exact { t.Fatal("did not get an exact target adjustment") } if adjustment != 1 { t.Error("block did not adjust itself to the same target") } // Create a double-speed node and get the base adjustment. doubleSpeedNode := &blockNode{ block: types.Block{Timestamp: types.Timestamp(10000 + types.BlockFrequency)}, } doubleSpeedNode.parent = exactTimeNode adjustment, exact = doubleSpeedNode.targetAdjustmentBase().Float64() if !exact { t.Fatal("did not get an exact adjustment") } if adjustment != 0.5 { t.Error("double speed node did not get a base to halve the target") } // Create a half-speed node and get the base adjustment. halfSpeedNode := &blockNode{ block: types.Block{Timestamp: types.Timestamp(10000 + types.BlockFrequency*6)}, } halfSpeedNode.parent = doubleSpeedNode adjustment, exact = halfSpeedNode.targetAdjustmentBase().Float64() if !exact { t.Fatal("did not get an exact adjustment") } if adjustment != 2 { t.Error("double speed node did not get a base to halve the target") } if testing.Short() { t.SkipNow() } // Create a chain of nodes so that the genesis node is no longer the point // of comparison. comparisonNode := &blockNode{ block: types.Block{Timestamp: 125000}, } comparisonNode.parent = halfSpeedNode startingNode := comparisonNode for i := types.BlockHeight(0); i < types.TargetWindow; i++ { newNode := new(blockNode) newNode.parent = startingNode startingNode = newNode } startingNode.block.Timestamp = types.Timestamp(125000 + types.BlockFrequency*types.TargetWindow) adjustment, exact = startingNode.targetAdjustmentBase().Float64() if !exact { t.Error("failed to get exact result") } if adjustment != 1 { t.Error("got wrong long-range adjustment") } startingNode.block.Timestamp = types.Timestamp(125000 + 2*types.BlockFrequency*types.TargetWindow) adjustment, exact = startingNode.targetAdjustmentBase().Float64() if !exact { t.Error("failed to get exact result") } if adjustment != 2 { t.Error("got wrong long-range adjustment") } }