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