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 storage
7
8import (
9	"encoding/binary"
10	"fmt"
11	"reflect"
12	"sync"
13
14	"github.com/syndtr/goleveldb/leveldb"
15	ldb_opt "github.com/syndtr/goleveldb/leveldb/opt"
16
17	"github.com/bitmark-inc/bitmarkd/fault"
18	"github.com/bitmark-inc/logger"
19)
20
21// exported storage pools
22//
23// note all must be exported (i.e. initial capital) or initialisation will panic
24type pools struct {
25	Blocks            Handle `prefix:"B" pool:"PoolHandle"`
26	BlockHeaderHash   Handle `prefix:"2" pool:"PoolHandle"`
27	BlockOwnerPayment Handle `prefix:"H" pool:"PoolHandle"`
28	BlockOwnerTxIndex Handle `prefix:"I" pool:"PoolHandle"`
29	Assets            Handle `prefix:"A" pool:"PoolNB"`
30	Transactions      Handle `prefix:"T" pool:"PoolNB"`
31	OwnerNextCount    Handle `prefix:"N" pool:"PoolHandle"`
32	OwnerList         Handle `prefix:"L" pool:"PoolHandle"`
33	OwnerTxIndex      Handle `prefix:"D" pool:"PoolHandle"`
34	OwnerData         Handle `prefix:"O" pool:"PoolHandle"`
35	Shares            Handle `prefix:"F" pool:"PoolHandle"`
36	ShareQuantity     Handle `prefix:"Q" pool:"PoolHandle"`
37	TestData          Handle `prefix:"Z" pool:"PoolHandle"`
38}
39
40// Pool - the set of exported pools
41var Pool pools
42
43// for database version
44var (
45	versionKey    = []byte{0x00, 'V', 'E', 'R', 'S', 'I', 'O', 'N'}
46	needMigration = false
47)
48
49const (
50	currentBitmarksDBVersion = 0x1
51	bitmarksDBName           = "bitmarks"
52)
53
54// holds the database handle
55var poolData struct {
56	sync.RWMutex
57	bitmarksDB    *leveldb.DB
58	trx           Transaction
59	bitmarksBatch *leveldb.Batch
60	cache         Cache
61}
62
63var PaymentStorage struct {
64	Btc P2PStorage
65	Ltc P2PStorage
66}
67
68// pool access modes
69const (
70	ReadOnly  = true
71	ReadWrite = false
72)
73
74// Initialise - open up the database connection
75//
76// this must be called before any pool is accessed
77func Initialise(dbPrefix string, readOnly bool) error {
78	poolData.Lock()
79	defer poolData.Unlock()
80
81	ok := false
82
83	if nil != poolData.bitmarksDB {
84		return fault.AlreadyInitialised
85	}
86
87	defer func() {
88		if !ok {
89			dbClose()
90		}
91	}()
92
93	bitmarksDBVersion, err := openBitmarkdDB(dbPrefix, readOnly)
94	if err != nil {
95		return err
96	}
97
98	err = validateBitmarksDBVersion(bitmarksDBVersion, readOnly)
99	if err != nil {
100		return err
101	}
102
103	err = setupBitmarksDB()
104	if err != nil {
105		return err
106	}
107
108	// payment dbPrefix
109	btcDatabase := dbPrefix + "-btc.leveldb"
110	ltcDatabase := dbPrefix + "-ltc.leveldb"
111
112	db, _, err := getDB(btcDatabase, readOnly)
113	if nil != err {
114		return err
115	}
116	PaymentStorage.Btc = NewLevelDBPaymentStore(db)
117
118	db, _, err = getDB(ltcDatabase, readOnly)
119	if nil != err {
120		return err
121	}
122	PaymentStorage.Ltc = NewLevelDBPaymentStore(db)
123
124	ok = true // prevent db close
125	return nil
126}
127
128func setupBitmarksDB() error {
129	bitmarksDBAccess := setupBitmarksDBTransaction()
130
131	err := setupPools(bitmarksDBAccess)
132	if err != nil {
133		return err
134	}
135
136	return nil
137}
138
139func setupBitmarksDBTransaction() Access {
140	poolData.bitmarksBatch = new(leveldb.Batch)
141	poolData.cache = newCache()
142	bitmarksDBAccess := newDA(poolData.bitmarksDB, poolData.bitmarksBatch, poolData.cache)
143	poolData.trx = newTransaction([]Access{bitmarksDBAccess})
144
145	return bitmarksDBAccess
146}
147
148func setupPools(bitmarksDBAccess Access) error {
149	// this will be a struct type
150	poolType := reflect.TypeOf(Pool)
151	// get write access by using pointer + Elem()
152	poolValue := reflect.ValueOf(&Pool).Elem()
153
154	// scan each field
155	for i := 0; i < poolType.NumField(); i += 1 {
156		fieldInfo := poolType.Field(i)
157		prefixTag := fieldInfo.Tag.Get("prefix")
158		poolTag := fieldInfo.Tag.Get("pool")
159
160		if 1 != len(prefixTag) || 0 == len(poolTag) {
161			return fmt.Errorf("pool: %v has invalid prefix: %q, poolTag: %s", fieldInfo, prefixTag, poolTag)
162		}
163
164		prefix := prefixTag[0]
165		limit := []byte(nil)
166		if prefix < 255 {
167			limit = []byte{prefix + 1}
168		}
169
170		p := &PoolHandle{
171			prefix:     prefix,
172			limit:      limit,
173			dataAccess: bitmarksDBAccess,
174		}
175
176		if poolTag == "PoolNB" {
177			pNB := &PoolNB{
178				pool: p,
179			}
180			newNB := reflect.ValueOf(pNB)
181			poolValue.Field(i).Set(newNB)
182		} else {
183			newPool := reflect.ValueOf(p)
184			poolValue.Field(i).Set(newPool)
185		}
186	}
187	return nil
188}
189
190func openBitmarkdDB(dbPrefix string, readOnly bool) (int, error) {
191	name := fmt.Sprintf("%s-%s.leveldb", dbPrefix, bitmarksDBName)
192
193	db, version, err := getDB(name, readOnly)
194	if nil != err {
195		return 0, err
196	}
197	poolData.bitmarksDB = db
198
199	return version, err
200}
201
202func validateBitmarksDBVersion(bitmarksDBVersion int, readOnly bool) error {
203	// ensure no database downgrade
204	if bitmarksDBVersion > currentBitmarksDBVersion {
205		msg := fmt.Sprintf("bitmarksDB database version: %d > current version: %d", bitmarksDBVersion, currentBitmarksDBVersion)
206
207		logger.Critical(msg)
208		return nil
209	}
210
211	// prevent readOnly from modifying the database
212	if readOnly && bitmarksDBVersion != currentBitmarksDBVersion {
213		msg := fmt.Sprintf("database inconsistent: bitmarksDB: %d  current: %d ", bitmarksDBVersion, currentBitmarksDBVersion)
214
215		logger.Critical(msg)
216		return nil
217	}
218
219	if 0 < bitmarksDBVersion && bitmarksDBVersion < currentBitmarksDBVersion {
220		needMigration = true
221	} else if 0 == bitmarksDBVersion {
222		// database was empty so tag as current version
223		err := putVersion(poolData.bitmarksDB, currentBitmarksDBVersion)
224		if err != nil {
225			return nil
226		}
227	}
228
229	return nil
230}
231
232func dbClose() {
233	if nil != poolData.bitmarksDB {
234		if err := poolData.bitmarksDB.Close(); nil != err {
235			logger.Criticalf("close bitmarkd db with error: %s", err)
236		}
237		poolData.bitmarksDB = nil
238	}
239
240	if nil != PaymentStorage.Btc {
241		if err := PaymentStorage.Btc.Close(); nil != err {
242			logger.Criticalf("close btc db with error: %s", err)
243		}
244		PaymentStorage.Btc = nil
245	}
246
247	if nil != PaymentStorage.Ltc {
248		if err := PaymentStorage.Ltc.Close(); nil != err {
249			logger.Criticalf("close btc db with error: %s", err)
250		}
251		PaymentStorage.Ltc = nil
252	}
253}
254
255// Finalise - close the database connection
256func Finalise() {
257	poolData.Lock()
258	dbClose()
259	poolData.Unlock()
260}
261
262// return:
263//   database handle
264//   version number
265func getDB(name string, readOnly bool) (*leveldb.DB, int, error) {
266	opt := &ldb_opt.Options{
267		ErrorIfExist:   false,
268		ErrorIfMissing: readOnly,
269		ReadOnly:       readOnly,
270	}
271
272	db, err := leveldb.OpenFile(name, opt)
273	if nil != err {
274		return nil, 0, err
275	}
276
277	versionValue, err := db.Get(versionKey, nil)
278	if leveldb.ErrNotFound == err {
279		return db, 0, nil
280	} else if nil != err {
281		e := db.Close()
282		if nil != e {
283			logger.Criticalf("close %s database with error: %s", name, e)
284		}
285		return nil, 0, err
286	}
287
288	if 4 != len(versionValue) {
289		e := db.Close()
290		if nil != e {
291			logger.Criticalf("close %s database with error: %s", name, e)
292		}
293		return nil, 0, fmt.Errorf("incompatible database version length: expected: %d  actual: %d", 4, len(versionValue))
294	}
295
296	version := int(binary.BigEndian.Uint32(versionValue))
297	return db, version, nil
298}
299
300func putVersion(db *leveldb.DB, version int) error {
301	currentVersion := make([]byte, 4)
302	binary.BigEndian.PutUint32(currentVersion, uint32(version))
303
304	return db.Put(versionKey, currentVersion, nil)
305}
306
307// IsMigrationNeed - check if bitmarks database needs migration
308func IsMigrationNeed() bool {
309	return needMigration
310}
311
312func NewDBTransaction() (Transaction, error) {
313	err := poolData.trx.Begin()
314	if nil != err {
315		return nil, err
316	}
317	return poolData.trx, nil
318}
319