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