357 lines
9.5 KiB
Go
357 lines
9.5 KiB
Go
// Copyright 2017-2021 DERO Project. All rights reserved.
|
|
// Use of this source code in any form is governed by RESEARCH license.
|
|
// license can be found in the LICENSE file.
|
|
// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8
|
|
//
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
|
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
|
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
|
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
package blockchain
|
|
|
|
import "fmt"
|
|
import "sync"
|
|
import "math/big"
|
|
import "io/ioutil"
|
|
import "crypto/rand"
|
|
import "path/filepath"
|
|
import log "github.com/sirupsen/logrus"
|
|
import "github.com/deroproject/derohe/globals"
|
|
import "github.com/deroproject/derohe/block"
|
|
import "github.com/deroproject/derohe/crypto"
|
|
|
|
import "github.com/deroproject/graviton"
|
|
|
|
import "github.com/golang/groupcache/lru"
|
|
|
|
// note we are keeping the tree name small for disk savings, since they will be stored n times (atleast or archival nodes)
|
|
const BALANCE_TREE = "B"
|
|
|
|
// though these can be done within a single DB, these are separated for completely clarity purposes
|
|
type storage struct {
|
|
Balance_store *graviton.Store // stores most critical data, only history can be purged, its merkle tree is stored in the block
|
|
Block_tx_store storefs // stores blocks which can be discarded at any time(only past but keep recent history for rollback)
|
|
Topo_store storetopofs // stores topomapping which can only be discarded by punching holes in the start of the file
|
|
}
|
|
|
|
var store_logger *log.Entry
|
|
|
|
func (s *storage) Initialize(params map[string]interface{}) (err error) {
|
|
store_logger = globals.Logger.WithFields(log.Fields{"com": "STORE"})
|
|
current_path := filepath.Join(globals.GetDataDirectory())
|
|
|
|
if params["--simulator"] == true {
|
|
if s.Balance_store, err = graviton.NewMemStore(); err == nil {
|
|
|
|
current_path, err := ioutil.TempDir("", "dero_simulation")
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = s.Topo_store.Open(current_path); err == nil {
|
|
s.Block_tx_store.basedir = current_path
|
|
}
|
|
}
|
|
|
|
} else {
|
|
if s.Balance_store, err = graviton.NewDiskStore(filepath.Join(current_path, "balances")); err == nil {
|
|
if err = s.Topo_store.Open(current_path); err == nil {
|
|
s.Block_tx_store.basedir = current_path
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
store_logger.Fatalf("Cannot open store err %s", err)
|
|
}
|
|
store_logger.Infof("Initialized store at path %s", current_path)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *storage) IsBalancesIntialized() bool {
|
|
|
|
var err error
|
|
var buf [64]byte
|
|
var balancehash, random_hash [32]byte
|
|
|
|
balance_ss, _ := s.Balance_store.LoadSnapshot(0) // load most recent snapshot
|
|
balancetree, _ := balance_ss.GetTree(BALANCE_TREE)
|
|
|
|
// avoid hardcoding any hash
|
|
if balancehash, err = balancetree.Hash(); err == nil {
|
|
if _, err = rand.Read(buf[:]); err == nil {
|
|
random_tree, _ := balance_ss.GetTree(string(buf[:]))
|
|
if random_hash, err = random_tree.Hash(); err == nil {
|
|
if random_hash == balancehash {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
panic("database issues")
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (chain *Blockchain) StoreBlock(bl *block.Block) {
|
|
hash := bl.GetHash()
|
|
serialized_bytes := bl.Serialize() // we are storing the miner transactions within
|
|
// calculate cumulative difficulty at last block
|
|
|
|
if len(bl.Tips) == 0 { // genesis block has no parent
|
|
difficulty_of_current_block := new(big.Int).SetUint64(1) // this is never used, as genesis block is a sync block, only its cumulative difficulty is used
|
|
cumulative_difficulty := new(big.Int).SetUint64(1) // genesis block cumulative difficulty is 1
|
|
|
|
err := chain.Store.Block_tx_store.WriteBlock(hash, serialized_bytes, difficulty_of_current_block, cumulative_difficulty)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("error while writing block"))
|
|
}
|
|
} else {
|
|
|
|
difficulty_of_current_block := chain.Get_Difficulty_At_Tips(bl.Tips)
|
|
|
|
// NOTE: difficulty must be stored before cumulative difficulty calculation, since it is used while calculating Cdiff
|
|
|
|
base, base_height := chain.find_common_base(bl.Tips)
|
|
|
|
// this function requires block and difficulty to be pre-saveed
|
|
//work_map, cumulative_difficulty := chain.FindTipWorkScore( hash, base, base_height)
|
|
|
|
//this function is a copy of above function, however, it uses memory copies
|
|
work_map, cumulative_difficulty := chain.FindTipWorkScore_duringsave(bl, difficulty_of_current_block, base, base_height)
|
|
_ = work_map
|
|
|
|
err := chain.Store.Block_tx_store.WriteBlock(hash, serialized_bytes, difficulty_of_current_block, cumulative_difficulty)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("error while writing block"))
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// loads a block from disk, deserializes it
|
|
func (chain *Blockchain) Load_BL_FROM_ID(hash [32]byte) (*block.Block, error) {
|
|
var bl block.Block
|
|
|
|
if block_data, err := chain.Store.Block_tx_store.ReadBlock(hash); err == nil {
|
|
|
|
if err = bl.Deserialize(block_data); err != nil { // we should deserialize the block here
|
|
logger.Warnf("fError deserialiing block, block id %s len(data) %d data %x", hash[:], len(block_data), block_data)
|
|
return nil, err
|
|
}
|
|
|
|
return &bl, nil
|
|
} else {
|
|
return nil, err
|
|
}
|
|
|
|
/*else if xerrors.Is(err,graviton.ErrNotFound){
|
|
}*/
|
|
|
|
}
|
|
|
|
// confirm whether the block exist in the data
|
|
// this only confirms whether the block has been downloaded
|
|
// a separate check is required, whether the block is valid ( satifies PoW and other conditions)
|
|
// we will not add a block to store, until it satisfies PoW
|
|
func (chain *Blockchain) Block_Exists(h crypto.Hash) bool {
|
|
_, err := chain.Load_BL_FROM_ID(h)
|
|
if err == nil {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// This will get the biggest height of tip for hardfork version and other calculations
|
|
// get biggest height of parent, add 1
|
|
func (chain *Blockchain) Calculate_Height_At_Tips(tips []crypto.Hash) int64 {
|
|
height := int64(0)
|
|
if len(tips) == 0 { // genesis block has no parent
|
|
|
|
} else { // find the best height of past
|
|
for i := range tips {
|
|
bl, err := chain.Load_BL_FROM_ID(tips[i])
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
past_height := int64(bl.Height)
|
|
if height <= past_height {
|
|
height = past_height
|
|
}
|
|
}
|
|
height++
|
|
}
|
|
return height
|
|
|
|
}
|
|
|
|
func (chain *Blockchain) Load_Block_Timestamp(h crypto.Hash) uint64 {
|
|
bl, err := chain.Load_BL_FROM_ID(h)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return bl.Timestamp
|
|
}
|
|
|
|
func (chain *Blockchain) Load_Block_Height(h crypto.Hash) (height int64) {
|
|
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
height = -1
|
|
}
|
|
}()
|
|
|
|
bl, err := chain.Load_BL_FROM_ID(h)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
height = int64(bl.Height)
|
|
|
|
return
|
|
}
|
|
func (chain *Blockchain) Load_Height_for_BL_ID(h crypto.Hash) int64 {
|
|
return chain.Load_Block_Height(h)
|
|
}
|
|
|
|
var past_cache = lru.New(10240)
|
|
var past_cache_lock sync.Mutex
|
|
|
|
// all the immediate past of a block
|
|
func (chain *Blockchain) Get_Block_Past(hash crypto.Hash) (blocks []crypto.Hash) {
|
|
|
|
//fmt.Printf("loading tips for block %x\n", hash)
|
|
past_cache_lock.Lock()
|
|
defer past_cache_lock.Unlock()
|
|
|
|
if keysi, ok := past_cache.Get(hash); ok {
|
|
keys := keysi.([]crypto.Hash)
|
|
blocks = make([]crypto.Hash, len(keys))
|
|
for i := range keys {
|
|
copy(blocks[i][:], keys[i][:])
|
|
}
|
|
return
|
|
}
|
|
|
|
bl, err := chain.Load_BL_FROM_ID(hash)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
blocks = make([]crypto.Hash, 0, len(bl.Tips))
|
|
for i := range bl.Tips {
|
|
blocks = append(blocks, bl.Tips[i])
|
|
}
|
|
|
|
cache_copy := make([]crypto.Hash, len(blocks), len(blocks))
|
|
for i := range blocks {
|
|
cache_copy[i] = blocks[i]
|
|
}
|
|
|
|
//set in cache
|
|
past_cache.Add(hash, cache_copy)
|
|
|
|
return
|
|
}
|
|
|
|
func (chain *Blockchain) Load_Block_Difficulty(h crypto.Hash) *big.Int {
|
|
if diff, err := chain.Store.Block_tx_store.ReadBlockDifficulty(h); err != nil {
|
|
panic(err)
|
|
} else {
|
|
return diff
|
|
}
|
|
}
|
|
|
|
func (chain *Blockchain) Load_Block_Cumulative_Difficulty(h crypto.Hash) *big.Int {
|
|
if cdiff, err := chain.Store.Block_tx_store.ReadBlockCDifficulty(h); err != nil {
|
|
panic(err)
|
|
} else {
|
|
return cdiff
|
|
}
|
|
}
|
|
|
|
func (chain *Blockchain) Get_Top_ID() crypto.Hash {
|
|
var h crypto.Hash
|
|
|
|
topo_count := chain.Store.Topo_store.Count()
|
|
|
|
if topo_count == 0 {
|
|
return h
|
|
}
|
|
|
|
cindex := topo_count - 1
|
|
for {
|
|
r, err := chain.Store.Topo_store.Read(cindex)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
if !r.IsClean() {
|
|
return r.BLOCK_ID
|
|
}
|
|
|
|
if cindex == 0 {
|
|
return h
|
|
}
|
|
|
|
cindex--
|
|
}
|
|
}
|
|
|
|
// faster bootstrap
|
|
func (chain *Blockchain) Load_TOP_HEIGHT() int64 {
|
|
return chain.Load_Block_Height(chain.Get_Top_ID())
|
|
}
|
|
|
|
func (chain *Blockchain) Load_TOPO_HEIGHT() int64 {
|
|
|
|
topo_count := chain.Store.Topo_store.Count()
|
|
|
|
if topo_count == 0 {
|
|
return 0
|
|
}
|
|
|
|
cindex := topo_count - 1
|
|
for {
|
|
r, err := chain.Store.Topo_store.Read(cindex)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
if !r.IsClean() {
|
|
return cindex
|
|
}
|
|
|
|
if cindex == 0 {
|
|
return 0
|
|
}
|
|
|
|
cindex--
|
|
}
|
|
}
|
|
|
|
func (chain *Blockchain) Load_Block_Topological_order_at_index(index_pos int64) (hash crypto.Hash, err error) {
|
|
|
|
r, err := chain.Store.Topo_store.Read(index_pos)
|
|
if err != nil {
|
|
return hash, err
|
|
}
|
|
if !r.IsClean() {
|
|
return r.BLOCK_ID, nil
|
|
} else {
|
|
panic("cnnot query clean block id")
|
|
}
|
|
|
|
}
|