OSDN Git Service

1731a5c7019d63e8783de68c7b3ea1997ad8dad8
[bytom/bytom.git] / blockchain / txbuilder / txbuilder_test.go
1 package txbuilder
2
3 import (
4         "context"
5         "encoding/hex"
6         "math"
7         "testing"
8         "time"
9
10         "github.com/davecgh/go-spew/spew"
11
12         "golang.org/x/crypto/sha3"
13
14         "github.com/bytom/crypto/ed25519"
15         "github.com/bytom/crypto/ed25519/chainkd"
16         "github.com/bytom/encoding/json"
17         "github.com/bytom/errors"
18         "github.com/bytom/protocol/bc"
19         "github.com/bytom/protocol/bc/legacy"
20         "github.com/bytom/protocol/vm"
21         "github.com/bytom/protocol/vm/vmutil"
22         "github.com/bytom/testutil"
23 )
24
25 type testAction bc.AssetAmount
26
27 func (t testAction) Build(ctx context.Context, b *TemplateBuilder) error {
28         in := legacy.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), *t.AssetId, t.Amount, 0, nil, bc.Hash{}, nil)
29         tplIn := &SigningInstruction{}
30
31         err := b.AddInput(in, tplIn)
32         if err != nil {
33                 return err
34         }
35         return b.AddOutput(legacy.NewTxOutput(*t.AssetId, t.Amount, []byte("change"), nil))
36 }
37
38 func newControlProgramAction(assetAmt bc.AssetAmount, script []byte) *controlProgramAction {
39         return &controlProgramAction{
40                 AssetAmount: assetAmt,
41                 Program:     script,
42         }
43 }
44
45 func TestBuild(t *testing.T) {
46         ctx := context.Background()
47
48         assetID1 := bc.NewAssetID([32]byte{1})
49         assetID2 := bc.NewAssetID([32]byte{2})
50
51         actions := []Action{
52                 newControlProgramAction(bc.AssetAmount{AssetId: &assetID2, Amount: 6}, []byte("dest")),
53                 testAction(bc.AssetAmount{AssetId: &assetID1, Amount: 5}),
54                 &setTxRefDataAction{Data: []byte("xyz")},
55         }
56         expiryTime := time.Now().Add(time.Minute)
57         got, err := Build(ctx, nil, actions, expiryTime)
58         if err != nil {
59                 testutil.FatalErr(t, err)
60         }
61
62         want := &Template{
63                 Transaction: legacy.NewTx(legacy.TxData{
64                         Version: 1,
65                         Inputs: []*legacy.TxInput{
66                                 legacy.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), assetID1, 5, 0, nil, bc.Hash{}, nil),
67                         },
68                         Outputs: []*legacy.TxOutput{
69                                 legacy.NewTxOutput(assetID2, 6, []byte("dest"), nil),
70                                 legacy.NewTxOutput(assetID1, 5, []byte("change"), nil),
71                         },
72                         ReferenceData: []byte("xyz"),
73                 }),
74                 SigningInstructions: []*SigningInstruction{{
75                         WitnessComponents: []witnessComponent{},
76                 }},
77         }
78
79         if !testutil.DeepEqual(got.Transaction.TxData, want.Transaction.TxData) {
80                 t.Errorf("got tx:\n%s\nwant tx:\n%s", spew.Sdump(got.Transaction.TxData), spew.Sdump(want.Transaction.TxData))
81         }
82
83         if !testutil.DeepEqual(got.SigningInstructions, want.SigningInstructions) {
84                 t.Errorf("got signing instructions:\n\t%#v\nwant signing instructions:\n\t%#v", got.SigningInstructions, want.SigningInstructions)
85         }
86
87         // setting tx refdata twice should fail
88         actions = append(actions, &setTxRefDataAction{Data: []byte("lmnop")})
89         _, err = Build(ctx, nil, actions, expiryTime)
90         if errors.Root(err) != ErrAction {
91                 t.Errorf("got error %#v, want ErrAction", err)
92         }
93         errs := errors.Data(err)["actions"].([]error)
94         if len(errs) != 1 {
95                 t.Errorf("got error %v action errors, want 1", len(errs))
96         }
97         if errors.Root(errs[0]) != ErrBadRefData {
98                 t.Errorf("got error %v in action error, want ErrBadRefData", errs[0])
99         }
100 }
101
102 func TestSignatureWitnessMaterialize(t *testing.T) {
103         var initialBlockHash bc.Hash
104         privkey1, pubkey1, err := chainkd.NewXKeys(nil)
105         if err != nil {
106                 t.Fatal(err)
107         }
108         privkey2, pubkey2, err := chainkd.NewXKeys(nil)
109         if err != nil {
110                 t.Fatal(err)
111         }
112         privkey3, pubkey3, err := chainkd.NewXKeys(nil)
113         if err != nil {
114                 t.Fatal(err)
115         }
116         issuanceProg, _ := vmutil.P2SPMultiSigProgram([]ed25519.PublicKey{pubkey1.PublicKey(), pubkey2.PublicKey(), pubkey3.PublicKey()}, 2)
117         assetID := bc.ComputeAssetID(issuanceProg, &initialBlockHash, 1, &bc.EmptyStringHash)
118         outscript := mustDecodeHex("76a914c5d128911c28776f56baaac550963f7b88501dc388c0")
119         unsigned := legacy.NewTx(legacy.TxData{
120                 Version: 1,
121                 Inputs: []*legacy.TxInput{
122                         legacy.NewIssuanceInput([]byte{1}, 100, nil, initialBlockHash, issuanceProg, nil, nil),
123                 },
124                 Outputs: []*legacy.TxOutput{
125                         legacy.NewTxOutput(assetID, 100, outscript, nil),
126                 },
127         })
128
129         tpl := &Template{
130                 Transaction: unsigned,
131         }
132         h := tpl.Hash(0)
133         builder := vmutil.NewBuilder()
134         builder.AddData(h.Bytes())
135         builder.AddOp(vm.OP_TXSIGHASH).AddOp(vm.OP_EQUAL)
136         prog, _ := builder.Build()
137         msg := sha3.Sum256(prog)
138         sig1 := privkey1.Sign(msg[:])
139         sig2 := privkey2.Sign(msg[:])
140         sig3 := privkey3.Sign(msg[:])
141         want := [][]byte{
142                 vm.Int64Bytes(0),
143                 sig1,
144                 sig2,
145                 prog,
146         }
147
148         // Test with more signatures than required, in correct order
149         tpl.SigningInstructions = []*SigningInstruction{{
150                 WitnessComponents: []witnessComponent{
151                         &SignatureWitness{
152                                 Quorum: 2,
153                                 Keys: []keyID{
154                                         {
155                                                 XPub:           pubkey1,
156                                                 DerivationPath: []json.HexBytes{{0, 0, 0, 0}},
157                                         },
158                                         {
159                                                 XPub:           pubkey2,
160                                                 DerivationPath: []json.HexBytes{{0, 0, 0, 0}},
161                                         },
162                                         {
163                                                 XPub:           pubkey3,
164                                                 DerivationPath: []json.HexBytes{{0, 0, 0, 0}},
165                                         },
166                                 },
167                                 Program: prog,
168                                 Sigs:    []json.HexBytes{sig1, sig2, sig3},
169                         },
170                 },
171         }}
172         err = materializeWitnesses(tpl)
173         if err != nil {
174                 testutil.FatalErr(t, err)
175         }
176         got := tpl.Transaction.Inputs[0].Arguments()
177         if !testutil.DeepEqual(got, want) {
178                 t.Errorf("got input witness %v, want input witness %v", got, want)
179         }
180
181         // Test with exact amount of signatures required, in correct order
182         component := tpl.SigningInstructions[0].WitnessComponents[0].(*SignatureWitness)
183         component.Sigs = []json.HexBytes{sig1, sig2}
184         err = materializeWitnesses(tpl)
185         if err != nil {
186                 testutil.FatalErr(t, err)
187         }
188         got = tpl.Transaction.Inputs[0].Arguments()
189         if !testutil.DeepEqual(got, want) {
190                 t.Errorf("got input witness %v, want input witness %v", got, want)
191         }
192 }
193
194 func mustDecodeHex(str string) []byte {
195         data, err := hex.DecodeString(str)
196         if err != nil {
197                 panic(err)
198         }
199         return data
200 }
201
202 func TestCheckBlankCheck(t *testing.T) {
203         cases := []struct {
204                 tx   *legacy.TxData
205                 want error
206         }{{
207                 tx: &legacy.TxData{
208                         Inputs: []*legacy.TxInput{legacy.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), bc.AssetID{}, 5, 0, nil, bc.Hash{}, nil)},
209                 },
210                 want: ErrBlankCheck,
211         }, {
212                 tx: &legacy.TxData{
213                         Inputs:  []*legacy.TxInput{legacy.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), bc.AssetID{}, 5, 0, nil, bc.Hash{}, nil)},
214                         Outputs: []*legacy.TxOutput{legacy.NewTxOutput(bc.AssetID{}, 3, nil, nil)},
215                 },
216                 want: ErrBlankCheck,
217         }, {
218                 tx: &legacy.TxData{
219                         Inputs: []*legacy.TxInput{
220                                 legacy.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), bc.AssetID{}, 5, 0, nil, bc.Hash{}, nil),
221                                 legacy.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), bc.NewAssetID([32]byte{1}), 5, 0, nil, bc.Hash{}, nil),
222                         },
223                         Outputs: []*legacy.TxOutput{legacy.NewTxOutput(bc.AssetID{}, 5, nil, nil)},
224                 },
225                 want: ErrBlankCheck,
226         }, {
227                 tx: &legacy.TxData{
228                         Inputs: []*legacy.TxInput{legacy.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), bc.AssetID{}, 5, 0, nil, bc.Hash{}, nil)},
229                         Outputs: []*legacy.TxOutput{
230                                 legacy.NewTxOutput(bc.AssetID{}, math.MaxInt64, nil, nil),
231                                 legacy.NewTxOutput(bc.AssetID{}, 7, nil, nil),
232                         },
233                 },
234                 want: ErrBadAmount,
235         }, {
236                 tx: &legacy.TxData{
237                         Inputs: []*legacy.TxInput{
238                                 legacy.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), bc.AssetID{}, 5, 0, nil, bc.Hash{}, nil),
239                                 legacy.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), bc.AssetID{}, math.MaxInt64, 0, nil, bc.Hash{}, nil),
240                         },
241                 },
242                 want: ErrBadAmount,
243         }, {
244                 tx: &legacy.TxData{
245                         Inputs:  []*legacy.TxInput{legacy.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), bc.AssetID{}, 5, 0, nil, bc.Hash{}, nil)},
246                         Outputs: []*legacy.TxOutput{legacy.NewTxOutput(bc.AssetID{}, 5, nil, nil)},
247                 },
248                 want: nil,
249         }, {
250                 tx: &legacy.TxData{
251                         Outputs: []*legacy.TxOutput{legacy.NewTxOutput(bc.AssetID{}, 5, nil, nil)},
252                 },
253                 want: nil,
254         }, {
255                 tx: &legacy.TxData{
256                         Inputs:  []*legacy.TxInput{legacy.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), bc.AssetID{}, 5, 0, nil, bc.Hash{}, nil)},
257                         Outputs: []*legacy.TxOutput{legacy.NewTxOutput(bc.NewAssetID([32]byte{1}), 5, nil, nil)},
258                 },
259                 want: nil,
260         }}
261
262         for _, c := range cases {
263                 got := checkBlankCheck(c.tx)
264                 if errors.Root(got) != c.want {
265                         t.Errorf("checkUnsafe(%+v) err = %v want %v", c.tx, errors.Root(got), c.want)
266                 }
267         }
268 }