github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/types/transactions.go (about)

     1  package types
     2  
     3  // transaction.go defines the transaction type and all of the sub-fields of the
     4  // transaction, as well as providing helper functions for working with
     5  // transactions. The various IDs are designed such that, in a legal blockchain,
     6  // it is cryptographically unlikely that any two objects would share an id.
     7  
     8  import (
     9  	"errors"
    10  
    11  	"SiaPrime/build"
    12  	"SiaPrime/crypto"
    13  	"SiaPrime/encoding"
    14  )
    15  
    16  const (
    17  	// SpecifierLen is the length in bytes of a Specifier.
    18  	SpecifierLen = 16
    19  
    20  	// UnlockHashChecksumSize is the size of the checksum used to verify
    21  	// human-readable addresses. It is not a crypytographically secure
    22  	// checksum, it's merely intended to prevent typos. 6 is chosen because it
    23  	// brings the total size of the address to 38 bytes, leaving 2 bytes for
    24  	// potential version additions in the future.
    25  	UnlockHashChecksumSize = 6
    26  )
    27  
    28  // These Specifiers are used internally when calculating a type's ID. See
    29  // Specifier for more details.
    30  var (
    31  	ErrTransactionIDWrongLen = errors.New("input has wrong length to be an encoded transaction id")
    32  
    33  	SpecifierClaimOutput          = Specifier{'c', 'l', 'a', 'i', 'm', ' ', 'o', 'u', 't', 'p', 'u', 't'}
    34  	SpecifierFileContract         = Specifier{'f', 'i', 'l', 'e', ' ', 'c', 'o', 'n', 't', 'r', 'a', 'c', 't'}
    35  	SpecifierFileContractRevision = Specifier{'f', 'i', 'l', 'e', ' ', 'c', 'o', 'n', 't', 'r', 'a', 'c', 't', ' ', 'r', 'e'}
    36  	SpecifierMinerFee             = Specifier{'m', 'i', 'n', 'e', 'r', ' ', 'f', 'e', 'e'}
    37  	SpecifierMinerPayout          = Specifier{'m', 'i', 'n', 'e', 'r', ' ', 'p', 'a', 'y', 'o', 'u', 't'}
    38  	SpecifierSiacoinInput         = Specifier{'s', 'i', 'a', 'c', 'o', 'i', 'n', ' ', 'i', 'n', 'p', 'u', 't'}
    39  	SpecifierSiacoinOutput        = Specifier{'s', 'i', 'a', 'c', 'o', 'i', 'n', ' ', 'o', 'u', 't', 'p', 'u', 't'}
    40  	SpecifierSiafundInput         = Specifier{'s', 'i', 'a', 'f', 'u', 'n', 'd', ' ', 'i', 'n', 'p', 'u', 't'}
    41  	SpecifierSiafundOutput        = Specifier{'s', 'i', 'a', 'f', 'u', 'n', 'd', ' ', 'o', 'u', 't', 'p', 'u', 't'}
    42  	SpecifierStorageProof         = Specifier{'s', 't', 'o', 'r', 'a', 'g', 'e', ' ', 'p', 'r', 'o', 'o', 'f'}
    43  	SpecifierStorageProofOutput   = Specifier{'s', 't', 'o', 'r', 'a', 'g', 'e', ' ', 'p', 'r', 'o', 'o', 'f'}
    44  )
    45  
    46  type (
    47  	// A Specifier is a fixed-length byte-array that serves two purposes. In
    48  	// the wire protocol, they are used to identify a particular encoding
    49  	// algorithm, signature algorithm, etc. This allows nodes to communicate on
    50  	// their own terms; for example, to reduce bandwidth costs, a node might
    51  	// only accept compressed messages.
    52  	//
    53  	// Internally, Specifiers are used to guarantee unique IDs. Various
    54  	// consensus types have an associated ID, calculated by hashing the data
    55  	// contained in the type. By prepending the data with Specifier, we can
    56  	// guarantee that distinct types will never produce the same hash.
    57  	Specifier [SpecifierLen]byte
    58  
    59  	// IDs are used to refer to a type without revealing its contents. They
    60  	// are constructed by hashing specific fields of the type, along with a
    61  	// Specifier. While all of these types are hashes, defining type aliases
    62  	// gives us type safety and makes the code more readable.
    63  
    64  	// TransactionID uniquely identifies a transaction
    65  	TransactionID crypto.Hash
    66  	// SiacoinOutputID uniquely identifies a siacoin output
    67  	SiacoinOutputID crypto.Hash
    68  	// SiafundOutputID uniquely identifies a siafund output
    69  	SiafundOutputID crypto.Hash
    70  	// FileContractID uniquely identifies a file contract
    71  	FileContractID crypto.Hash
    72  	// OutputID uniquely identifies an output
    73  	OutputID crypto.Hash
    74  
    75  	// A Transaction is an atomic component of a block. Transactions can contain
    76  	// inputs and outputs, file contracts, storage proofs, and even arbitrary
    77  	// data. They can also contain signatures to prove that a given party has
    78  	// approved the transaction, or at least a particular subset of it.
    79  	//
    80  	// Transactions can depend on other previous transactions in the same block,
    81  	// but transactions cannot spend outputs that they create or otherwise be
    82  	// self-dependent.
    83  	Transaction struct {
    84  		SiacoinInputs         []SiacoinInput         `json:"siacoininputs"`
    85  		SiacoinOutputs        []SiacoinOutput        `json:"siacoinoutputs"`
    86  		FileContracts         []FileContract         `json:"filecontracts"`
    87  		FileContractRevisions []FileContractRevision `json:"filecontractrevisions"`
    88  		StorageProofs         []StorageProof         `json:"storageproofs"`
    89  		SiafundInputs         []SiafundInput         `json:"siafundinputs"`
    90  		SiafundOutputs        []SiafundOutput        `json:"siafundoutputs"`
    91  		MinerFees             []Currency             `json:"minerfees"`
    92  		ArbitraryData         [][]byte               `json:"arbitrarydata"`
    93  		TransactionSignatures []TransactionSignature `json:"transactionsignatures"`
    94  	}
    95  
    96  	// A SiacoinInput consumes a SiacoinOutput and adds the siacoins to the set of
    97  	// siacoins that can be spent in the transaction. The ParentID points to the
    98  	// output that is getting consumed, and the UnlockConditions contain the rules
    99  	// for spending the output. The UnlockConditions must match the UnlockHash of
   100  	// the output.
   101  	SiacoinInput struct {
   102  		ParentID         SiacoinOutputID  `json:"parentid"`
   103  		UnlockConditions UnlockConditions `json:"unlockconditions"`
   104  	}
   105  
   106  	// A SiacoinOutput holds a volume of siacoins. Outputs must be spent
   107  	// atomically; that is, they must all be spent in the same transaction. The
   108  	// UnlockHash is the hash of the UnlockConditions that must be fulfilled
   109  	// in order to spend the output.
   110  	SiacoinOutput struct {
   111  		Value      Currency   `json:"value"`
   112  		UnlockHash UnlockHash `json:"unlockhash"`
   113  	}
   114  
   115  	// A SiafundInput consumes a SiafundOutput and adds the siafunds to the set of
   116  	// siafunds that can be spent in the transaction. The ParentID points to the
   117  	// output that is getting consumed, and the UnlockConditions contain the rules
   118  	// for spending the output. The UnlockConditions must match the UnlockHash of
   119  	// the output.
   120  	SiafundInput struct {
   121  		ParentID         SiafundOutputID  `json:"parentid"`
   122  		UnlockConditions UnlockConditions `json:"unlockconditions"`
   123  		ClaimUnlockHash  UnlockHash       `json:"claimunlockhash"`
   124  	}
   125  
   126  	// A SiafundOutput holds a volume of siafunds. Outputs must be spent
   127  	// atomically; that is, they must all be spent in the same transaction. The
   128  	// UnlockHash is the hash of a set of UnlockConditions that must be fulfilled
   129  	// in order to spend the output.
   130  	//
   131  	// When the SiafundOutput is spent, a SiacoinOutput is created, where:
   132  	//
   133  	//     SiacoinOutput.Value := (SiafundPool - ClaimStart) / 10,000 * Value
   134  	//     SiacoinOutput.UnlockHash := SiafundInput.ClaimUnlockHash
   135  	//
   136  	// When a SiafundOutput is put into a transaction, the ClaimStart must always
   137  	// equal zero. While the transaction is being processed, the ClaimStart is set
   138  	// to the value of the SiafundPool.
   139  	SiafundOutput struct {
   140  		Value      Currency   `json:"value"`
   141  		UnlockHash UnlockHash `json:"unlockhash"`
   142  		ClaimStart Currency   `json:"claimstart"`
   143  	}
   144  
   145  	// An UnlockHash is a specially constructed hash of the UnlockConditions type.
   146  	// "Locked" values can be unlocked by providing the UnlockConditions that hash
   147  	// to a given UnlockHash. See UnlockConditions.UnlockHash for details on how the
   148  	// UnlockHash is constructed.
   149  	UnlockHash crypto.Hash
   150  )
   151  
   152  // ID returns the id of a transaction, which is taken by marshalling all of the
   153  // fields except for the signatures and taking the hash of the result.
   154  func (t Transaction) ID() TransactionID {
   155  	// Get the transaction id by hashing all data minus the signatures.
   156  	var txid TransactionID
   157  	h := crypto.NewHash()
   158  	t.MarshalSiaNoSignatures(h)
   159  	h.Sum(txid[:0])
   160  
   161  	// Sanity check in debug builds to make sure that the ids are going to be
   162  	// the same.
   163  	if build.DEBUG {
   164  		verify := TransactionID(crypto.HashAll(
   165  			t.SiacoinInputs,
   166  			t.SiacoinOutputs,
   167  			t.FileContracts,
   168  			t.FileContractRevisions,
   169  			t.StorageProofs,
   170  			t.SiafundInputs,
   171  			t.SiafundOutputs,
   172  			t.MinerFees,
   173  			t.ArbitraryData,
   174  		))
   175  
   176  		if verify != txid {
   177  			panic("TransactionID is not marshalling correctly")
   178  		}
   179  	}
   180  
   181  	return txid
   182  }
   183  
   184  // SiacoinOutputID returns the ID of a siacoin output at the given index,
   185  // which is calculated by hashing the concatenation of the SiacoinOutput
   186  // Specifier, all of the fields in the transaction (except the signatures),
   187  // and output index.
   188  func (t Transaction) SiacoinOutputID(i uint64) SiacoinOutputID {
   189  	// Create the id.
   190  	var id SiacoinOutputID
   191  	h := crypto.NewHash()
   192  	h.Write(SpecifierSiacoinOutput[:])
   193  	t.MarshalSiaNoSignatures(h) // Encode non-signature fields into hash.
   194  	encoding.WriteUint64(h, i)  // Writes index of this output.
   195  	h.Sum(id[:0])
   196  
   197  	// Sanity check - verify that the optimized code is always returning the
   198  	// same ids as the unoptimized code.
   199  	if build.DEBUG {
   200  		verificationID := SiacoinOutputID(crypto.HashAll(
   201  			SpecifierSiacoinOutput,
   202  			t.SiacoinInputs,
   203  			t.SiacoinOutputs,
   204  			t.FileContracts,
   205  			t.FileContractRevisions,
   206  			t.StorageProofs,
   207  			t.SiafundInputs,
   208  			t.SiafundOutputs,
   209  			t.MinerFees,
   210  			t.ArbitraryData,
   211  			i,
   212  		))
   213  		if id != verificationID {
   214  			panic("SiacoinOutputID is not marshalling correctly")
   215  		}
   216  	}
   217  
   218  	return id
   219  }
   220  
   221  // FileContractID returns the ID of a file contract at the given index, which
   222  // is calculated by hashing the concatenation of the FileContract Specifier,
   223  // all of the fields in the transaction (except the signatures), and the
   224  // contract index.
   225  func (t Transaction) FileContractID(i uint64) FileContractID {
   226  	var id FileContractID
   227  	h := crypto.NewHash()
   228  	h.Write(SpecifierFileContract[:])
   229  	t.MarshalSiaNoSignatures(h) // Encode non-signature fields into hash.
   230  	encoding.WriteUint64(h, i)  // Writes index of this output.
   231  	h.Sum(id[:0])
   232  
   233  	// Sanity check - verify that the optimized code is always returning the
   234  	// same ids as the unoptimized code.
   235  	if build.DEBUG {
   236  		verificationID := FileContractID(crypto.HashAll(
   237  			SpecifierFileContract,
   238  			t.SiacoinInputs,
   239  			t.SiacoinOutputs,
   240  			t.FileContracts,
   241  			t.FileContractRevisions,
   242  			t.StorageProofs,
   243  			t.SiafundInputs,
   244  			t.SiafundOutputs,
   245  			t.MinerFees,
   246  			t.ArbitraryData,
   247  			i,
   248  		))
   249  		if id != verificationID {
   250  			panic("FileContractID is not marshalling correctly")
   251  		}
   252  	}
   253  
   254  	return id
   255  }
   256  
   257  // SiafundOutputID returns the ID of a SiafundOutput at the given index, which
   258  // is calculated by hashing the concatenation of the SiafundOutput Specifier,
   259  // all of the fields in the transaction (except the signatures), and output
   260  // index.
   261  func (t Transaction) SiafundOutputID(i uint64) SiafundOutputID {
   262  	var id SiafundOutputID
   263  	h := crypto.NewHash()
   264  	h.Write(SpecifierSiafundOutput[:])
   265  	t.MarshalSiaNoSignatures(h) // Encode non-signature fields into hash.
   266  	encoding.WriteUint64(h, i)  // Writes index of this output.
   267  	h.Sum(id[:0])
   268  
   269  	// Sanity check - verify that the optimized code is always returning the
   270  	// same ids as the unoptimized code.
   271  	if build.DEBUG {
   272  		verificationID := SiafundOutputID(crypto.HashAll(
   273  			SpecifierSiafundOutput,
   274  			t.SiacoinInputs,
   275  			t.SiacoinOutputs,
   276  			t.FileContracts,
   277  			t.FileContractRevisions,
   278  			t.StorageProofs,
   279  			t.SiafundInputs,
   280  			t.SiafundOutputs,
   281  			t.MinerFees,
   282  			t.ArbitraryData,
   283  			i,
   284  		))
   285  		if id != verificationID {
   286  			panic("SiafundOutputID is not marshalling correctly")
   287  		}
   288  	}
   289  	return id
   290  }
   291  
   292  // SiacoinOutputSum returns the sum of all the siacoin outputs in the
   293  // transaction, which must match the sum of all the siacoin inputs. Siacoin
   294  // outputs created by storage proofs and siafund outputs are not considered, as
   295  // they were considered when the contract responsible for funding them was
   296  // created.
   297  func (t Transaction) SiacoinOutputSum() (sum Currency) {
   298  	// Add the siacoin outputs.
   299  	for _, sco := range t.SiacoinOutputs {
   300  		sum = sum.Add(sco.Value)
   301  	}
   302  
   303  	// Add the file contract payouts.
   304  	for _, fc := range t.FileContracts {
   305  		sum = sum.Add(fc.Payout)
   306  	}
   307  
   308  	// Add the miner fees.
   309  	for _, fee := range t.MinerFees {
   310  		sum = sum.Add(fee)
   311  	}
   312  
   313  	return
   314  }
   315  
   316  // SiaClaimOutputID returns the ID of the SiacoinOutput that is created when
   317  // the siafund output is spent. The ID is the hash the SiafundOutputID.
   318  func (id SiafundOutputID) SiaClaimOutputID() SiacoinOutputID {
   319  	return SiacoinOutputID(crypto.HashObject(id))
   320  }