Blockchain functions: Return version number. Add record deletion and refactoring function. Allow to delete files.

This commit is contained in:
Kleissner
2021-09-19 00:01:31 +02:00
parent 67bf27a4af
commit 06b846ca34
2 changed files with 181 additions and 52 deletions

View File

@@ -26,6 +26,7 @@ import (
"github.com/PeernetOfficial/core/store"
"github.com/btcsuite/btcd/btcec"
"github.com/google/uuid"
)
// BlockchainHeight is the current count of blocks
@@ -139,6 +140,14 @@ const (
BlockchainStatusDataNotFound = 4 // Requested data not available in the blockchain
)
// blockNumberToKey returns the database key for the given block number
func blockNumberToKey(number uint64) (key []byte) {
var target [8]byte
binary.LittleEndian.PutUint64(target[:], number)
return target[:]
}
// blockchainIterate iterates over the blockchain. Status is BlockchainStatusX.
// If the callback returns non-zero, the function aborts and returns the inner status code.
func blockchainIterate(callback func(block *Block) int) (status int) {
@@ -146,9 +155,7 @@ func blockchainIterate(callback func(block *Block) int) (status int) {
height := userBlockchainHeader.height
for blockN := uint64(0); blockN < height; blockN++ {
var target [8]byte
binary.LittleEndian.PutUint64(target[:], blockN)
blockRaw, found := userBlockchainDB.Get(target[:])
blockRaw, found := userBlockchainDB.Get(blockNumberToKey(blockN))
if !found || len(blockRaw) == 0 {
return BlockchainStatusBlockNotFound
}
@@ -166,6 +173,89 @@ func blockchainIterate(callback func(block *Block) int) (status int) {
return BlockchainStatusOK
}
// blockchainIterateDeleteRecord iterates over the blockchain to find records to delete. Status is BlockchainStatusX.
// If the callback returns true, the record will be deleted. The blockchain will be automatically refactored and height and version updated.
func blockchainIterateDeleteRecord(callback func(record *BlockRecordRaw) (delete, corrupt bool)) (newHeight, newVersion uint64, status int) {
userBlockchainHeader.Lock()
defer userBlockchainHeader.Unlock()
// New blockchain keeps track of the new blocks. If anything changes in the blockchain, it must be recalculated and the version number increased.
var blockchainNew []Block
refactorBlockchain := false
refactorVersion := userBlockchainHeader.version + 1
// Read all blocks until height is reached. At the end the height and version might be different if blocks are deleted.
height := userBlockchainHeader.height
for blockN := uint64(0); blockN < height; blockN++ {
blockRaw, found := userBlockchainDB.Get(blockNumberToKey(blockN))
if !found || len(blockRaw) == 0 {
return 0, 0, BlockchainStatusBlockNotFound
}
block, err := decodeBlock(blockRaw)
if err != nil {
return 0, 0, BlockchainStatusCorruptBlock
}
// loop through all records in this block
refactorBlock := false
var newRecordsRaw []BlockRecordRaw
for n := range block.RecordsRaw {
// delete the block?
if delete, corrupt := callback(&block.RecordsRaw[n]); corrupt {
return 0, 0, BlockchainStatusCorruptBlockRecord
} else if delete {
refactorBlock = true
refactorBlockchain = true
} else {
newRecordsRaw = append(newRecordsRaw, block.RecordsRaw[n])
}
}
// If refactor, re-calculate the block. All later blocks need to be re-encoded due to change of previous block hash. The version number needs to change.
if refactorBlock {
if len(newRecordsRaw) > 0 {
blockchainNew = append(blockchainNew, Block{OwnerPublicKey: peerPublicKey, RecordsRaw: newRecordsRaw, BlockchainVersion: refactorVersion, Number: uint64(len(blockchainNew))})
}
} else {
blockchainNew = append(blockchainNew, Block{OwnerPublicKey: peerPublicKey, RecordsRaw: block.RecordsRaw, BlockchainVersion: refactorVersion, Number: uint64(len(blockchainNew))})
}
}
if refactorBlockchain {
var lastBlockHash []byte
for _, block := range blockchainNew {
block.LastBlockHash = lastBlockHash
raw, err := encodeBlock(&block, peerPrivateKey)
if err != nil {
return 0, 0, BlockchainStatusCorruptBlock
}
// store the block
userBlockchainDB.Set(blockNumberToKey(block.Number), raw)
lastBlockHash = hashData(raw)
}
userBlockchainHeader.height = uint64(len(blockchainNew))
userBlockchainHeader.version = refactorVersion
// update the blockchain header in the database
blockchainHeaderWrite(userBlockchainDB, peerPrivateKey, userBlockchainHeader.height, userBlockchainHeader.version)
// delete orphaned blocks
for n := userBlockchainHeader.height; n < height; n++ {
userBlockchainDB.Delete(blockNumberToKey(n))
}
}
return userBlockchainHeader.height, userBlockchainHeader.version, BlockchainStatusOK
}
// ---- blockchain manipulation functions ----
// UserBlockchainHeader returns the users blockchain header which stores the height and version number.
@@ -174,8 +264,8 @@ func UserBlockchainHeader() (publicKey *btcec.PublicKey, height uint64, version
}
// UserBlockchainAppend appends a new block to the blockchain based on the provided raw records.
// Status: 0 = Success, 1 = Error previous block not found, 2 = Error block encoding
func UserBlockchainAppend(RecordsRaw []BlockRecordRaw) (newHeight uint64, status int) {
// Status: BlockchainStatusX (0-2): 0 = Success, 1 = Error block not found, 2 = Error block encoding
func UserBlockchainAppend(RecordsRaw []BlockRecordRaw) (newHeight, newVersion uint64, status int) {
userBlockchainHeader.Lock()
defer userBlockchainHeader.Unlock()
@@ -183,11 +273,9 @@ func UserBlockchainAppend(RecordsRaw []BlockRecordRaw) (newHeight uint64, status
// set the last block hash first
if userBlockchainHeader.height > 0 {
var target [8]byte
binary.LittleEndian.PutUint64(target[:], userBlockchainHeader.height-1)
previousBlockRaw, found := userBlockchainDB.Get(target[:])
previousBlockRaw, found := userBlockchainDB.Get(blockNumberToKey(userBlockchainHeader.height - 1))
if !found || len(previousBlockRaw) == 0 {
return 0, 1
return 0, 0, BlockchainStatusBlockNotFound
}
block.LastBlockHash = hashData(previousBlockRaw)
@@ -198,21 +286,19 @@ func UserBlockchainAppend(RecordsRaw []BlockRecordRaw) (newHeight uint64, status
raw, err := encodeBlock(block, peerPrivateKey)
if err != nil {
return 0, 2
return 0, 0, BlockchainStatusCorruptBlock
}
// increase blockchain height
userBlockchainHeader.height++
// store the block
var numberB [8]byte
binary.LittleEndian.PutUint64(numberB[:], block.Number)
userBlockchainDB.Set(numberB[:], raw)
userBlockchainDB.Set(blockNumberToKey(block.Number), raw)
// update the blockchain header in the database
blockchainHeaderWrite(userBlockchainDB, peerPrivateKey, userBlockchainHeader.height, userBlockchainHeader.version)
return userBlockchainHeader.height, 0
return userBlockchainHeader.height, userBlockchainHeader.version, BlockchainStatusOK
}
// UserBlockchainRead reads the block number from the blockchain.
@@ -223,9 +309,7 @@ func UserBlockchainRead(number uint64) (decoded *BlockDecoded, status int, err e
return nil, 1, errors.New("block number exceeds blockchain height")
}
var target [8]byte
binary.LittleEndian.PutUint64(target[:], number)
blockRaw, found := userBlockchainDB.Get(target[:])
blockRaw, found := userBlockchainDB.Get(blockNumberToKey(number))
if !found || len(blockRaw) == 0 {
return nil, 1, errors.New("block not found")
}
@@ -246,10 +330,10 @@ func UserBlockchainRead(number uint64) (decoded *BlockDecoded, status int, err e
// UserBlockchainAddFiles adds files to the blockchain
// Status: 0 = Success, 1 = Error previous block not found, 2 = Error block encoding, 3 = Error block record encoding
// It makes sense to group all files in the same directory into one call, since only one directory record will be created per unique directory per block.
func UserBlockchainAddFiles(files []BlockRecordFile) (newHeight uint64, status int) {
func UserBlockchainAddFiles(files []BlockRecordFile) (newHeight, newVersion uint64, status int) {
encoded, err := encodeBlockRecordFiles(files)
if err != nil {
return 0, BlockchainStatusCorruptBlockRecord
return 0, 0, BlockchainStatusCorruptBlockRecord
}
return UserBlockchainAppend(encoded)
@@ -372,11 +456,34 @@ func UserProfileList() (fields []BlockRecordProfileField, blobs []BlockRecordPro
// UserProfileWrite writes profile fields and blobs to the blockchain
// Status: 0 = Success, 1 = Error previous block not found, 2 = Error block encoding, 3 = Error block record encoding
func UserProfileWrite(profile BlockRecordProfile) (newHeight uint64, status int) {
func UserProfileWrite(profile BlockRecordProfile) (newHeight, newVersion uint64, status int) {
encoded, err := encodeBlockRecordProfile(profile)
if err != nil {
return 0, BlockchainStatusCorruptBlockRecord
return 0, 0, BlockchainStatusCorruptBlockRecord
}
return UserBlockchainAppend(encoded)
}
// UserBlockchainDeleteFiles deletes files from the blockchain. Status is BlockchainStatusX.
func UserBlockchainDeleteFiles(IDs []uuid.UUID) (newHeight, newVersion uint64, status int) {
return blockchainIterateDeleteRecord(func(record *BlockRecordRaw) (delete, corrupt bool) {
if record.Type != RecordTypeFile {
return false, false
}
filesDecoded, err := decodeBlockRecordFiles([]BlockRecordRaw{*record})
if err != nil || len(filesDecoded) != 1 {
// corruption
return false, true
}
for _, id := range IDs {
if id == filesDecoded[0].ID { // found a file ID to delete?
return true, false
}
}
return false, false
})
}

View File

@@ -131,21 +131,7 @@ func TestBlockEncoding(t *testing.T) {
for _, decodedR := range decoded.RecordsDecoded {
switch record := decodedR.(type) {
case BlockRecordFile:
file := record
fmt.Printf("* File %s\n", file.ID.String())
fmt.Printf(" Size %d\n", file.Size)
fmt.Printf(" Type %d\n", file.Type)
fmt.Printf(" Format %d\n", file.Format)
fmt.Printf(" Hash %s\n", hex.EncodeToString(file.Hash))
for _, decodedT := range file.TagsDecoded {
switch v := decodedT.(type) {
case FileTagName:
fmt.Printf(" Name %s\n", v.Name)
case FileTagFolder:
fmt.Printf(" Folder %s\n", v.Name)
}
}
printFile(record)
case BlockRecordProfile:
profile := record
@@ -177,7 +163,7 @@ func TestBlockchainAdd(t *testing.T) {
file1.TagsDecoded = append(file1.TagsDecoded, FileTagName{Name: "Filename 1.txt"})
file1.TagsDecoded = append(file1.TagsDecoded, FileTagFolder{Name: "documents\\sub folder"})
newHeight, status := UserBlockchainAddFiles([]BlockRecordFile{file1})
newHeight, newVersion, status := UserBlockchainAddFiles([]BlockRecordFile{file1})
switch status {
case 0:
@@ -195,7 +181,7 @@ func TestBlockchainAdd(t *testing.T) {
return
}
fmt.Printf("Success adding files to blockchain. New blockchain height: %d\n", newHeight)
fmt.Printf("Success adding files to blockchain. New blockchain height %d version %d\n", newHeight, newVersion)
}
func TestBlockchainRead(t *testing.T) {
@@ -224,20 +210,56 @@ func TestBlockchainRead(t *testing.T) {
for _, decodedR := range decoded.RecordsDecoded {
if file, ok := decodedR.(BlockRecordFile); ok {
fmt.Printf("* File %s\n", file.ID.String())
fmt.Printf(" Size %d\n", file.Size)
fmt.Printf(" Type %d\n", file.Type)
fmt.Printf(" Format %d\n", file.Format)
fmt.Printf(" Hash %s\n", hex.EncodeToString(file.Hash))
for _, decodedT := range file.TagsDecoded {
switch v := decodedT.(type) {
case FileTagName:
fmt.Printf(" Name %s\n", v.Name)
case FileTagFolder:
fmt.Printf(" Folder %s\n", v.Name)
}
}
printFile(file)
}
}
}
func printFile(file BlockRecordFile) {
fmt.Printf("* File %s\n", file.ID.String())
fmt.Printf(" Size %d\n", file.Size)
fmt.Printf(" Type %d\n", file.Type)
fmt.Printf(" Format %d\n", file.Format)
fmt.Printf(" Hash %s\n", hex.EncodeToString(file.Hash))
for _, decodedT := range file.TagsDecoded {
switch v := decodedT.(type) {
case FileTagName:
fmt.Printf(" Name %s\n", v.Name)
case FileTagFolder:
fmt.Printf(" Folder %s\n", v.Name)
}
}
}
func TestBlockchainDelete(t *testing.T) {
initTestPrivateKey()
initFilters()
initUserBlockchain()
// test add file
file1 := BlockRecordFile{Hash: hashData([]byte("Test data")), Type: TypeText, Format: FormatText, Size: 9, ID: uuid.New()}
file1.TagsDecoded = append(file1.TagsDecoded, FileTagName{Name: "Test file to be deleted.txt"})
file1.TagsDecoded = append(file1.TagsDecoded, FileTagFolder{Name: "documents\\sub folder"})
newHeight, newVersion, status := UserBlockchainAddFiles([]BlockRecordFile{file1})
fmt.Printf("Added file: Status %d height %d version %d\n", status, newHeight, newVersion)
// list files
files, _ := UserBlockchainListFiles()
for _, file := range files {
printFile(file)
}
fmt.Printf("----------------\n")
// delete the file
newHeight, newVersion, status = UserBlockchainDeleteFiles([]uuid.UUID{file1.ID})
fmt.Printf("Deleted file: Status %d height %d version %d\n", status, newHeight, newVersion)
// list all files
files, _ = UserBlockchainListFiles()
for _, file := range files {
printFile(file)
}
}