-
Notifications
You must be signed in to change notification settings - Fork 361
/
txbuilder.go
131 lines (116 loc) · 4.11 KB
/
txbuilder.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
// Package txbuilder builds a Chain Protocol transaction from
// a list of actions.
package txbuilder
import (
"context"
"time"
"chain/crypto/ed25519/chainkd"
"chain/errors"
"chain/math/checked"
"chain/protocol/bc"
"chain/protocol/bc/legacy"
)
var (
ErrBadRefData = errors.New("transaction reference data does not match previous template's reference data")
ErrBadTxInputIdx = errors.New("unsigned tx missing input")
ErrBadWitnessComponent = errors.New("invalid witness component")
ErrBadAmount = errors.New("bad asset amount")
ErrBlankCheck = errors.New("unsafe transaction: leaves assets free to control")
ErrAction = errors.New("errors occurred in one or more actions")
ErrMissingFields = errors.New("required field is missing")
)
// Build builds or adds on to a transaction.
// Initially, inputs are left unconsumed, and destinations unsatisfied.
// Build partners then satisfy and consume inputs and destinations.
// The final party must ensure that the transaction is
// balanced before calling finalize.
func Build(ctx context.Context, tx *legacy.TxData, actions []Action, maxTime time.Time) (*Template, error) {
builder := TemplateBuilder{
base: tx,
maxTime: maxTime,
}
// Build all of the actions, updating the builder.
var errs []error
for i, action := range actions {
err := action.Build(ctx, &builder)
if err != nil {
err = errors.WithData(err, "index", i)
errs = append(errs, err)
}
}
// If there were any errors, rollback and return a composite error.
if len(errs) > 0 {
builder.rollback()
return nil, errors.WithData(ErrAction, "actions", errs)
}
// Build the transaction template.
tpl, tx, err := builder.Build()
if err != nil {
builder.rollback()
return nil, err
}
err = checkBlankCheck(tx)
if err != nil {
builder.rollback()
return nil, err
}
return tpl, nil
}
func Sign(ctx context.Context, tpl *Template, xpubs []chainkd.XPub, signFn SignFunc) error {
for i, sigInst := range tpl.SigningInstructions {
for j, sw := range sigInst.SignatureWitnesses {
err := sw.sign(ctx, tpl, uint32(i), xpubs, signFn)
if err != nil {
return errors.WithDetailf(err, "adding signature(s) to witness component %d of input %d", j, i)
}
}
}
return materializeWitnesses(tpl)
}
func checkBlankCheck(tx *legacy.TxData) error {
assetMap := make(map[bc.AssetID]int64)
var ok bool
for _, in := range tx.Inputs {
asset := in.AssetID() // AssetID() is calculated for IssuanceInputs, so grab once
assetMap[asset], ok = checked.AddInt64(assetMap[asset], int64(in.Amount()))
if !ok {
return errors.WithDetailf(ErrBadAmount, "cumulative amounts for asset %s overflow the allowed asset amount 2^63", asset)
}
}
for _, out := range tx.Outputs {
assetMap[*out.AssetId], ok = checked.SubInt64(assetMap[*out.AssetId], int64(out.Amount))
if !ok {
return errors.WithDetailf(ErrBadAmount, "cumulative amounts for asset %x overflow the allowed asset amount 2^63", out.AssetId.Bytes())
}
}
var requiresOutputs, requiresInputs bool
for _, amt := range assetMap {
if amt > 0 {
requiresOutputs = true
}
if amt < 0 {
requiresInputs = true
}
}
// 4 possible cases here:
// 1. requiresOutputs - false requiresInputs - false
// This is a balanced transaction with no free assets to consume.
// It could potentially be a complete transaction.
// 2. requiresOutputs - true requiresInputs - false
// This is an unbalanced transaction with free assets to consume
// 3. requiresOutputs - false requiresInputs - true
// This is an unbalanced transaction with a requiring assets to be spent
// 4. requiresOutputs - true requiresInputs - true
// This is an unbalanced transaction with free assets to consume
// and requiring assets to be spent.
// The only case that needs to be protected against is 2.
if requiresOutputs && !requiresInputs {
return errors.Wrap(ErrBlankCheck)
}
return nil
}
// MissingFieldsError returns a wrapped error ErrMissingFields
// with a data item containing the given field names.
func MissingFieldsError(name ...string) error {
return errors.WithData(ErrMissingFields, "missing_fields", name)
}