2020-12-19 10:01:29 +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
import "os"
import "fmt"
import "math"
import "path/filepath"
import "encoding/binary"
import "github.com/deroproject/derohe/config"
import "github.com/deroproject/derohe/crypto"
type TopoRecord struct {
BLOCK_ID [ 32 ] byte
State_Version uint64
Height int64
}
const TOPORECORD_SIZE int64 = 48
// this file implements a filesystem store which is used to store topo to block mapping directly in the file system and the state version directly tied
type storetopofs struct {
topomapping * os . File
}
2020-12-27 13:44:23 +00:00
func ( s TopoRecord ) String ( ) string {
return fmt . Sprintf ( "blid %x state version %d height %d" , s . BLOCK_ID [ : ] , s . State_Version , s . Height )
2020-12-21 15:38:03 +00:00
}
2020-12-19 10:01:29 +00:00
func ( s * storetopofs ) Open ( basedir string ) ( err error ) {
s . topomapping , err = os . OpenFile ( filepath . Join ( basedir , "topo.map" ) , os . O_RDWR | os . O_CREATE , 0700 )
return err
}
func ( s * storetopofs ) Count ( ) int64 {
fstat , err := s . topomapping . Stat ( )
if err != nil {
panic ( fmt . Sprintf ( "cannot stat topofile. err %s" , err ) )
}
2020-12-27 13:44:23 +00:00
count := int64 ( fstat . Size ( ) / int64 ( TOPORECORD_SIZE ) )
for ; count >= 1 ; count -- {
if record , err := s . Read ( count - 1 ) ; err == nil && ! record . IsClean ( ) {
break
} else if err != nil {
panic ( fmt . Sprintf ( "cannot read topofile. err %s" , err ) )
}
}
return count
2020-12-19 10:01:29 +00:00
}
// it basically represents Load_Block_Topological_order_at_index
// reads an entry at specific location
func ( s * storetopofs ) Read ( index int64 ) ( TopoRecord , error ) {
var buf [ TOPORECORD_SIZE ] byte
var record TopoRecord
if n , err := s . topomapping . ReadAt ( buf [ : ] , index * TOPORECORD_SIZE ) ; int64 ( n ) != TOPORECORD_SIZE {
return record , err
}
copy ( record . BLOCK_ID [ : ] , buf [ : ] )
record . State_Version = binary . LittleEndian . Uint64 ( buf [ len ( record . BLOCK_ID ) : ] )
record . Height = int64 ( binary . LittleEndian . Uint64 ( buf [ len ( record . BLOCK_ID ) + 8 : ] ) )
return record , nil
}
func ( s * storetopofs ) Write ( index int64 , blid [ 32 ] byte , state_version uint64 , height int64 ) ( err error ) {
var buf [ TOPORECORD_SIZE ] byte
var record TopoRecord
copy ( buf [ : ] , blid [ : ] )
binary . LittleEndian . PutUint64 ( buf [ len ( record . BLOCK_ID ) : ] , state_version )
//height := chain.Load_Height_for_BL_ID(blid)
binary . LittleEndian . PutUint64 ( buf [ len ( record . BLOCK_ID ) + 8 : ] , uint64 ( height ) )
_ , err = s . topomapping . WriteAt ( buf [ : ] , index * TOPORECORD_SIZE )
return err
}
func ( s * storetopofs ) Clean ( index int64 ) ( err error ) {
var state_version uint64
var blid [ 32 ] byte
return s . Write ( index , blid , state_version , 0 )
}
// whether record is clean
func ( r * TopoRecord ) IsClean ( ) bool {
if r . State_Version != 0 {
return false
}
for _ , x := range r . BLOCK_ID {
if x != 0 {
return false
}
}
return true
}
var pruned_till int64 = - 1
// locates prune topoheight till where the history has been pruned
// this is not used anywhere in the consensus and can be modified any way possible
// this is for the wallet
func ( s * storetopofs ) LocatePruneTopo ( ) int64 {
if pruned_till >= 0 { // return cached result
return pruned_till
}
count := s . Count ( )
if count < 10 {
return 0
}
zero_block , err := s . Read ( 0 )
if err != nil || zero_block . IsClean ( ) {
return 0
}
fifth_block , err := s . Read ( 5 )
if err != nil || fifth_block . IsClean ( ) {
return 0
}
// we are assumming atleast 5 blocks are pruned
if zero_block . State_Version != fifth_block . State_Version {
return 0
}
// now we must find the point where version number = zero_block.State_Version + 1
low := int64 ( 0 ) // in case of purging DB, this should start from N
high := int64 ( count )
prune_topo := int64 ( math . MaxInt64 )
for low <= high {
median := ( low + high ) / 2
median_block , _ := s . Read ( median )
if median_block . State_Version >= ( zero_block . State_Version + 1 ) {
if prune_topo > median {
prune_topo = median
}
high = median - 1
} else {
low = median + 1
}
}
prune_topo --
// fmt.Printf("pruned topo %d\n",prune_topo)
return prune_topo
}
// exported from chain
func ( chain * Blockchain ) LocatePruneTopo ( ) int64 {
return chain . Store . Topo_store . LocatePruneTopo ( )
}
func ( s * storetopofs ) binarySearchHeight ( targetheight int64 ) ( blids [ ] crypto . Hash , topos [ ] int64 ) {
startIndex := int64 ( 0 )
2020-12-27 13:44:23 +00:00
total_records := int64 ( s . Count ( ) )
2020-12-19 10:01:29 +00:00
endIndex := total_records
midIndex := total_records / 2
if endIndex < 0 { // no record
return
}
for startIndex <= endIndex {
record , _ := s . Read ( midIndex )
if record . Height >= targetheight - ( ( config . STABLE_LIMIT * 4 ) / 2 ) && record . Height <= targetheight + ( ( config . STABLE_LIMIT * 4 ) / 2 ) {
break
}
if record . Height >= targetheight {
endIndex = midIndex - 1
midIndex = ( startIndex + endIndex ) / 2
continue
}
startIndex = midIndex + 1
midIndex = ( startIndex + endIndex ) / 2
}
for i , count := midIndex , 0 ; i >= 0 && count < 100 ; i , count = i - 1 , count + 1 {
record , _ := s . Read ( i )
if record . Height == targetheight {
blids = append ( blids , record . BLOCK_ID )
topos = append ( topos , i )
}
}
for i , count := midIndex , 0 ; i <= total_records && count < 100 ; i , count = i + 1 , count + 1 {
record , _ := s . Read ( i )
if record . Height == targetheight {
blids = append ( blids , record . BLOCK_ID )
topos = append ( topos , i )
}
}
blids , topos = SliceUniqTopoRecord ( blids , topos ) // unique the record
return
}
// SliceUniq removes duplicate values in given slice
func SliceUniqTopoRecord ( s [ ] crypto . Hash , h [ ] int64 ) ( [ ] crypto . Hash , [ ] int64 ) {
for i := 0 ; i < len ( s ) ; i ++ {
for i2 := i + 1 ; i2 < len ( s ) ; i2 ++ {
if s [ i ] == s [ i2 ] {
// delete
s = append ( s [ : i2 ] , s [ i2 + 1 : ] ... )
h = append ( h [ : i2 ] , h [ i2 + 1 : ] ... )
i2 --
}
}
}
return s , h
}
func ( chain * Blockchain ) Get_Blocks_At_Height ( height int64 ) [ ] crypto . Hash {
blids , _ := chain . Store . Topo_store . binarySearchHeight ( height )
return blids
}
// since topological order might mutate, instead of doing cleanup, we double check the pointers
// we first locate the block and its height, then we locate that height, then we traverse 50 blocks up and 50 blocks down
func ( chain * Blockchain ) Is_Block_Topological_order ( blid crypto . Hash ) bool {
bl_height := chain . Load_Height_for_BL_ID ( blid )
blids , _ := chain . Store . Topo_store . binarySearchHeight ( bl_height )
for i := range blids {
if blids [ i ] == blid {
return true
}
}
return false
}
func ( chain * Blockchain ) Load_Block_Topological_order ( blid crypto . Hash ) int64 {
bl_height := chain . Load_Height_for_BL_ID ( blid )
blids , topos := chain . Store . Topo_store . binarySearchHeight ( bl_height )
for i := range blids {
if blids [ i ] == blid {
return topos [ i ]
}
}
return - 1
}