Commit 042ed114 authored by Luke Champine's avatar Luke Champine

Merge pull request #299 from NebulousLabs/testing

Testing Round One
parents 356b9974 bfc436f2
renterDownload
cover
hostdir
release
renterDownload
whitepaper.aux
whitepaper.log
whitepaper.pdf
......
language: go
go:
- 1.3
- 1.4
install:
- make dependencies
......
# all will build and install developer binaries, which have debugging enabled
# and much faster mining and block constants.
all: install
# fmt calls go fmt on all packages.
fmt:
go fmt ./...
# install builds and installs developer binaries.
install: fmt
go install -a -tags=dev ./...
# clean removes all directories that get automatically created during
# development.
clean:
rm -rf hostdir release whitepaper.aux whitepaper.log whitepaper.pdf \
sia.wallet sia/test.wallet sia/hostdir* sia/renterDownload
sia.wallet sia/test.wallet sia/hostdir* sia/renterDownload cover
# test runs the short tests for Sia, and aims to always take less than 2
# seconds.
#
# Touching a file in the consensus folder forces the build tag files to be
# rebuilt. This can also be achieved with 'go test -a', however using the '-a'
# flag results in a multi-second compile time, which is undesirable. Leaving
# out both the touch and the '-a' means that sometimes the tests will be run
# using the developer constants, which is very slow.
test: clean fmt
go test -a -short -tags=test ./...
@touch consensus/blocknode.go
go test -short -tags=test ./...
test-long: test
# test-long does a forced rebuild of all packages, and then runs both the
# short and long tests with the race libraries enabled. test-long aims to be
# thorough.
test-long: clean fmt
go test -a -v -race -short -tags=test ./...
go test -a -v -race -tags=test ./...
# run twice to ensure references are updated properly
# cover runs the long tests and creats html files that show you which lines
# have been hit during testing and how many times each line has been hit.
coverpackages = consensus crypto encoding hash modules/hostdb network siad
cover: clean
@mkdir -p cover/modules
@for package in $(coverpackages); do \
go test -a -v -tags=test -covermode=atomic -coverprofile=cover/$$package.out ./$$package ; \
go tool cover -html=cover/$$package.out -o=cover/$$package.html ; \
rm cover/$$package.out ; \
done
# whitepaper builds the whitepaper from whitepaper.tex. pdflatex has to be
# called twice because references will not update correctly the first time.
whitepaper:
@pdflatex whitepaper.tex > /dev/null
pdflatex whitepaper.tex
# dependencies installs all of the dependencies that are required for building
# Sia.
dependencies:
go install -race std
go get -u code.google.com/p/gcfg
go get -u github.com/agl/ed25519
go get -u github.com/inconshreveable/go-update
go get -u github.com/laher/goxc
go get -u github.com/mitchellh/go-homedir
go get -u github.com/spf13/cobra
go get -u github.com/inconshreveable/go-update
go get -u github.com/agl/ed25519
go get -u golang.org/x/crypto/twofish
go get -u github.com/stretchr/graceful
go get -u github.com/laher/goxc
go get -u golang.org/x/crypto/twofish
go get -u golang.org/x/tools/cmd/cover
# release builds and installs release binaries.
release: dependencies test-long
go install -a ./...
# xc builds and packages release binaries for all systems by using goxc.
# Cross Compile - makes binaries for windows, linux, and mac, 32 and 64 bit.
xc: dependencies test-long
goxc -arch="amd64" -bc="linux windows darwin" -d=release -pv=0.2.0 \
......@@ -42,4 +78,4 @@ xc: dependencies test-long
-tasks-=deb,deb-dev,deb-source,go-test
# Need some command here to make sure that the release constants got used.
.PHONY: all fmt install clean test test-long whitepaper dependencies release xc
.PHONY: all fmt install clean test test-long cover whitepaper dependencies release xc
......@@ -7,42 +7,42 @@ import (
// A BlockNode contains a block and the list of children to the block. Also
// contains some consensus information like which contracts have terminated and
// where there were missed storage proofs.
type BlockNode struct {
Block Block
Parent *BlockNode
Children []*BlockNode
Height BlockHeight
Depth Target // Cumulative weight of all parents.
Target Target // Target for next block.
DiffsGenerated bool
OutputDiffs []OutputDiff
ContractDiffs []ContractDiff
type blockNode struct {
block Block
parent *blockNode
children []*blockNode
height BlockHeight
depth Target // Cumulative weight of all parents.
target Target // Target for next block.
diffsGenerated bool
outputDiffs []OutputDiff
contractDiffs []ContractDiff
}
// childDepth returns the depth that any child node would have.
// childDepth := (1/parentTarget + 1/parentDepth)^-1
func (bn *BlockNode) childDepth() (depth Target) {
cumulativeDifficulty := new(big.Rat).Add(bn.Target.Inverse(), bn.Depth.Inverse())
func (bn *blockNode) childDepth() (depth Target) {
cumulativeDifficulty := new(big.Rat).Add(bn.target.Inverse(), bn.depth.Inverse())
return RatToTarget(new(big.Rat).Inv(cumulativeDifficulty))
}
// setTarget calculates the target for a node and sets the node's target equal
// to the calculated value.
func (node *BlockNode) setTarget() {
func (node *blockNode) setTarget() {
// To calculate the target, we need to compare our timestamp with the
// timestamp of the reference node, which is `TargetWindow` blocks earlier,
// or if the height is less than `TargetWindow`, it's the genesis block.
var i BlockHeight
referenceNode := node
for i = 0; i < TargetWindow && referenceNode.Parent != nil; i++ {
referenceNode = referenceNode.Parent
for i = 0; i < TargetWindow && referenceNode.parent != nil; i++ {
referenceNode = referenceNode.parent
}
// Calculate the amount to adjust the target by dividing the amount of time
// passed by the expected amount of time passed.
timePassed := node.Block.Timestamp - referenceNode.Block.Timestamp
timePassed := node.block.Timestamp - referenceNode.block.Timestamp
expectedTimePassed := BlockFrequency * Timestamp(i)
targetAdjustment := big.NewRat(int64(timePassed), int64(expectedTimePassed))
......@@ -54,33 +54,28 @@ func (node *BlockNode) setTarget() {
}
// Multiply the previous target by the adjustment to get the new target.
parentTarget := node.Parent.Target
parentTarget := node.parent.target
newRatTarget := new(big.Rat).Mul(parentTarget.Rat(), targetAdjustment)
node.Target = RatToTarget(newRatTarget)
node.target = RatToTarget(newRatTarget)
}
// State.addBlockToTree() takes a block and a parent node, and adds a child
// node to the parent containing the block. No validation is done.
// addBlockToTree takes a block and a parent node, and adds a child node to the
// parent containing the block. No validation is done.
func (s *State) addBlockToTree(b Block) (err error) {
err = s.validHeader(b)
if err != nil {
return
}
parentNode := s.blockMap[b.ParentBlockID]
newNode := &BlockNode{
Block: b,
Parent: parentNode,
newNode := &blockNode{
block: b,
parent: parentNode,
Height: parentNode.Height + 1,
Depth: parentNode.childDepth(),
height: parentNode.height + 1,
depth: parentNode.childDepth(),
}
newNode.setTarget()
// Add the node to the block map and update the list of its parents
// children.
s.blockMap[b.ID()] = newNode
parentNode.Children = append(parentNode.Children, newNode)
parentNode.children = append(parentNode.children, newNode)
if s.heavierFork(newNode) {
err = s.forkBlockchain(newNode)
......
......@@ -26,10 +26,13 @@ var (
// Exported Errors
var (
BlockKnownErr = errors.New("block exists in block map.")
FutureBlockErr = errors.New("timestamp too far in future, will try again later.")
KnownOrphanErr = errors.New("block is a known orphan")
UnknownOrphanErr = errors.New("block is an unknown orphan")
BlockKnownErr = errors.New("block exists in block map.")
EarlyTimestampErr = errors.New("block timestamp is too early, block is illegal.")
FutureBlockErr = errors.New("timestamp too far in future, will try again later.")
KnownOrphanErr = errors.New("block is a known orphan")
LargeBlockErr = errors.New("block is too large to be accepted")
MissedTargetErr = errors.New("block does not meet target")
UnknownOrphanErr = errors.New("block is an unknown orphan")
)
// handleOrphanBlock adds a block to the list of orphans, returning an error
......@@ -65,14 +68,14 @@ func (s *State) handleOrphanBlock(b Block) error {
// earliestChildTimestamp returns the earliest timestamp that a child node
// can have while still being valid. See section 'Timestamp Rules' in
// Consensus.md.
func (bn *BlockNode) earliestChildTimestamp() Timestamp {
func (bn *blockNode) earliestChildTimestamp() Timestamp {
// Get the previous `MedianTimestampWindow` timestamps.
var intTimestamps []int
referenceNode := bn
for i := 0; i < MedianTimestampWindow; i++ {
intTimestamps = append(intTimestamps, int(referenceNode.Block.Timestamp))
if referenceNode.Parent != nil {
referenceNode = referenceNode.Parent
intTimestamps = append(intTimestamps, int(referenceNode.block.Timestamp))
if referenceNode.parent != nil {
referenceNode = referenceNode.parent
}
}
sort.Ints(intTimestamps)
......@@ -86,14 +89,14 @@ func (bn *BlockNode) earliestChildTimestamp() Timestamp {
func (s *State) validHeader(b Block) (err error) {
parent := s.blockMap[b.ParentBlockID]
// Check the id meets the target.
if !b.CheckTarget(parent.Target) {
err = errors.New("block does not meet target")
if !b.CheckTarget(parent.target) {
err = MissedTargetErr
return
}
// If timestamp is too far in the past, reject and put in bad blocks.
if parent.earliestChildTimestamp() > b.Timestamp {
err = errors.New("timestamp invalid for being in the past")
err = EarlyTimestampErr
return
}
......@@ -107,14 +110,7 @@ func (s *State) validHeader(b Block) (err error) {
// Check that the block is the correct size.
encodedBlock := encoding.Marshal(b)
if len(encodedBlock) > BlockSizeLimit {
err = errors.New("Block is too large, will not be accepted.")
return
}
// Check that the transaction merkle root matches the transactions
// included into the block.
if b.MerkleRoot != b.TransactionMerkleRoot() {
err = errors.New("merkle root does not match transactions sent.")
err = LargeBlockErr
return
}
......@@ -148,6 +144,11 @@ func (s *State) AcceptBlock(b Block) (err error) {
return
}
err = s.validHeader(b)
if err != nil {
return
}
err = s.addBlockToTree(b)
if err != nil {
return
......
This diff is collapsed.
......@@ -8,17 +8,13 @@ import (
// Though these are variables, they should never be changed during runtime.
// They get altered during testing.
//
// TODO: on startup, there should be a check that panics if one of the
// constants has a funky or unusable value. For example, MedianTimestampWindow
// should really be an odd number.
const (
DEBUG = true
BlockSizeLimit = 1024 * 1024 * 1024 // Blocks cannot be more than 1MB.
BlockSizeLimit = 1024 * 1024 // Blocks cannot be more than 1MB.
BlockFrequency = Timestamp(10) // In seconds.
TargetWindow = BlockHeight(80) // Number of blocks to use when calculating the target.
MedianTimestampWindow = 11 // Number of blocks that get considered when determining if a timestamp is valid.
MedianTimestampWindow = 11 // Number of blocks that get considered when determining if a timestamp is valid. Should be an odd number.
FutureThreshold = Timestamp(3 * 60 * 60) // Seconds into the future block timestamps are valid.
InitialCoinbase = Currency(300000)
......
......@@ -9,12 +9,12 @@ import (
// Though these are variables, they should never be changed during runtime.
// They get altered during testing.
const (
DEBUG = false
DEBUG = true // This is a temporary setting, will stay during beta.
BlockSizeLimit = 1024 * 1024 * 1024 // Blocks cannot be more than 1MB.
BlockSizeLimit = 1024 * 1024 // Blocks cannot be more than 1MB.
BlockFrequency = Timestamp(600) // In seconds.
TargetWindow = BlockHeight(2000) // Number of blocks to use when calculating the target.
MedianTimestampWindow = 11 // Number of blocks that get considered when determining if a timestamp is valid.
MedianTimestampWindow = 11 // Number of blocks that get considered when determining if a timestamp is valid - should be an odd number.
FutureThreshold = Timestamp(3 * 60 * 60) // Seconds into the future block timestamps are valid.
InitialCoinbase = Currency(300000)
......@@ -30,5 +30,5 @@ var (
MaxAdjustmentUp = big.NewRat(1001, 1000)
MaxAdjustmentDown = big.NewRat(999, 1000)
GenesisAddress = CoinAddress{} // TODO: NEED TO CREATE A HARDCODED ADDRESS.
GenesisAddress = CoinAddress{} // TODO: get a hardcoded premine address
)
......@@ -9,10 +9,10 @@ import (
const (
DEBUG = true
BlockSizeLimit = 1024 * 1024 * 1024 // Blocks cannot be more than 1MB.
BlockSizeLimit = 1024 * 1024 // Blocks cannot be more than 1MB.
BlockFrequency = Timestamp(1) // In seconds.
TargetWindow = BlockHeight(1000) // Number of blocks to use when calculating the target.
MedianTimestampWindow = 11 // Number of blocks that get considered when determining if a timestamp is valid.
MedianTimestampWindow = 11 // Number of blocks that get considered when determining if a timestamp is valid - should be an odd number.
FutureThreshold = Timestamp(3 * 60 * 60) // Seconds into the future block timestamps are valid.
InitialCoinbase = Currency(300000)
......
......@@ -18,8 +18,9 @@ func (s *State) storageProofSegment(contractID ContractID) (index uint64, err er
// Get the id of the block used as the seed.
triggerHeight := contract.Start - 1
triggerBlock, err := s.blockAtHeight(triggerHeight)
if err != nil {
triggerBlock, exists := s.blockAtHeight(triggerHeight)
if !exists {
err = errors.New("no block found at contract trigger block height")
return
}
triggerID := triggerBlock.ID()
......
......@@ -97,10 +97,10 @@ func (s *State) BlockOutputDiffs(id BlockID) (diffs []OutputDiff, err error) {
err = errors.New("requested an unknown block")
return
}
if !node.DiffsGenerated {
if !node.diffsGenerated {
err = errors.New("diffs have not been generated for the requested block.")
return
}
diffs = node.OutputDiffs
diffs = node.outputDiffs
return
}
......@@ -8,20 +8,20 @@ import (
// State.heavierFork() returns true if the input node is 5% heavier than the
// current node of the ConsensusState.
func (s *State) heavierFork(newNode *BlockNode) bool {
func (s *State) heavierFork(newNode *blockNode) bool {
threshold := new(big.Rat).Mul(s.currentBlockWeight(), SurpassThreshold)
currentCumDiff := s.depth().Inverse()
requiredCumDiff := new(big.Rat).Add(currentCumDiff, threshold)
newNodeCumDiff := newNode.Depth.Inverse()
newNodeCumDiff := newNode.depth.Inverse()
return newNodeCumDiff.Cmp(requiredCumDiff) == 1
}
// backtrackToBlockchain returns a list of nodes that go from the current node
// to the first parent that is in the current blockchain.
func (s *State) backtrackToBlockchain(bn *BlockNode) (nodes []*BlockNode) {
func (s *State) backtrackToBlockchain(bn *blockNode) (nodes []*blockNode) {
nodes = append(nodes, bn)
for s.currentPath[bn.Height] != bn.Block.ID() {
bn = bn.Parent
for s.currentPath[bn.height] != bn.block.ID() {
bn = bn.parent
nodes = append(nodes, bn)
}
return
......@@ -32,30 +32,30 @@ func (s *State) invertRecentBlock() {
// Invert all of the diffs.
direction := false // blockchain is inverting, set direction flag to false.
for _, od := range bn.OutputDiffs {
for _, od := range bn.outputDiffs {
s.commitOutputDiff(od, direction)
}
for _, cd := range bn.ContractDiffs {
for _, cd := range bn.contractDiffs {
s.commitContractDiff(cd, direction)
}
// Update the current path and currentBlockID
delete(s.currentPath, bn.Height)
s.currentBlockID = bn.Parent.Block.ID()
delete(s.currentPath, bn.height)
s.currentBlockID = bn.parent.block.ID()
}
// rewindToNode will rewind blocks until `bn` is the highest block.
func (s *State) rewindToNode(bn *BlockNode) (rewoundNodes []*BlockNode) {
func (s *State) rewindToNode(bn *blockNode) (rewoundNodes []*blockNode) {
// Sanity check - make sure that bn is in the currentPath.
if DEBUG {
if bn.Block.ID() != s.currentPath[bn.Height] {
if bn.block.ID() != s.currentPath[bn.height] {
panic("bad use of rewindToNode")
}
}
// Remove blocks from the ConsensusState until we get to the
// same parent that we are forking from.
for s.currentBlockID != bn.Block.ID() {
for s.currentBlockID != bn.block.ID() {
rewoundNodes = append(rewoundNodes, s.currentBlockNode())
s.invertRecentBlock()
}
......@@ -64,21 +64,27 @@ func (s *State) rewindToNode(bn *BlockNode) (rewoundNodes []*BlockNode) {
// s.integrateBlock() will verify the block and then integrate it into the
// consensus state.
func (s *State) generateAndApplyDiff(bn *BlockNode) (err error) {
func (s *State) generateAndApplyDiff(bn *blockNode) (err error) {
// Sanity check - generate should only be called if the diffs have not yet
// been generated.
if DEBUG {
if bn.DiffsGenerated {
if bn.diffsGenerated {
panic("misuse of generateAndApplyDiff")
}
}
// Sanity check - current node must be the input node's parent.
if DEBUG {
if bn.parent.block.ID() != s.currentBlockID {
panic("applying a block node when it's not a valid successor")
}
}
// Update the current block and current path.
s.currentBlockID = bn.Block.ID()
s.currentPath[bn.Height] = bn.Block.ID()
s.currentBlockID = bn.block.ID()
s.currentPath[bn.height] = bn.block.ID()
minerSubsidy := CalculateCoinbase(s.height())
for _, txn := range bn.Block.Transactions {
for _, txn := range bn.block.Transactions {
err = s.validTransaction(txn)
if err != nil {
break
......@@ -86,8 +92,8 @@ func (s *State) generateAndApplyDiff(bn *BlockNode) (err error) {
// Apply the transaction to the ConsensusState, adding it to the list of applied transactions.
outputDiffs, contractDiffs := s.applyTransaction(txn)
bn.OutputDiffs = append(bn.OutputDiffs, outputDiffs...)
bn.ContractDiffs = append(bn.ContractDiffs, contractDiffs...)
bn.outputDiffs = append(bn.outputDiffs, outputDiffs...)
bn.contractDiffs = append(bn.contractDiffs, contractDiffs...)
// Add the miner fees to the miner subsidy.
for _, fee := range txn.MinerFees {
......@@ -101,54 +107,55 @@ func (s *State) generateAndApplyDiff(bn *BlockNode) (err error) {
// Perform maintanence on all open contracts.
outputDiffs, contractDiffs := s.applyContractMaintenance()
bn.OutputDiffs = append(bn.OutputDiffs, outputDiffs...)
bn.ContractDiffs = append(bn.ContractDiffs, contractDiffs...)
bn.outputDiffs = append(bn.outputDiffs, outputDiffs...)
bn.contractDiffs = append(bn.contractDiffs, contractDiffs...)
// Add output contianing miner subsidy.
subsidyOutput := Output{
Value: minerSubsidy,
SpendHash: bn.Block.MinerAddress,
SpendHash: bn.block.MinerAddress,
}
subsidyDiff := OutputDiff{
New: true,
ID: bn.Block.SubsidyID(),
ID: bn.block.SubsidyID(),
Output: subsidyOutput,
}
s.unspentOutputs[bn.Block.SubsidyID()] = subsidyOutput
bn.OutputDiffs = append(bn.OutputDiffs, subsidyDiff)
s.unspentOutputs[bn.block.SubsidyID()] = subsidyOutput
bn.outputDiffs = append(bn.outputDiffs, subsidyDiff)
bn.diffsGenerated = true
return
}
// invalidateNode() is a recursive function that deletes all of the
// children of a block and puts them on the bad blocks list.
func (s *State) invalidateNode(node *BlockNode) {
for i := range node.Children {
s.invalidateNode(node.Children[i])
func (s *State) invalidateNode(node *blockNode) {
for i := range node.children {
s.invalidateNode(node.children[i])
}
delete(s.blockMap, node.Block.ID())
s.badBlocks[node.Block.ID()] = struct{}{}
delete(s.blockMap, node.block.ID())
s.badBlocks[node.block.ID()] = struct{}{}
}
func (s *State) applyBlockNode(bn *BlockNode) {
func (s *State) applyBlockNode(bn *blockNode) {
// Sanity check - current node must be the input node's parent.
if DEBUG {
if bn.Parent.Block.ID() != s.currentBlockID {
if bn.parent.block.ID() != s.currentBlockID {
panic("applying a block node when it's not a valid successor")
}
}
// Update current id and current path.
s.currentBlockID = bn.Block.ID()
s.currentPath[bn.Height] = bn.Block.ID()
s.currentBlockID = bn.block.ID()
s.currentPath[bn.height] = bn.block.ID()
// Apply all of the diffs.
direction := true // blockchain is going forward, set direction flag to true.
for _, od := range bn.OutputDiffs {
for _, od := range bn.outputDiffs {
s.commitOutputDiff(od, direction)
}
for _, cd := range bn.ContractDiffs {
for _, cd := range bn.contractDiffs {
s.commitContractDiff(cd, direction)
}
}
......@@ -156,7 +163,7 @@ func (s *State) applyBlockNode(bn *BlockNode) {
// forkBlockchain() will go from the current block over to a block on a
// different fork, rewinding and integrating blocks as needed. forkBlockchain()
// will return an error if any of the blocks in the new fork are invalid.
func (s *State) forkBlockchain(newNode *BlockNode) (err error) {
func (s *State) forkBlockchain(newNode *blockNode) (err error) {
// Get the state hash before attempting a fork.
var stateHash hash.Hash
if DEBUG {
......@@ -174,19 +181,16 @@ func (s *State) forkBlockchain(newNode *BlockNode) (err error) {
// the state as we go. If at some point a block doesn't
// verify, you get to walk all the way backwards and forwards
// again.
var appliedNodes []*BlockNode
for i := len(backtrackNodes) - 1; i >= 0; i-- {
//
// The final block in backtrackNodes has already been applied.
var appliedNodes []*blockNode
for i := len(backtrackNodes) - 2; i >= 0; i-- {
appliedNodes = append(appliedNodes, backtrackNodes[i])
// If the diffs for this node have already been generated, apply them
// directly instead of generating them.
if backtrackNodes[i].DiffsGenerated {
for _, outputDiff := range backtrackNodes[i].OutputDiffs {
s.commitOutputDiff(outputDiff, true)
}
for _, contractDiff := range backtrackNodes[i].ContractDiffs {
s.commitContractDiff(contractDiff, true)
}
if backtrackNodes[i].diffsGenerated {
s.applyBlockNode(backtrackNodes[i])
continue
}
......@@ -217,10 +221,5 @@ func (s *State) forkBlockchain(newNode *BlockNode) (err error) {
}
}
// Sanity check - make sure the currentPath makes sense.
if DEBUG {
s.currentPathCheck()
}
return
}
......@@ -6,22 +6,61 @@ import (
"github.com/NebulousLabs/Sia/hash"
)
// BlockAtHeight() returns the block from the current history at the
// input height.
func (s *State) blockAtHeight(height BlockHeight) (b Block, exists bool) {
bn, exists := s.blockMap[s.currentPath[height]]
if !exists {
return
}
b = bn.block
return
}
// currentBlockNode returns the node of the most recent block in the
// longest fork.
func (s *State) currentBlockNode() *blockNode {
return s.blockMap[s.currentBlockID]
}
// CurrentBlockWeight() returns the weight of the current block in the
// heaviest fork.
func (s *State) currentBlockWeight() BlockWeight {
return s.currentBlockNode().target.Inverse()
}
// depth returns the depth of the current block of the state.
func (s *State) depth() Target {
return s.currentBlockNode().depth
}
// height returns the current height of the state.
func (s *State) height() BlockHeight {
return s.blockMap[s.currentBlockID].height
}
// State.Output returns the Output associated with the id provided for input,
// but only if the output is a part of the utxo set.
func (s *State) output(id OutputID) (output Output, exists bool) {
output, exists = s.unspentOutputs[id]
return
}
// Sorted UtxoSet returns all of the unspent transaction outputs sorted
// according to the numerical value of their id.
func (s *State) sortedUtxoSet() (sortedOutputs []Output) {
// Get all of the outputs in string form and sort the strings.
var unspentOutputStrings []string
for outputID := range s.unspentOutputs {
unspentOutputStrings = append(unspentOutputStrings, string(outputID[:]))
}
sort.Strings(unspentOutputStrings)
// Get the outputs in order according to their sorted string form.
for _, utxoString := range unspentOutputStrings {
var outputID OutputID
copy(outputID[:], utxoString)
output, exists := s.output(outputID)
if !exists {
panic("output doesn't exist?")
}
output, _ := s.output(outputID)
sortedOutputs = append(sortedOutputs, output)
}
return
......@@ -44,10 +83,10 @@ func (s *State) stateHash() hash.Hash {
leaves := []hash.Hash{
hash.Hash(s.currentBlockID),
hash.HashObject(s.height()),
hash.HashObject(s.currentBlockNode().Target),
hash.HashObject(s.currentBlockNode().Depth),
hash.HashObject(s.currentBlockNode().target),
hash.HashObject(s.currentBlockNode().depth),
hash.HashObject(s.currentBlockNode().earliestChildTimestamp()),
hash.Hash(s.blockRoot.Block.ID()),
hash.Hash(s.blockRoot.block.ID()),
}
// Add all the blocks in the current path.
......@@ -80,6 +119,96 @@ func (s *State) stateHash() hash.Hash {
return hash.MerkleRoot(leaves)
}
// Block returns the block associated with the given id.
func (s *State) Block(id BlockID) (b Block, exists bool) {
s.mu.RLock()
defer s.mu.RUnlock()
node, exists := s.blockMap[id]
if !exists {
return
}
b = node.block
return
}
// BlockAtHeight returns the block in the current fork found at `height`.
func (s *State) BlockAtHeight(height BlockHeight) (b Block, exists bool) {
s.mu.RLock()
defer s.mu.RUnlock()
bn, exists := s.blockMap[s.currentPath[height]]
if !exists {
return
}
b = bn.block
return
}
// Contract returns a the contract associated with the input id, and whether
// the contract exists.
func (s *State) Contract(id ContractID) (fc FileContract, exists bool) {
s.mu.RLock()
defer s.mu.RUnlock()
fc, exists = s.openContracts[id]
if !exists {
return