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