2230 lines
74 KiB
Go
2230 lines
74 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
|
|
|
|
// This file runs the core consensus protocol
|
|
// please think before randomly editing for after effects
|
|
// We must not call any packages that can call panic
|
|
// NO Panics or FATALs please
|
|
|
|
/*
|
|
import "os"
|
|
|
|
|
|
*/
|
|
import "fmt"
|
|
import "sync"
|
|
import "time"
|
|
import "runtime/debug"
|
|
import "bytes"
|
|
import "sort"
|
|
import "math/big"
|
|
|
|
//import "runtime"
|
|
//import "bufio"
|
|
import "golang.org/x/crypto/sha3"
|
|
import "github.com/romana/rlog"
|
|
|
|
import "sync/atomic"
|
|
|
|
import log "github.com/sirupsen/logrus"
|
|
|
|
import "github.com/golang/groupcache/lru"
|
|
import hashicorp_lru "github.com/hashicorp/golang-lru"
|
|
|
|
import "github.com/deroproject/derohe/config"
|
|
import "github.com/deroproject/derohe/cryptography/crypto"
|
|
import "github.com/deroproject/derohe/errormsg"
|
|
import "github.com/prometheus/client_golang/prometheus"
|
|
|
|
//import "github.com/deroproject/derosuite/address"
|
|
import "github.com/deroproject/derohe/block"
|
|
import "github.com/deroproject/derohe/globals"
|
|
import "github.com/deroproject/derohe/transaction"
|
|
import "github.com/deroproject/derohe/blockchain/mempool"
|
|
import "github.com/deroproject/derohe/blockchain/regpool"
|
|
import "github.com/deroproject/derohe/rpc"
|
|
|
|
/*
|
|
import "github.com/deroproject/derosuite/emission"
|
|
|
|
import "github.com/deroproject/derosuite/storage"
|
|
import "github.com/deroproject/derosuite/crypto/ringct"
|
|
|
|
|
|
import "github.com/deroproject/derosuite/checkpoints"
|
|
import "github.com/deroproject/derosuite/metrics"
|
|
|
|
import "github.com/deroproject/derosuite/blockchain/inputmaturity"
|
|
*/
|
|
|
|
import "github.com/deroproject/graviton"
|
|
|
|
// all components requiring access to blockchain must use , this struct to communicate
|
|
// this structure must be update while mutex
|
|
type Blockchain struct {
|
|
Store storage // interface to storage layer
|
|
Height int64 // chain height is always 1 more than block
|
|
height_seen int64 // height seen on peers
|
|
Top_ID crypto.Hash // id of the top block
|
|
Pruned int64 // until where the chain has been pruned
|
|
|
|
Tips map[crypto.Hash]crypto.Hash // current tips
|
|
|
|
dag_unsettled map[crypto.Hash]bool // current unsettled dag
|
|
dag_past_unsettled_cache *lru.Cache
|
|
dag_future_unsettled_cache *lru.Cache
|
|
lrucache_workscore *lru.Cache
|
|
lrucache_fullorder *lru.Cache // keeps full order for tips upto a certain height
|
|
|
|
MINING_BLOCK bool // used to pause mining
|
|
|
|
Difficulty uint64 // current cumulative difficulty
|
|
Median_Block_Size uint64 // current median block size
|
|
Mempool *mempool.Mempool // normal tx pool
|
|
Regpool *regpool.Regpool // registration pool
|
|
Exit_Event chan bool // blockchain is shutting down and we must quit ASAP
|
|
|
|
Top_Block_Median_Size uint64 // median block size of current top block
|
|
Top_Block_Base_Reward uint64 // top block base reward
|
|
|
|
checkpints_disabled bool // are checkpoints disabled
|
|
simulator bool // is simulator mode
|
|
|
|
P2P_Block_Relayer func(*block.Complete_Block, uint64) // tell p2p to broadcast any block this daemon hash found
|
|
|
|
RPC_NotifyNewBlock *sync.Cond // used to notify rpc that a new block has been found
|
|
RPC_NotifyHeightChanged *sync.Cond // used to notify rpc that chain height has changed due to addition of block
|
|
|
|
Dev_Address_Bytes []byte // used to fund reward every block
|
|
|
|
Sync bool // whether the sync is active, used while bootstrapping
|
|
|
|
sync.RWMutex
|
|
}
|
|
|
|
var logger *log.Entry
|
|
|
|
//var Exit_Event = make(chan bool) // causes all threads to exit
|
|
|
|
// All blockchain activity is store in a single
|
|
|
|
/* do initialisation , setup storage, put genesis block and chain in store
|
|
This is the first component to get up
|
|
Global parameters are picked up from the config package
|
|
*/
|
|
|
|
func Blockchain_Start(params map[string]interface{}) (*Blockchain, error) {
|
|
|
|
var err error
|
|
var chain Blockchain
|
|
|
|
_ = err
|
|
|
|
logger = globals.Logger.WithFields(log.Fields{"com": "CORE"})
|
|
logger.Infof("Initialising blockchain")
|
|
//init_static_checkpoints() // init some hard coded checkpoints
|
|
//checkpoints.LoadCheckPoints(logger) // load checkpoints from file if provided
|
|
|
|
chain.Store.Initialize(params)
|
|
chain.Tips = map[crypto.Hash]crypto.Hash{}
|
|
|
|
//chain.Tips = map[crypto.Hash]crypto.Hash{} // initialize Tips map
|
|
chain.lrucache_workscore = lru.New(8191) // temporary cache for work caclculation
|
|
chain.lrucache_fullorder = lru.New(20480) // temporary cache for fullorder caclculation
|
|
|
|
if globals.Arguments["--disable-checkpoints"] != nil {
|
|
chain.checkpints_disabled = globals.Arguments["--disable-checkpoints"].(bool)
|
|
}
|
|
|
|
if params["--simulator"] == true {
|
|
chain.simulator = true // enable simulator mode, this will set hard coded difficulty to 1
|
|
}
|
|
|
|
chain.Exit_Event = make(chan bool) // init exit channel
|
|
|
|
// init mempool before chain starts
|
|
chain.Mempool, err = mempool.Init_Mempool(params)
|
|
chain.Regpool, err = regpool.Init_Regpool(params)
|
|
|
|
chain.RPC_NotifyNewBlock = sync.NewCond(&sync.Mutex{}) // used by dero daemon to notify all websockets that new block has arrived
|
|
chain.RPC_NotifyHeightChanged = sync.NewCond(&sync.Mutex{}) // used by dero daemon to notify all websockets that chain height has changed
|
|
|
|
if !chain.Store.IsBalancesIntialized() {
|
|
logger.Debugf("Genesis block not in store, add it now")
|
|
var complete_block block.Complete_Block
|
|
bl := Generate_Genesis_Block()
|
|
complete_block.Bl = &bl
|
|
|
|
if err, ok := chain.Add_Complete_Block(&complete_block); !ok {
|
|
logger.Fatalf("Failed to add genesis block, we can no longer continue. err %s", err)
|
|
}
|
|
}
|
|
|
|
/*
|
|
|
|
// genesis block not in chain, add it to chain, together with its miner tx
|
|
// make sure genesis is in the store
|
|
|
|
//if !chain.Block_Exists(globals.Config.Genesis_Block_Hash) {
|
|
if !chain.Block_Exists(nil, bl.GetHash()) {
|
|
//chain.Store_TOP_ID(globals.Config.Genesis_Block_Hash) // store top id , exception of genesis block
|
|
|
|
|
|
|
|
|
|
logger.Infof("Added block successfully")
|
|
|
|
//chain.store_Block_Settled(bl.GetHash(),true) // genesis block is always settled
|
|
|
|
|
|
|
|
bl_current_hash := bl.GetHash()
|
|
// store total reward
|
|
//dbtx.StoreUint64(BLOCKCHAIN_UNIVERSE, GALAXY_BLOCK, bl_current_hash[:], PLANET_MINERTX_REWARD, bl.Miner_TX.Vout[0].Amount)
|
|
|
|
// store base reward
|
|
//dbtx.StoreUint64(BLOCKCHAIN_UNIVERSE, GALAXY_BLOCK, bl_current_hash[:], PLANET_BASEREWARD, bl.Miner_TX.Vout[0].Amount)
|
|
|
|
// store total generated coins
|
|
// this is hardcoded at initial chain import, keeping original emission schedule
|
|
if globals.IsMainnet(){
|
|
//dbtx.StoreUint64(BLOCKCHAIN_UNIVERSE, GALAXY_BLOCK, bl_current_hash[:], PLANET_ALREADY_GENERATED_COINS, config.MAINNET_HARDFORK_1_TOTAL_SUPPLY)
|
|
}else{
|
|
//dbtx.StoreUint64(BLOCKCHAIN_UNIVERSE, GALAXY_BLOCK, bl_current_hash[:], PLANET_ALREADY_GENERATED_COINS, config.TESTNET_HARDFORK_1_TOTAL_SUPPLY)
|
|
}
|
|
|
|
|
|
//chain.Store_Block_Topological_order(dbtx, bl.GetHash(), 0) // genesis block is the lowest
|
|
//chain.Store_TOPO_HEIGHT(dbtx, 0) //
|
|
//chain.Store_TOP_HEIGHT(dbtx, 0)
|
|
|
|
//chain.store_TIPS(dbtx, []crypto.Hash{bl.GetHash()})
|
|
|
|
//dbtx.Commit()
|
|
|
|
}
|
|
*/
|
|
|
|
//fmt.Printf("Genesis Block should be present at height 0\n")
|
|
//blocks := chain.Get_Blocks_At_Height(0)
|
|
// fmt.Printf("blocks at height 0 %+v\n", blocks)
|
|
|
|
// fmt.Printf("Past of genesis %+v\n", chain.Get_Block_Past(bl.GetHash()))
|
|
// fmt.Printf("Future of genesis %+v\n", chain.Get_Block_Future(bl.GetHash()))
|
|
|
|
// fmt.Printf("Future of zero block %+v\n", chain.Get_Block_Future(ZERO_HASH))
|
|
|
|
// hard forks must be initialized asap
|
|
init_hard_forks(params)
|
|
|
|
// parse dev address once and for all
|
|
if addr, err := rpc.NewAddress(globals.Config.Dev_Address); err != nil {
|
|
logger.Fatalf("Could not parse dev address, err:%s", err)
|
|
} else {
|
|
chain.Dev_Address_Bytes = addr.PublicKey.EncodeCompressed()
|
|
}
|
|
|
|
// load the chain from the disk
|
|
chain.Initialise_Chain_From_DB()
|
|
|
|
chain.Sync = true
|
|
//if globals.Arguments["--fullnode"] != nil {
|
|
if chain.Get_Height() <= 1 {
|
|
chain.Sync = false
|
|
if globals.Arguments["--fullnode"].(bool) {
|
|
chain.Sync = globals.Arguments["--fullnode"].(bool)
|
|
}
|
|
}
|
|
//}
|
|
|
|
// logger.Fatalf("Testing complete quitting")
|
|
|
|
go clean_up_valid_cache() // clean up valid cache
|
|
|
|
/* txlist := chain.Mempool.Mempool_List_TX()
|
|
for i := range txlist {
|
|
// if fmt.Sprintf("%s", txlist[i]) == "0fe0e7270ba911956e91d9ea099e4d12aa1bce2473d4064e239731bc37acfd86"{
|
|
logger.Infof("Verifying tx %s %+v", txlist[i], chain.Verify_Transaction_NonCoinbase(chain.Mempool.Mempool_Get_TX(txlist[i])))
|
|
|
|
//}
|
|
//p2p.Broadcast_Tx(chain.Mempool.Mempool_Get_TX(txlist[i]))
|
|
}
|
|
|
|
|
|
if chain.checkpints_disabled {
|
|
logger.Infof("Internal Checkpoints are disabled")
|
|
} else {
|
|
logger.Debugf("Internal Checkpoints are enabled")
|
|
}
|
|
|
|
_ = err
|
|
|
|
*/
|
|
|
|
/*
|
|
// register the metrics with the metrics registry
|
|
metrics.Registry.MustRegister(blockchain_tx_counter)
|
|
metrics.Registry.MustRegister(mempool_tx_counter)
|
|
metrics.Registry.MustRegister(mempool_tx_count)
|
|
metrics.Registry.MustRegister(block_size)
|
|
metrics.Registry.MustRegister(transaction_size)
|
|
metrics.Registry.MustRegister(block_tx_count)
|
|
metrics.Registry.MustRegister(block_processing_time)
|
|
*/
|
|
atomic.AddUint32(&globals.Subsystem_Active, 1) // increment subsystem
|
|
|
|
return &chain, nil
|
|
}
|
|
|
|
// this is the only entrypoint for new / old blocks even for genesis block
|
|
// this will add the entire block atomically to the chain
|
|
// this is the only function which can add blocks to the chain
|
|
// this is exported, so ii can be fed new blocks by p2p layer
|
|
// genesis block is no different
|
|
// TODO: we should stop mining while adding the new block
|
|
func (chain *Blockchain) Add_Complete_Block(cbl *block.Complete_Block) (err error, result bool) {
|
|
|
|
var block_hash crypto.Hash
|
|
chain.Lock()
|
|
defer chain.Unlock()
|
|
result = false
|
|
height_changed := false
|
|
|
|
chain.MINING_BLOCK = true
|
|
|
|
processing_start := time.Now()
|
|
|
|
//old_top := chain.Load_TOP_ID() // store top as it may change
|
|
defer func() {
|
|
|
|
// safety so if anything wrong happens, verification fails
|
|
if r := recover(); r != nil {
|
|
logger.Warnf("Recovered while adding new block, Stack trace below block_hash %s", block_hash)
|
|
logger.Warnf("Stack trace \n%s", debug.Stack())
|
|
result = false
|
|
err = errormsg.ErrPanic
|
|
}
|
|
|
|
chain.MINING_BLOCK = false
|
|
|
|
if result == true { // block was successfully added, commit it atomically
|
|
rlog.Infof("Block successfully acceppted by chain %s", block_hash)
|
|
|
|
// gracefully try to instrument
|
|
func() {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
rlog.Warnf("Recovered while instrumenting")
|
|
rlog.Warnf("Stack trace \n%s", debug.Stack())
|
|
|
|
}
|
|
}()
|
|
blockchain_tx_counter.Add(float64(len(cbl.Bl.Tx_hashes)))
|
|
block_tx_count.Observe(float64(len(cbl.Bl.Tx_hashes)))
|
|
block_processing_time.Observe(float64(time.Now().Sub(processing_start).Round(time.Millisecond) / 1000000))
|
|
|
|
// tracks counters for tx_size
|
|
|
|
{
|
|
complete_block_size := 0
|
|
for i := 0; i < len(cbl.Txs); i++ {
|
|
tx_size := len(cbl.Txs[i].Serialize())
|
|
complete_block_size += tx_size
|
|
transaction_size.Observe(float64(tx_size))
|
|
}
|
|
block_size.Observe(float64(complete_block_size))
|
|
}
|
|
}()
|
|
|
|
// notify everyone who needs to know that a new block is in the chain
|
|
chain.RPC_NotifyNewBlock.L.Lock()
|
|
chain.RPC_NotifyNewBlock.Broadcast()
|
|
chain.RPC_NotifyNewBlock.L.Unlock()
|
|
|
|
if height_changed {
|
|
chain.RPC_NotifyHeightChanged.L.Lock()
|
|
chain.RPC_NotifyHeightChanged.Broadcast()
|
|
chain.RPC_NotifyHeightChanged.L.Unlock()
|
|
}
|
|
|
|
//dbtx.Sync() // sync the DB to disk after every execution of this function
|
|
|
|
//if old_top != chain.Load_TOP_ID() { // if top has changed, discard mining templates and start afresh
|
|
// TODO discard mining templates or something else, if top chnages requires some action
|
|
|
|
//}
|
|
} else {
|
|
//dbtx.Rollback() // if block could not be added, rollback all changes to previous block
|
|
rlog.Infof("Block rejected by chain %s err %s", block_hash, err)
|
|
}
|
|
}()
|
|
|
|
bl := cbl.Bl // small pointer to block
|
|
|
|
// first of all lets do some quick checks
|
|
// before doing extensive checks
|
|
result = false
|
|
|
|
block_hash = bl.GetHash()
|
|
block_logger := logger.WithFields(log.Fields{"blid": block_hash})
|
|
|
|
// check if block already exist skip it
|
|
if chain.Is_Block_Topological_order(block_hash) {
|
|
block_logger.Debugf("block already in chain skipping it ")
|
|
return errormsg.ErrAlreadyExists, false
|
|
}
|
|
|
|
for k := range chain.Tips {
|
|
if block_hash == k {
|
|
block_logger.Debugf("block already in chain skipping it ")
|
|
return errormsg.ErrAlreadyExists, false
|
|
}
|
|
}
|
|
|
|
// only 3 tips allowed in block
|
|
if len(bl.Tips) >= 4 {
|
|
rlog.Warnf("More than 3 tips present in block %s rejecting", block_hash)
|
|
return errormsg.ErrPastMissing, false
|
|
}
|
|
|
|
// check whether the tips exist in our chain, if not reject
|
|
for i := range bl.Tips { //if !chain.Is_Block_Topological_order(bl.Tips[i]) {
|
|
if !chain.Block_Exists(bl.Tips[i]) { // alt-tips might not have a topo order at this point, so make sure they exist on disk
|
|
rlog.Warnf("Tip %s is NOT present in chain current block %s, skipping it till we get a parent", bl.Tips[i], block_hash)
|
|
return errormsg.ErrPastMissing, false
|
|
}
|
|
}
|
|
|
|
block_height := chain.Calculate_Height_At_Tips(bl.Tips)
|
|
|
|
if block_height == 0 && int64(bl.Height) == block_height && len(bl.Tips) != 0 {
|
|
block_logger.Warnf("Genesis block cannot have tips. len of tips(%d)", len(bl.Tips))
|
|
return errormsg.ErrInvalidBlock, false
|
|
}
|
|
|
|
if len(bl.Tips) >= 1 && bl.Height == 0 {
|
|
block_logger.Warnf("Genesis block can only be at height 0. len of tips(%d)", len(bl.Tips))
|
|
return errormsg.ErrInvalidBlock, false
|
|
}
|
|
|
|
if block_height != 0 && block_height < chain.Get_Stable_Height() {
|
|
rlog.Warnf("Block %s rejected since it is stale stable height %d block height %d", bl.GetHash(), chain.Get_Stable_Height(), block_height)
|
|
return errormsg.ErrInvalidBlock, false
|
|
}
|
|
|
|
// use checksum to quick jump
|
|
/*
|
|
if chain.checkpints_disabled == false && checkpoints.IsCheckSumKnown(chain.BlockCheckSum(cbl)) {
|
|
rlog.Debugf("Skipping Deep Checks for block %s ", block_hash)
|
|
goto skip_checks
|
|
} else {
|
|
rlog.Debugf("Deep Checks for block %s ", block_hash)
|
|
}
|
|
*/
|
|
|
|
// make sure time is NOT into future, we do not have any margin here
|
|
// some OS have trouble syncing with more than 1 sec granularity
|
|
// if clock diff is more than 1 secs, reject the block
|
|
if bl.Timestamp > (uint64(time.Now().UTC().Unix())) {
|
|
block_logger.Warnf("Rejecting Block, timestamp is too much into future, make sure that system clock is correct")
|
|
return errormsg.ErrFutureTimestamp, false
|
|
}
|
|
|
|
// verify that the clock is not being run in reverse
|
|
// the block timestamp cannot be less than any of the parents
|
|
for i := range bl.Tips {
|
|
if chain.Load_Block_Timestamp(bl.Tips[i]) > bl.Timestamp {
|
|
block_logger.Warnf("Block timestamp is less than its parent, rejecting block %x ", bl.Serialize())
|
|
return errormsg.ErrInvalidTimestamp, false
|
|
}
|
|
}
|
|
|
|
//logger.Infof("current version %d height %d", chain.Get_Current_Version_at_Height( 2500), chain.Calculate_Height_At_Tips(dbtx, bl.Tips))
|
|
// check whether the major version ( hard fork) is valid
|
|
if !chain.Check_Block_Version(bl) {
|
|
block_logger.Warnf("Rejecting !! Block has invalid fork version actual %d expected %d", bl.Major_Version, chain.Get_Current_Version_at_Height(chain.Calculate_Height_At_Tips(bl.Tips)))
|
|
return errormsg.ErrInvalidBlock, false
|
|
}
|
|
|
|
// verify whether the tips are unreachable from one another
|
|
if !chain.VerifyNonReachability(bl) {
|
|
block_logger.Warnf("Rejecting !! Block has invalid reachability")
|
|
return errormsg.ErrInvalidBlock, false
|
|
|
|
}
|
|
|
|
// if the block is referencing any past tip too distant into main chain discard now
|
|
// TODO FIXME this need to computed
|
|
for i := range bl.Tips {
|
|
rusty_tip_base_distance := chain.calculate_mainchain_distance(bl.Tips[i])
|
|
|
|
// tips of deviation >= 8 will rejected
|
|
if (int64(chain.Get_Height()) - rusty_tip_base_distance) >= config.STABLE_LIMIT {
|
|
block_logger.Warnf("Rusty TIP mined by ROGUE miner discarding block %s best height %d deviation %d rusty_tip %d", bl.Tips[i], chain.Get_Height(), (int64(chain.Get_Height()) - rusty_tip_base_distance), rusty_tip_base_distance)
|
|
return errormsg.ErrInvalidBlock, false
|
|
}
|
|
}
|
|
|
|
// verify difficulty of tips provided
|
|
if len(bl.Tips) > 1 {
|
|
best_tip := chain.find_best_tip_cumulative_difficulty(bl.Tips)
|
|
for i := range bl.Tips {
|
|
if best_tip != bl.Tips[i] {
|
|
if !chain.validate_tips(best_tip, bl.Tips[i]) { // reference is first
|
|
block_logger.Warnf("Rusty tip mined by ROGUE miner, discarding block")
|
|
return errormsg.ErrInvalidBlock, false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// check whether the block crosses the size limit
|
|
// block size is calculate by adding all the txs
|
|
// block header/miner tx is excluded, only tx size if calculated
|
|
{
|
|
block_size := 0
|
|
for i := 0; i < len(cbl.Txs); i++ {
|
|
block_size += len(cbl.Txs[i].Serialize())
|
|
if uint64(block_size) >= config.STARGATE_HE_MAX_BLOCK_SIZE {
|
|
block_logger.Warnf("Block is bigger than max permitted, Rejecting it Actual %d MAX %d ", block_size, config.STARGATE_HE_MAX_BLOCK_SIZE)
|
|
return errormsg.ErrInvalidSize, false
|
|
}
|
|
}
|
|
}
|
|
|
|
//logger.Infof("pow hash %s height %d", bl.GetPoWHash(), block_height)
|
|
|
|
// Verify Blocks Proof-Of-Work
|
|
// check if the PoW is satisfied
|
|
if !chain.VerifyPoW(bl) { // if invalid Pow, reject the bloc
|
|
block_logger.Warnf("Block has invalid PoW, rejecting it %x", bl.Serialize())
|
|
return errormsg.ErrInvalidPoW, false
|
|
}
|
|
|
|
{ // miner TX checks are here
|
|
if bl.Height == 0 && !bl.Miner_TX.IsPremine() { // genesis block contain premine tx a
|
|
block_logger.Warnf("Miner tx failed verification for genesis rejecting ")
|
|
return errormsg.ErrInvalidBlock, false
|
|
}
|
|
|
|
if bl.Height != 0 && !bl.Miner_TX.IsCoinbase() { // all blocks except genesis block contain coinbase TX
|
|
block_logger.Warnf("Miner tx failed it is not coinbase ")
|
|
return errormsg.ErrInvalidBlock, false
|
|
}
|
|
|
|
// always check whether the coin base tx is okay
|
|
if bl.Height != 0 {
|
|
if err = chain.Verify_Transaction_Coinbase(cbl, &bl.Miner_TX); err != nil { // if miner address is not registered give error
|
|
block_logger.Warnf("Error verifying coinbase tx, err :'%s'", err)
|
|
return err, false
|
|
}
|
|
}
|
|
|
|
// TODO we need to verify address whether they are valid points on curve or not
|
|
}
|
|
|
|
// now we need to verify each and every tx in detail
|
|
// we need to verify each and every tx contained in the block, sanity check everything
|
|
// first of all check, whether all the tx contained in the block, match their hashes
|
|
{
|
|
if len(bl.Tx_hashes) != len(cbl.Txs) {
|
|
block_logger.Warnf("Block says it has %d txs , however complete block contained %d txs", len(bl.Tx_hashes), len(cbl.Txs))
|
|
return errormsg.ErrInvalidBlock, false
|
|
}
|
|
|
|
// first check whether the complete block contains any diplicate hashes
|
|
tx_checklist := map[crypto.Hash]bool{}
|
|
for i := 0; i < len(bl.Tx_hashes); i++ {
|
|
tx_checklist[bl.Tx_hashes[i]] = true
|
|
}
|
|
|
|
if len(tx_checklist) != len(bl.Tx_hashes) { // block has duplicate tx, reject
|
|
block_logger.Warnf("Block has %d duplicate txs, reject it", len(bl.Tx_hashes)-len(tx_checklist))
|
|
return errormsg.ErrInvalidBlock, false
|
|
|
|
}
|
|
// now lets loop through complete block, matching each tx
|
|
// detecting any duplicates using txid hash
|
|
for i := 0; i < len(cbl.Txs); i++ {
|
|
tx_hash := cbl.Txs[i].GetHash()
|
|
if _, ok := tx_checklist[tx_hash]; !ok {
|
|
// tx is NOT found in map, RED alert reject the block
|
|
block_logger.Warnf("Block says it has tx %s, but complete block does not have it", tx_hash)
|
|
return errormsg.ErrInvalidBlock, false
|
|
}
|
|
}
|
|
}
|
|
|
|
// another check, whether the block contains any duplicate registration within the block
|
|
// block wide duplicate input detector
|
|
{
|
|
nonce_map := map[string]bool{}
|
|
for i := 0; i < len(cbl.Txs); i++ {
|
|
|
|
if cbl.Txs[i].TransactionType == transaction.REGISTRATION {
|
|
if _, ok := nonce_map[string(cbl.Txs[i].MinerAddress[:])]; ok {
|
|
block_logger.Warnf("Double Registration within block %s", cbl.Txs[i].GetHash())
|
|
return errormsg.ErrTXDoubleSpend, false
|
|
}
|
|
nonce_map[string(cbl.Txs[i].MinerAddress[:])] = true
|
|
}
|
|
}
|
|
}
|
|
|
|
// another check, whether the tx is build with the latest snapshot of balance tree
|
|
{
|
|
for i := 0; i < len(cbl.Txs); i++ {
|
|
if cbl.Txs[i].TransactionType == transaction.NORMAL || cbl.Txs[i].TransactionType == transaction.BURN_TX || cbl.Txs[i].TransactionType == transaction.SC_TX {
|
|
if cbl.Txs[i].Height+1 != cbl.Bl.Height {
|
|
block_logger.Warnf("invalid tx mined %s", cbl.Txs[i].GetHash())
|
|
return errormsg.ErrTXDoubleSpend, false
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
// another check, whether the tx contains any duplicate nonces within the block
|
|
// block wide duplicate input detector
|
|
{
|
|
nonce_map := map[crypto.Hash]bool{}
|
|
for i := 0; i < len(cbl.Txs); i++ {
|
|
|
|
if cbl.Txs[i].TransactionType == transaction.NORMAL || cbl.Txs[i].TransactionType == transaction.BURN_TX || cbl.Txs[i].TransactionType == transaction.SC_TX {
|
|
if _, ok := nonce_map[cbl.Txs[i].Payloads[0].Proof.Nonce()]; ok {
|
|
block_logger.Warnf("Double Spend attack within block %s", cbl.Txs[i].GetHash())
|
|
return errormsg.ErrTXDoubleSpend, false
|
|
}
|
|
nonce_map[cbl.Txs[i].Payloads[0].Proof.Nonce()] = true
|
|
}
|
|
}
|
|
}
|
|
|
|
// we also need to reject if the the immediately reachable history, has spent the nonce
|
|
// both the checks works on the basis of nonces and not on the basis of txhash
|
|
/*
|
|
{
|
|
reachable_nonces := chain.BuildReachabilityNonces(bl)
|
|
for i := 0; i < len(cbl.Txs); i++ { // loop through all the TXs
|
|
if cbl.Txs[i].TransactionType == transaction.NORMAL {
|
|
if _, ok := reachable_nonces[cbl.Txs[i].Proof.Nonce()]; ok {
|
|
block_logger.Warnf("Double spend attack tx %s is already mined, rejecting ", cbl.Txs[i].GetHash())
|
|
return errormsg.ErrTXDoubleSpend, false
|
|
}
|
|
}
|
|
}
|
|
}*/
|
|
|
|
// we need to anyways verify the TXS since proofs are not covered by checksum
|
|
{
|
|
fail_count := int32(0)
|
|
wg := sync.WaitGroup{}
|
|
wg.Add(len(cbl.Txs)) // add total number of tx as work
|
|
|
|
hf_version := chain.Get_Current_Version_at_Height(chain.Calculate_Height_At_Tips(bl.Tips))
|
|
for i := 0; i < len(cbl.Txs); i++ {
|
|
go func(j int) {
|
|
if err := chain.Verify_Transaction_NonCoinbase(hf_version, cbl.Txs[j]); err != nil { // transaction verification failed
|
|
atomic.AddInt32(&fail_count, 1) // increase fail count by 1
|
|
block_logger.Warnf("Block verification failed rejecting since TX %s verification failed, err:'%s'", cbl.Txs[j].GetHash(), err)
|
|
}
|
|
wg.Done()
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait() // wait for verifications to finish
|
|
if fail_count > 0 { // check the result
|
|
block_logger.Warnf("Block verification failed rejecting since TX verification failed ")
|
|
return errormsg.ErrInvalidTX, false
|
|
}
|
|
|
|
}
|
|
|
|
// we are here means everything looks good, proceed and save to chain
|
|
//skip_checks:
|
|
|
|
// save all the txs
|
|
// and then save the block
|
|
{ // first lets save all the txs, together with their link to this block as height
|
|
for i := 0; i < len(cbl.Txs); i++ {
|
|
if err = chain.Store.Block_tx_store.WriteTX(bl.Tx_hashes[i], cbl.Txs[i].Serialize()); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
chain.StoreBlock(bl)
|
|
|
|
// if the block is on a lower height tip, the block will not increase chain height
|
|
height := chain.Load_Height_for_BL_ID(block_hash)
|
|
if height > chain.Get_Height() || height == 0 { // exception for genesis block
|
|
atomic.StoreInt64(&chain.Height, height)
|
|
//chain.Store_TOP_HEIGHT(dbtx, height)
|
|
|
|
height_changed = true
|
|
rlog.Infof("Chain extended new height %d blid %s", chain.Height, block_hash)
|
|
|
|
} else {
|
|
rlog.Infof("Chain extended but height is same %d blid %s", chain.Height, block_hash)
|
|
|
|
}
|
|
|
|
// calculate new set of tips
|
|
// this is done by removing all known tips which are in the past
|
|
// and add this block as tip
|
|
|
|
//past := chain.Get_Block_Past( bl.GetHash())
|
|
|
|
tips := chain.Get_TIPS()
|
|
tips = tips[:0]
|
|
//chain.Tips[bl.GetHash()] = bl.GetHash() // add this new block as tip
|
|
|
|
for k := range chain.Tips {
|
|
for i := range bl.Tips {
|
|
if bl.Tips[i] == k {
|
|
goto skip_tip
|
|
}
|
|
|
|
}
|
|
tips = append(tips, k)
|
|
skip_tip:
|
|
}
|
|
|
|
tips = append(tips, bl.GetHash()) // add current block as new tip
|
|
|
|
// find the biggest tip in terms of work
|
|
{
|
|
|
|
base, base_height := chain.find_common_base(tips)
|
|
best := chain.find_best_tip(tips, base, base_height)
|
|
|
|
// we only generate full order for the biggest tip
|
|
|
|
//gbl := Generate_Genesis_Block()
|
|
// full_order := chain.Generate_Full_Order( bl.GetHash(), gbl.GetHash(), 0,0)
|
|
//base_topo_index := chain.Load_Block_Topological_order(gbl.GetHash())
|
|
|
|
full_order := chain.Generate_Full_Order(best, base, base_height, 0)
|
|
base_topo_index := chain.Load_Block_Topological_order(base)
|
|
|
|
// we will directly use graviton to mov in to history
|
|
rlog.Debugf("Full order %+v base %s base topo pos %d", full_order, base, base_topo_index)
|
|
|
|
if len(bl.Tips) == 0 {
|
|
base_topo_index = 0
|
|
}
|
|
|
|
// any blocks which have not changed their topo will be skipped using graviton trick
|
|
skip := true
|
|
|
|
for i := int64(0); i < int64(len(full_order)); i++ {
|
|
|
|
// check whether the new block is at the same position at the last position
|
|
current_topo_block := i + base_topo_index
|
|
previous_topo_block := current_topo_block - 1
|
|
if skip {
|
|
if current_topo_block < chain.Store.Topo_store.Count() {
|
|
toporecord, err := chain.Store.Topo_store.Read(current_topo_block)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
if full_order[i] == toporecord.BLOCK_ID { // skip reprocessing if not required
|
|
continue
|
|
}
|
|
}
|
|
|
|
skip = false // if one block processed, process every higher block
|
|
}
|
|
|
|
rlog.Debugf("will execute order from %d %s", i, full_order[i])
|
|
|
|
// TODO we must run smart contracts and TXs in this order
|
|
// basically client protocol must run here
|
|
// even if the HF has triggered we may still accept, old blocks for some time
|
|
// so hf is detected block-wise and processed as such
|
|
|
|
bl_current_hash := full_order[i]
|
|
bl_current, err1 := chain.Load_BL_FROM_ID(bl_current_hash)
|
|
if err1 != nil {
|
|
block_logger.Debugf("Cannot load block %s for client protocol,probably DB corruption", bl_current_hash)
|
|
return errormsg.ErrInvalidBlock, false
|
|
}
|
|
|
|
//fmt.Printf("\ni %d bl %+v\n",i, bl_current)
|
|
|
|
height_current := chain.Calculate_Height_At_Tips(bl_current.Tips)
|
|
hard_fork_version_current := chain.Get_Current_Version_at_Height(height_current)
|
|
|
|
// this version does not require client protocol as of now
|
|
// run full client protocol and find valid transactions
|
|
// rlog.Debugf("running client protocol for %s minertx %s topo %d", bl_current_hash, bl_current.Miner_TX.GetHash(), highest_topo)
|
|
|
|
// generate miner TX rewards as per client protocol
|
|
if hard_fork_version_current == 1 {
|
|
|
|
}
|
|
|
|
var balance_tree, sc_meta *graviton.Tree
|
|
_ = sc_meta
|
|
|
|
var ss *graviton.Snapshot
|
|
if bl_current.Height == 0 { // if it's genesis block
|
|
if ss, err = chain.Store.Balance_store.LoadSnapshot(0); err != nil {
|
|
panic(err)
|
|
} else if balance_tree, err = ss.GetTree(config.BALANCE_TREE); err != nil {
|
|
panic(err)
|
|
} else if sc_meta, err = ss.GetTree(config.SC_META); err != nil {
|
|
panic(err)
|
|
}
|
|
} else { // we already have a block before us, use it
|
|
|
|
record_version := uint64(0)
|
|
if previous_topo_block >= 0 {
|
|
toporecord, err := chain.Store.Topo_store.Read(previous_topo_block)
|
|
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
record_version = toporecord.State_Version
|
|
}
|
|
|
|
ss, err = chain.Store.Balance_store.LoadSnapshot(record_version)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if balance_tree, err = ss.GetTree(config.BALANCE_TREE); err != nil {
|
|
panic(err)
|
|
}
|
|
if sc_meta, err = ss.GetTree(config.SC_META); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
fees_collected := uint64(0)
|
|
|
|
// side blocks only represent chain strenth , else they are are ignored
|
|
// this means they donot get any reward , 0 reward
|
|
// their transactions are ignored
|
|
|
|
//chain.Store.Topo_store.Write(i+base_topo_index, full_order[i],0, int64(bl_current.Height)) // write entry so as sideblock could work
|
|
var data_trees []*graviton.Tree
|
|
|
|
if !chain.isblock_SideBlock_internal(full_order[i], current_topo_block, int64(bl_current.Height)) {
|
|
|
|
sc_change_cache := map[crypto.Hash]*graviton.Tree{} // cache entire changes for entire block
|
|
|
|
for _, txhash := range bl_current.Tx_hashes { // execute all the transactions
|
|
if tx_bytes, err := chain.Store.Block_tx_store.ReadTX(txhash); err != nil {
|
|
panic(err)
|
|
} else {
|
|
var tx transaction.Transaction
|
|
if err = tx.DeserializeHeader(tx_bytes); err != nil {
|
|
panic(err)
|
|
}
|
|
for t := range tx.Payloads {
|
|
if !tx.Payloads[t].SCID.IsZero() {
|
|
tree, _ := ss.GetTree(string(tx.Payloads[t].SCID[:]))
|
|
sc_change_cache[tx.Payloads[t].SCID] = tree
|
|
}
|
|
}
|
|
// we have loaded a tx successfully, now lets execute it
|
|
tx_fees := chain.process_transaction(sc_change_cache, tx, balance_tree)
|
|
|
|
//fmt.Printf("transaction %s type %s data %+v\n", txhash, tx.TransactionType, tx.SCDATA)
|
|
if tx.TransactionType == transaction.SC_TX {
|
|
tx_fees, err = chain.process_transaction_sc(sc_change_cache, ss, bl_current.Height, uint64(current_topo_block), bl_current_hash, tx, balance_tree, sc_meta)
|
|
|
|
//fmt.Printf("Processsing sc err %s\n", err)
|
|
if err == nil { // TODO process gasg here
|
|
|
|
}
|
|
}
|
|
fees_collected += tx_fees
|
|
}
|
|
}
|
|
|
|
// at this point, we must commit all the SCs, so entire tree hash is interlinked
|
|
for scid, v := range sc_change_cache {
|
|
meta_bytes, err := sc_meta.Get(SC_Meta_Key(scid))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
var meta SC_META_DATA // the meta contains metadata about SC
|
|
if err := meta.UnmarshalBinary(meta_bytes); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if meta.DataHash, err = v.Hash(); err != nil { // encode data tree hash
|
|
panic(err)
|
|
}
|
|
|
|
sc_meta.Put(SC_Meta_Key(scid), meta.MarshalBinary())
|
|
data_trees = append(data_trees, v)
|
|
|
|
/*fmt.Printf("will commit tree name %x \n", v.GetName())
|
|
c := v.Cursor()
|
|
for k, v, err := c.First(); err == nil; k, v, err = c.Next() {
|
|
fmt.Printf("key=%x, value=%x\n", k, v)
|
|
}*/
|
|
|
|
}
|
|
|
|
chain.process_miner_transaction(bl_current.Miner_TX, bl_current.Height == 0, balance_tree, fees_collected, bl_current.Height)
|
|
} else {
|
|
rlog.Debugf("this block is a side block block height %d blid %s ", chain.Load_Block_Height(full_order[i]), full_order[i])
|
|
|
|
}
|
|
|
|
// we are here, means everything is okay, lets commit the update balance tree
|
|
|
|
data_trees = append(data_trees, balance_tree, sc_meta)
|
|
|
|
//fmt.Printf("committing data trees %+v\n", data_trees)
|
|
|
|
commit_version, err := graviton.Commit(data_trees...)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
//fmt.Printf("committed trees version %d\n", commit_version)
|
|
|
|
chain.Store.Topo_store.Write(current_topo_block, full_order[i], commit_version, chain.Load_Block_Height(full_order[i]))
|
|
rlog.Debugf("%d %s topo_index %d base topo %d", i, full_order[i], current_topo_block, base_topo_index)
|
|
|
|
// this tx must be stored, linked with this block
|
|
|
|
}
|
|
|
|
// set main chain as new topo order
|
|
// we must discard any rusty tips after they go stale
|
|
best_height := int64(chain.Load_Height_for_BL_ID(best))
|
|
|
|
new_tips := []crypto.Hash{}
|
|
for i := range tips {
|
|
rusty_tip_base_distance := chain.calculate_mainchain_distance(tips[i])
|
|
// tips of deviation > 6 will be rejected
|
|
if (best_height - rusty_tip_base_distance) < (config.STABLE_LIMIT - 1) {
|
|
new_tips = append(new_tips, tips[i])
|
|
|
|
} else { // this should be a rarest event, probably should never occur, until the network is under sever attack
|
|
logger.Warnf("Rusty TIP declared stale %s best height %d deviation %d rusty_tip %d", tips[i], best_height, (best_height - rusty_tip_base_distance), rusty_tip_base_distance)
|
|
//chain.transaction_scavenger(dbtx, tips[i]) // scavenge tx if possible
|
|
// TODO we must include any TX from the orphan blocks back to the mempool to avoid losing any TX
|
|
}
|
|
}
|
|
|
|
// do more cleanup of tips for byzantine behaviour
|
|
// this copy is necessary, otherwise data corruption occurs
|
|
tips = append([]crypto.Hash{}, new_tips...)
|
|
best_tip := chain.find_best_tip_cumulative_difficulty(tips)
|
|
|
|
{
|
|
new_tips := map[crypto.Hash]crypto.Hash{}
|
|
new_tips[best_tip] = best_tip
|
|
for i := range tips {
|
|
if best_tip != tips[i] {
|
|
if !chain.validate_tips(best_tip, tips[i]) { // reference is first
|
|
logger.Warnf("Rusty tip %s declaring stale", tips[i])
|
|
//chain.transaction_scavenger(dbtx, tips[i]) // scavenge tx if possible
|
|
} else {
|
|
//new_tips = append(new_tips, tips[i])
|
|
new_tips[tips[i]] = tips[i]
|
|
}
|
|
}
|
|
}
|
|
|
|
rlog.Debugf("New tips(after adding %s) %+v", bl.GetHash(), new_tips)
|
|
|
|
chain.Tips = new_tips
|
|
}
|
|
}
|
|
|
|
//chain.store_TIPS(chain.)
|
|
|
|
//chain.Top_ID = block_hash // set new top block id
|
|
|
|
// every 200 block print a line
|
|
if chain.Get_Height()%200 == 0 {
|
|
block_logger.Infof("Chain Height %d", chain.Height)
|
|
}
|
|
|
|
result = true
|
|
|
|
// TODO fix hard fork
|
|
// maintain hard fork votes to keep them SANE
|
|
//chain.Recount_Votes() // does not return anything
|
|
|
|
// enable mempool book keeping
|
|
|
|
func() {
|
|
if r := recover(); r != nil {
|
|
logger.Warnf("Mempool House Keeping triggered panic height = %d", block_height)
|
|
}
|
|
|
|
// discard the transactions from mempool if they are present there
|
|
chain.Mempool.Monitor()
|
|
|
|
for i := 0; i < len(cbl.Txs); i++ {
|
|
txid := cbl.Txs[i].GetHash()
|
|
|
|
switch cbl.Txs[i].TransactionType {
|
|
|
|
case transaction.REGISTRATION:
|
|
if chain.Regpool.Regpool_TX_Exist(txid) {
|
|
rlog.Tracef(1, "Deleting TX from regpool txid=%s", txid)
|
|
chain.Regpool.Regpool_Delete_TX(txid)
|
|
continue
|
|
}
|
|
|
|
case transaction.NORMAL, transaction.BURN_TX, transaction.SC_TX:
|
|
if chain.Mempool.Mempool_TX_Exist(txid) {
|
|
rlog.Tracef(1, "Deleting TX from mempool txid=%s", txid)
|
|
chain.Mempool.Mempool_Delete_TX(txid)
|
|
continue
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// give mempool an oppurtunity to clean up tx, but only if they are not mined
|
|
chain.Mempool.HouseKeeping(uint64(block_height))
|
|
|
|
// ggive regpool a chance to register
|
|
if ss, err := chain.Store.Balance_store.LoadSnapshot(0); err == nil {
|
|
if balance_tree, err := ss.GetTree(config.BALANCE_TREE); err == nil {
|
|
|
|
chain.Regpool.HouseKeeping(uint64(block_height), func(tx *transaction.Transaction) bool {
|
|
if tx.TransactionType != transaction.REGISTRATION { // tx not registration so delete
|
|
return true
|
|
}
|
|
|
|
if _, err := balance_tree.Get(tx.MinerAddress[:]); err != nil { // address already registered
|
|
return true
|
|
}
|
|
return false // account not already registered, so give another chance
|
|
})
|
|
|
|
}
|
|
}
|
|
|
|
}()
|
|
|
|
return // run any handlers necesary to atomically
|
|
}
|
|
|
|
// this function is called to read blockchain state from DB
|
|
// It is callable at any point in time
|
|
|
|
func (chain *Blockchain) Initialise_Chain_From_DB() {
|
|
chain.Lock()
|
|
defer chain.Unlock()
|
|
|
|
chain.Pruned = chain.LocatePruneTopo()
|
|
if chain.Pruned >= 1 {
|
|
logger.Debugf("Chain Pruned until %d\n", chain.Pruned)
|
|
}
|
|
|
|
// find the tips from the chain , first by reaching top height
|
|
// then downgrading to top-10 height
|
|
// then reworking the chain to get the tip
|
|
best_height := chain.Load_TOP_HEIGHT()
|
|
chain.Height = best_height
|
|
|
|
chain.Tips = map[crypto.Hash]crypto.Hash{} // reset the map
|
|
// reload top tip from disk
|
|
top := chain.Get_Top_ID()
|
|
|
|
chain.Tips[top] = top // we only can load a single tip from db
|
|
|
|
// get dag unsettled, it's only possible when we have the tips
|
|
// chain.dag_unsettled = chain.Get_DAG_Unsettled() // directly off the disk
|
|
|
|
logger.Debugf("Reloaded Chain Tips %+v Height %d", chain.Tips, chain.Height)
|
|
|
|
}
|
|
|
|
// before shutdown , make sure p2p is confirmed stopped
|
|
func (chain *Blockchain) Shutdown() {
|
|
|
|
chain.Lock() // take the lock as chain is no longer in unsafe mode
|
|
close(chain.Exit_Event) // send signal to everyone we are shutting down
|
|
|
|
chain.Mempool.Shutdown() // shutdown mempool first
|
|
chain.Regpool.Shutdown() // shutdown regpool first
|
|
|
|
logger.Infof("Stopping Blockchain")
|
|
//chain.Store.Shutdown()
|
|
atomic.AddUint32(&globals.Subsystem_Active, ^uint32(0)) // this decrement 1 fom subsystem
|
|
}
|
|
|
|
// get top unstable height
|
|
// this is obtained by getting the highest topo block and getting its height
|
|
func (chain *Blockchain) Get_Height() int64 {
|
|
topo_count := chain.Store.Topo_store.Count()
|
|
if topo_count == 0 {
|
|
return 0
|
|
}
|
|
|
|
//return atomic.LoadUint64(&chain.Height)
|
|
return chain.Load_TOP_HEIGHT()
|
|
}
|
|
|
|
// get height where chain is now stable
|
|
func (chain *Blockchain) Get_Stable_Height() int64 {
|
|
tips := chain.Get_TIPS()
|
|
base, base_height := chain.find_common_base(tips)
|
|
_ = base
|
|
|
|
return int64(base_height)
|
|
}
|
|
|
|
// we should be holding lock at this time, atleast read only
|
|
|
|
func (chain *Blockchain) Get_TIPS() (tips []crypto.Hash) {
|
|
for _, x := range chain.Tips {
|
|
tips = append(tips, x)
|
|
|
|
}
|
|
return tips
|
|
}
|
|
|
|
func (chain *Blockchain) Get_Difficulty() uint64 {
|
|
return chain.Get_Difficulty_At_Tips(chain.Get_TIPS()).Uint64()
|
|
}
|
|
|
|
/*
|
|
func (chain *Blockchain) Get_Cumulative_Difficulty() uint64 {
|
|
|
|
return 0 //chain.Load_Block_Cumulative_Difficulty(chain.Top_ID)
|
|
}
|
|
|
|
func (chain *Blockchain) Get_Median_Block_Size() uint64 { // get current cached median size
|
|
return chain.Median_Block_Size
|
|
}
|
|
*/
|
|
func (chain *Blockchain) Get_Network_HashRate() uint64 {
|
|
return chain.Get_Difficulty() / chain.Get_Current_BlockTime()
|
|
}
|
|
|
|
// this is used to for quick syncs as entire blocks as SHA1,
|
|
// entires block can skipped for verification, if checksum matches what the devs have stored
|
|
func (chain *Blockchain) BlockCheckSum(cbl *block.Complete_Block) []byte {
|
|
h := sha3.New256()
|
|
h.Write(cbl.Bl.Serialize())
|
|
for i := range cbl.Txs {
|
|
h.Write(cbl.Txs[i].Serialize())
|
|
}
|
|
return h.Sum(nil)
|
|
}
|
|
|
|
// various counters/gauges which track a numer of metrics
|
|
// such as number of txs, number of inputs, number of outputs
|
|
// mempool total addition, current mempool size
|
|
// block processing time etcs
|
|
|
|
// Try it once more, this time with a help string.
|
|
var blockchain_tx_counter = prometheus.NewCounter(prometheus.CounterOpts{
|
|
Name: "blockchain_tx_counter",
|
|
Help: "Number of tx mined",
|
|
})
|
|
|
|
var mempool_tx_counter = prometheus.NewCounter(prometheus.CounterOpts{
|
|
Name: "mempool_tx_counter",
|
|
Help: "Total number of tx added in mempool",
|
|
})
|
|
var mempool_tx_count = prometheus.NewGauge(prometheus.GaugeOpts{
|
|
Name: "mempool_tx_count",
|
|
Help: "Number of tx in mempool at this point",
|
|
})
|
|
|
|
// track block size about 2 MB
|
|
var block_size = prometheus.NewHistogram(prometheus.HistogramOpts{
|
|
Name: "block_size_byte",
|
|
Help: "Block size in byte (complete)",
|
|
Buckets: prometheus.LinearBuckets(0, 102400, 10), // start block size 0, each 1 KB step, 2048 such buckets .
|
|
})
|
|
|
|
// track transaction size upto 500 KB
|
|
var transaction_size = prometheus.NewHistogram(prometheus.HistogramOpts{
|
|
Name: "tx_size_byte",
|
|
Help: "TX size in byte",
|
|
Buckets: prometheus.LinearBuckets(0, 10240, 16), // start 0 byte, each 1024 byte, 512 such buckets.
|
|
})
|
|
|
|
// number of tx per block
|
|
var block_tx_count = prometheus.NewHistogram(prometheus.HistogramOpts{
|
|
Name: "block_tx_count",
|
|
Help: "Number of TX in the block",
|
|
Buckets: prometheus.LinearBuckets(0, 20, 25), // start 0 byte, each 1024 byte, 1024 such buckets.
|
|
})
|
|
|
|
//
|
|
var block_processing_time = prometheus.NewHistogram(prometheus.HistogramOpts{
|
|
Name: "block_processing_time_ms",
|
|
Help: "Block processing time milliseconds",
|
|
Buckets: prometheus.LinearBuckets(0, 100, 20), // start 0 ms, each 100 ms, 200 such buckets.
|
|
})
|
|
|
|
// this is the only entrypoint for new txs in the chain
|
|
// add a transaction to MEMPOOL,
|
|
// verifying everything means everything possible
|
|
// this only change mempool, no DB changes
|
|
func (chain *Blockchain) Add_TX_To_Pool(tx *transaction.Transaction) error {
|
|
var err error
|
|
|
|
if tx.IsPremine() {
|
|
return fmt.Errorf("premine tx not mineable")
|
|
}
|
|
if tx.IsRegistration() { // registration tx will not go any forward
|
|
// ggive regpool a chance to register
|
|
if ss, err := chain.Store.Balance_store.LoadSnapshot(0); err == nil {
|
|
if balance_tree, err := ss.GetTree(config.BALANCE_TREE); err == nil {
|
|
if _, err := balance_tree.Get(tx.MinerAddress[:]); err == nil { // address already registered
|
|
return nil
|
|
} else { // add to regpool
|
|
if chain.Regpool.Regpool_Add_TX(tx, 0) {
|
|
return nil
|
|
} else {
|
|
return fmt.Errorf("registration for address is already pending")
|
|
}
|
|
}
|
|
} else {
|
|
return err
|
|
}
|
|
} else {
|
|
return err
|
|
}
|
|
|
|
}
|
|
|
|
switch tx.TransactionType {
|
|
case transaction.BURN_TX, transaction.NORMAL, transaction.SC_TX:
|
|
default:
|
|
return fmt.Errorf("such transaction type cannot appear in mempool")
|
|
}
|
|
|
|
// track counter for the amount of mempool tx
|
|
defer mempool_tx_count.Set(float64(len(chain.Mempool.Mempool_List_TX())))
|
|
|
|
txhash := tx.GetHash()
|
|
|
|
// Coin base TX can not come through this path
|
|
if tx.IsCoinbase() {
|
|
logger.WithFields(log.Fields{"txid": txhash}).Warnf("TX rejected coinbase tx cannot appear in mempool")
|
|
return fmt.Errorf("TX rejected coinbase tx cannot appear in mempool")
|
|
}
|
|
|
|
chain_height := uint64(chain.Get_Height())
|
|
if chain_height > tx.Height {
|
|
rlog.Tracef(2, "TX %s rejected since chain has already progressed", txhash)
|
|
return fmt.Errorf("TX %s rejected since chain has already progressed", txhash)
|
|
}
|
|
|
|
// quick check without calculating everything whether tx is in pool, if yes we do nothing
|
|
if chain.Mempool.Mempool_TX_Exist(txhash) {
|
|
rlog.Tracef(2, "TX %s rejected Already in MEMPOOL", txhash)
|
|
return fmt.Errorf("TX %s rejected Already in MEMPOOL", txhash)
|
|
}
|
|
|
|
// check whether tx is already mined
|
|
if _, err = chain.Store.Block_tx_store.ReadTX(txhash); err == nil {
|
|
rlog.Tracef(2, "TX %s rejected Already mined in some block", txhash)
|
|
return fmt.Errorf("TX %s rejected Already mined in some block", txhash)
|
|
}
|
|
|
|
hf_version := chain.Get_Current_Version_at_Height(int64(chain_height))
|
|
|
|
// if TX is too big, then it cannot be mined due to fixed block size, reject such TXs here
|
|
// currently, limits are as per consensus
|
|
if uint64(len(tx.Serialize())) > config.STARGATE_HE_MAX_TX_SIZE {
|
|
logger.WithFields(log.Fields{"txid": txhash}).Warnf("TX rejected Size %d byte Max possible %d", len(tx.Serialize()), config.STARGATE_HE_MAX_TX_SIZE)
|
|
return fmt.Errorf("TX rejected Size %d byte Max possible %d", len(tx.Serialize()), config.STARGATE_HE_MAX_TX_SIZE)
|
|
}
|
|
|
|
// check whether enough fees is provided in the transaction
|
|
calculated_fee := chain.Calculate_TX_fee(hf_version, uint64(len(tx.Serialize())))
|
|
provided_fee := tx.Fees() // get fee from tx
|
|
|
|
_ = calculated_fee
|
|
_ = provided_fee
|
|
|
|
//logger.WithFields(log.Fields{"txid": txhash}).Warnf("TX fees check disabled provided fee %d calculated fee %d", provided_fee, calculated_fee)
|
|
|
|
/*
|
|
if calculated_fee > provided_fee { // 2 % margin see blockchain.cpp L 2913
|
|
logger.WithFields(log.Fields{"txid": txhash}).Warnf("TX rejected due to low fees provided fee %d calculated fee %d", provided_fee, calculated_fee)
|
|
|
|
rlog.Warnf("TX %s rejected due to low fees provided fee %d calculated fee %d", txhash, provided_fee, calculated_fee)
|
|
return false
|
|
}
|
|
*/
|
|
|
|
if err := chain.Verify_Transaction_NonCoinbase(hf_version, tx); err != nil {
|
|
rlog.Warnf("Incoming TX %s could not be verified, err %s", txhash, err)
|
|
return fmt.Errorf("Incoming TX %s could not be verified, err %s", txhash, err)
|
|
}
|
|
|
|
if chain.Mempool.Mempool_Add_TX(tx, 0) { // new tx come with 0 marker
|
|
rlog.Tracef(2, "Successfully added tx %s to pool", txhash)
|
|
|
|
mempool_tx_counter.Inc()
|
|
return nil
|
|
} else {
|
|
rlog.Tracef(2, "TX %s rejected by pool", txhash)
|
|
return fmt.Errorf("TX %s rejected by pool", txhash)
|
|
}
|
|
|
|
}
|
|
|
|
// structure used to rank/sort blocks on a number of factors
|
|
type BlockScore struct {
|
|
BLID crypto.Hash
|
|
// Weight uint64
|
|
Height int64 // block height
|
|
Cumulative_Difficulty *big.Int // used to score blocks on cumulative difficulty
|
|
}
|
|
|
|
// Heighest node weight is ordered first, the condition is reverted see eg. at https://golang.org/pkg/sort/#Slice
|
|
// if weights are equal, nodes are sorted by their block ids which will never collide , hopefullly
|
|
// block ids are sorted by lowest byte first diff
|
|
func sort_descending_by_cumulative_difficulty(tips_scores []BlockScore) {
|
|
|
|
sort.Slice(tips_scores, func(i, j int) bool {
|
|
if tips_scores[i].Cumulative_Difficulty.Cmp(tips_scores[j].Cumulative_Difficulty) != 0 { // if diffculty mismatch use them
|
|
|
|
if tips_scores[i].Cumulative_Difficulty.Cmp(tips_scores[j].Cumulative_Difficulty) > 0 { // if i diff > j diff
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
|
|
} else {
|
|
return bytes.Compare(tips_scores[i].BLID[:], tips_scores[j].BLID[:]) == -1
|
|
}
|
|
})
|
|
}
|
|
|
|
func sort_ascending_by_height(tips_scores []BlockScore) {
|
|
|
|
// base is the lowest height
|
|
sort.Slice(tips_scores, func(i, j int) bool { return tips_scores[i].Height < tips_scores[j].Height })
|
|
|
|
}
|
|
|
|
// this will sort the tips based on cumulative difficulty and/or block ids
|
|
// the tips will sorted in descending order
|
|
func (chain *Blockchain) SortTips(tips []crypto.Hash) (sorted []crypto.Hash) {
|
|
if len(tips) == 0 {
|
|
panic("tips cannot be 0")
|
|
}
|
|
if len(tips) == 1 {
|
|
sorted = []crypto.Hash{tips[0]}
|
|
return
|
|
}
|
|
|
|
tips_scores := make([]BlockScore, len(tips), len(tips))
|
|
for i := range tips {
|
|
tips_scores[i].BLID = tips[i]
|
|
tips_scores[i].Cumulative_Difficulty = chain.Load_Block_Cumulative_Difficulty(tips[i])
|
|
}
|
|
|
|
sort_descending_by_cumulative_difficulty(tips_scores)
|
|
|
|
for i := range tips_scores {
|
|
sorted = append(sorted, tips_scores[i].BLID)
|
|
}
|
|
return
|
|
}
|
|
|
|
// side blocks are blocks which lost the race the to become part
|
|
// of main chain, but there transactions are honoured,
|
|
// they are given 67 % reward
|
|
// a block is a side block if it satisfies the following condition
|
|
// if block height is less than or equal to height of past 3*config.STABLE_LIMIT topographical blocks
|
|
// this is part of consensus rule
|
|
// this is the topoheight of this block itself
|
|
func (chain *Blockchain) Isblock_SideBlock(blid crypto.Hash) bool {
|
|
block_topoheight := chain.Load_Block_Topological_order(blid)
|
|
if block_topoheight == 0 {
|
|
return false
|
|
}
|
|
// lower reward for byzantine behaviour
|
|
// for as many block as added
|
|
block_height := chain.Load_Height_for_BL_ID(blid)
|
|
|
|
return chain.isblock_SideBlock_internal(blid, block_topoheight, block_height)
|
|
}
|
|
|
|
// todo optimize/ run more checks
|
|
func (chain *Blockchain) isblock_SideBlock_internal(blid crypto.Hash, block_topoheight int64, block_height int64) (result bool) {
|
|
if block_topoheight == 0 {
|
|
return false
|
|
}
|
|
counter := int64(0)
|
|
for i := block_topoheight - 1; i >= 0 && counter < 16*config.STABLE_LIMIT; i-- {
|
|
counter++
|
|
toporecord, err := chain.Store.Topo_store.Read(i)
|
|
if err != nil {
|
|
panic("Could not load block from previous order")
|
|
}
|
|
if block_height <= toporecord.Height { // lost race (or byzantine behaviour)
|
|
return true // give only 67 % reward
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// this will return the tx combination as valid/invalid
|
|
// this is not used as core consensus but reports only to user that his tx though in the blockchain is invalid
|
|
// a tx is valid, if it exist in a block which is not a side block
|
|
func (chain *Blockchain) IS_TX_Valid(txhash crypto.Hash) (valid_blid crypto.Hash, invalid_blid []crypto.Hash, valid bool) {
|
|
|
|
var tx_bytes []byte
|
|
var err error
|
|
|
|
if tx_bytes, err = chain.Store.Block_tx_store.ReadTX(txhash); err != nil {
|
|
return
|
|
}
|
|
|
|
var tx transaction.Transaction
|
|
if err = tx.DeserializeHeader(tx_bytes); err != nil {
|
|
return
|
|
}
|
|
|
|
blids, _ := chain.Store.Topo_store.binarySearchHeight(int64(tx.Height + 1))
|
|
|
|
var exist_list []crypto.Hash
|
|
|
|
for _, blid := range blids {
|
|
bl, err := chain.Load_BL_FROM_ID(blid)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
for _, bltxhash := range bl.Tx_hashes {
|
|
if bltxhash == txhash {
|
|
exist_list = append(exist_list, blid)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, blid := range exist_list {
|
|
if chain.Isblock_SideBlock(blid) {
|
|
invalid_blid = append(invalid_blid, blid)
|
|
} else {
|
|
valid_blid = blid
|
|
valid = true
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
/*
|
|
|
|
|
|
// runs the client protocol which includes the following operations
|
|
// if any TX are being duplicate or double-spend ignore them
|
|
// mark all the valid transactions as valid
|
|
// mark all invalid transactions as invalid
|
|
// calculate total fees based on valid TX
|
|
// we need NOT check ranges/ring signatures here, as they have been done already by earlier steps
|
|
func (chain *Blockchain) client_protocol(dbtx storage.DBTX, bl *block.Block, blid crypto.Hash, height int64, topoheight int64) (total_fees uint64) {
|
|
// run client protocol for all TXs
|
|
for i := range bl.Tx_hashes {
|
|
tx, err := chain.Load_TX_FROM_ID(dbtx, bl.Tx_hashes[i])
|
|
if err != nil {
|
|
panic(fmt.Errorf("Cannot load tx for %x err %s ", bl.Tx_hashes[i], err))
|
|
}
|
|
// mark TX found in this block also for explorer
|
|
chain.store_TX_in_Block(dbtx, blid, bl.Tx_hashes[i])
|
|
|
|
// check all key images as double spend, if double-spend detected mark invalid, else consider valid
|
|
if chain.Verify_Transaction_NonCoinbase_DoubleSpend_Check(dbtx, tx) {
|
|
|
|
chain.consume_keyimages(dbtx, tx, height) // mark key images as consumed
|
|
total_fees += tx.RctSignature.Get_TX_Fee()
|
|
|
|
chain.Store_TX_Height(dbtx, bl.Tx_hashes[i], topoheight) // link the tx with the topo height
|
|
|
|
//mark tx found in this block is valid
|
|
chain.mark_TX(dbtx, blid, bl.Tx_hashes[i], true)
|
|
|
|
} else { // TX is double spend or reincluded by 2 blocks simultaneously
|
|
rlog.Tracef(1,"Double spend TX is being ignored %s %s", blid, bl.Tx_hashes[i])
|
|
chain.mark_TX(dbtx, blid, bl.Tx_hashes[i], false)
|
|
}
|
|
}
|
|
|
|
return total_fees
|
|
}
|
|
|
|
// this undoes everything that is done by client protocol
|
|
// NOTE: this will have any effect, only if client protocol has been run on this block earlier
|
|
func (chain *Blockchain) client_protocol_reverse(dbtx storage.DBTX, bl *block.Block, blid crypto.Hash) {
|
|
// run client protocol for all TXs
|
|
for i := range bl.Tx_hashes {
|
|
tx, err := chain.Load_TX_FROM_ID(dbtx, bl.Tx_hashes[i])
|
|
if err != nil {
|
|
panic(fmt.Errorf("Cannot load tx for %x err %s ", bl.Tx_hashes[i], err))
|
|
}
|
|
// only the valid TX must be revoked
|
|
if chain.IS_TX_Valid(dbtx, blid, bl.Tx_hashes[i]) {
|
|
chain.revoke_keyimages(dbtx, tx) // mark key images as not used
|
|
|
|
chain.Store_TX_Height(dbtx, bl.Tx_hashes[i], -1) // unlink the tx with the topo height
|
|
|
|
//mark tx found in this block is invalid
|
|
chain.mark_TX(dbtx, blid, bl.Tx_hashes[i], false)
|
|
|
|
} else { // TX is double spend or reincluded by 2 blocks simultaneously
|
|
// invalid tx is related
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// scavanger for transactions from rusty/stale tips to reinsert them into pool
|
|
func (chain *Blockchain) transaction_scavenger(dbtx storage.DBTX, blid crypto.Hash) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
logger.Warnf("Recovered while transaction scavenging, Stack trace below ")
|
|
logger.Warnf("Stack trace \n%s", debug.Stack())
|
|
}
|
|
}()
|
|
|
|
logger.Debugf("scavenging transactions from blid %s", blid)
|
|
reachable_blocks := chain.BuildReachableBlocks(dbtx, []crypto.Hash{blid})
|
|
reachable_blocks[blid] = true // add self
|
|
for k, _ := range reachable_blocks {
|
|
if chain.Is_Block_Orphan(k) {
|
|
bl, err := chain.Load_BL_FROM_ID(dbtx, k)
|
|
if err == nil {
|
|
for i := range bl.Tx_hashes {
|
|
tx, err := chain.Load_TX_FROM_ID(dbtx, bl.Tx_hashes[i])
|
|
if err != nil {
|
|
rlog.Warnf("err while scavenging blid %s txid %s err %s", k, bl.Tx_hashes[i], err)
|
|
} else {
|
|
// add tx to pool, it will do whatever is necessarry
|
|
chain.Add_TX_To_Pool(tx)
|
|
}
|
|
}
|
|
} else {
|
|
rlog.Warnf("err while scavenging blid %s err %s", k, err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
*/
|
|
// Finds whether a block is orphan
|
|
// since we donot store any fields, we need to calculate/find the block as orphan
|
|
// using an algorithm
|
|
// if the block is NOT topo ordered , it is orphan/stale
|
|
func (chain *Blockchain) Is_Block_Orphan(hash crypto.Hash) bool {
|
|
return !chain.Is_Block_Topological_order(hash)
|
|
}
|
|
|
|
// this is used to find if a tx is orphan, YES orphan TX
|
|
// these can occur during when they lie only in a side block
|
|
// so the TX becomes orphan ( chances are less may be less that .000001 % but they are there)
|
|
// if a tx is not valid in any of the blocks, it has been mined it is orphan
|
|
func (chain *Blockchain) Is_TX_Orphan(hash crypto.Hash) (result bool) {
|
|
_, _, result = chain.IS_TX_Valid(hash)
|
|
return !result
|
|
}
|
|
|
|
// verifies whether we are lagging
|
|
// return true if we need resync
|
|
// returns false if we are good and resync is not required
|
|
func (chain *Blockchain) IsLagging(peer_cdiff *big.Int) bool {
|
|
|
|
our_diff := new(big.Int).SetInt64(0)
|
|
|
|
high_block, err := chain.Load_Block_Topological_order_at_index(chain.Load_TOPO_HEIGHT())
|
|
if err != nil {
|
|
return false
|
|
} else {
|
|
our_diff = chain.Load_Block_Cumulative_Difficulty(high_block)
|
|
}
|
|
rlog.Tracef(2, "P_cdiff %s cdiff %d our top block %s", peer_cdiff.String(), our_diff.String(), high_block)
|
|
|
|
if our_diff.Cmp(peer_cdiff) < 0 {
|
|
return true // peer's cumulative difficulty is more than ours , active resync
|
|
}
|
|
return false
|
|
}
|
|
|
|
// this function will rewind the chain from the topo height one block at a time
|
|
// this function also runs the client protocol in reverse and also deletes the block from the storage
|
|
func (chain *Blockchain) Rewind_Chain(rewind_count int) (result bool) {
|
|
defer chain.Initialise_Chain_From_DB()
|
|
|
|
chain.Lock()
|
|
defer chain.Unlock()
|
|
|
|
// we must till we reach a safe point
|
|
// safe point is point where a single block exists at specific height
|
|
// this may lead us to rewinding a it more
|
|
//safe := false
|
|
|
|
// TODO we must fix safeness using the stable calculation
|
|
|
|
if rewind_count == 0 {
|
|
return
|
|
}
|
|
|
|
top_block_topo_index := chain.Load_TOPO_HEIGHT()
|
|
rewinded := int64(0)
|
|
|
|
for { // rewind as many as possible
|
|
if top_block_topo_index-rewinded < 1 || rewinded >= int64(rewind_count) {
|
|
break
|
|
}
|
|
|
|
rewinded++
|
|
}
|
|
|
|
for { // rewinf till we reach a safe point
|
|
r, err := chain.Store.Topo_store.Read(top_block_topo_index - rewinded)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if chain.IsBlockSyncBlockHeight(r.BLOCK_ID) || r.Height == 1 {
|
|
break
|
|
}
|
|
|
|
rewinded++
|
|
}
|
|
|
|
for i := int64(0); i != rewinded; i++ {
|
|
chain.Store.Topo_store.Clean(top_block_topo_index - i)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// build reachability graph upto 2*config deeps to answer reachability queries
|
|
func (chain *Blockchain) buildReachability_internal(reachmap map[crypto.Hash]bool, blid crypto.Hash, level int) {
|
|
bl, err := chain.Load_BL_FROM_ID(blid)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
past := bl.Tips
|
|
reachmap[blid] = true // add self to reach map
|
|
|
|
if level >= int(2*config.STABLE_LIMIT) { // stop recursion must be more than checks in add complete block
|
|
return
|
|
}
|
|
for i := range past { // if no past == genesis return
|
|
if _, ok := reachmap[past[i]]; !ok { // process a node, only if has not been processed earlier
|
|
chain.buildReachability_internal(reachmap, past[i], level+1)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// build reachability graph upto 2*limit deeps to answer reachability queries
|
|
func (chain *Blockchain) buildReachability(blid crypto.Hash) map[crypto.Hash]bool {
|
|
reachmap := map[crypto.Hash]bool{}
|
|
chain.buildReachability_internal(reachmap, blid, 0)
|
|
return reachmap
|
|
}
|
|
|
|
// this is part of consensus rule, 2 tips cannot refer to their common parent
|
|
func (chain *Blockchain) VerifyNonReachability(bl *block.Block) bool {
|
|
return chain.verifyNonReachabilitytips(bl.Tips)
|
|
}
|
|
|
|
// this is part of consensus rule, 2 tips cannot refer to their common parent
|
|
func (chain *Blockchain) verifyNonReachabilitytips(tips []crypto.Hash) bool {
|
|
|
|
reachmaps := make([]map[crypto.Hash]bool, len(tips), len(tips))
|
|
for i := range tips {
|
|
reachmaps[i] = chain.buildReachability(tips[i])
|
|
}
|
|
|
|
// bruteforce all reachability combinations, max possible 3x3 = 9 combinations
|
|
for i := range tips {
|
|
for j := range tips {
|
|
if i == j { // avoid self test
|
|
continue
|
|
}
|
|
|
|
if _, ok := reachmaps[j][tips[i]]; ok { // if a tip can be referenced as another's past, this is not a tip , probably malicious, discard block
|
|
return false
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// used in the difficulty calculation for consensus and while scavenging
|
|
func (chain *Blockchain) BuildReachableBlocks(tips []crypto.Hash) map[crypto.Hash]bool {
|
|
reachblocks := map[crypto.Hash]bool{} // contains a list of all reachable blocks
|
|
for i := range tips {
|
|
reachmap := chain.buildReachability(tips[i])
|
|
for k, _ := range reachmap {
|
|
reachblocks[k] = true // build unique block list
|
|
}
|
|
}
|
|
return reachblocks
|
|
}
|
|
|
|
// this is part of consensus rule, reachable blocks cannot have keyimages collision with new blocks
|
|
// this is to avoid dishonest miners including dead transactions
|
|
//
|
|
func (chain *Blockchain) BuildReachabilityNonces(bl *block.Block) map[crypto.Hash]bool {
|
|
|
|
nonce_reach_map := map[crypto.Hash]bool{}
|
|
reachblocks := map[crypto.Hash]bool{} // contains a list of all reachable blocks
|
|
for i := range bl.Tips {
|
|
reachmap := chain.buildReachability(bl.Tips[i])
|
|
for k, _ := range reachmap {
|
|
reachblocks[k] = true // build unique block list
|
|
}
|
|
}
|
|
|
|
// load all blocks and process their TX as per client protocol
|
|
for blid, _ := range reachblocks {
|
|
bl, err := chain.Load_BL_FROM_ID(blid)
|
|
if err != nil {
|
|
panic(fmt.Errorf("Cannot load block for %s err %s", blid, err))
|
|
}
|
|
|
|
for i := 0; i < len(bl.Tx_hashes); i++ { // load all tx one by one, skipping as per client_protocol
|
|
|
|
tx_bytes, err := chain.Store.Block_tx_store.ReadTX(bl.Tx_hashes[i])
|
|
if err != nil {
|
|
panic(fmt.Errorf("Cannot load tx for %s err %s", bl.Tx_hashes[i], err))
|
|
}
|
|
|
|
var tx transaction.Transaction
|
|
if err = tx.DeserializeHeader(tx_bytes); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// tx has been loaded, now lets get the nonce
|
|
nonce_reach_map[tx.Payloads[0].Proof.Nonce()] = true // add element to map for next check
|
|
}
|
|
}
|
|
return nonce_reach_map
|
|
}
|
|
|
|
// sync blocks have the following specific property
|
|
// 1) the block is singleton at this height
|
|
// basically the condition allow us to confirm weight of future blocks with reference to sync blocks
|
|
// these are the one who settle the chain and guarantee it
|
|
func (chain *Blockchain) IsBlockSyncBlockHeight(blid crypto.Hash) bool {
|
|
return chain.IsBlockSyncBlockHeightSpecific(blid, chain.Get_Height())
|
|
}
|
|
|
|
func (chain *Blockchain) IsBlockSyncBlockHeightSpecific(blid crypto.Hash, chain_height int64) bool {
|
|
|
|
// TODO make sure that block exist
|
|
height := chain.Load_Height_for_BL_ID(blid)
|
|
if height == 0 { // genesis is always a sync block
|
|
return true
|
|
}
|
|
|
|
// top blocks are always considered unstable
|
|
if (height + config.STABLE_LIMIT) > chain_height {
|
|
return false
|
|
}
|
|
|
|
// if block is not ordered, it can never be sync block
|
|
if !chain.Is_Block_Topological_order(blid) {
|
|
return false
|
|
}
|
|
|
|
blocks := chain.Get_Blocks_At_Height(height)
|
|
|
|
if len(blocks) == 0 && height != 0 { // this should NOT occur
|
|
panic("No block exists at this height, not possible")
|
|
}
|
|
|
|
// if len(blocks) == 1 { // ideal blockchain case, it is a sync block
|
|
// return true
|
|
// }
|
|
|
|
// check whether single block exists in the TOPO order index, if no we are NOT a sync block
|
|
|
|
// we are here means we have one oor more block
|
|
blocks_in_main_chain := 0
|
|
for i := range blocks {
|
|
if chain.Is_Block_Topological_order(blocks[i]) {
|
|
blocks_in_main_chain++
|
|
if blocks_in_main_chain >= 2 {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
// we are here if we only have one block in topological order, others are dumped/rejected blocks
|
|
|
|
// collect all blocks of past LIMIT heights
|
|
var preblocks []crypto.Hash
|
|
for i := height - 1; i >= (height-config.STABLE_LIMIT) && i != 0; i-- {
|
|
blocks := chain.Get_Blocks_At_Height(i)
|
|
for j := range blocks { //TODO BUG BUG BUG we need to make sure only main chain blocks are considered
|
|
preblocks = append(preblocks, blocks[j])
|
|
}
|
|
}
|
|
|
|
// we need to find a common base to compare them, otherwise comparision is futile due to duplication
|
|
sync_block_cumulative_difficulty := chain.Load_Block_Cumulative_Difficulty(blid) //+ chain.Load_Block_Difficulty(blid)
|
|
|
|
// if any of the blocks has a cumulative difficulty more than sync block, this situation affects consensus, so mitigate it
|
|
for i := range preblocks {
|
|
cumulative_difficulty := chain.Load_Block_Cumulative_Difficulty(preblocks[i]) // + chain.Load_Block_Difficulty(preblocks[i])
|
|
|
|
//if cumulative_difficulty >= sync_block_cumulative_difficulty {
|
|
if cumulative_difficulty.Cmp(sync_block_cumulative_difficulty) >= 0 {
|
|
rlog.Warnf("Mitigating CONSENSUS issue on block %s height %d child %s cdiff %d sync block cdiff %d", blid, height, preblocks[i], cumulative_difficulty, sync_block_cumulative_difficulty)
|
|
return false
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// key is string of blid and appendded chain height
|
|
var tipbase_cache, _ = hashicorp_lru.New(10240)
|
|
|
|
// base of a tip is last known sync point
|
|
// weight of bases in mentioned in term of height
|
|
// this must not employ any cache
|
|
func (chain *Blockchain) FindTipBase(blid crypto.Hash, chain_height int64) (bs BlockScore) {
|
|
|
|
// see if cache contains it
|
|
if bsi, ok := tipbase_cache.Get(fmt.Sprintf("%s%d", blid, chain_height)); ok {
|
|
bs = bsi.(BlockScore)
|
|
return bs
|
|
}
|
|
|
|
defer func() { // capture return value of bs to cache
|
|
z := bs
|
|
tipbase_cache.Add(fmt.Sprintf("%s%d", blid, chain_height), z)
|
|
}()
|
|
|
|
// if we are genesis return genesis block as base
|
|
|
|
tips := chain.Get_Block_Past(blid)
|
|
if len(tips) == 0 {
|
|
gbl := Generate_Genesis_Block()
|
|
bs = BlockScore{gbl.GetHash(), 0, nil}
|
|
return
|
|
}
|
|
|
|
bases := make([]BlockScore, len(tips), len(tips))
|
|
|
|
for i := range tips {
|
|
if chain.IsBlockSyncBlockHeightSpecific(tips[i], chain_height) {
|
|
// rlog.Tracef(2, "SYNC block %s", tips[i])
|
|
bs = BlockScore{tips[i], chain.Load_Height_for_BL_ID(tips[i]), nil}
|
|
return
|
|
}
|
|
bases[i] = chain.FindTipBase(tips[i], chain_height)
|
|
}
|
|
|
|
sort_ascending_by_height(bases)
|
|
|
|
// logger.Infof("return BASE %s",bases[0])
|
|
bs = bases[0]
|
|
return bs
|
|
}
|
|
|
|
// this will find the sum of work done ( skipping any repetive nodes )
|
|
// all the information is privided in unique_map
|
|
func (chain *Blockchain) FindTipWorkScore_internal(unique_map map[crypto.Hash]*big.Int, blid crypto.Hash, base crypto.Hash, base_height int64) {
|
|
|
|
tips := chain.Get_Block_Past(blid)
|
|
|
|
for i := range tips {
|
|
if _, ok := unique_map[tips[i]]; !ok {
|
|
|
|
ordered := chain.Is_Block_Topological_order(tips[i])
|
|
|
|
if !ordered {
|
|
chain.FindTipWorkScore_internal(unique_map, tips[i], base, base_height) // recursively process any nodes
|
|
//logger.Infof("IBlock is not ordered %s", tips[i])
|
|
} else if ordered && chain.Load_Block_Topological_order(tips[i]) >= chain.Load_Block_Topological_order(base) {
|
|
chain.FindTipWorkScore_internal(unique_map, tips[i], base, base_height) // recursively process any nodes
|
|
|
|
//logger.Infof("IBlock ordered %s %d %d", tips[i],chain.Load_Block_Topological_order(tips[i]), chain.Load_Block_Topological_order(base) )
|
|
}
|
|
}
|
|
}
|
|
|
|
unique_map[blid] = chain.Load_Block_Difficulty(blid)
|
|
|
|
}
|
|
|
|
type cachekey struct {
|
|
blid crypto.Hash
|
|
base crypto.Hash
|
|
base_height int64
|
|
}
|
|
|
|
// find the score of the tip in reference to a base (NOTE: base is always a sync block otherwise results will be wrong )
|
|
func (chain *Blockchain) FindTipWorkScore(blid crypto.Hash, base crypto.Hash, base_height int64) (map[crypto.Hash]*big.Int, *big.Int) {
|
|
|
|
//logger.Infof("BASE %s",base)
|
|
if tmp_map_i, ok := chain.lrucache_workscore.Get(cachekey{blid, base, base_height}); ok {
|
|
work_score := tmp_map_i.(map[crypto.Hash]*big.Int)
|
|
|
|
map_copy := map[crypto.Hash]*big.Int{}
|
|
score := new(big.Int).SetInt64(0)
|
|
for k, v := range work_score {
|
|
map_copy[k] = v
|
|
score.Add(score, v)
|
|
}
|
|
return map_copy, score
|
|
}
|
|
|
|
bl, err := chain.Load_BL_FROM_ID(blid)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("Block NOT found %s", blid))
|
|
}
|
|
unique_map := map[crypto.Hash]*big.Int{}
|
|
|
|
for i := range bl.Tips {
|
|
if _, ok := unique_map[bl.Tips[i]]; !ok {
|
|
//if chain.Load_Height_for_BL_ID(bl.Tips[i]) > base_height {
|
|
// chain.FindTipWorkScore_internal(unique_map,bl.Tips[i],base,base_height) // recursively process any nodes
|
|
//}
|
|
|
|
ordered := chain.Is_Block_Topological_order(bl.Tips[i])
|
|
if !ordered {
|
|
chain.FindTipWorkScore_internal(unique_map, bl.Tips[i], base, base_height) // recursively process any nodes
|
|
// logger.Infof("Block is not ordered %s", bl.Tips[i])
|
|
} else if ordered && chain.Load_Block_Topological_order(bl.Tips[i]) >= chain.Load_Block_Topological_order(base) {
|
|
chain.FindTipWorkScore_internal(unique_map, bl.Tips[i], base, base_height) // recursively process any nodes
|
|
|
|
// logger.Infof("Block ordered %s %d %d", bl.Tips[i],chain.Load_Block_Topological_order(bl.Tips[i]), chain.Load_Block_Topological_order(base) )
|
|
}
|
|
}
|
|
}
|
|
|
|
if base != blid {
|
|
unique_map[base] = chain.Load_Block_Cumulative_Difficulty(base)
|
|
}
|
|
|
|
unique_map[blid] = chain.Load_Block_Difficulty(blid)
|
|
score := new(big.Int).SetInt64(0)
|
|
for _, v := range unique_map {
|
|
score.Add(score, v)
|
|
}
|
|
|
|
//set in cache, save a copy in cache
|
|
{
|
|
map_copy := map[crypto.Hash]*big.Int{}
|
|
for k, v := range unique_map {
|
|
map_copy[k] = v
|
|
}
|
|
chain.lrucache_workscore.Add(cachekey{blid, base, base_height}, map_copy)
|
|
}
|
|
|
|
return unique_map, score
|
|
|
|
}
|
|
|
|
// find the score of the tip in reference to a base (NOTE: base is always a sync block otherwise results will be wrong )
|
|
func (chain *Blockchain) FindTipWorkScore_duringsave(bl *block.Block, diff *big.Int, base crypto.Hash, base_height int64) (map[crypto.Hash]*big.Int, *big.Int) {
|
|
|
|
blid := bl.GetHash()
|
|
//logger.Infof("BASE %s",base)
|
|
if tmp_map_i, ok := chain.lrucache_workscore.Get(cachekey{blid, base, base_height}); ok {
|
|
work_score := tmp_map_i.(map[crypto.Hash]*big.Int)
|
|
|
|
map_copy := map[crypto.Hash]*big.Int{}
|
|
score := new(big.Int).SetInt64(0)
|
|
for k, v := range work_score {
|
|
map_copy[k] = v
|
|
score.Add(score, v)
|
|
}
|
|
return map_copy, score
|
|
}
|
|
|
|
unique_map := map[crypto.Hash]*big.Int{}
|
|
|
|
for i := range bl.Tips {
|
|
if _, ok := unique_map[bl.Tips[i]]; !ok {
|
|
//if chain.Load_Height_for_BL_ID(bl.Tips[i]) > base_height {
|
|
// chain.FindTipWorkScore_internal(unique_map,bl.Tips[i],base,base_height) // recursively process any nodes
|
|
//}
|
|
|
|
ordered := chain.Is_Block_Topological_order(bl.Tips[i])
|
|
if !ordered {
|
|
chain.FindTipWorkScore_internal(unique_map, bl.Tips[i], base, base_height) // recursively process any nodes
|
|
// logger.Infof("Block is not ordered %s", bl.Tips[i])
|
|
} else if ordered && chain.Load_Block_Topological_order(bl.Tips[i]) >= chain.Load_Block_Topological_order(base) {
|
|
chain.FindTipWorkScore_internal(unique_map, bl.Tips[i], base, base_height) // recursively process any nodes
|
|
|
|
// logger.Infof("Block ordered %s %d %d", bl.Tips[i],chain.Load_Block_Topological_order(bl.Tips[i]), chain.Load_Block_Topological_order(base) )
|
|
}
|
|
}
|
|
}
|
|
|
|
if base != blid {
|
|
unique_map[base] = chain.Load_Block_Cumulative_Difficulty(base)
|
|
}
|
|
|
|
unique_map[blid] = new(big.Int).Set(diff) // use whatever diff was supplied
|
|
score := new(big.Int).SetInt64(0)
|
|
for _, v := range unique_map {
|
|
score.Add(score, v)
|
|
}
|
|
|
|
//set in cache, save a copy in cache
|
|
{
|
|
map_copy := map[crypto.Hash]*big.Int{}
|
|
for k, v := range unique_map {
|
|
map_copy[k] = v
|
|
}
|
|
chain.lrucache_workscore.Add(cachekey{blid, base, base_height}, map_copy)
|
|
}
|
|
|
|
return unique_map, score
|
|
|
|
}
|
|
|
|
// this function finds a common base which can be used to compare tips
|
|
// weight is replace by height
|
|
func (chain *Blockchain) find_common_base(tips []crypto.Hash) (base crypto.Hash, base_height int64) {
|
|
|
|
scores := make([]BlockScore, len(tips), len(tips))
|
|
|
|
// var base crypto.Hash
|
|
var best_height int64
|
|
for i := range tips {
|
|
tip_height := chain.Load_Height_for_BL_ID(tips[i])
|
|
if tip_height > best_height {
|
|
best_height = tip_height
|
|
}
|
|
}
|
|
|
|
for i := range tips {
|
|
scores[i] = chain.FindTipBase(tips[i], best_height) // we should chose the lowest weight
|
|
scores[i].Height = chain.Load_Height_for_BL_ID(scores[i].BLID)
|
|
}
|
|
// base is the lowest height
|
|
sort_ascending_by_height(scores)
|
|
|
|
base = scores[0].BLID
|
|
base_height = scores[0].Height
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// this function finds a common base which can be used to compare tips based on cumulative difficulty
|
|
func (chain *Blockchain) find_best_tip(tips []crypto.Hash, base crypto.Hash, base_height int64) (best crypto.Hash) {
|
|
|
|
tips_scores := make([]BlockScore, len(tips), len(tips))
|
|
|
|
for i := range tips {
|
|
tips_scores[i].BLID = tips[i] // we should chose the lowest weight
|
|
_, tips_scores[i].Cumulative_Difficulty = chain.FindTipWorkScore(tips[i], base, base_height)
|
|
}
|
|
|
|
sort_descending_by_cumulative_difficulty(tips_scores)
|
|
|
|
best = tips_scores[0].BLID
|
|
// base_height = scores[0].Weight
|
|
|
|
return best
|
|
|
|
}
|
|
|
|
func (chain *Blockchain) calculate_mainchain_distance_internal_recursive(unique_map map[crypto.Hash]int64, blid crypto.Hash) {
|
|
tips := chain.Get_Block_Past(blid)
|
|
for i := range tips {
|
|
ordered := chain.Is_Block_Topological_order(tips[i])
|
|
if ordered {
|
|
unique_map[tips[i]] = chain.Load_Height_for_BL_ID(tips[i])
|
|
} else {
|
|
chain.calculate_mainchain_distance_internal_recursive(unique_map, tips[i]) // recursively process any nodes
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// NOTE: some of the past may not be in the main chain right now and need to be travelled recursively
|
|
// distance is number of hops to find a node, which is itself
|
|
func (chain *Blockchain) calculate_mainchain_distance(blid crypto.Hash) int64 {
|
|
|
|
unique_map := map[crypto.Hash]int64{}
|
|
//tips := chain.Get_Block_Past(dbtx, blid)
|
|
|
|
//fmt.Printf("tips %+v \n", tips)
|
|
|
|
// if the block is already in order, no need to look back
|
|
|
|
ordered := chain.Is_Block_Topological_order(blid)
|
|
if ordered {
|
|
unique_map[blid] = chain.Load_Height_for_BL_ID(blid)
|
|
} else {
|
|
chain.calculate_mainchain_distance_internal_recursive(unique_map, blid)
|
|
}
|
|
|
|
//for i := range tips {
|
|
//}
|
|
|
|
//fmt.Printf("unique_map %+v \n", unique_map)
|
|
|
|
lowest_height := int64(0x7FFFFFFFFFFFFFFF) // max possible
|
|
// now we need to find the lowest height
|
|
for k, v := range unique_map {
|
|
_ = k
|
|
if lowest_height >= v {
|
|
lowest_height = v
|
|
}
|
|
}
|
|
|
|
return int64(lowest_height)
|
|
}
|
|
|
|
// converts a DAG's partial order into a full order, this function is recursive
|
|
// generate full order should be only callled on the basis of base blocks which satisfy sync block properties as follows
|
|
// generate full order is called on maximum weight tip at every tip change
|
|
// blocks are ordered recursively, till we find a find a block which is already in the chain
|
|
func (chain *Blockchain) Generate_Full_Order(blid crypto.Hash, base crypto.Hash, base_height int64, level int) (order_bucket []crypto.Hash) {
|
|
|
|
// return from cache if possible
|
|
if tmp_order, ok := chain.lrucache_fullorder.Get(cachekey{blid, base, base_height}); ok {
|
|
order := tmp_order.([]crypto.Hash)
|
|
order_bucket = make([]crypto.Hash, len(order), len(order))
|
|
copy(order_bucket, order[0:])
|
|
return
|
|
}
|
|
|
|
bl, err := chain.Load_BL_FROM_ID(blid)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("Block NOT found %s", blid))
|
|
}
|
|
|
|
if len(bl.Tips) == 0 {
|
|
gbl := Generate_Genesis_Block()
|
|
order_bucket = append(order_bucket, gbl.GetHash())
|
|
return
|
|
}
|
|
|
|
// if the block has been previously ordered, stop the recursion and return it as base
|
|
//if chain.Is_Block_Topological_order(blid){
|
|
if blid == base {
|
|
order_bucket = append(order_bucket, blid)
|
|
// logger.Infof("Generate order base reached base %s", base)
|
|
return
|
|
}
|
|
|
|
// we need to order previous tips first
|
|
var tips_scores []BlockScore
|
|
//tips_scores := make([]BlockScore,len(bl.Tips),len(bl.Tips))
|
|
|
|
node_maps := map[crypto.Hash]map[crypto.Hash]*big.Int{}
|
|
_ = node_maps
|
|
for i := range bl.Tips {
|
|
|
|
ordered := chain.Is_Block_Topological_order(bl.Tips[i])
|
|
|
|
if !ordered {
|
|
var score BlockScore
|
|
score.BLID = bl.Tips[i]
|
|
//node_maps[bl.Tips[i]], score.Weight = chain.FindTipWorkScore(bl.Tips[i],base,base_height)
|
|
score.Cumulative_Difficulty = chain.Load_Block_Cumulative_Difficulty(bl.Tips[i])
|
|
|
|
tips_scores = append(tips_scores, score)
|
|
|
|
} else if ordered && chain.Load_Block_Topological_order(bl.Tips[i]) >= chain.Load_Block_Topological_order(base) {
|
|
|
|
// logger.Infof("Generate order topo order wrt base %d %d", chain.Load_Block_Topological_order(dbtx,bl.Tips[i]), chain.Load_Block_Topological_order(dbtx,base))
|
|
var score BlockScore
|
|
score.BLID = bl.Tips[i]
|
|
|
|
//score.Weight = chain.Load_Block_Cumulative_Difficulty(bl.Tips[i])
|
|
score.Cumulative_Difficulty = chain.Load_Block_Cumulative_Difficulty(bl.Tips[i])
|
|
|
|
tips_scores = append(tips_scores, score)
|
|
}
|
|
|
|
}
|
|
|
|
sort_descending_by_cumulative_difficulty(tips_scores)
|
|
|
|
// now we must add the nodes in the topographical order
|
|
|
|
for i := range tips_scores {
|
|
tmp_bucket := chain.Generate_Full_Order(tips_scores[i].BLID, base, base_height, level+1)
|
|
for j := range tmp_bucket {
|
|
//only process if this block is unsettled
|
|
//if !chain.IsBlockSettled(tmp_bucket[j]) {
|
|
// if order is already decided, do not order it again
|
|
if !sliceExists(order_bucket, tmp_bucket[j]) {
|
|
order_bucket = append(order_bucket, tmp_bucket[j])
|
|
}
|
|
//}
|
|
}
|
|
}
|
|
// add self to the end, since all past nodes have been ordered
|
|
order_bucket = append(order_bucket, blid)
|
|
|
|
// logger.Infof("Generate Order %s %+v %+v", blid , order_bucket, tips_scores)
|
|
|
|
//set in cache, save a copy in cache
|
|
{
|
|
order_copy := make([]crypto.Hash, len(order_bucket), len(order_bucket))
|
|
copy(order_copy, order_bucket[0:])
|
|
|
|
chain.lrucache_fullorder.Add(cachekey{blid, base, base_height}, order_copy)
|
|
}
|
|
|
|
if level == 0 {
|
|
//logger.Warnf("generating full order for block %s %d", blid, level)
|
|
//
|
|
// for i := range order_bucket{
|
|
// logger.Infof("%2d %s", i, order_bucket[i])
|
|
// }
|
|
|
|
//logger.Warnf("generating full order finished")
|
|
}
|
|
return
|
|
}
|
|
|
|
// tells whether the hash already exists in slice
|
|
func sliceExists(slice []crypto.Hash, hash crypto.Hash) bool {
|
|
for i := range slice {
|
|
if slice[i] == hash {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|