1// SPDX-License-Identifier: ISC
2// Copyright (c) 2014-2020 Bitmark Inc.
3// Use of this source code is governed by an ISC
4// license that can be found in the LICENSE file.
5
6package reservoir
7
8import (
9	"bytes"
10	"encoding/binary"
11	"fmt"
12	"os"
13
14	"github.com/bitmark-inc/bitmarkd/asset"
15	"github.com/bitmark-inc/bitmarkd/fault"
16	"github.com/bitmark-inc/bitmarkd/mode"
17	"github.com/bitmark-inc/bitmarkd/pay"
18	"github.com/bitmark-inc/bitmarkd/transactionrecord"
19	"github.com/bitmark-inc/logger"
20)
21
22type tagType byte
23
24// record types in cache file
25const (
26	taggedBOF         tagType = iota
27	taggedEOF         tagType = iota
28	taggedTransaction tagType = iota
29	taggedProof       tagType = iota
30)
31
32// the BOF tag to check file version
33// exact match is required
34var bofData = []byte("bitmark-cache v1.0")
35
36// LoadFromFile - load transactions from file
37// called later when system is able to handle the tx and proofs
38func LoadFromFile(handles Handles) error {
39	Disable()
40	defer Enable()
41
42	log := globalData.log
43
44	log.Info("starting…")
45
46	f, err := os.Open(globalData.filename)
47	if nil != err {
48		return err
49	}
50	defer f.Close()
51
52	// must have BOF record first
53	tag, packed, err := readRecord(f)
54	if nil != err {
55		return err
56	}
57
58	if taggedBOF != tag {
59		return fmt.Errorf("expected BOF: %d but read: %d", taggedBOF, tag)
60	}
61
62	if !bytes.Equal(bofData, packed) {
63		return fmt.Errorf("expected BOF: %q but read: %q", bofData, packed)
64	}
65
66	log.Infof("restore from file: %s", globalData.filename)
67
68restore_loop:
69	for {
70		tag, packed, err := readRecord(f)
71		if nil != err {
72			log.Errorf("read record with error: %s\n", err)
73			continue restore_loop
74		}
75
76		switch tag {
77
78		case taggedEOF:
79			break restore_loop
80
81		case taggedTransaction:
82			unpacked, _, err := packed.Unpack(mode.IsTesting())
83			if nil != err {
84				log.Errorf("unable to unpack asset: %s", err)
85				continue restore_loop
86			}
87
88			restorer, err := NewTransactionRestorer(unpacked, packed, handles)
89			if nil != err {
90				log.Errorf("create transaction restorer with error: %s", err)
91				continue restore_loop
92			}
93
94			err = restorer.Restore()
95			if nil != err {
96				log.Errorf("restore %s with error: %s", restorer, err)
97				continue restore_loop
98			}
99
100		case taggedProof:
101			var payId pay.PayId
102			pn := len(payId)
103			if len(packed) <= pn {
104				log.Errorf("unable to unpack proof: record too short: %d  expected > %d", len(packed), pn)
105				continue restore_loop
106			}
107			copy(payId[:], packed[:pn])
108			nonce := packed[pn:]
109			tryProof(payId, nonce)
110
111		default:
112			// in case any unsupported tag exist
113			msg := fmt.Errorf("abort, read invalid tag: 0x%02x", tag)
114			log.Error(msg.Error())
115			return msg
116		}
117	}
118	log.Info("restore completed")
119	return nil
120}
121
122// save transactions to file
123func saveToFile() error {
124	globalData.Lock()
125	defer globalData.Unlock()
126
127	log := globalData.log
128
129	if !globalData.initialised {
130		log.Error("save when not initialised")
131		return fault.NotInitialised
132	}
133
134	log.Info("saving…")
135
136	f, err := os.OpenFile(globalData.filename, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
137	if nil != err {
138		return err
139	}
140	defer f.Close()
141
142	// write beginning of file marker
143	err = writeRecord(f, taggedBOF, bofData)
144	if nil != err {
145		return err
146	}
147
148	// all assets at start of file
149	err = backupAssets(f)
150	if nil != err {
151		return err
152	}
153
154	// verified
155
156	for _, item := range globalData.verifiedTransactions {
157		err := writeRecord(f, taggedTransaction, item.packed)
158		if nil != err {
159			return err
160		}
161	}
162	for _, item := range globalData.verifiedFreeIssues {
163		err := writeBlock(f, taggedTransaction, item.txs)
164		if nil != err {
165			return err
166		}
167		err = writeRecord(f, taggedProof, packProof(item.payId, item.nonce))
168		if nil != err {
169			return err
170		}
171	}
172	for _, item := range globalData.verifiedPaidIssues {
173		err := writeBlock(f, taggedTransaction, item.txs)
174		if nil != err {
175			return err
176		}
177	}
178
179	// pending
180
181	for _, item := range globalData.pendingTransactions {
182		err := writeRecord(f, taggedTransaction, item.tx.packed)
183		if nil != err {
184			return err
185		}
186	}
187	for _, item := range globalData.pendingFreeIssues {
188		err := writeBlock(f, taggedTransaction, item.txs)
189		if nil != err {
190			return err
191		}
192		err = writeRecord(f, taggedProof, packProof(item.payId, item.nonce))
193		if nil != err {
194			return err
195		}
196	}
197	for _, item := range globalData.pendingPaidIssues {
198		err := writeBlock(f, taggedTransaction, item.txs)
199		if nil != err {
200			return err
201		}
202	}
203
204	// end the file
205	err = writeRecord(f, taggedEOF, []byte("EOF"))
206	if nil != err {
207		return err
208	}
209
210	log.Info("save completed")
211	return nil
212}
213
214func backupAssets(f *os.File) error {
215	allAssets := make(map[transactionrecord.AssetIdentifier]struct{})
216
217	// verified
218
219	for _, item := range globalData.verifiedFreeIssues {
220		for _, tx := range item.txs {
221			if tx, ok := tx.transaction.(*transactionrecord.BitmarkIssue); ok {
222				allAssets[tx.AssetId] = struct{}{}
223			}
224		}
225	}
226	for _, item := range globalData.verifiedPaidIssues {
227		for _, tx := range item.txs {
228			if tx, ok := tx.transaction.(*transactionrecord.BitmarkIssue); ok {
229				allAssets[tx.AssetId] = struct{}{}
230			}
231		}
232	}
233
234	// pending
235
236	for _, item := range globalData.pendingFreeIssues {
237		for _, tx := range item.txs {
238			if tx, ok := tx.transaction.(*transactionrecord.BitmarkIssue); ok {
239				allAssets[tx.AssetId] = struct{}{}
240			}
241		}
242	}
243	for _, item := range globalData.pendingPaidIssues {
244		for _, tx := range item.txs {
245			if tx, ok := tx.transaction.(*transactionrecord.BitmarkIssue); ok {
246				allAssets[tx.AssetId] = struct{}{}
247			}
248		}
249	}
250
251	// save pending assets
252backup_loop:
253	for assetId := range allAssets {
254		packedAsset := asset.Get(assetId)
255		if nil == packedAsset {
256			globalData.log.Errorf("asset [%s]: not in pending buffer", assetId)
257			continue backup_loop // skip the corresponding issue since asset is corrupt
258		}
259		err := writeRecord(f, taggedTransaction, packedAsset)
260		if nil != err {
261			return err
262		}
263	}
264	return nil
265}
266
267// pack up a proof record
268func packProof(payId pay.PayId, nonce PayNonce) []byte {
269
270	lp := len(payId)
271	ln := len(nonce)
272	packed := make([]byte, lp+ln)
273	copy(packed[:], payId[:])
274	copy(packed[lp:], nonce[:])
275
276	return packed
277}
278
279// write a tagged block record
280func writeBlock(f *os.File, tag tagType, txs []*transactionData) error {
281	buffer := make([]byte, 0, 65535)
282	for _, tx := range txs {
283		buffer = append(buffer, tx.packed...)
284	}
285	return writeRecord(f, tag, buffer)
286}
287
288// write a tagged record
289func writeRecord(f *os.File, tag tagType, packed []byte) error {
290
291	if len(packed) > 65535 {
292		globalData.log.Criticalf("write record packed length: %d > 65535", len(packed))
293		logger.Panicf("write record packed length: %d > 65535", len(packed))
294	}
295
296	_, err := f.Write([]byte{byte(tag)})
297	if nil != err {
298		return err
299	}
300
301	count := make([]byte, 2)
302	binary.BigEndian.PutUint16(count, uint16(len(packed)))
303	_, err = f.Write(count)
304	if nil != err {
305		return err
306	}
307	_, err = f.Write(packed)
308	return err
309}
310
311func readRecord(f *os.File) (tagType, transactionrecord.Packed, error) {
312
313	tag := make([]byte, 1)
314	n, err := f.Read(tag)
315	if nil != err {
316		return taggedEOF, []byte{}, err
317	}
318	if 1 != n {
319		return taggedEOF, []byte{}, fmt.Errorf("read record name: read: %d, expected: %d", n, 1)
320	}
321
322	countBuffer := make([]byte, 2)
323	n, err = f.Read(countBuffer)
324	if nil != err {
325		return taggedEOF, []byte{}, err
326	}
327	if 2 != n {
328		return taggedEOF, []byte{}, fmt.Errorf("read record key count: read: %d, expected: %d", n, 2)
329	}
330
331	count := int(binary.BigEndian.Uint16(countBuffer))
332
333	if count > 0 {
334		buffer := make([]byte, count)
335		n, err := f.Read(buffer)
336		if nil != err {
337			return taggedEOF, []byte{}, err
338		}
339		if count != n {
340			return taggedEOF, []byte{}, fmt.Errorf("read record read: %d, expected: %d", n, count)
341		}
342		return tagType(tag[0]), buffer, nil
343	}
344	return tagType(tag[0]), []byte{}, nil
345}
346