forked from ava-labs/avalanchego
-
Notifications
You must be signed in to change notification settings - Fork 4
/
tx.go
162 lines (137 loc) · 4.29 KB
/
tx.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
// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package txs
import (
"errors"
"fmt"
"github.com/MetalBlockchain/metalgo/codec"
"github.com/MetalBlockchain/metalgo/ids"
"github.com/MetalBlockchain/metalgo/network/p2p/gossip"
"github.com/MetalBlockchain/metalgo/snow"
"github.com/MetalBlockchain/metalgo/utils/crypto/secp256k1"
"github.com/MetalBlockchain/metalgo/utils/hashing"
"github.com/MetalBlockchain/metalgo/vms/components/avax"
"github.com/MetalBlockchain/metalgo/vms/components/verify"
"github.com/MetalBlockchain/metalgo/vms/secp256k1fx"
)
var (
_ gossip.Gossipable = (*Tx)(nil)
ErrNilSignedTx = errors.New("nil signed tx is not valid")
errSignedTxNotInitialized = errors.New("signed tx was never initialized and is not valid")
)
// Tx is a signed transaction
type Tx struct {
// The body of this transaction
Unsigned UnsignedTx `serialize:"true" json:"unsignedTx"`
// The credentials of this transaction
Creds []verify.Verifiable `serialize:"true" json:"credentials"`
TxID ids.ID `json:"id"`
bytes []byte
}
func NewSigned(
unsigned UnsignedTx,
c codec.Manager,
signers [][]*secp256k1.PrivateKey,
) (*Tx, error) {
res := &Tx{Unsigned: unsigned}
return res, res.Sign(c, signers)
}
func (tx *Tx) Initialize(c codec.Manager) error {
signedBytes, err := c.Marshal(CodecVersion, tx)
if err != nil {
return fmt.Errorf("couldn't marshal ProposalTx: %w", err)
}
unsignedBytesLen, err := c.Size(CodecVersion, &tx.Unsigned)
if err != nil {
return fmt.Errorf("couldn't calculate UnsignedTx marshal length: %w", err)
}
unsignedBytes := signedBytes[:unsignedBytesLen]
tx.SetBytes(unsignedBytes, signedBytes)
return nil
}
func (tx *Tx) SetBytes(unsignedBytes, signedBytes []byte) {
tx.Unsigned.SetBytes(unsignedBytes)
tx.bytes = signedBytes
tx.TxID = hashing.ComputeHash256Array(signedBytes)
}
// Parse signed tx starting from its byte representation.
// Note: We explicitly pass the codec in Parse since we may need to parse
// P-Chain genesis txs whose length exceed the max length of txs.Codec.
func Parse(c codec.Manager, signedBytes []byte) (*Tx, error) {
tx := &Tx{}
if _, err := c.Unmarshal(signedBytes, tx); err != nil {
return nil, fmt.Errorf("couldn't parse tx: %w", err)
}
unsignedBytesLen, err := c.Size(CodecVersion, &tx.Unsigned)
if err != nil {
return nil, fmt.Errorf("couldn't calculate UnsignedTx marshal length: %w", err)
}
unsignedBytes := signedBytes[:unsignedBytesLen]
tx.SetBytes(unsignedBytes, signedBytes)
return tx, nil
}
func (tx *Tx) Bytes() []byte {
return tx.bytes
}
func (tx *Tx) ID() ids.ID {
return tx.TxID
}
func (tx *Tx) GossipID() ids.ID {
return tx.TxID
}
// UTXOs returns the UTXOs transaction is producing.
func (tx *Tx) UTXOs() []*avax.UTXO {
outs := tx.Unsigned.Outputs()
utxos := make([]*avax.UTXO, len(outs))
for i, out := range outs {
utxos[i] = &avax.UTXO{
UTXOID: avax.UTXOID{
TxID: tx.TxID,
OutputIndex: uint32(i),
},
Asset: avax.Asset{ID: out.AssetID()},
Out: out.Out,
}
}
return utxos
}
func (tx *Tx) SyntacticVerify(ctx *snow.Context) error {
switch {
case tx == nil:
return ErrNilSignedTx
case tx.TxID == ids.Empty:
return errSignedTxNotInitialized
default:
return tx.Unsigned.SyntacticVerify(ctx)
}
}
// Sign this transaction with the provided signers
// Note: We explicitly pass the codec in Sign since we may need to sign P-Chain
// genesis txs whose length exceed the max length of txs.Codec.
func (tx *Tx) Sign(c codec.Manager, signers [][]*secp256k1.PrivateKey) error {
unsignedBytes, err := c.Marshal(CodecVersion, &tx.Unsigned)
if err != nil {
return fmt.Errorf("couldn't marshal UnsignedTx: %w", err)
}
// Attach credentials
hash := hashing.ComputeHash256(unsignedBytes)
for _, keys := range signers {
cred := &secp256k1fx.Credential{
Sigs: make([][secp256k1.SignatureLen]byte, len(keys)),
}
for i, key := range keys {
sig, err := key.SignHash(hash) // Sign hash
if err != nil {
return fmt.Errorf("problem generating credential: %w", err)
}
copy(cred.Sigs[i][:], sig)
}
tx.Creds = append(tx.Creds, cred) // Attach credential
}
signedBytes, err := c.Marshal(CodecVersion, tx)
if err != nil {
return fmt.Errorf("couldn't marshal ProposalTx: %w", err)
}
tx.SetBytes(unsignedBytes, signedBytes)
return nil
}