-
Notifications
You must be signed in to change notification settings - Fork 646
/
builder.go
195 lines (164 loc) · 5.17 KB
/
builder.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
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package spdagvm
import (
"errors"
"github.com/ava-labs/gecko/ids"
"github.com/ava-labs/gecko/utils/crypto"
"github.com/ava-labs/gecko/utils/hashing"
"github.com/ava-labs/gecko/utils/math"
)
var (
errInputSignerMismatch = errors.New("wrong number of signers for the inputs")
errNilSigner = errors.New("nil signer")
errNilChainID = errors.New("nil chain id")
)
// Builder defines the functionality for building payment objects.
type Builder struct {
NetworkID uint32
ChainID ids.ID
}
// NewInputPayment returns a new input that consumes a UTXO.
func (b Builder) NewInputPayment(sourceID ids.ID, sourceIndex uint32, amount uint64, sigs []*Sig) Input {
SortTxSig(sigs)
return &InputPayment{
sourceID: sourceID,
sourceIndex: sourceIndex,
amount: amount,
sigs: sigs,
}
}
// NewOutputPayment returns a new output that generates a standard UTXO.
func (b Builder) NewOutputPayment(
amount,
locktime uint64,
threshold uint32,
addresses []ids.ShortID,
) Output {
ids.SortShortIDs(addresses)
return &OutputPayment{
amount: amount,
locktime: locktime,
threshold: threshold,
addresses: addresses,
}
}
// NewOutputTakeOrLeave returns a new output that generates a UTXO with all the
// features of the standard UTXO, plus the ability to have a different set of
// addresses spend the asset after the specified time.
func (b Builder) NewOutputTakeOrLeave(
amount,
locktime1 uint64,
threshold1 uint32,
addresses1 []ids.ShortID,
locktime2 uint64,
threshold2 uint32,
addresses2 []ids.ShortID,
) Output {
ids.SortShortIDs(addresses1)
ids.SortShortIDs(addresses2)
return &OutputTakeOrLeave{
amount: amount,
locktime1: locktime1,
threshold1: threshold1,
addresses1: addresses1,
locktime2: locktime2,
threshold2: threshold2,
addresses2: addresses2,
}
}
// NewSig returns a new signature object. This object will specify the address
// in the UTXO that will be used to authorize the wrapping input.
func (b Builder) NewSig(index uint32) *Sig { return &Sig{index: index} }
// NewTxFromUTXOs returns a new transaction where:
// * One of the outputs is an Output with [amount] avax that is controlled by [toAddr].
// * This output can't be spent until at least [locktime].
// * If there is any "change" there is another output controlled by [changeAddr] with the change.
// * The UTXOs consumed to make this transaction are a subset of [utxos].
// * The keys controlling [utxos] are in [keychain]
func (b Builder) NewTxFromUTXOs(keychain *Keychain, utxos []*UTXO, amount, txFee, locktime uint64,
threshold uint32, toAddrs []ids.ShortID, changeAddr ids.ShortID, currentTime uint64) (*Tx, error) {
ins := []Input{} // Consumed by this transaction
signers := []*InputSigner{} // Each corresponds to an input consumed by this transaction
amountPlusTxFee, err := math.Add64(amount, txFee)
if err != nil {
return nil, errAmountOverflow
}
spent := uint64(0) // The sum of the UTXOs consumed in this transaction
for i := 0; i < len(utxos) && amountPlusTxFee > spent; i++ {
utxo := utxos[i]
if in, signer, err := keychain.Spend(utxo, currentTime); err == nil {
ins = append(ins, in)
amount := in.(*InputPayment).Amount()
spent += amount
signers = append(signers, signer)
}
}
if spent < amountPlusTxFee {
return nil, errInsufficientFunds
}
outs := []Output{ // List of outputs
b.NewOutputPayment(amount, locktime, threshold, toAddrs), // The primary output
}
// If there is "change", add another output
if spent > amountPlusTxFee {
outs = append(outs,
b.NewOutputPayment(spent-amountPlusTxFee, 0, 1, []ids.ShortID{changeAddr}),
)
}
return b.NewTx(ins, outs, signers) // Sort, marshal, sign, build the transaction
}
// NewTx returns a new transaction where:
// * The inputs to the Tx are [ins], sorted.
// * The outputs of the Tx are [outs], sorted
// * The ith signer will be used to sign the ith input. This means that len([inputs]) must be == len([signers]).
// TODO: Should the signer be part of the input
func (b Builder) NewTx(ins []Input, outs []Output, signers []*InputSigner) (*Tx, error) {
if b.ChainID.IsZero() {
return nil, errNilChainID
}
if len(ins) != len(signers) {
return nil, errInputSignerMismatch
}
SortOuts(outs)
SortIns(ins, signers)
t := &Tx{
networkID: b.NetworkID,
chainID: b.ChainID,
ins: ins,
outs: outs,
}
c := Codec{}
unsignedBytes, err := c.MarshalUnsignedTx(t)
if err != nil {
return nil, err
}
unsignedHash := hashing.ComputeHash256(unsignedBytes)
for i, rawIn := range t.ins {
switch in := rawIn.(type) {
case *InputPayment:
signer := signers[i]
if signer == nil {
return nil, errNilSigner
}
for j, key := range signer.Keys {
if !crypto.EnableCrypto {
in.sigs[j].sig = make([]byte, crypto.SECP256K1RSigLen)
continue
}
sig, err := key.SignHash(unsignedHash)
if err != nil {
return nil, err
}
in.sigs[j].sig = sig
}
}
}
bytes, err := c.MarshalTx(t)
if err != nil {
return nil, err
}
t.bytes = bytes
t.id = ids.NewID(hashing.ComputeHash256Array(t.bytes))
return t, nil
}