Commit 7d7eb852 authored by David Vorick's avatar David Vorick

break apart consensus package by function

parent d8af1e34
......@@ -61,19 +61,12 @@ func (bn *blockNode) heavierThan(cmp *blockNode) bool {
return diff.Cmp(threshold) > 0
}
// timestampSlice is used for sorting.
type timestampSlice []Timestamp
func (ts timestampSlice) Len() int { return len(ts) }
func (ts timestampSlice) Less(i, j int) bool { return ts[i] < ts[j] }
func (ts timestampSlice) Swap(i, j int) { ts[i], ts[j] = ts[j], ts[i] }
// 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 {
// Get the previous MedianTimestampWindow timestamps.
windowTimes := make(timestampSlice, MedianTimestampWindow)
windowTimes := make(TimestampSlice, MedianTimestampWindow)
traverse := bn
for i := 0; i < MedianTimestampWindow; i++ {
windowTimes[i] = traverse.block.Timestamp
......
......@@ -6,12 +6,6 @@ import (
"github.com/NebulousLabs/Sia/sync"
)
// The ZeroUnlockHash and ZeroCurrency are convenience variables.
var (
ZeroUnlockHash = UnlockHash{0}
ZeroCurrency = NewCurrency64(0)
)
// The State is the object responsible for tracking the current status of the
// blockchain. Broadly speaking, it is responsible for maintaining consensus.
// It accepts blocks and constructs a blockchain, forking when necessary.
......
package consensus
import (
"errors"
"math/big"
"github.com/NebulousLabs/Sia/crypto"
)
// validSiacoins checks that the siacoin inputs and outputs are valid in the
// context of the current consensus set.
func (s *State) validSiacoins(t Transaction) (err error) {
var inputSum Currency
for _, sci := range t.SiacoinInputs {
// Check that the input spends an existing output.
sco, exists := s.siacoinOutputs[sci.ParentID]
if !exists {
return ErrMissingSiacoinOutput
}
// Check that the unlock conditions match the required unlock hash.
if sci.UnlockConditions.UnlockHash() != sco.UnlockHash {
return errors.New("siacoin unlock conditions do not meet required unlock hash")
}
inputSum = inputSum.Add(sco.Value)
}
if inputSum.Cmp(t.SiacoinOutputSum()) != 0 {
return errors.New("inputs do not equal outputs for transaction")
}
return
}
// storageProofSegment returns the index of the segment that needs to be proven
// exists in a file contract.
func (s *State) storageProofSegment(fcid FileContractID) (index uint64, err error) {
// Get the file contract associated with the input id.
fc, exists := s.fileContracts[fcid]
if !exists {
err = errors.New("unrecognized file contract id")
return
}
// Get the ID of the trigger block.
triggerHeight := fc.Start - 1
if triggerHeight > s.height() {
err = errors.New("no block found at contract trigger block height")
return
}
triggerID := s.currentPath[triggerHeight]
// Get the index by appending the file contract ID to the trigger block and
// taking the hash, then converting the hash to a numerical value and
// modding it against the number of segments in the file. The result is a
// random number in range [0, numSegments]. The probability is very
// slightly weighted towards the beginning of the file, but because the
// size difference between the number of segments and the random number
// being modded, the difference is too small to make any practical
// difference.
seed := crypto.HashBytes(append(triggerID[:], fcid[:]...))
numSegments := int64(crypto.CalculateSegments(fc.FileSize))
seedInt := new(big.Int).SetBytes(seed[:])
index = seedInt.Mod(seedInt, big.NewInt(numSegments)).Uint64()
return
}
// validStorageProofs checks that the storage proofs are valid in the context
// of the consensus set.
func (s *State) validStorageProofs(t Transaction) error {
for _, sp := range t.StorageProofs {
fc, exists := s.fileContracts[sp.ParentID]
if !exists {
return errors.New("unrecognized file contract ID in storage proof")
}
// Check that the storage proof itself is valid.
segmentIndex, err := s.storageProofSegment(sp.ParentID)
if err != nil {
return err
}
verified := crypto.VerifySegment(
sp.Segment,
sp.HashSet,
crypto.CalculateSegments(fc.FileSize),
segmentIndex,
fc.FileMerkleRoot,
)
if !verified {
return errors.New("provided storage proof is invalid")
}
}
return nil
}
// validFileContractTerminations checks that each file contract termination is
// valid in the context of the current consensus set.
func (s *State) validFileContractTerminations(t Transaction) (err error) {
for _, fct := range t.FileContractTerminations {
// Check that the FileContractTermination terminates an existing
// FileContract.
fc, exists := s.fileContracts[fct.ParentID]
if !exists {
return ErrMissingFileContract
}
// Check that the height is less than fc.Start - terminations are not
// allowed to be submitted once the storage proof window has opened.
// This reduces complexity for unconfirmed transactions.
if fc.Start < s.height() {
return errors.New("contract termination submitted too late")
}
// Check that the unlock conditions match the unlock hash.
if fct.TerminationConditions.UnlockHash() != fc.TerminationHash {
return errors.New("termination conditions don't match required termination hash")
}
// Check that the payouts in the termination add up to the payout of the
// contract.
var payoutSum Currency
for _, payout := range fct.Payouts {
payoutSum = payoutSum.Add(payout.Value)
}
if payoutSum.Cmp(fc.Payout) != 0 {
return errors.New("contract termination has incorrect payouts")
}
}
return
}
// validSiafunds checks that the siafund portions of the transaction are valid
// in the context of the consensus set.
func (s *State) validSiafunds(t Transaction) (err error) {
// Compare the number of input siafunds to the output siafunds.
var siafundInputSum Currency
var siafundOutputSum Currency
for _, sfi := range t.SiafundInputs {
sfo, exists := s.siafundOutputs[sfi.ParentID]
if !exists {
return ErrMissingSiafundOutput
}
// Check the unlock conditions match the unlock hash.
if sfi.UnlockConditions.UnlockHash() != sfo.UnlockHash {
return errors.New("unlock conditions don't match required unlock hash")
}
siafundInputSum = siafundInputSum.Add(sfo.Value)
}
for _, sfo := range t.SiafundOutputs {
siafundOutputSum = siafundOutputSum.Add(sfo.Value)
}
if siafundOutputSum.Cmp(siafundInputSum) != 0 {
return errors.New("siafund inputs do not equal siafund outpus within transaction")
}
return
}
// validTransaction checks that all fields are valid within the current
// consensus state. If not an error is returned.
func (s *State) validTransaction(t Transaction) (err error) {
// StandaloneValid will check things like signatures and properties that
// should be inherent to the transaction. (storage proof rules, etc.)
err = t.StandaloneValid(s.height())
if err != nil {
return
}
// Check that each portion of the transaction is legal given the current
// consensus set.
err = s.validSiacoins(t)
if err != nil {
return
}
err = s.validFileContractTerminations(t)
if err != nil {
return
}
err = s.validStorageProofs(t)
if err != nil {
return
}
err = s.validSiafunds(t)
if err != nil {
return
}
return
}
// ValidStorageProofs checks that the storage proofs are valid in the context
// of the consensus set.
func (s *State) ValidStorageProofs(t Transaction) (err error) {
id := s.mu.RLock()
defer s.mu.RUnlock(id)
return s.validStorageProofs(t)
}
package consensus
import (
"bytes"
"github.com/NebulousLabs/Sia/crypto"
)
type (
BlockHeight uint64
BlockID crypto.Hash
)
// A Block is a summary of changes to the state that have occurred since the
// previous block. Blocks reference the ID of the previous block (their
// "parent"), creating the linked-list commonly known as the blockchain. Their
// primary function is to bundle together transactions on the network. Blocks
// are created by "miners," who collect transactions from other nodes, and
// then try to pick a Nonce that results in a block whose BlockID is below a
// given Target.
type Block struct {
ParentID BlockID
Nonce uint64
Timestamp Timestamp
MinerPayouts []SiacoinOutput
Transactions []Transaction
}
// CalculateCoinbase calculates the coinbase for a given height. The coinbase
// equation is:
//
// coinbase := max(InitialCoinbase - height, MinimumCoinbase) * CoinbaseAugment
func CalculateCoinbase(height BlockHeight) (c Currency) {
base := InitialCoinbase - uint64(height)
if base < MinimumCoinbase {
base = MinimumCoinbase
}
return NewCurrency64(base).Mul(NewCurrency(CoinbaseAugment))
}
// ID returns the ID of a Block, which is calculated by hashing the
// concatenation of the block's parent ID, nonce, and Merkle root.
func (b Block) ID() BlockID {
return BlockID(crypto.HashAll(
b.ParentID,
b.Nonce,
b.MerkleRoot(),
))
}
// CheckTarget returns true if the block's ID meets the given target.
func (b Block) CheckTarget(target Target) bool {
blockHash := b.ID()
return bytes.Compare(target[:], blockHash[:]) >= 0
}
// MerkleRoot calculates the Merkle root of a Block. The leaves of the Merkle
// tree are composed of the Timestamp, the miner outputs (one leaf per
// payout), and the transactions (one leaf per transaction).
func (b Block) MerkleRoot() crypto.Hash {
tree := crypto.NewTree()
tree.PushObject(b.Timestamp)
for _, payout := range b.MinerPayouts {
tree.PushObject(payout)
}
for _, txn := range b.Transactions {
tree.PushObject(txn)
}
return tree.Root()
}
// MinerPayoutID returns the ID of the miner payout at the given index, which
// is calculated by hashing the concatenation of the BlockID and the payout
// index.
func (b Block) MinerPayoutID(i int) SiacoinOutputID {
return SiacoinOutputID(crypto.HashAll(
b.ID(),
i,
))
}
......@@ -7,6 +7,10 @@ import (
"math/big"
)
var (
ZeroCurrency = NewCurrency64(0)
)
// currency.go defines the internal currency object. One major design goal of
// the currency type is immutability. Another is non-negativity: the currency
// object should never have a negative value.
......
......@@ -8,7 +8,20 @@ import (
)
var (
// These Specifiers enumerate the types of signatures that are recognized
// by this implementation. If a signature's type is unrecognized, the
// signature is treated as valid. Signatures using the special "entropy"
// type are always treated as invalid; see Consensus.md for more details.
SignatureEntropy = Specifier{'e', 'n', 't', 'r', 'o', 'p', 'y'}
SignatureEd25519 = Specifier{'e', 'd', '2', '5', '5', '1', '9'}
ErrMissingSignatures = errors.New("transaction has inputs with missing signatures")
ZeroUnlockHash = UnlockHash{0}
)
type (
Signature string
)
// Each input has a list of public keys and a required number of signatures.
......
package consensus
import (
"math/big"
"github.com/NebulousLabs/Sia/crypto"
)
type (
// A Target is a hash that a block's ID must be "less than" in order for
// the block to be considered valid. Miners vary the block's 'Nonce' field
// in order to brute-force such an ID. The inverse of a Target is called
// the "difficulty," because it is proportional to the amount of time
// required to brute-force the Target.
Target crypto.Hash
)
// Int converts a Target to a big.Int.
func (t Target) Int() *big.Int {
return new(big.Int).SetBytes(t[:])
}
// Rat converts a Target to a big.Rat.
func (t Target) Rat() *big.Rat {
return new(big.Rat).SetInt(t.Int())
}
// Inverse returns the inverse of a Target as a big.Rat
func (t Target) Inverse() *big.Rat {
return new(big.Rat).Inv(t.Rat())
}
// IntToTarget converts a big.Int to a Target.
func IntToTarget(i *big.Int) (t Target) {
// i may overflow the maximum target.
// In the event of overflow, return the maximum.
if i.BitLen() > 256 {
return RootDepth
}
b := i.Bytes()
// need to preserve big-endianness
offset := len(t[:]) - len(b)
copy(t[offset:], b)
return
}
// RatToTarget converts a big.Rat to a Target.
func RatToTarget(r *big.Rat) Target {
// conversion to big.Int truncates decimal
i := new(big.Int).Div(r.Num(), r.Denom())
return IntToTarget(i)
}
package consensus
import (
"time"
)
type (
Timestamp uint64
TimestampSlice []Timestamp
)
func (ts TimestampSlice) Len() int {
return len(ts)
}
func (ts TimestampSlice) Less(i, j int) bool {
return ts[i] < ts[j]
}
func (ts TimestampSlice) Swap(i, j int) {
ts[i], ts[j] = ts[j], ts[i]
}
// CurrentTimestamp returns the current time as a Timestamp.
func CurrentTimestamp() Timestamp {
return Timestamp(time.Now().Unix())
}
package consensus
import (
"bytes"
"math/big"
"time"
"github.com/NebulousLabs/Sia/crypto"
"github.com/NebulousLabs/Sia/encoding"
)
type (
Timestamp uint64
BlockHeight uint64
Siafund Currency // arbitrary-precision unsigned integer
Siafund Currency // arbitrary-precision unsigned integer
// A Specifier is a fixed-length string that serves two purposes. In the
// wire protocol, they are used to identify a particular encoding
......@@ -26,15 +20,10 @@ type (
// guarantee that distinct types will never produce the same hash.
Specifier [16]byte
// The Signature type is arbitrary-length to enable a variety of signature
// algorithms.
Signature string
// IDs are used to refer to a type without revealing its contents. They
// are constructed by hashing specific fields of the type, along with a
// Specifier. While all of these types are hashes, defining type aliases
// gives us type safety and makes the code more readable.
BlockID crypto.Hash
SiacoinOutputID crypto.Hash
SiafundOutputID crypto.Hash
FileContractID crypto.Hash
......@@ -44,13 +33,6 @@ type (
// that hash to a given UnlockHash. See SpendConditions.UnlockHash for
// details on how the UnlockHash is constructed.
UnlockHash crypto.Hash
// A Target is a hash that a block's ID must be "less than" in order for
// the block to be considered valid. Miners vary the block's 'Nonce' field
// in order to brute-force such an ID. The inverse of a Target is called
// the "difficulty," because it is proportional to the amount of time
// required to brute-force the Target.
Target crypto.Hash
)
// These Specifiers are used internally when calculating a type's ID. See
......@@ -63,30 +45,6 @@ var (
SpecifierSiafundOutput = Specifier{'s', 'i', 'a', 'f', 'u', 'n', 'd', ' ', 'o', 'u', 't', 'p', 'u', 't'}
)
// These Specifiers enumerate the types of signatures that are recognized by
// this implementation. If a signature's type is unrecognized, the signature
// is treated as valid. Signatures using the special "entropy" type are always
// treated as invalid; see Consensus.md for more details.
var (
SignatureEntropy = Specifier{'e', 'n', 't', 'r', 'o', 'p', 'y'}
SignatureEd25519 = Specifier{'e', 'd', '2', '5', '5', '1', '9'}
)
// A Block is a summary of changes to the state that have occurred since the
// previous block. Blocks reference the ID of the previous block (their
// "parent"), creating the linked-list commonly known as the blockchain. Their
// primary function is to bundle together transactions on the network. Blocks
// are created by "miners," who collect transactions from other nodes, and
// then try to pick a Nonce that results in a block whose BlockID is below a
// given Target.
type Block struct {
ParentID BlockID
Nonce uint64
Timestamp Timestamp
MinerPayouts []SiacoinOutput
Transactions []Transaction
}
// A Transaction is an atomic component of a block. Transactions can contain
// inputs and outputs, file contracts, storage proofs, and even arbitrary
// data. They can also contain signatures to prove that a given party has
......@@ -295,60 +253,6 @@ type CoveredFields struct {
Signatures []uint64
}
// CurrentTimestamp returns the current time as a Timestamp.
func CurrentTimestamp() Timestamp {
return Timestamp(time.Now().Unix())
}
// CalculateCoinbase calculates the coinbase for a given height. The coinbase
// equation is:
//
// coinbase := max(InitialCoinbase - height, MinimumCoinbase) * CoinbaseAugment
func CalculateCoinbase(height BlockHeight) (c Currency) {
base := InitialCoinbase - uint64(height)
if base < MinimumCoinbase {
base = MinimumCoinbase
}
return NewCurrency64(base).Mul(NewCurrency(CoinbaseAugment))
}
// Int converts a Target to a big.Int.
func (t Target) Int() *big.Int {
return new(big.Int).SetBytes(t[:])
}
// Rat converts a Target to a big.Rat.
func (t Target) Rat() *big.Rat {
return new(big.Rat).SetInt(t.Int())
}
// Inverse returns the inverse of a Target as a big.Rat
func (t Target) Inverse() *big.Rat {
return new(big.Rat).Inv(t.Rat())
}
// IntToTarget converts a big.Int to a Target.
func IntToTarget(i *big.Int) (t Target) {
// i may overflow the maximum target.
// In the event of overflow, return the maximum.
if i.BitLen() > 256 {
return RootDepth
}
b := i.Bytes()
// need to preserve big-endianness
offset := len(t[:]) - len(b)
copy(t[offset:], b)
return
}
// RatToTarget converts a big.Rat to a Target.
func RatToTarget(r *big.Rat) Target {
// conversion to big.Int truncates decimal
i := new(big.Int).Div(r.Num(), r.Denom())
return IntToTarget(i)
}
// Tax returns the amount of Currency that will be taxed from fc.
func (fc FileContract) Tax() Currency {
return fc.Payout.MulFloat(SiafundPortion).RoundDown(SiafundCount)
......@@ -370,47 +274,6 @@ func (uc UnlockConditions) UnlockHash() UnlockHash {
return UnlockHash(tree.Root())
}
// ID returns the ID of a Block, which is calculated by hashing the
// concatenation of the block's parent ID, nonce, and Merkle root.
func (b Block) ID() BlockID {
return BlockID(crypto.HashAll(
b.ParentID,
b.Nonce,
b.MerkleRoot(),
))
}
// CheckTarget returns true if the block's ID meets the given target.
func (b Block) CheckTarget(target Target) bool {
blockHash := b.ID()
return bytes.Compare(target[:], blockHash[:]) >= 0
}
// MerkleRoot calculates the Merkle root of a Block. The leaves of the Merkle
// tree are composed of the Timestamp, the miner outputs (one leaf per
// payout), and the transactions (one leaf per transaction).
func (b Block) MerkleRoot() crypto.Hash {
tree := crypto.NewTree()
tree.PushObject(b.Timestamp)
for _, payout := range b.MinerPayouts {
tree.PushObject(payout)
}
for _, txn := range b.Transactions {
tree.PushObject(txn)
}
return tree.Root()
}
// MinerPayoutID returns the ID of the miner payout at the given index, which
// is calculated by hashing the concatenation of the BlockID and the payout
// index.
func (b Block) MinerPayoutID(i int) SiacoinOutputID {
return SiacoinOutputID(crypto.HashAll(
b.ID(),
i,
))
}
// SiacoinOutputID returns the ID of a siacoin output at the given index,
// which is calculated by hashing the concatenation of the SiacoinOutput
// Specifier, all of the fields in the transaction (except the signatures),
......
......@@ -2,9 +2,7 @@ package consensus
import (
"errors"
"math/big"
"github.com/NebulousLabs/Sia/crypto"
"github.com/NebulousLabs/Sia/encoding"
)
......@@ -251,195 +249,3 @@ func (t Transaction) StandaloneValid(currentHeight BlockHeight) (err error) {
}
return
}
// validSiacoins checks that the siacoin inputs and outputs are valid in the
// context of the current consensus set.
func (s *State) validSiacoins(t Transaction) (err error) {
var inputSum Currency
for _, sci := range t.SiacoinInputs {
// Check that the input spends an existing output.
sco, exists := s.siacoinOutputs[sci.ParentID]
if !exists {
return ErrMissingSiacoinOutput
}
// Check that the unlock conditions match the required unlock hash.
if sci.UnlockConditions.UnlockHash() != sco.UnlockHash {
return errors.New("siacoin unlock conditions do not meet required unlock hash")
}
inputSum = inputSum.Add(sco.Value)
}
if inputSum.Cmp(t.SiacoinOutputSum()) != 0 {
return errors.New("inputs do not equal outputs for transaction")
}
return
}
// storageProofSegment returns the index of the segment that needs to be proven
// exists in a file contract.
func (s *State) storageProofSegment(fcid FileContractID) (index uint64, err error) {
// Get the file contract associated with the input id.
fc, exists := s.fileContracts[fcid]
if !exists {
err = errors.New("unrecognized file contract id")
return
}
// Get the ID of the trigger block.
triggerHeight := fc.Start - 1
if triggerHeight > s.height() {
err = errors.New("no block found at contract trigger block height")
return
}
triggerID := s.currentPath[triggerHeight]
// Get the index by appending the file contract ID to the trigger block and
// taking the hash, then converting the hash to a numerical value and
// modding it against the number of segments in the file. The result is a
// random number in range [0, numSegments]. The probability is very
// slightly weighted towards the beginning of the file, but because the
// size difference between the number of segments and the random number
// being modded, the difference is too small to make any practical
// difference.
seed := crypto.HashBytes(append(triggerID[:], fcid[:]...))
numSegments := int64(crypto.CalculateSegments(fc.FileSize))
seedInt := new(big.Int).SetBytes(seed[:])
index = seedInt.Mod(seedInt, big.NewInt(numSegments)).Uint64()
return
}
// validStorageProofs checks that the storage proofs are valid in the context
// of the consensus set.
func (s *State) validStorageProofs(t Transaction) error {
for _, sp := range t.StorageProofs {
fc, exists := s.fileContracts[sp.ParentID]
if !exists {
return errors.New("unrecognized file contract ID in storage proof")
}