From e10ece73585b2549f1940e680707e0b1054c6227 Mon Sep 17 00:00:00 2001 From: Kleissner Date: Mon, 29 Nov 2021 02:06:51 +0100 Subject: [PATCH] Blockchain: blockchain.IterateDeleteRecord decode all records at once, fixing multiple issues with file tags that could result in corrupted or orphaned records. Fix #66, fix #37, fix #38 --- blockchain/Blockchain.go | 77 +++++++++++++++++++++++++++++++++------- blockchain/File.go | 19 +++------- blockchain/Profile.go | 2 +- 3 files changed, 70 insertions(+), 28 deletions(-) diff --git a/blockchain/Blockchain.go b/blockchain/Blockchain.go index 0f11d81..12027bd 100644 --- a/blockchain/Blockchain.go +++ b/blockchain/Blockchain.go @@ -171,7 +171,7 @@ func (blockchain *Blockchain) Iterate(callback func(block *Block) int) (status i // IterateDeleteRecord iterates over the blockchain to find records to delete. Status is StatusX. // deleteAction is 0 = no action on record, 1 = delete record, 2 = replace record, 3 = error blockchain corrupt // If the callback returns true, the record will be deleted. The blockchain will be automatically refactored and height and version updated. -func (blockchain *Blockchain) IterateDeleteRecord(callback func(record *BlockRecordRaw) (deleteAction int)) (newHeight, newVersion uint64, status int) { +func (blockchain *Blockchain) IterateDeleteRecord(callbackFile func(file *BlockRecordFile) (deleteAction int), callbackOther func(record *BlockRecordRaw) (deleteAction int)) (newHeight, newVersion uint64, status int) { blockchain.Lock() defer blockchain.Unlock() @@ -194,32 +194,83 @@ func (blockchain *Blockchain) IterateDeleteRecord(callback func(record *BlockRec return 0, 0, StatusCorruptBlock } - // loop through all records in this block refactorBlock := false + + // Decode all file records at once. This is needed due to potential referenced tags. + // If a file is deleted or referenced tag data changed, it would corrupt the blockchain if the other records were not updated. + filesD, err := decodeBlockRecordFiles(block.RecordsRaw, block.NodeID) + if err != nil { + return 0, 0, StatusCorruptBlock + } + + // loop through all file records in this block + var newFileRecords []BlockRecordFile + + if callbackFile == nil { + newFileRecords = filesD + } else { + for n := range filesD { + switch callbackFile(&filesD[n]) { + case 0: // no action on record + newFileRecords = append(newFileRecords, filesD[n]) + + case 1: // delete record + refactorBlock = true + refactorBlockchain = true + + case 2: // replace record + newFileRecords = append(newFileRecords, filesD[n]) + refactorBlock = true + refactorBlockchain = true + + case 3: // error blockchain corrupt + return 0, 0, StatusCorruptBlockRecord + } + } + } + + // loop through all other (non-file) records in this block var newRecordsRaw []BlockRecordRaw for n := range block.RecordsRaw { - switch callback(&block.RecordsRaw[n]) { - case 0: // no action on record + // File and Tag records were already handled in above loop. + if block.RecordsRaw[n].Type == RecordTypeFile || block.RecordsRaw[n].Type == RecordTypeTagData { + continue + } + + if callbackOther == nil { newRecordsRaw = append(newRecordsRaw, block.RecordsRaw[n]) + } else { + switch callbackOther(&block.RecordsRaw[n]) { + case 0: // no action on record + newRecordsRaw = append(newRecordsRaw, block.RecordsRaw[n]) - case 1: // delete record - refactorBlock = true - refactorBlockchain = true + case 1: // delete record + refactorBlock = true + refactorBlockchain = true - case 2: // replace record - newRecordsRaw = append(newRecordsRaw, block.RecordsRaw[n]) - refactorBlock = true - refactorBlockchain = true + case 2: // replace record + newRecordsRaw = append(newRecordsRaw, block.RecordsRaw[n]) + refactorBlock = true + refactorBlockchain = true - case 3: // error blockchain corrupt - return 0, 0, StatusCorruptBlockRecord + case 3: // error blockchain corrupt + return 0, 0, StatusCorruptBlockRecord + } } } // 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. // Note: Deleting records may leave referenced records orphaned, such as RecordTypeTagData for deleted file records. if refactorBlock { + // re-encode the block + filesRecords, err := encodeBlockRecordFiles(newFileRecords) + if err != nil { + return 0, 0, StatusCorruptBlock + } + + newRecordsRaw = append(newRecordsRaw, filesRecords...) + if len(newRecordsRaw) > 0 { blockchainNew = append(blockchainNew, Block{OwnerPublicKey: blockchain.publicKey, RecordsRaw: newRecordsRaw, BlockchainVersion: refactorVersion, Number: uint64(len(blockchainNew))}) } diff --git a/blockchain/File.go b/blockchain/File.go index 7713f26..218d95e 100644 --- a/blockchain/File.go +++ b/blockchain/File.go @@ -62,26 +62,17 @@ func (blockchain *Blockchain) FileExists(hash []byte) (files []BlockRecordFile, } // DeleteFiles deletes files from the blockchain. Status is StatusX. -func (blockchain *Blockchain) DeleteFiles(IDs []uuid.UUID) (newHeight, newVersion uint64, deletedFiles []BlockRecordFile, status int) { - newHeight, newVersion, status = blockchain.IterateDeleteRecord(func(record *BlockRecordRaw) (deleteAction int) { - if record.Type != RecordTypeFile { - return 0 // no action on record - } - - filesDecoded, err := decodeBlockRecordFiles([]BlockRecordRaw{*record}, nil) - if err != nil || len(filesDecoded) != 1 { - return 3 // error blockchain corrupt - } - +func (blockchain *Blockchain) DeleteFiles(IDs []uuid.UUID) (newHeight, newVersion uint64, deletedFiles []*BlockRecordFile, status int) { + newHeight, newVersion, status = blockchain.IterateDeleteRecord(func(file *BlockRecordFile) (deleteAction int) { for _, id := range IDs { - if id == filesDecoded[0].ID { // found a file ID to delete? - deletedFiles = append(deletedFiles, filesDecoded[0]) + if file.ID == id { // found a file ID to delete? + deletedFiles = append(deletedFiles, file) return 1 // delete record } } return 0 // no action on record - }) + }, nil) return } diff --git a/blockchain/Profile.go b/blockchain/Profile.go index 984fcda..7ab740e 100644 --- a/blockchain/Profile.go +++ b/blockchain/Profile.go @@ -74,7 +74,7 @@ func (blockchain *Blockchain) ProfileWrite(fields []BlockRecordProfile) (newHeig // ProfileDelete deletes fields and blobs from the blockchain. Status is StatusX. func (blockchain *Blockchain) ProfileDelete(fields []uint16) (newHeight, newVersion uint64, status int) { - return blockchain.IterateDeleteRecord(func(record *BlockRecordRaw) (deleteAction int) { + return blockchain.IterateDeleteRecord(nil, func(record *BlockRecordRaw) (deleteAction int) { if record.Type != RecordTypeProfile { return 0 // no action }