2021-12-04 16:42:11 +00:00
// 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 "bytes"
import "runtime/debug"
import "strings"
import "runtime"
import "context"
import "golang.org/x/crypto/sha3"
import "golang.org/x/sync/semaphore"
import "github.com/go-logr/logr"
import "sync/atomic"
import "github.com/hashicorp/golang-lru"
import "github.com/deroproject/derohe/rpc"
import "github.com/deroproject/derohe/config"
import "github.com/deroproject/derohe/cryptography/crypto"
import "github.com/deroproject/derohe/errormsg"
import "github.com/deroproject/derohe/metrics"
2022-01-26 10:05:01 +00:00
import "github.com/deroproject/derohe/dvm"
2021-12-04 16:42:11 +00:00
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/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
Top_ID crypto . Hash // id of the top block
Pruned int64 // until where the chain has been pruned
MiniBlocks * block . MiniBlocksCollection // used for consensus
Tips map [ crypto . Hash ] crypto . Hash // current tips
mining_blocks_cache * lru . Cache // used to cache blocks which have been supplied to mining
cache_IsMiniblockPowValid * lru . Cache // used to cache mini blocks pow test result
cache_IsNonceValidTips * lru . Cache // used to cache nonce tests on specific tips
cache_IsAddressHashValid * lru . Cache // used to cache some outputs
cache_Get_Difficulty_At_Tips * lru . Cache // used to cache some outputs
cache_BlockPast * lru . Cache // used to cache a blocks past
cache_BlockHeight * lru . Cache // used to cache a blocks past
cache_VersionMerkle * lru . Cache // used to cache a versions merkle root
integrator_address rpc . Address // integrator rewards will be given to this address
cache_enabled bool // enables all cache, based on ENV DISABLE_CACHE
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
simulator bool // is simulator mode
P2P_Block_Relayer func ( * block . Complete_Block , uint64 ) // tell p2p to broadcast any block this daemon hash found
P2P_MiniBlock_Relayer func ( mbl block . MiniBlock , peerid uint64 )
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
RPC_NotifyNewMiniBlock * sync . Cond // used to notify rpc that a new mini block has been found
Sync bool // whether the sync is active, used while bootstrapping
sync . RWMutex
}
var logger logr . Logger = logr . Discard ( ) // default discard all logs
// 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
logger = globals . Logger . WithName ( "CORE" )
logger . V ( 1 ) . Info ( "Initialising" )
if err = chain . Store . Initialize ( params ) ; err != nil {
return nil , err
}
chain . Tips = map [ crypto . Hash ] crypto . Hash { }
chain . MiniBlocks = block . CreateMiniBlockCollection ( )
var addr * rpc . Address
if params [ "--integrator-address" ] == nil {
if addr , err = rpc . NewAddress ( strings . TrimSpace ( globals . Config . Dev_Address ) ) ; err != nil {
return nil , err
}
} else {
if addr , err = rpc . NewAddress ( strings . TrimSpace ( params [ "--integrator-address" ] . ( string ) ) ) ; err != nil {
return nil , err
}
}
chain . integrator_address = * addr
logger . Info ( "will use" , "integrator_address" , chain . integrator_address . String ( ) )
if chain . cache_IsMiniblockPowValid , err = lru . New ( 8192 ) ; err != nil { // temporary cache for miniblock difficulty
return nil , err
}
if chain . cache_Get_Difficulty_At_Tips , err = lru . New ( 8192 ) ; err != nil { // temporary cache for difficulty
return nil , err
}
if chain . cache_IsNonceValidTips , err = lru . New ( 100 * 1024 ) ; err != nil { // temporary cache for nonce checks
return nil , err
}
if chain . cache_IsAddressHashValid , err = lru . New ( 100 * 1024 ) ; err != nil { // temporary cache for valid address
return nil , err
}
if chain . mining_blocks_cache , err = lru . New ( 256 ) ; err != nil { // temporary cache for miniing blocks
return nil , err
}
if chain . cache_BlockPast , err = lru . New ( 1024 ) ; err != nil { // temporary cache for a blocks past
return nil , err
}
if chain . cache_BlockHeight , err = lru . New ( 10 * 1024 ) ; err != nil { // temporary cache for a blocks height
return nil , err
}
if chain . cache_VersionMerkle , err = lru . New ( 1024 ) ; err != nil { // temporary cache for a snapshot version
return nil , err
}
chain . cache_enabled = os . Getenv ( "DISABLE_CACHE" ) == "" // disable cache if the environ var is set
if ! chain . cache_enabled {
logger . Info ( "All caching except mining jobs will be disabled" )
}
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
if chain . Mempool , err = mempool . Init_Mempool ( params ) ; err != nil {
return nil , err
}
if chain . Regpool , err = regpool . Init_Regpool ( params ) ; err != nil {
return nil , err
}
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
chain . RPC_NotifyNewMiniBlock = sync . NewCond ( & sync . Mutex { } ) // used by dero daemon to notify all websockets that new miniblock has arrived
if ! chain . Store . IsBalancesIntialized ( ) {
logger . Info ( "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 . Error ( err , "Failed to add genesis block, we can no longer continue." )
return nil , err
}
}
init_hard_forks ( params ) // hard forks must be initialized asap
chain . Initialise_Chain_From_DB ( ) // load the chain from the disk
if chain . Pruned >= 1 {
logger . Info ( "Chain Pruned till" , "topoheight" , chain . Pruned )
}
2022-03-10 11:15:46 +00:00
// detect case if chain was corrupted earlier,so as it can be deleted and resynced
2022-03-04 15:05:04 +00:00
if chain . Pruned < globals . Config . HF1_HEIGHT && globals . IsMainnet ( ) && chain . Get_Height ( ) >= globals . Config . HF1_HEIGHT + 1 {
toporecord , err := chain . Store . Topo_store . Read ( globals . Config . HF1_HEIGHT + 1 )
if err != nil {
panic ( err )
}
var ss * graviton . Snapshot
ss , err = chain . Store . Balance_store . LoadSnapshot ( toporecord . State_Version )
if err != nil {
panic ( err )
}
var namehash_scid crypto . Hash
namehash_scid [ 31 ] = 1
sc_data_tree , err := ss . GetTree ( string ( namehash_scid [ : ] ) )
if err != nil {
panic ( err )
}
if code_bytes , err := sc_data_tree . Get ( dvm . SC_Code_Key ( namehash_scid ) ) ; err == nil {
var v dvm . Variable
if err = v . UnmarshalBinary ( code_bytes ) ; err != nil {
panic ( "Unmarshal error" )
}
if ! strings . Contains ( v . ValueString , "UpdateCode" ) {
logger . Error ( nil , "Chain corruption detected" )
logger . Error ( nil , "You need to delete existing mainnet folder and resync chain again" )
os . Exit ( - 1 )
return nil , err
}
} else {
panic ( err )
}
}
2021-12-04 16:42:11 +00:00
metrics . Version = config . Version . String ( )
go metrics . Dump_metrics_data_directly ( logger , globals . Arguments [ "--node-tag" ] ) // enable metrics if someone needs them
chain . Sync = true
if chain . Get_Height ( ) <= 1 {
if globals . Arguments [ "--fastsync" ] != nil && globals . Arguments [ "--fastsync" ] . ( bool ) {
chain . Sync = ! globals . Arguments [ "--fastsync" ] . ( bool )
}
}
atomic . AddUint32 ( & globals . Subsystem_Active , 1 ) // increment subsystem
globals . Cron . AddFunc ( "@every 360s" , clean_up_valid_cache ) // cleanup valid tx cache
globals . Cron . AddFunc ( "@every 60s" , func ( ) { // mempool house keeping
stable_height := int64 ( 0 )
if r := recover ( ) ; r != nil {
logger . Error ( nil , "Mempool House Keeping triggered panic" , "r" , r , "height" , stable_height )
}
stable_height = chain . Get_Stable_Height ( )
// give mempool an oppurtunity to clean up tx, but only if they are not mined
chain . Mempool . HouseKeeping ( uint64 ( stable_height ) )
top_block_topo_index := chain . Load_TOPO_HEIGHT ( )
2021-12-09 15:59:01 +00:00
if top_block_topo_index < 2 {
2021-12-04 16:42:11 +00:00
return
}
2021-12-09 15:59:01 +00:00
top_block_topo_index -= 2
2021-12-04 16:42:11 +00:00
blid , err := chain . Load_Block_Topological_order_at_index ( top_block_topo_index )
if err != nil {
panic ( err )
}
record_version , err := chain . ReadBlockSnapshotVersion ( blid )
if err != nil {
panic ( err )
}
// give regpool a chance to register
if ss , err := chain . Store . Balance_store . LoadSnapshot ( record_version ) ; err == nil {
if balance_tree , err := ss . GetTree ( config . BALANCE_TREE ) ; err == nil {
chain . Regpool . HouseKeeping ( uint64 ( stable_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 & chain , nil
}
// return integrator address
func ( chain * Blockchain ) IntegratorAddress ( ) rpc . Address {
return chain . integrator_address
}
2022-02-06 07:06:32 +00:00
func ( chain * Blockchain ) SetIntegratorAddress ( addr rpc . Address ) {
chain . integrator_address = addr
}
2021-12-04 16:42:11 +00:00
// 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 ( )
// 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 . 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
2022-03-04 15:05:04 +00:00
logger . V ( 1 ) . Info ( "Reloaded Chain from disk" , "Tips" , chain . Tips , "Height" , best_height )
2021-12-04 16:42:11 +00:00
}
// 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 . Info ( "Stopping Blockchain" )
//chain.Store.Shutdown()
atomic . AddUint32 ( & globals . Subsystem_Active , ^ uint32 ( 0 ) ) // this decrement 1 fom subsystem
logger . Info ( "Stopped Blockchain" )
}
// 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 ( )
defer globals . Recover ( 1 )
bl := cbl . Bl // small pointer to block
block_hash = bl . GetHash ( )
block_logger := logger . WithName ( fmt . Sprintf ( "blid_%s" , block_hash ) ) . V ( 1 )
for k := range chain . Tips { // very fast path
if block_hash == k {
return errormsg . ErrAlreadyExists , false // block already in chain skipping it
}
}
// check if block already exist skip it
if chain . Is_Block_Topological_order ( block_hash ) {
return errormsg . ErrAlreadyExists , false // block already in chain skipping it
}
result = false
height_changed := false
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 . V ( 1 ) . Error ( nil , "Recovered while adding new block" , "blid" , block_hash , "r" , r , "stack" , fmt . Sprintf ( "%s" , string ( debug . Stack ( ) ) ) )
result = false
err = errormsg . ErrPanic
}
if result == true { // block was successfully added, commit it atomically
logger . V ( 2 ) . Info ( "Block successfully accepted by chain" , "blid" , block_hash . String ( ) , "err" , err )
// gracefully try to instrument
func ( ) {
defer func ( ) {
if r := recover ( ) ; r != nil {
logger . V ( 1 ) . Error ( nil , "Recovered while instrumenting" , "r" , r , "stack" , debug . Stack ( ) )
}
} ( )
metrics . Set . GetOrCreateCounter ( "blockchain_tx_total" ) . Add ( len ( cbl . Bl . Tx_hashes ) )
metrics . Set . GetOrCreateHistogram ( "block_txcount_histogram_short" ) . Update ( float64 ( len ( cbl . Bl . Tx_hashes ) ) )
metrics . Set . GetOrCreateHistogram ( "block_processing_duration_histogram_seconds" ) . UpdateDuration ( processing_start )
// tracks counters for tx internals, do we need to serialize everytime, just for stats
{
complete_block_size := 0
for i := 0 ; i < len ( cbl . Txs ) ; i ++ {
tx_size := len ( cbl . Txs [ i ] . Serialize ( ) )
complete_block_size += tx_size
metrics . Set . GetOrCreateHistogram ( "transaction_size_histogram_bytes" ) . Update ( float64 ( tx_size ) )
metrics . Set . GetOrCreateCounter ( fmt . Sprintf ( ` transaction_total { type="%s"} ` , cbl . Txs [ i ] . TransactionType . String ( ) ) ) . Inc ( )
if len ( cbl . Txs [ i ] . Payloads ) >= 1 {
metrics . Set . GetOrCreateHistogram ( "transaction_ring_histogram_short" ) . Update ( float64 ( cbl . Txs [ i ] . Payloads [ 0 ] . Statement . RingSize ) )
metrics . Set . GetOrCreateHistogram ( "transaction_payloads_histogram_short" ) . Update ( float64 ( len ( cbl . Txs [ i ] . Payloads ) ) )
}
}
metrics . Set . GetOrCreateHistogram ( "block_size_histogram_bytes" ) . Update ( 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 ( )
}
} else {
logger . V ( 1 ) . Error ( err , "Block rejected by chain" , "BLID" , block_hash )
}
} ( )
// first of all lets do some quick checks
// before doing extensive checks
result = false
// check if block already exist skip it
if chain . Is_Block_Topological_order ( block_hash ) {
return errormsg . ErrAlreadyExists , false // block already in chain skipping it
}
for k := range chain . Tips {
if block_hash == k {
return errormsg . ErrAlreadyExists , false // block already in chain skipping it
}
}
2021-12-09 15:59:01 +00:00
// only 1 tips allowed in block
if len ( bl . Tips ) > 1 {
block_logger . V ( 1 ) . Error ( fmt . Errorf ( "More than 1 tips present in block rejecting" ) , "" )
2021-12-04 16:42:11 +00:00
return errormsg . ErrPastMissing , false
}
// check whether the tips exist in our chain, if not reject
for i := range bl . Tips {
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
block_logger . V ( 1 ) . Error ( fmt . Errorf ( "Tip is NOT present in chain, skipping it till we get a parent" ) , "" , "missing_tip" , bl . Tips [ i ] . String ( ) )
return errormsg . ErrPastMissing , false
}
}
block_height := chain . Calculate_Height_At_Tips ( bl . Tips )
for i := range bl . Tips { // previous block can be refer to only recent blocks, making some attacks almost impossible
if block_height != chain . Load_Block_Height ( bl . Tips [ i ] ) + 1 {
block_logger . V ( 1 ) . Error ( fmt . Errorf ( "Block rejected since it is in too past" ) , "" , "block_height" , block_height , "tip_height" , chain . Load_Block_Height ( bl . Tips [ i ] ) )
return errormsg . ErrInvalidBlock , false
}
}
if block_height == 0 && int64 ( bl . Height ) == block_height && len ( bl . Tips ) != 0 {
block_logger . Error ( fmt . Errorf ( "Genesis block cannot have tips." ) , "" , "tip_count" , len ( bl . Tips ) )
return errormsg . ErrInvalidBlock , false
}
if len ( bl . Tips ) >= 1 && bl . Height == 0 {
block_logger . Error ( fmt . Errorf ( "Genesis block can only be at height 0" ) , "" , "tip_count" , len ( bl . Tips ) )
return errormsg . ErrInvalidBlock , false
}
2021-12-09 15:59:01 +00:00
//if block_height != 0 && block_height < chain.Get_Stable_Height() {
// block_logger.Error(fmt.Errorf("Block rejected since it is stale."), "", "stable height", chain.Get_Stable_Height(), "block height", block_height)
// return errormsg.ErrInvalidBlock, false
//}
2021-12-04 16:42:11 +00:00
// make sure time is NOT into future,
// if clock diff is more than 50 millisecs, reject the block
if bl . Timestamp > ( uint64 ( globals . Time ( ) . UTC ( ) . UnixMilli ( ) + 50 ) ) { // give 50 millisec passing
block_logger . Error ( fmt . Errorf ( "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 {
//fmt.Printf("timestamp prev %d cur timestamp %d\n", chain.Load_Block_Timestamp(bl.Tips[i]), bl.Timestamp)
block_logger . Error ( fmt . Errorf ( "Block timestamp is less than its parent." ) , "rejecting block" )
return errormsg . ErrInvalidTimestamp , false
}
}
// check whether the major version ( hard fork) is valid
if ! chain . Check_Block_Version ( bl ) {
block_logger . Error ( fmt . Errorf ( "Rejecting !! Block has invalid fork version" ) , "actual" , bl . Major_Version , "expected" , chain . Get_Current_Version_at_Height ( chain . Calculate_Height_At_Tips ( bl . Tips ) ) )
return errormsg . ErrInvalidBlock , false
}
// verify whether the tips are reachable from one another
if bl . Height >= 2 && ! chain . CheckDagStructure ( bl . Tips ) {
block_logger . Error ( fmt . Errorf ( "Rejecting !! Block has invalid reachability" ) , "Invalid rechability" , "tips" , bl . Tips )
return errormsg . ErrInvalidBlock , false
}
// if the block is referencing any past tip too distant into its history
for i := range bl . Tips {
if int64 ( bl . Height ) - 1 != chain . Load_Block_Height ( bl . Tips [ i ] ) {
block_logger . Error ( fmt . Errorf ( "Rusty TIP mined by ROGUE miner discarding block" ) , "" , "best height" , bl . Height , "deviation" , int64 ( bl . Height ) - chain . Load_Block_Height ( bl . Tips [ i ] ) )
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 . Error ( fmt . Errorf ( "Block is bigger than max permitted" ) , "Rejecting" , "Actual" , block_size , "MAX" , config . STARGATE_HE_MAX_BLOCK_SIZE )
return errormsg . ErrInvalidSize , false
}
}
}
// verify everything related to miniblocks in one go
if ! chain . simulator {
if err = Verify_MiniBlocks ( * cbl . Bl ) ; err != nil { // verifies the miniblocks all refer to current block
return err , false
}
if bl . Height != 0 { // a genesis block doesn't have miniblock
// verify hash of miniblock for corruption
if err = chain . Verify_MiniBlocks_HashCheck ( cbl ) ; err != nil {
return err , false
}
}
for _ , mbl := range bl . MiniBlocks {
var miner_hash crypto . Hash
copy ( miner_hash [ : ] , mbl . KeyHash [ : ] )
if mbl . Final == false && ! chain . IsAddressHashValid ( true , miner_hash ) {
err = fmt . Errorf ( "miner address not registered" )
return err , false
}
}
// verify Pow of miniblocks
for i , mbl := range bl . MiniBlocks {
if ! chain . VerifyMiniblockPoW ( bl , mbl ) {
block_logger . Error ( fmt . Errorf ( "MiniBlock has invalid PoW" ) , "rejecting" , "i" , i )
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 . Error ( fmt . Errorf ( "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 . Error ( fmt . Errorf ( "Miner tx failed it is not coinbase" ) , "rejecting" )
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
}
}
}
// 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 . Error ( fmt . Errorf ( "Missing TX" ) , "Incomplete block" , "expected_tx" , len ( bl . Tx_hashes ) , "actual_tx" , 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 . Error ( fmt . Errorf ( "duplicate TX" ) , "Incomplete block" , "duplicate count" , len ( bl . Tx_hashes ) - len ( tx_checklist ) )
return errormsg . ErrInvalidBlock , false
}
for i , tx := range cbl . Txs {
if tx . Height >= bl . Height {
block_logger . Error ( fmt . Errorf ( "Invalid TX Height" ) , "TX height cannot be more than block" , "txid" , cbl . Txs [ i ] . GetHash ( ) . String ( ) )
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 . Error ( fmt . Errorf ( "Missing TX" ) , "TX missing" , "txid" , tx_hash . String ( ) )
return errormsg . ErrInvalidBlock , false
}
}
}
// another check, whether the block contains any duplicate registration within the block
// block wide duplicate input detector
{
reg_map := map [ string ] bool { }
for i := 0 ; i < len ( cbl . Txs ) ; i ++ {
if cbl . Txs [ i ] . TransactionType == transaction . REGISTRATION {
if _ , ok := reg_map [ string ( cbl . Txs [ i ] . MinerAddress [ : ] ) ] ; ok {
block_logger . Error ( fmt . Errorf ( "Double Registration TX" ) , "duplicate registration" , "txid" , cbl . Txs [ i ] . GetHash ( ) )
return errormsg . ErrTXDoubleSpend , false
}
2022-02-06 07:06:32 +00:00
tx_hash := cbl . Txs [ i ] . GetHash ( )
if chain . simulator == false && tx_hash [ 0 ] != 0 && tx_hash [ 1 ] != 0 {
return fmt . Errorf ( "Registration TX has not solved PoW" ) , false
}
2021-12-04 16:42:11 +00:00
reg_map [ string ( cbl . Txs [ i ] . MinerAddress [ : ] ) ] = true
}
}
}
// another check, whether the block contains any colliding txs
if len ( bl . Tips ) == 2 {
for i := range cbl . Txs {
if cbl . Txs [ i ] . IsProofRequired ( ) {
if cbl . Txs [ i ] . BLID == bl . Tips [ 0 ] || cbl . Txs [ i ] . BLID == bl . Tips [ 1 ] {
block_logger . Error ( fmt . Errorf ( "Colliding TXs" ) , "may contain colliding transactions" , "txid" , 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 {
for j := range cbl . Txs [ i ] . Payloads {
if _ , ok := nonce_map [ cbl . Txs [ i ] . Payloads [ j ] . Proof . Nonce ( ) ] ; ok {
block_logger . Error ( fmt . Errorf ( "Double Spend TX within block" ) , "duplicate nonce" , "txid" , cbl . Txs [ i ] . GetHash ( ) )
return errormsg . ErrTXDoubleSpend , false
}
nonce_map [ cbl . Txs [ i ] . Payloads [ j ] . Proof . Nonce ( ) ] = true
}
}
}
}
// all blocks except genesis will have history
// so make sure txs are connected
if bl . Height >= 1 && len ( cbl . Txs ) > 0 {
history := map [ crypto . Hash ] bool { }
var history_array [ ] crypto . Hash
for i := range bl . Tips {
h := int64 ( bl . Height ) - 25
if h < 0 {
h = 0
}
history_array = append ( history_array , chain . get_ordered_past ( bl . Tips [ i ] , h ) ... )
}
for _ , h := range history_array {
history [ h ] = true
}
block_height = chain . Calculate_Height_At_Tips ( bl . Tips )
for i , tx := range cbl . Txs {
if cbl . Txs [ i ] . TransactionType == transaction . NORMAL || cbl . Txs [ i ] . TransactionType == transaction . BURN_TX || cbl . Txs [ i ] . TransactionType == transaction . SC_TX {
if history [ cbl . Txs [ i ] . BLID ] != true {
block_logger . Error ( fmt . Errorf ( "Double Spend TX within block" ) , "unreferable history" , "txid" , cbl . Txs [ i ] . GetHash ( ) )
return errormsg . ErrTXDoubleSpend , false
}
if tx . Height != uint64 ( chain . Load_Height_for_BL_ID ( cbl . Txs [ i ] . BLID ) ) {
block_logger . Error ( fmt . Errorf ( "Double Spend TX within block" ) , "blid/height mismatch" , "txid" , cbl . Txs [ i ] . GetHash ( ) )
return errormsg . ErrTXDoubleSpend , false
}
if block_height - int64 ( tx . Height ) < TX_VALIDITY_HEIGHT {
} else {
block_logger . Error ( fmt . Errorf ( "Double Spend TX within block" ) , "long distance tx not supported" , "txid" , cbl . Txs [ i ] . GetHash ( ) )
return errormsg . ErrTXDoubleSpend , false
}
if tx . TransactionType == transaction . SC_TX {
if tx . SCDATA . Has ( rpc . SCACTION , rpc . DataUint64 ) {
if rpc . SC_INSTALL == rpc . SC_ACTION ( tx . SCDATA . Value ( rpc . SCACTION , rpc . DataUint64 ) . ( uint64 ) ) {
txid := tx . GetHash ( )
if txid [ 0 ] < 0x80 || txid [ 31 ] < 0x80 { // last byte should be more than 0x80
block_logger . Error ( fmt . Errorf ( "Invalid SCID" ) , "SCID installing tx must end with >0x80 byte" , "txid" , cbl . Txs [ i ] . GetHash ( ) )
return errormsg . ErrTXDoubleSpend , false
}
}
}
}
}
}
}
// we need to verify each tx with tips
{
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 ) )
sem := semaphore . NewWeighted ( int64 ( runtime . NumCPU ( ) ) )
for i := 0 ; i < len ( cbl . Txs ) ; i ++ {
sem . Acquire ( context . Background ( ) , 1 )
go func ( j int ) {
defer sem . Release ( 1 )
defer wg . Done ( )
if atomic . LoadInt32 ( & fail_count ) >= 1 { // fail fast
return
}
if err := chain . Verify_Transaction_NonCoinbase_CheckNonce_Tips ( hf_version , cbl . Txs [ j ] , bl . Tips ) ; err != nil { // transaction verification failed
atomic . AddInt32 ( & fail_count , 1 ) // increase fail count by 1
block_logger . Error ( err , "tx nonce verification failed" , "txid" , cbl . Txs [ j ] . GetHash ( ) )
}
} ( i )
}
wg . Wait ( ) // wait for verifications to finish
if fail_count > 0 { // check the result
block_logger . Error ( fmt . Errorf ( "TX nonce verification failed" ) , "rejecting block" )
return errormsg . ErrInvalidTX , 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
sem := semaphore . NewWeighted ( int64 ( runtime . NumCPU ( ) ) )
for i := 0 ; i < len ( cbl . Txs ) ; i ++ {
sem . Acquire ( context . Background ( ) , 1 )
go func ( j int ) {
defer sem . Release ( 1 )
defer wg . Done ( )
if atomic . LoadInt32 ( & fail_count ) >= 1 { // fail fast
return
}
if err := chain . Verify_Transaction_NonCoinbase ( cbl . Txs [ j ] ) ; err != nil { // transaction verification failed
atomic . AddInt32 ( & fail_count , 1 ) // increase fail count by 1
block_logger . Error ( err , "tx verification failed" , "txid" , cbl . Txs [ j ] . GetHash ( ) )
}
} ( i )
}
wg . Wait ( ) // wait for verifications to finish
if fail_count > 0 { // check the result
block_logger . Error ( fmt . Errorf ( "TX verification failed" ) , "rejecting block" )
return errormsg . ErrInvalidTX , false
}
}
// we need to do more checks but only after tx has been expanded
{
var check_data cbl_verify // used to verify sanity of new block
for i := 0 ; i < len ( cbl . Txs ) ; i ++ {
if ! ( cbl . Txs [ i ] . IsCoinbase ( ) || cbl . Txs [ i ] . IsRegistration ( ) ) { // all other tx must go through this check
if err = check_data . check ( cbl . Txs [ i ] , false ) ; err == nil {
check_data . check ( cbl . Txs [ i ] , true ) // keep in record for future tx
} else {
block_logger . Error ( err , "Invalid TX within block" , "txid" , cbl . Txs [ i ] . GetHash ( ) )
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 , 0 )
// 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
2021-12-09 15:59:01 +00:00
//atomic.StoreInt64(&chain.Height, height)
2021-12-04 16:42:11 +00:00
height_changed = true
2022-03-04 15:05:04 +00:00
block_logger . Info ( "Chain extended" , "new height" , height )
2021-12-04 16:42:11 +00:00
} else {
2022-03-04 15:05:04 +00:00
block_logger . Info ( "Chain extended but height is same" , "new height" , height )
2021-12-04 16:42:11 +00:00
}
2021-12-09 15:59:01 +00:00
{
// 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
2021-12-04 16:42:11 +00:00
2021-12-09 15:59:01 +00:00
bl_current_hash := cbl . Bl . GetHash ( )
bl_current := cbl . Bl
current_topo_block := bl_current . Height
logger . V ( 3 ) . Info ( "will insert block " , "blid" , bl_current_hash . String ( ) )
2021-12-04 16:42:11 +00:00
2021-12-09 15:59:01 +00:00
//fmt.Printf("\ni %d bl %+v\n",i, bl_current)
2021-12-04 16:42:11 +00:00
2021-12-09 15:59:01 +00:00
height_current := chain . Calculate_Height_At_Tips ( bl_current . Tips )
hard_fork_version_current := chain . Get_Current_Version_at_Height ( height_current )
// generate miner TX rewards as per client protocol
if hard_fork_version_current == 1 {
2021-12-04 16:42:11 +00:00
}
2021-12-09 15:59:01 +00:00
var balance_tree , sc_meta * graviton . Tree
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 { // we already have a block before us, use it
2021-12-04 16:42:11 +00:00
2021-12-09 15:59:01 +00:00
record_version , err := chain . ReadBlockSnapshotVersion ( bl . Tips [ 0 ] )
if err != nil {
panic ( err )
2021-12-04 16:42:11 +00:00
}
2021-12-09 15:59:01 +00:00
logger . V ( 1 ) . Info ( "reading block snapshot" , "blid" , bl . Tips [ 0 ] , "record_version" , record_version )
2021-12-04 16:42:11 +00:00
2021-12-09 15:59:01 +00:00
ss , err = chain . Store . Balance_store . LoadSnapshot ( record_version )
if err != nil {
panic ( err )
2021-12-04 16:42:11 +00:00
}
2021-12-09 15:59:01 +00:00
}
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 )
}
2021-12-04 16:42:11 +00:00
2021-12-09 15:59:01 +00:00
fees_collected := uint64 ( 0 )
2021-12-04 16:42:11 +00:00
2021-12-09 15:59:01 +00:00
// side blocks only represent chain strenth , else they are are ignored
// this means they donot get any reward , 0 reward
// their transactions are ignored
2021-12-04 16:42:11 +00:00
2021-12-09 15:59:01 +00:00
//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
2021-12-04 16:42:11 +00:00
2021-12-09 15:59:01 +00:00
{
2021-12-04 16:42:11 +00:00
2021-12-09 15:59:01 +00:00
sc_change_cache := map [ crypto . Hash ] * graviton . Tree { } // cache entire changes for entire block
// install hardcoded contracts
if err = chain . install_hardcoded_contracts ( sc_change_cache , ss , balance_tree , sc_meta , bl_current . Height ) ; err != nil {
panic ( err )
2021-12-04 16:42:11 +00:00
}
2021-12-09 15:59:01 +00:00
for _ , txhash := range bl_current . Tx_hashes { // execute all the transactions
if tx_bytes , err := chain . Store . Block_tx_store . ReadTX ( txhash ) ; err != nil {
2021-12-04 16:42:11 +00:00
panic ( err )
2021-12-09 15:59:01 +00:00
} else {
var tx transaction . Transaction
if err = tx . Deserialize ( 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 , bl_current . Height )
//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 . Timestamp / 1000 , 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
2021-12-04 16:42:11 +00:00
}
2021-12-09 15:59:01 +00:00
}
2021-12-04 16:42:11 +00:00
2021-12-09 15:59:01 +00:00
// at this point, we must commit all the SCs, so entire tree hash is interlinked
for scid , v := range sc_change_cache {
2022-01-26 10:05:01 +00:00
meta_bytes , err := sc_meta . Get ( dvm . SC_Meta_Key ( scid ) )
2021-12-04 16:42:11 +00:00
if err != nil {
panic ( err )
}
2022-01-26 10:05:01 +00:00
var meta dvm . SC_META_DATA // the meta contains metadata about SC
2021-12-04 16:42:11 +00:00
2022-03-04 15:05:04 +00:00
if bl_current . Height < uint64 ( globals . Config . HF2_HEIGHT ) {
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 ( dvm . SC_Meta_Key ( scid ) , meta . MarshalBinary ( ) )
} else {
if err := meta . UnmarshalBinaryGood ( meta_bytes ) ; err != nil {
panic ( err )
}
if meta . DataHash , err = v . Hash ( ) ; err != nil { // encode data tree hash
panic ( err )
}
sc_meta . Put ( dvm . SC_Meta_Key ( scid ) , meta . MarshalBinaryGood ( ) )
2021-12-04 16:42:11 +00:00
}
2021-12-09 15:59:01 +00:00
data_trees = append ( data_trees , v )
2021-12-04 16:42:11 +00:00
2021-12-09 15:59:01 +00:00
/ * 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 )
} * /
2021-12-04 16:42:11 +00:00
2021-12-09 15:59:01 +00:00
}
2021-12-04 16:42:11 +00:00
2021-12-09 15:59:01 +00:00
chain . process_miner_transaction ( bl_current , bl_current . Height == 0 , balance_tree , fees_collected , bl_current . Height )
}
2021-12-04 16:42:11 +00:00
2021-12-09 15:59:01 +00:00
// 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 )
}
2021-12-04 16:42:11 +00:00
2021-12-09 15:59:01 +00:00
chain . StoreBlock ( bl_current , commit_version )
2021-12-04 16:42:11 +00:00
2021-12-09 15:59:01 +00:00
if height_changed {
// we need to write history until the entire chain is fixed
2021-12-04 16:42:11 +00:00
2021-12-09 15:59:01 +00:00
fix_bl := bl_current
fix_pos := bl_current . Height
fix_commit_version := commit_version
for ; ; fix_pos -- {
2021-12-04 16:42:11 +00:00
2021-12-09 15:59:01 +00:00
chain . Store . Topo_store . Write ( int64 ( fix_bl . Height ) , fix_bl . GetHash ( ) , fix_commit_version , int64 ( fix_bl . Height ) )
logger . V ( 1 ) . Info ( "fixed loop" , "topo" , fix_pos )
2021-12-04 16:42:11 +00:00
2021-12-09 15:59:01 +00:00
if fix_pos == 0 { // break if we reached genesis
break
}
r , err := chain . Store . Topo_store . Read ( int64 ( fix_pos - 1 ) )
if err != nil {
panic ( err )
2021-12-04 16:42:11 +00:00
}
2021-12-09 15:59:01 +00:00
if fix_bl . Tips [ 0 ] == r . BLOCK_ID { // break if we reached a common point
break
}
2021-12-04 16:42:11 +00:00
2021-12-09 15:59:01 +00:00
// prepare for another round
fix_commit_version , err = chain . ReadBlockSnapshotVersion ( fix_bl . Tips [ 0 ] )
if err != nil {
panic ( err )
}
2021-12-04 16:42:11 +00:00
2021-12-09 15:59:01 +00:00
fix_bl , err = chain . Load_BL_FROM_ID ( fix_bl . Tips [ 0 ] )
2021-12-04 16:42:11 +00:00
if err != nil {
panic ( err )
}
2021-12-09 15:59:01 +00:00
2021-12-04 16:42:11 +00:00
}
}
2021-12-09 15:59:01 +00:00
if logger . V ( 1 ) . Enabled ( ) {
merkle_root , err := chain . Load_Merkle_Hash ( commit_version )
if err != nil {
panic ( err )
}
logger . V ( 1 ) . Info ( "storing topo" , "topo" , int64 ( bl_current . Height ) , "blid" , bl_current_hash , "topoheight" , current_topo_block , "commit_version" , commit_version , "committed_merkle" , merkle_root )
}
2021-12-04 16:42:11 +00:00
}
{
// calculate new set of tips
// this is done by removing all known tips which are in the past
// and add this block as tip
old_tips := chain . Get_TIPS ( )
var tips [ ] crypto . Hash
new_tips := map [ crypto . Hash ] crypto . Hash { }
for i := range old_tips {
for j := range bl . Tips {
if bl . Tips [ j ] == old_tips [ i ] {
goto skip_tip
}
}
tips = append ( tips , old_tips [ i ] )
skip_tip :
}
tips = append ( tips , bl . GetHash ( ) ) // add current block as new tip
chain_height := chain . Get_Height ( )
for i := range tips {
tip_height := int64 ( chain . Load_Height_for_BL_ID ( tips [ i ] ) )
if ( chain_height - tip_height ) == 0 {
new_tips [ tips [ i ] ] = tips [ i ]
} else { // this should be a rare event, unless network has very high latency
logger . V ( 2 ) . Info ( "Rusty TIP declared stale" , "tip" , tips [ i ] , "best height" , chain_height , "tip_height" , tip_height )
//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
}
}
//block_logger.Info("New tips(after adding block) ", "tips", new_tips)
chain . Tips = new_tips
}
// every 2000 block print a line
if chain . Get_Height ( ) % 2000 == 0 {
2022-03-04 15:05:04 +00:00
block_logger . Info ( fmt . Sprintf ( "Chain Height %d" , chain . Get_Height ( ) ) )
2021-12-04 16:42:11 +00:00
}
purge_count := chain . MiniBlocks . PurgeHeight ( chain . Get_Stable_Height ( ) ) // purge all miniblocks upto this height
logger . V ( 2 ) . Info ( "Purged miniblock" , "count" , purge_count )
result = true
// TODO fix hard fork
// maintain hard fork votes to keep them SANE
//chain.Recount_Votes() // does not return anything
return // run any handlers necesary to atomically
}
// 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 {
return chain . Get_Height ( ) - config . STABLE_LIMIT
}
// 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
}
// check whether the block is a tip
func ( chain * Blockchain ) Is_Block_Tip ( blid crypto . Hash ) ( result bool ) {
for k := range chain . Tips {
if blid == k {
return true
}
}
return false
}
func ( chain * Blockchain ) Get_Difficulty ( ) uint64 {
return chain . Get_Difficulty_At_Tips ( chain . Get_TIPS ( ) ) . Uint64 ( )
}
func ( chain * Blockchain ) Get_Network_HashRate ( ) uint64 {
return chain . Get_Difficulty ( )
}
// 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 )
}
// 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
2022-02-06 07:06:32 +00:00
tx_hash := tx . GetHash ( )
2022-03-18 15:52:33 +00:00
if chain . simulator == false && ! ( tx_hash [ 0 ] == 0 && tx_hash [ 1 ] == 0 && tx_hash [ 2 ] == 0 ) {
2022-02-06 07:06:32 +00:00
return fmt . Errorf ( "TX doesn't solve Pow" )
}
2021-12-04 16:42:11 +00:00
// 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 fmt . Errorf ( "address already registered" )
} 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" )
}
txhash := tx . GetHash ( )
// Coin base TX can not come through this path
if tx . IsCoinbase ( ) {
logger . Error ( fmt . Errorf ( "coinbase tx cannot appear in mempool" ) , "tx_rejected" , "txid" , txhash )
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 )
}
2022-01-26 10:05:01 +00:00
toporecord , err := chain . Store . Topo_store . Read ( int64 ( tx . Height ) )
if err != nil {
return fmt . Errorf ( "TX %s rejected height(%d) reference not found" , txhash , tx . Height )
}
if toporecord . BLOCK_ID != tx . BLID {
return fmt . Errorf ( "TX %s rejected block (%s) reference not found" , txhash , tx . BLID )
}
2021-12-04 16:42:11 +00:00
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 . Error ( fmt . Errorf ( "Huge TX" ) , "TX rejected" , "Actual Size" , len ( tx . Serialize ( ) ) , "max possible " , 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
//logger.WithFields(log.Fields{"txid": txhash}).Warnf("TX fees check disabled provided fee %d calculated fee %d", provided_fee, calculated_fee)
if ! chain . simulator && calculated_fee > provided_fee {
err = fmt . Errorf ( "TX %s rejected due to low fees provided fee %d calculated fee %d" , txhash , provided_fee , calculated_fee )
return err
}
if tx . TransactionType == transaction . SC_TX {
if tx . SCDATA . Has ( rpc . SCACTION , rpc . DataUint64 ) {
if rpc . SC_INSTALL == rpc . SC_ACTION ( tx . SCDATA . Value ( rpc . SCACTION , rpc . DataUint64 ) . ( uint64 ) ) {
txid := tx . GetHash ( )
if txid [ 0 ] < 0x80 || txid [ 31 ] < 0x80 { // last byte should be more than 0x80
return fmt . Errorf ( "Invalid SCID ID,it must not start with 0x80" )
}
}
}
}
if err := chain . Verify_Transaction_NonCoinbase_CheckNonce_Tips ( hf_version , tx , chain . Get_TIPS ( ) ) ; err != nil { // transaction verification failed
logger . V ( 2 ) . Error ( err , "Incoming TX nonce verification failed" , "txid" , txhash , "stacktrace" , globals . StackTrace ( false ) )
return fmt . Errorf ( "Incoming TX %s nonce verification failed, err %s" , txhash , err )
}
if err := chain . Verify_Transaction_NonCoinbase ( tx ) ; err != nil {
logger . V ( 2 ) . Error ( err , "Incoming TX could not be verified" , "txid" , txhash )
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)
return nil
} else {
//rlog.Tracef(2, "TX %s rejected by pool by mempool", txhash)
return fmt . Errorf ( "TX %s rejected by pool by mempool" , txhash )
}
}
// side blocks are blocks which lost the race the to become part
// of main chain,
// a block is a side block if it satisfies the following condition
// if no other block exists on this height before this
// 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 )
2021-12-09 15:59:01 +00:00
if block_topoheight >= 0 {
2021-12-04 16:42:11 +00:00
return false
}
2021-12-09 15:59:01 +00:00
return true
2021-12-04 16:42:11 +00:00
}
// 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 { // genesis cannot be side block
return false
}
toporecord , err := chain . Store . Topo_store . Read ( block_topoheight - 1 )
if err != nil {
panic ( "Could not load block from previous order" )
}
if block_height == toporecord . Height { // lost race (or byzantine behaviour)
return true
}
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 . Deserialize ( tx_bytes ) ; err != nil {
return
}
blids_list := chain . Find_Blocks_Height_Range ( int64 ( tx . Height + 1 ) , int64 ( tx . Height + 1 ) + 2 * TX_VALIDITY_HEIGHT )
var exist_list [ ] crypto . Hash
for _ , blid := range blids_list {
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 )
}
}
}
for _ , blid := range exist_list {
2021-12-09 15:59:01 +00:00
valid_blid = blid
valid = true
2021-12-04 16:42:11 +00:00
}
return
}
// 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
}
// 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 ( )
if rewind_count == 0 {
return
}
top_block_topo_index := chain . Load_TOPO_HEIGHT ( )
rewinded := int64 ( 0 )
for {
r , err := chain . Store . Topo_store . Read ( top_block_topo_index - rewinded )
if err != nil {
panic ( err )
}
if top_block_topo_index - rewinded < 1 || rewinded >= int64 ( rewind_count ) {
break
}
if r . Height == 1 {
break
}
rewinded ++
}
for i := int64 ( 0 ) ; i < rewinded ; i ++ {
chain . Store . Topo_store . Clean ( top_block_topo_index - i )
}
2021-12-09 15:59:01 +00:00
chain . MiniBlocks . PurgeHeight ( 0xffffffffffffff ) // purge all miniblocks upto this height
2021-12-04 16:42:11 +00:00
return true
}
// this is part of consensus rule, 2 tips cannot refer to different parents
func ( chain * Blockchain ) CheckDagStructure ( tips [ ] crypto . Hash ) bool {
if chain . Load_Height_for_BL_ID ( tips [ 0 ] ) <= 2 { // before this we cannot complete checks
return true
}
for i := range tips { // first make sure all the tips are at same height
if chain . Load_Height_for_BL_ID ( tips [ 0 ] ) != chain . Load_Height_for_BL_ID ( tips [ i ] ) {
return false
}
}
if len ( tips ) == 2 && tips [ 0 ] == tips [ 1 ] {
return false
}
switch len ( tips ) {
case 1 :
past := chain . Get_Block_Past ( tips [ 0 ] )
switch len ( past ) {
case 1 : // nothing to do here
case 2 :
if chain . Load_Height_for_BL_ID ( past [ 0 ] ) != chain . Load_Height_for_BL_ID ( past [ 1 ] ) {
return false
}
past0 := chain . Get_Block_Past ( past [ 0 ] )
if len ( past0 ) != 1 { //only 1 tip in past
return false
}
past1 := chain . Get_Block_Past ( past [ 1 ] )
if len ( past1 ) != 1 { //only 1 tip in past
fmt . Printf ( "checking tips %+v past1 failed %d for %s\n" , tips , len ( past0 ) , tips [ 0 ] )
return false
}
if past0 [ 0 ] != past1 [ 0 ] { // avoid any tips which fail reachability test
return false
}
}
case 2 : // lets make sure both tips originate from same parent
pasttip0 := chain . Get_Block_Past ( tips [ 0 ] )
if len ( pasttip0 ) != 1 { //only 1 tip in past
return false
}
pasttip1 := chain . Get_Block_Past ( tips [ 1 ] )
if len ( pasttip0 ) != len ( pasttip1 ) {
return false
}
if pasttip0 [ 0 ] != pasttip1 [ 0 ] { // avoid any tips which fail reachability test
return false
}
default :
return false
}
return true
}
// 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 false
}
return true
}
// we will collect atleast 50 blocks or till genesis
func ( chain * Blockchain ) get_ordered_past ( tip crypto . Hash , tillheight int64 ) ( order [ ] crypto . Hash ) {
order = append ( order , tip )
current := tip
for chain . Load_Height_for_BL_ID ( current ) > tillheight {
past := chain . Get_Block_Past ( current )
switch len ( past ) {
case 0 : // we reached genesis return
return
case 1 :
order = append ( order , past [ 0 ] )
current = past [ 0 ]
case 2 :
if bytes . Compare ( past [ 0 ] [ : ] , past [ 1 ] [ : ] ) < 0 {
order = append ( order , past [ 0 ] , past [ 1 ] )
} else {
order = append ( order , past [ 1 ] , past [ 0 ] )
}
current = past [ 0 ]
default :
panic ( "data corruption" )
}
}
for i , j := 0 , len ( order ) - 1 ; i < j ; i , j = i + 1 , j - 1 {
order [ i ] , order [ j ] = order [ j ] , order [ i ]
}
return
}
2021-12-09 15:59:01 +00:00
// this will flip chain top, depending on which block has more work
// more worked block is normally identified in < 2 secs
func ( chain * Blockchain ) flip_top ( ) {
2022-03-03 10:49:59 +00:00
return
2021-12-09 15:59:01 +00:00
chain . Lock ( )
defer chain . Unlock ( )
height := chain . Get_Height ( )
var tips [ ] crypto . Hash
if len ( chain . Get_TIPS ( ) ) <= 1 {
return
}
// lets fill in the tips from miniblocks, list is already sorted
if keys := chain . MiniBlocks . GetAllKeys ( height + 1 ) ; len ( keys ) >= 1 {
top_id := chain . Get_Top_ID ( )
for _ , key := range keys {
mbls := chain . MiniBlocks . GetAllMiniBlocks ( key )
if len ( mbls ) < 1 {
continue
}
mbl := mbls [ 0 ]
tips = tips [ : 0 ]
tip := convert_uint32_to_crypto_hash ( mbl . Past [ 0 ] )
if ehash , ok := chain . ExpandMiniBlockTip ( tip ) ; ok {
if ehash != top_id { // we need to flip top
fix_commit_version , err := chain . ReadBlockSnapshotVersion ( ehash )
if err != nil {
panic ( err )
}
fix_bl , err := chain . Load_BL_FROM_ID ( ehash )
if err != nil {
panic ( err )
}
chain . Store . Topo_store . Write ( int64 ( fix_bl . Height ) , ehash , fix_commit_version , int64 ( fix_bl . Height ) )
// fmt.Printf("flipped top from %s to %s\n", top_id, ehash)
return
}
}
}
}
}