kaspad/blockdag/dag_test.go
stasatdaglabs f8b18e09d6
[NOD-805] Redesign the database (#685)
* [NOD-828] Reimplement FFLDB (#663)

* [NOD-828] Create the database2 package that will some day replace the database package.

* [NOD-828] Implement a "bucket" key mechanism.

* [NOD-828] Move bucket.go into the ffldb2 package.

* [NOD-828] Delete the un-interfaced ffldb package from database2, since we aren't going to be using it anyway.

* [NOD-828] Copy over + fixup flat file structs from the old ffldb.

* [NOD-828] Implement flatFilePath.

* [NOD-828] Implement flatFileStore.write().

* [NOD-828] Implement flatFileStore.read().

* [NOD-828] Implement flatFileStore.rollback().

* [NOD-828] Sync the file to disk at the end of write().

* [NOD-828] Extract crc32ByteOrder to a separate variable.

* [NOD-828] Add a sanity test.

* [NOD-828] Remove context-unrelated methods from the Database interface.

* [NOD-828] Create an ffldb object. Simply work against a context.

* [NOD-828] Open the new database on start.

* [NOD-828] Create the leveldb package.

* [NOD-828] Implement opening/closing leveldb.

* [NOD-828] Implement get/put out of/into leveldb.

* [NOD-828] Implement transactions and make them implement a generic database interface.

* [NOD-828] Write sanity tests for leveldb with and without transactions.

* [NOD-828] Add another case to the transaction sanity test.

* [NOD-828] Implement AppendBlock/RetrieveBlock.

* [NOD-828] Refactor so that concepts such as "block" and "metadata" don't leak into the database package.

* [NOD-828] Add RollbackFlatData to the database interface.

* [NOD-828] Remove anything from dbaccess that I'm not planning to implement as part of this ticket.

* [NOD-828] Implement StoreBlock.

* [NOD-828] Implement FetchBlock.

* [NOD-828] Implement HasBlock.

* [NOD-828] Write a sanity test for block insertion.

* [NOD-828] Implement CurrentFlatDataLocation.

* [NOD-828] Implement storing the current block location.

* [NOD-828] Implement initializing/syncing the flat file block store and the "metadata".

* [NOD-828] Add InitBlockStore to TestBlockStoreSanity.

* [NOD-828] Fix rename errors.

* [NOD-828] Fix lint errors in the root database package.

* [NOD-828] Fix lint errors in the ffldb.go.

* [NOD-828] Fix lint errors in the flatfile/db.go.

* [NOD-828] Rename packages in such a way to make the linter happy.

* [NOD-828] Finish satisfying the linter.

* [NOD-828] Fix doc.go.

* [NOD-828] Fix comments in block.go.

* [NOD-828] Move dbaccess out of the database package.

* [NOD-828] Move opening/closing the database to dbaccess.

* [NOD-828] Move the Database interface to the root database package, since it's meant to be generic.

* [NOD-828] Make ffldb generic to later support additional databases.

* [NOD-828] Make ffldb.Open return DatabaseHandle, since ffldb is no longer exported.

* [NOD-828] Fix comments.

* [NOD-828] Rename AppendFlatData to AppendToStore and RetrieveFlatData to RetrieveFromStore.

* [NOD-828] Make buckets nicer to use.

* [NOD-828] Implement cursors that iterate over some bucket.

* [NOD-828] Generalize flat-file repairing and move block database repairing into ffldb.

* [NOD-828] Write a test making sure that flat file repair works.

* [NOD-828] Properly close the database in TestRepairFlatFiles.

* [NOD-828] Add a comment warning against putting and getting the same data within the same transaction.

* [NOD-828] Fix the flatFilesBucket description.

* [NOD-828] Remove superfluous whitespace.

* [NOD-828] Fix the ffldb description.

* [NOD-828] Rename Database to DataAccessor and rename Handle to Database.

* [NOD-828] Make Get return nil if the value doesn't exist.

* [NOD-828] Attempt to close leveldb even if closing ffdb failed.

* [NOD-828] Fix a bug where the wrong location would be written to the current store location bucket.

* [NOD-828] Fix not updating the store location in ffldb transactions.

* [NOD-828] Make scanFlatFiles return an error if os.Stat fails for any reason other than file-not-found.

* [NOD-828] Update the README and doc.go.

* [NOD-828] Simplify Bucket.Path().

* [NOD-828] Since LevelDBCursor satisfied the database2.Cursor interface, use it directly.

* [NOD-828] Combine two lines into one.

* [NOD-828] Combine another two lines into one.

* [NOD-828] Move a misplaced comment.

* [NOD-828] Use Wrapf instead of Errorf where appropriate.

* [NOD-828] Fix a comment.

* [NOD-828] Fix a comment.

* [NOD-828] Fix Wrapf calls.

* [NOD-828] Fix comments.

* [NOD-828] Remove superfluous whitespace.

* [NOD-828] Add a comment explaining the use of batches and snapshots.

* [NOD-828] Implement RollbackUnlessClosed().

* [NOD-828] Return both errors in StoreBlock rollback.

* [NOD-828] Move rollback-on-error logic into ffldb. Remove CurrentStoreLocation and RollbackStore from DataAccessor.

* [NOD-828] Make bucket a type alias instead of a struct.

* [NOD-828] Fix a typo.

* [NOD-828] Use copy instead of append in Bucket.

* [NOD-828] Extract flatFileLocationSerializedSize to a const.

* [NOD-828] Debugf -> Warnf in rollback.go.

* [NOD-828] Fix a comment.

* [NOD-828] Fix a comment.

* [NOD-828] Remove data length from flat file data format.

* [NOD-828] Rearrange TestLevelDBTransactionSanity a bit.

* [NOD-828] Add stack traces to all errors that come out of library functions.

* [NOD-828] Return errors from rollback().

* [NOD-828] Remove an irrelevant comment.

* [NOD-828] Remove redundant whitespace.

* [NOD-828] Handle nil in FetchBlock.

* [NOD-828] Move the explanation about batches and snapshots to the LevelDBTransaction struct.

* Revert "[NOD-828] Make bucket a type alias instead of a struct."

This reverts commit 1fd39652

* [NOD-828] Fix revert errors.

* Revert "[NOD-828] Remove data length from flat file data format."

This reverts commit ef408e32

* [NOD-862] Move Cursor() into the DataAccessor interface.

* [NOD-828] Add Delete to DataAccessor.

* [NOD-828] Fix a couple of places that erroneously referenced blocks.

* [NOD-828] Add a comment on top of flatFileLocationSerializedSize.

* [NOD-828] Add Seek to Cursor.

* [NOD-828] Add First to Cursor.

* [NOD-828] Rename db to accessor in Context.

* [NOD-828] Make Get/Fetch calls return a boolean to indicate whether the requested item was found.

* [NOD-828] Name the output parameters of all Get functions.

* [NOD-828] Make RetrieveFromStore return whether the data was found.

* [NOD-887] Add a couple of QoL features to Cursor (#674)

* [NOD-887] Changed First to not return an error.

* [NOD-887] Fix merge error.

* [NOD-887] Make Cursor.Key not return the entire key path.

* [NOD-888] Add RollbackUnlessClosed to Context (#676)

* [NOD-888] Add RollbackUnlessClosed to Context.

* [NOD-888] Fix copy+paste error.

* [NOD-889] Instead of returning a boolean for not-found, return an error (#677)

* [NOD-889] Instead of returning a boolean for not-found, return an error.

* [NOD-889] Wrapped ErrNotFound for Get calls with nicer error messages.

* [NOD-889] Fix format.

* [NOD-889] Fix double space in a comment.

* [NOD-889] Add IsNotFoundError to dbaccess.

* [NOD-862] Replace calls to Tx.StoreBlock, Tx.HasBlock, Tx.FetchBlock with appropriate calls in dbaccess (#672)

* [NOD-828] Create the database2 package that will some day replace the database package.

* [NOD-828] Implement a "bucket" key mechanism.

* [NOD-828] Move bucket.go into the ffldb2 package.

* [NOD-828] Delete the un-interfaced ffldb package from database2, since we aren't going to be using it anyway.

* [NOD-828] Copy over + fixup flat file structs from the old ffldb.

* [NOD-828] Implement flatFilePath.

* [NOD-828] Implement flatFileStore.write().

* [NOD-828] Implement flatFileStore.read().

* [NOD-828] Implement flatFileStore.rollback().

* [NOD-828] Sync the file to disk at the end of write().

* [NOD-828] Extract crc32ByteOrder to a separate variable.

* [NOD-828] Add a sanity test.

* [NOD-828] Remove context-unrelated methods from the Database interface.

* [NOD-828] Create an ffldb object. Simply work against a context.

* [NOD-828] Open the new database on start.

* [NOD-828] Create the leveldb package.

* [NOD-828] Implement opening/closing leveldb.

* [NOD-828] Implement get/put out of/into leveldb.

* [NOD-828] Implement transactions and make them implement a generic database interface.

* [NOD-828] Write sanity tests for leveldb with and without transactions.

* [NOD-828] Add another case to the transaction sanity test.

* [NOD-828] Implement AppendBlock/RetrieveBlock.

* [NOD-828] Refactor so that concepts such as "block" and "metadata" don't leak into the database package.

* [NOD-828] Add RollbackFlatData to the database interface.

* [NOD-828] Remove anything from dbaccess that I'm not planning to implement as part of this ticket.

* [NOD-828] Implement StoreBlock.

* [NOD-828] Implement FetchBlock.

* [NOD-828] Implement HasBlock.

* [NOD-828] Write a sanity test for block insertion.

* [NOD-828] Implement CurrentFlatDataLocation.

* [NOD-828] Implement storing the current block location.

* [NOD-828] Implement initializing/syncing the flat file block store and the "metadata".

* [NOD-828] Add InitBlockStore to TestBlockStoreSanity.

* [NOD-828] Fix rename errors.

* [NOD-828] Fix lint errors in the root database package.

* [NOD-828] Fix lint errors in the ffldb.go.

* [NOD-828] Fix lint errors in the flatfile/db.go.

* [NOD-828] Rename packages in such a way to make the linter happy.

* [NOD-828] Finish satisfying the linter.

* [NOD-828] Fix doc.go.

* [NOD-828] Fix comments in block.go.

* [NOD-828] Move dbaccess out of the database package.

* [NOD-828] Move opening/closing the database to dbaccess.

* [NOD-828] Move the Database interface to the root database package, since it's meant to be generic.

* [NOD-828] Make ffldb generic to later support additional databases.

* [NOD-828] Make ffldb.Open return DatabaseHandle, since ffldb is no longer exported.

* [NOD-828] Fix comments.

* [NOD-828] Rename AppendFlatData to AppendToStore and RetrieveFlatData to RetrieveFromStore.

* [NOD-828] Make buckets nicer to use.

* [NOD-828] Implement cursors that iterate over some bucket.

* [NOD-828] Generalize flat-file repairing and move block database repairing into ffldb.

* [NOD-828] Write a test making sure that flat file repair works.

* [NOD-828] Properly close the database in TestRepairFlatFiles.

* [NOD-828] Add a comment warning against putting and getting the same data within the same transaction.

* [NOD-862] Use dbaccess.HasBlock instead of Tx.HasBlock in initDAGState.

* [NOD-862] Use dbaccess.StoreBlock instead of dbStoreBlock.

* [NOD-862] Use dbaccess.FetchBlock instead of various block fetching mechanisms.

* [NOD-828] Fix the flatFilesBucket description.

* [NOD-828] Remove superfluous whitespace.

* [NOD-828] Fix the ffldb description.

* [NOD-828] Rename Database to DataAccessor and rename Handle to Database.

* [NOD-828] Make Get return nil if the value doesn't exist.

* [NOD-828] Attempt to close leveldb even if closing ffdb failed.

* [NOD-828] Fix a bug where the wrong location would be written to the current store location bucket.

* [NOD-828] Fix not updating the store location in ffldb transactions.

* [NOD-828] Make scanFlatFiles return an error if os.Stat fails for any reason other than file-not-found.

* [NOD-828] Update the README and doc.go.

* [NOD-828] Simplify Bucket.Path().

* [NOD-828] Since LevelDBCursor satisfied the database2.Cursor interface, use it directly.

* [NOD-828] Combine two lines into one.

* [NOD-828] Combine another two lines into one.

* [NOD-828] Move a misplaced comment.

* [NOD-828] Use Wrapf instead of Errorf where appropriate.

* [NOD-828] Fix a comment.

* [NOD-828] Fix a comment.

* [NOD-828] Fix Wrapf calls.

* [NOD-828] Fix comments.

* [NOD-828] Remove superfluous whitespace.

* [NOD-828] Add a comment explaining the use of batches and snapshots.

* [NOD-828] Implement RollbackUnlessClosed().

* [NOD-828] Return both errors in StoreBlock rollback.

* [NOD-828] Move rollback-on-error logic into ffldb. Remove CurrentStoreLocation and RollbackStore from DataAccessor.

* [NOD-828] Make bucket a type alias instead of a struct.

* [NOD-828] Fix a typo.

* [NOD-828] Use copy instead of append in Bucket.

* [NOD-828] Extract flatFileLocationSerializedSize to a const.

* [NOD-828] Debugf -> Warnf in rollback.go.

* [NOD-828] Fix a comment.

* [NOD-828] Fix a comment.

* [NOD-828] Remove data length from flat file data format.

* [NOD-828] Rearrange TestLevelDBTransactionSanity a bit.

* [NOD-828] Add stack traces to all errors that come out of library functions.

* [NOD-828] Return errors from rollback().

* [NOD-828] Remove an irrelevant comment.

* [NOD-828] Remove redundant whitespace.

* [NOD-828] Handle nil in FetchBlock.

* [NOD-828] Implement a dbaccess.BlockNode struct.

* [NOD-828] Move the explanation about batches and snapshots to the LevelDBTransaction struct.

* [NOD-828] Implement toDBBlockNode and fromDBBlockNode.

* Revert "[NOD-828] Make bucket a type alias instead of a struct."

This reverts commit 1fd39652

* [NOD-828] Fix revert errors.

* Revert "[NOD-828] Remove data length from flat file data format."

This reverts commit ef408e32

* [NOD-862] Implement storing index blocks.

* [NOD-862] Use database transactions where appropriate.

* [NOD-862] Fix tests failing on DAGSetup.

* [NOD-862] Fix bad make call.

* [NOD-862] Fix remaining database opening problems in tests.

* [NOD-862] Move Cursor() into the DataAccessor interface.

* [NOD-862] Move Cursor() into the DataAccessor interface.

* [NOD-862] Iterate over the new block index in dagio.

* [NOD-862] Fix block index key.

* [NOD-828] Add Delete to DataAccessor.

* [NOD-862] Pass byte slices to dbaccess instead of objects.

* [NOD-862] Fix errors.

* [NOD-862] Fix maybeAcceptBlock not checking block existence.

* [NOD-862] Fix TestAcceptanceIndexRecover.

* [NOD-862] Add comments to StoreBlockIndex and BlockIndexCursor.

* [NOD-828] Fix a couple of places that erroneously referenced blocks.

* [NOD-828] Add a comment on top of flatFileLocationSerializedSize.

* [NOD-828] Add Seek to Cursor.

* [NOD-828] Add First to Cursor.

* [NOD-828] Rename db to accessor in Context.

* [NOD-828] Make Get/Fetch calls return a boolean to indicate whether the requested item was found.

* [NOD-828] Name the output parameters of all Get functions.

* [NOD-828] Make RetrieveFromStore return whether the data was found.

* [NOD-862] Fix merge errors.

* [NOD-862] Fix DAGSetup using bad temp directories.

* [NOD-862] Fix TestProcessDelayedBlocks not closing the database properly.

* [NOD-862] Fix merge errors.

* [NOD-862] Merge flushToDBWithContext and flushToDB.

* [NOD-862] Remove TODO.

* [NOD-862] Add prefix to the temp dir in DAGSetup.

* [NOD-862] Bring back dbFetchBlockByHash.

* [NOD-862] Use BlockDAG.BlockByHash in p2p and rpc.

* [NOD-862] Use daghash.Hash in dbaccess.

* [NOD-862] Add defer to RollbackUnlessClosed after NewTx().

* [NOD-862] Extract dbStoreBlock to a separate function.

* [NOD-862] Fix grammar in comment.

* [NOD-862] Fix merge errors.

* [NOD-867] Migrate database logic in blockdag/dagio.go to dbaccess (#675)

* [NOD-828] Create the database2 package that will some day replace the database package.

* [NOD-828] Implement a "bucket" key mechanism.

* [NOD-828] Move bucket.go into the ffldb2 package.

* [NOD-828] Delete the un-interfaced ffldb package from database2, since we aren't going to be using it anyway.

* [NOD-828] Copy over + fixup flat file structs from the old ffldb.

* [NOD-828] Implement flatFilePath.

* [NOD-828] Implement flatFileStore.write().

* [NOD-828] Implement flatFileStore.read().

* [NOD-828] Implement flatFileStore.rollback().

* [NOD-828] Sync the file to disk at the end of write().

* [NOD-828] Extract crc32ByteOrder to a separate variable.

* [NOD-828] Add a sanity test.

* [NOD-828] Remove context-unrelated methods from the Database interface.

* [NOD-828] Create an ffldb object. Simply work against a context.

* [NOD-828] Open the new database on start.

* [NOD-828] Create the leveldb package.

* [NOD-828] Implement opening/closing leveldb.

* [NOD-828] Implement get/put out of/into leveldb.

* [NOD-828] Implement transactions and make them implement a generic database interface.

* [NOD-828] Write sanity tests for leveldb with and without transactions.

* [NOD-828] Add another case to the transaction sanity test.

* [NOD-828] Implement AppendBlock/RetrieveBlock.

* [NOD-828] Refactor so that concepts such as "block" and "metadata" don't leak into the database package.

* [NOD-828] Add RollbackFlatData to the database interface.

* [NOD-828] Remove anything from dbaccess that I'm not planning to implement as part of this ticket.

* [NOD-828] Implement StoreBlock.

* [NOD-828] Implement FetchBlock.

* [NOD-828] Implement HasBlock.

* [NOD-828] Write a sanity test for block insertion.

* [NOD-828] Implement CurrentFlatDataLocation.

* [NOD-828] Implement storing the current block location.

* [NOD-828] Implement initializing/syncing the flat file block store and the "metadata".

* [NOD-828] Add InitBlockStore to TestBlockStoreSanity.

* [NOD-828] Fix rename errors.

* [NOD-828] Fix lint errors in the root database package.

* [NOD-828] Fix lint errors in the ffldb.go.

* [NOD-828] Fix lint errors in the flatfile/db.go.

* [NOD-828] Rename packages in such a way to make the linter happy.

* [NOD-828] Finish satisfying the linter.

* [NOD-828] Fix doc.go.

* [NOD-828] Fix comments in block.go.

* [NOD-828] Move dbaccess out of the database package.

* [NOD-828] Move opening/closing the database to dbaccess.

* [NOD-828] Move the Database interface to the root database package, since it's meant to be generic.

* [NOD-828] Make ffldb generic to later support additional databases.

* [NOD-828] Make ffldb.Open return DatabaseHandle, since ffldb is no longer exported.

* [NOD-828] Fix comments.

* [NOD-828] Rename AppendFlatData to AppendToStore and RetrieveFlatData to RetrieveFromStore.

* [NOD-828] Make buckets nicer to use.

* [NOD-828] Implement cursors that iterate over some bucket.

* [NOD-828] Generalize flat-file repairing and move block database repairing into ffldb.

* [NOD-828] Write a test making sure that flat file repair works.

* [NOD-828] Properly close the database in TestRepairFlatFiles.

* [NOD-828] Add a comment warning against putting and getting the same data within the same transaction.

* [NOD-862] Use dbaccess.HasBlock instead of Tx.HasBlock in initDAGState.

* [NOD-862] Use dbaccess.StoreBlock instead of dbStoreBlock.

* [NOD-862] Use dbaccess.FetchBlock instead of various block fetching mechanisms.

* [NOD-828] Fix the flatFilesBucket description.

* [NOD-828] Remove superfluous whitespace.

* [NOD-828] Fix the ffldb description.

* [NOD-828] Rename Database to DataAccessor and rename Handle to Database.

* [NOD-828] Make Get return nil if the value doesn't exist.

* [NOD-828] Attempt to close leveldb even if closing ffdb failed.

* [NOD-828] Fix a bug where the wrong location would be written to the current store location bucket.

* [NOD-828] Fix not updating the store location in ffldb transactions.

* [NOD-828] Make scanFlatFiles return an error if os.Stat fails for any reason other than file-not-found.

* [NOD-828] Update the README and doc.go.

* [NOD-828] Simplify Bucket.Path().

* [NOD-828] Since LevelDBCursor satisfied the database2.Cursor interface, use it directly.

* [NOD-828] Combine two lines into one.

* [NOD-828] Combine another two lines into one.

* [NOD-828] Move a misplaced comment.

* [NOD-828] Use Wrapf instead of Errorf where appropriate.

* [NOD-828] Fix a comment.

* [NOD-828] Fix a comment.

* [NOD-828] Fix Wrapf calls.

* [NOD-828] Fix comments.

* [NOD-828] Remove superfluous whitespace.

* [NOD-828] Add a comment explaining the use of batches and snapshots.

* [NOD-828] Implement RollbackUnlessClosed().

* [NOD-828] Return both errors in StoreBlock rollback.

* [NOD-828] Move rollback-on-error logic into ffldb. Remove CurrentStoreLocation and RollbackStore from DataAccessor.

* [NOD-828] Make bucket a type alias instead of a struct.

* [NOD-828] Fix a typo.

* [NOD-828] Use copy instead of append in Bucket.

* [NOD-828] Extract flatFileLocationSerializedSize to a const.

* [NOD-828] Debugf -> Warnf in rollback.go.

* [NOD-828] Fix a comment.

* [NOD-828] Fix a comment.

* [NOD-828] Remove data length from flat file data format.

* [NOD-828] Rearrange TestLevelDBTransactionSanity a bit.

* [NOD-828] Add stack traces to all errors that come out of library functions.

* [NOD-828] Return errors from rollback().

* [NOD-828] Remove an irrelevant comment.

* [NOD-828] Remove redundant whitespace.

* [NOD-828] Handle nil in FetchBlock.

* [NOD-828] Implement a dbaccess.BlockNode struct.

* [NOD-828] Move the explanation about batches and snapshots to the LevelDBTransaction struct.

* [NOD-828] Implement toDBBlockNode and fromDBBlockNode.

* Revert "[NOD-828] Make bucket a type alias instead of a struct."

This reverts commit 1fd39652

* [NOD-828] Fix revert errors.

* Revert "[NOD-828] Remove data length from flat file data format."

This reverts commit ef408e32

* [NOD-862] Implement storing index blocks.

* [NOD-862] Use database transactions where appropriate.

* [NOD-862] Fix tests failing on DAGSetup.

* [NOD-862] Fix bad make call.

* [NOD-862] Fix remaining database opening problems in tests.

* [NOD-862] Move Cursor() into the DataAccessor interface.

* [NOD-862] Move Cursor() into the DataAccessor interface.

* [NOD-862] Iterate over the new block index in dagio.

* [NOD-862] Fix block index key.

* [NOD-828] Add Delete to DataAccessor.

* [NOD-862] Pass byte slices to dbaccess instead of objects.

* [NOD-862] Fix errors.

* [NOD-862] Fix maybeAcceptBlock not checking block existence.

* [NOD-862] Fix TestAcceptanceIndexRecover.

* [NOD-862] Add comments to StoreBlockIndex and BlockIndexCursor.

* [NOD-828] Fix a couple of places that erroneously referenced blocks.

* [NOD-828] Add a comment on top of flatFileLocationSerializedSize.

* [NOD-828] Add Seek to Cursor.

* [NOD-828] Add First to Cursor.

* [NOD-828] Rename db to accessor in Context.

* [NOD-828] Make Get/Fetch calls return a boolean to indicate whether the requested item was found.

* [NOD-828] Name the output parameters of all Get functions.

* [NOD-828] Make RetrieveFromStore return whether the data was found.

* [NOD-862] Fix merge errors.

* [NOD-862] Fix DAGSetup using bad temp directories.

* [NOD-862] Fix TestProcessDelayedBlocks not closing the database properly.

* [NOD-867] Remove blockIndexBucket from dagio.

* [NOD-867] Fix wrong key in StoreIndexBucket.

* [NOD-867] Migrate DAG state to dbaccess.

* [NOD-867] Remove utxoSetVersionKeyName.

* [NOD-862] Fix merge errors.

* [NOD-867] Move localSubnetworkID into dagState.

* [NOD-867] Fix a comment.

* [NOD-867] Remove an unused function.

* [NOD-867] Migrate the database's UTXO set to dbaccess.

* [NOD-867] Add missing error check.

* [NOD-867] Changed First to not return an error.

* [NOD-867] Make Cursor.Key not return the entire key path.

* [NOD-887] Fix the comment above BlockIndexCursorFrom.

* [NOD-862] Merge flushToDBWithContext and flushToDB.

* [NOD-862] Remove TODO.

* [NOD-862] Add prefix to the temp dir in DAGSetup.

* [NOD-862] Bring back dbFetchBlockByHash.

* [NOD-862] Use BlockDAG.BlockByHash in p2p and rpc.

* [NOD-862] Use daghash.Hash in dbaccess.

* [NOD-862] Add defer to RollbackUnlessClosed after NewTx().

* [NOD-862] Extract dbStoreBlock to a separate function.

* [NOD-867] Remove TODOs.

* [NOD-867] Fix merge errors.

* [NOD-867] Fix comments and errors.

* [NOD-867] Unexport blockIndexKey.

* [NOD-867] Fix merge errors.

* [NOD-867] Move a misplaced comment.

* [NOD-867] Fix an error message.

* [NOD-867] Remove preallocation in initDAGState.

* [NOD-866] Migrate database logic in blockdag/indexers package to dbaccess (#682)

* [NOD-865] Delete blockidhash.go.

* [NOD-865] Remove a lot of no-longer relevant logic from indexers.

* [NOD-865] Pass TxContext to ConnectBlock.

* [NOD-865] Migrate the acceptance index to dbaccess.

* [NOD-865] Fix a block not being sent to ConnectBlock.

* [NOD-865] Pass the block's hash instead of the whole block.

* [NOD-865] Add forgotten Commit call.

* [NOD-865] Add comments.

* [NOD-866] Fix a comment.

* [NOD-866] Fix a comment.

* [NOD-866] Remove pointless indirection in acceptanceindex.

* [NOD-866] Fix comment over ForEachHash.

* [NOD-866] Rename ClearAcceptanceIndex to DropAcceptanceIndex.

* [NOD-866] Explain collecting keys before deleting them.

* [NOD-865] Move misc db logic to db access (#681)

* [NOD-828] Create the database2 package that will some day replace the database package.

* [NOD-828] Implement a "bucket" key mechanism.

* [NOD-828] Move bucket.go into the ffldb2 package.

* [NOD-828] Delete the un-interfaced ffldb package from database2, since we aren't going to be using it anyway.

* [NOD-828] Copy over + fixup flat file structs from the old ffldb.

* [NOD-828] Implement flatFilePath.

* [NOD-828] Implement flatFileStore.write().

* [NOD-828] Implement flatFileStore.read().

* [NOD-828] Implement flatFileStore.rollback().

* [NOD-828] Sync the file to disk at the end of write().

* [NOD-828] Extract crc32ByteOrder to a separate variable.

* [NOD-828] Add a sanity test.

* [NOD-828] Remove context-unrelated methods from the Database interface.

* [NOD-828] Create an ffldb object. Simply work against a context.

* [NOD-828] Open the new database on start.

* [NOD-828] Create the leveldb package.

* [NOD-828] Implement opening/closing leveldb.

* [NOD-828] Implement get/put out of/into leveldb.

* [NOD-828] Implement transactions and make them implement a generic database interface.

* [NOD-828] Write sanity tests for leveldb with and without transactions.

* [NOD-828] Add another case to the transaction sanity test.

* [NOD-828] Implement AppendBlock/RetrieveBlock.

* [NOD-828] Refactor so that concepts such as "block" and "metadata" don't leak into the database package.

* [NOD-828] Add RollbackFlatData to the database interface.

* [NOD-828] Remove anything from dbaccess that I'm not planning to implement as part of this ticket.

* [NOD-828] Implement StoreBlock.

* [NOD-828] Implement FetchBlock.

* [NOD-828] Implement HasBlock.

* [NOD-828] Write a sanity test for block insertion.

* [NOD-828] Implement CurrentFlatDataLocation.

* [NOD-828] Implement storing the current block location.

* [NOD-828] Implement initializing/syncing the flat file block store and the "metadata".

* [NOD-828] Add InitBlockStore to TestBlockStoreSanity.

* [NOD-828] Fix rename errors.

* [NOD-828] Fix lint errors in the root database package.

* [NOD-828] Fix lint errors in the ffldb.go.

* [NOD-828] Fix lint errors in the flatfile/db.go.

* [NOD-828] Rename packages in such a way to make the linter happy.

* [NOD-828] Finish satisfying the linter.

* [NOD-828] Fix doc.go.

* [NOD-828] Fix comments in block.go.

* [NOD-828] Move dbaccess out of the database package.

* [NOD-828] Move opening/closing the database to dbaccess.

* [NOD-828] Move the Database interface to the root database package, since it's meant to be generic.

* [NOD-828] Make ffldb generic to later support additional databases.

* [NOD-828] Make ffldb.Open return DatabaseHandle, since ffldb is no longer exported.

* [NOD-828] Fix comments.

* [NOD-828] Rename AppendFlatData to AppendToStore and RetrieveFlatData to RetrieveFromStore.

* [NOD-828] Make buckets nicer to use.

* [NOD-828] Implement cursors that iterate over some bucket.

* [NOD-828] Generalize flat-file repairing and move block database repairing into ffldb.

* [NOD-828] Write a test making sure that flat file repair works.

* [NOD-828] Properly close the database in TestRepairFlatFiles.

* [NOD-828] Add a comment warning against putting and getting the same data within the same transaction.

* [NOD-828] Fix the flatFilesBucket description.

* [NOD-828] Remove superfluous whitespace.

* [NOD-828] Fix the ffldb description.

* [NOD-828] Rename Database to DataAccessor and rename Handle to Database.

* [NOD-828] Make Get return nil if the value doesn't exist.

* [NOD-828] Attempt to close leveldb even if closing ffdb failed.

* [NOD-828] Fix a bug where the wrong location would be written to the current store location bucket.

* [NOD-828] Fix not updating the store location in ffldb transactions.

* [NOD-828] Make scanFlatFiles return an error if os.Stat fails for any reason other than file-not-found.

* [NOD-828] Update the README and doc.go.

* [NOD-828] Simplify Bucket.Path().

* [NOD-828] Since LevelDBCursor satisfied the database2.Cursor interface, use it directly.

* [NOD-828] Combine two lines into one.

* [NOD-828] Combine another two lines into one.

* [NOD-828] Move a misplaced comment.

* [NOD-828] Use Wrapf instead of Errorf where appropriate.

* [NOD-828] Fix a comment.

* [NOD-828] Fix a comment.

* [NOD-828] Fix Wrapf calls.

* [NOD-828] Fix comments.

* [NOD-828] Remove superfluous whitespace.

* [NOD-828] Add a comment explaining the use of batches and snapshots.

* [NOD-828] Implement RollbackUnlessClosed().

* [NOD-828] Return both errors in StoreBlock rollback.

* [NOD-828] Move rollback-on-error logic into ffldb. Remove CurrentStoreLocation and RollbackStore from DataAccessor.

* [NOD-828] Make bucket a type alias instead of a struct.

* [NOD-828] Fix a typo.

* [NOD-828] Use copy instead of append in Bucket.

* [NOD-828] Extract flatFileLocationSerializedSize to a const.

* [NOD-828] Debugf -> Warnf in rollback.go.

* [NOD-828] Fix a comment.

* [NOD-828] Fix a comment.

* [NOD-828] Remove data length from flat file data format.

* [NOD-828] Rearrange TestLevelDBTransactionSanity a bit.

* [NOD-828] Add stack traces to all errors that come out of library functions.

* [NOD-828] Return errors from rollback().

* [NOD-828] Remove an irrelevant comment.

* [NOD-828] Remove redundant whitespace.

* [NOD-828] Handle nil in FetchBlock.

* [NOD-828] Move the explanation about batches and snapshots to the LevelDBTransaction struct.

* Revert "[NOD-828] Make bucket a type alias instead of a struct."

This reverts commit 1fd39652

* [NOD-828] Fix revert errors.

* Revert "[NOD-828] Remove data length from flat file data format."

This reverts commit ef408e32

* [NOD-862] Move Cursor() into the DataAccessor interface.

* [NOD-828] Add Delete to DataAccessor.

* [NOD-865] Move fee data db operations to dbaccess

* [NOD-865] Move reachability data db operations to dbaccess

* [NOD-865] Move UTXO diff data db operations to dbaccess

* [NOD-865] Move subnetwork data db operations to dbaccess

* [NOD-865] Fix createDAGState

* [NOD-865] Remove old Get signature with "exists"

* [NOD-865] Move multiset db operations to dbaccess

* [NOD-865] Use dbaccess transactions where possible

* [NOD-865] Remove old Get signature with "exists"

* [NOD-881] Recover TestGHOSTDAGErrors

* [NOD-865] Create function for db keys

* [NOD-865] Change Exists to Has, and use accessor.Has where possible

* [NOD-865] Make ClearReachabilityData transactive

* [NOD-865] Don't iterate cursors while changing db data

* [NOD-865] Rename RegisterSubnetwork -> StoreSubnetwork

* [NOD-865] Change bucket from utxodiffs to utxo-diffs

* [NOD-865] Rename SubnetworkExists->HasSubnetwork

* [NOD-865] Change a comment

* [NOD-865] Fix tests

* [NOD-865] Fix comment

* [NOD-865] Remove the prefix "db" from some functions

* [NOD-865] Remove redundant comments

* [NOD-865] Make clearBucket function

* [NOD-865] Make clear functions get a dbTx as an arg

* [NOD-865] Remove erroneous tx commit

Co-authored-by: stasatdaglabs <stas@daglabs.com>

* [NOD-868] Delete the old database package (#683)

* [NOD-828] Create the database2 package that will some day replace the database package.

* [NOD-828] Implement a "bucket" key mechanism.

* [NOD-828] Move bucket.go into the ffldb2 package.

* [NOD-828] Delete the un-interfaced ffldb package from database2, since we aren't going to be using it anyway.

* [NOD-828] Copy over + fixup flat file structs from the old ffldb.

* [NOD-828] Implement flatFilePath.

* [NOD-828] Implement flatFileStore.write().

* [NOD-828] Implement flatFileStore.read().

* [NOD-828] Implement flatFileStore.rollback().

* [NOD-828] Sync the file to disk at the end of write().

* [NOD-828] Extract crc32ByteOrder to a separate variable.

* [NOD-828] Add a sanity test.

* [NOD-828] Remove context-unrelated methods from the Database interface.

* [NOD-828] Create an ffldb object. Simply work against a context.

* [NOD-828] Open the new database on start.

* [NOD-828] Create the leveldb package.

* [NOD-828] Implement opening/closing leveldb.

* [NOD-828] Implement get/put out of/into leveldb.

* [NOD-828] Implement transactions and make them implement a generic database interface.

* [NOD-828] Write sanity tests for leveldb with and without transactions.

* [NOD-828] Add another case to the transaction sanity test.

* [NOD-828] Implement AppendBlock/RetrieveBlock.

* [NOD-828] Refactor so that concepts such as "block" and "metadata" don't leak into the database package.

* [NOD-828] Add RollbackFlatData to the database interface.

* [NOD-828] Remove anything from dbaccess that I'm not planning to implement as part of this ticket.

* [NOD-828] Implement StoreBlock.

* [NOD-828] Implement FetchBlock.

* [NOD-828] Implement HasBlock.

* [NOD-828] Write a sanity test for block insertion.

* [NOD-828] Implement CurrentFlatDataLocation.

* [NOD-828] Implement storing the current block location.

* [NOD-828] Implement initializing/syncing the flat file block store and the "metadata".

* [NOD-828] Add InitBlockStore to TestBlockStoreSanity.

* [NOD-828] Fix rename errors.

* [NOD-828] Fix lint errors in the root database package.

* [NOD-828] Fix lint errors in the ffldb.go.

* [NOD-828] Fix lint errors in the flatfile/db.go.

* [NOD-828] Rename packages in such a way to make the linter happy.

* [NOD-828] Finish satisfying the linter.

* [NOD-828] Fix doc.go.

* [NOD-828] Fix comments in block.go.

* [NOD-828] Move dbaccess out of the database package.

* [NOD-828] Move opening/closing the database to dbaccess.

* [NOD-828] Move the Database interface to the root database package, since it's meant to be generic.

* [NOD-828] Make ffldb generic to later support additional databases.

* [NOD-828] Make ffldb.Open return DatabaseHandle, since ffldb is no longer exported.

* [NOD-828] Fix comments.

* [NOD-828] Rename AppendFlatData to AppendToStore and RetrieveFlatData to RetrieveFromStore.

* [NOD-828] Make buckets nicer to use.

* [NOD-828] Implement cursors that iterate over some bucket.

* [NOD-828] Generalize flat-file repairing and move block database repairing into ffldb.

* [NOD-828] Write a test making sure that flat file repair works.

* [NOD-828] Properly close the database in TestRepairFlatFiles.

* [NOD-828] Add a comment warning against putting and getting the same data within the same transaction.

* [NOD-828] Fix the flatFilesBucket description.

* [NOD-828] Remove superfluous whitespace.

* [NOD-828] Fix the ffldb description.

* [NOD-828] Rename Database to DataAccessor and rename Handle to Database.

* [NOD-828] Make Get return nil if the value doesn't exist.

* [NOD-828] Attempt to close leveldb even if closing ffdb failed.

* [NOD-828] Fix a bug where the wrong location would be written to the current store location bucket.

* [NOD-828] Fix not updating the store location in ffldb transactions.

* [NOD-828] Make scanFlatFiles return an error if os.Stat fails for any reason other than file-not-found.

* [NOD-828] Update the README and doc.go.

* [NOD-828] Simplify Bucket.Path().

* [NOD-828] Since LevelDBCursor satisfied the database2.Cursor interface, use it directly.

* [NOD-828] Combine two lines into one.

* [NOD-828] Combine another two lines into one.

* [NOD-828] Move a misplaced comment.

* [NOD-828] Use Wrapf instead of Errorf where appropriate.

* [NOD-828] Fix a comment.

* [NOD-828] Fix a comment.

* [NOD-828] Fix Wrapf calls.

* [NOD-828] Fix comments.

* [NOD-828] Remove superfluous whitespace.

* [NOD-828] Add a comment explaining the use of batches and snapshots.

* [NOD-828] Implement RollbackUnlessClosed().

* [NOD-828] Return both errors in StoreBlock rollback.

* [NOD-828] Move rollback-on-error logic into ffldb. Remove CurrentStoreLocation and RollbackStore from DataAccessor.

* [NOD-828] Make bucket a type alias instead of a struct.

* [NOD-828] Fix a typo.

* [NOD-828] Use copy instead of append in Bucket.

* [NOD-828] Extract flatFileLocationSerializedSize to a const.

* [NOD-828] Debugf -> Warnf in rollback.go.

* [NOD-828] Fix a comment.

* [NOD-828] Fix a comment.

* [NOD-828] Remove data length from flat file data format.

* [NOD-828] Rearrange TestLevelDBTransactionSanity a bit.

* [NOD-828] Add stack traces to all errors that come out of library functions.

* [NOD-828] Return errors from rollback().

* [NOD-828] Remove an irrelevant comment.

* [NOD-828] Remove redundant whitespace.

* [NOD-828] Handle nil in FetchBlock.

* [NOD-828] Move the explanation about batches and snapshots to the LevelDBTransaction struct.

* Revert "[NOD-828] Make bucket a type alias instead of a struct."

This reverts commit 1fd39652

* [NOD-828] Fix revert errors.

* Revert "[NOD-828] Remove data length from flat file data format."

This reverts commit ef408e32

* [NOD-862] Move Cursor() into the DataAccessor interface.

* [NOD-828] Add Delete to DataAccessor.

* [NOD-865] Move fee data db operations to dbaccess

* [NOD-865] Move reachability data db operations to dbaccess

* [NOD-865] Move UTXO diff data db operations to dbaccess

* [NOD-865] Move subnetwork data db operations to dbaccess

* [NOD-865] Fix createDAGState

* [NOD-865] Remove old Get signature with "exists"

* [NOD-865] Move multiset db operations to dbaccess

* [NOD-865] Use dbaccess transactions where possible

* [NOD-865] Remove old Get signature with "exists"

* [NOD-881] Recover TestGHOSTDAGErrors

* [NOD-865] Create function for db keys

* [NOD-865] Change Exists to Has, and use accessor.Has where possible

* [NOD-865] Make ClearReachabilityData transactive

* [NOD-865] Don't iterate cursors while changing db data

* [NOD-865] Rename RegisterSubnetwork -> StoreSubnetwork

* [NOD-865] Change bucket from utxodiffs to utxo-diffs

* [NOD-865] Rename SubnetworkExists->HasSubnetwork

* [NOD-865] Change a comment

* [NOD-868] Remove all tests from old database.

* [NOD-868] Remove all unused methods from the old database's interfaces.

* [NOD-865] Fix tests

* [NOD-868] Remove references to DB.

* [NOD-865] Fix comment

* [NOD-868] Remove the old ffldb besides the interface and errors.go.

* [NOD-868] Remove errors.go.

* [NOD-868] Remove the old database package.

* [NOD-868] Add openDB to DAGSetup to emulate the old dbpath in dag.config.

* [NOD-868] Rename database2 to database.

* [NOD-868] Use NewTx instead of NoTx where required.

* [NOD-868] Fix merge errors.

* [NOD-868] Rename dbXXX functions to just xxx.

* [NOD-868] Rename putDAGState to saveDAGState.

* [NOD-868] Replace comments in initDAGState with logs.

* [NOD-868] Explain the openDB parameter in DAGSetup.

* [NOD-868] Fixup doc.go and README.md.

* [NOD-868] Remove pointless transactions.

Co-authored-by: Ori Newman <orinewman1@gmail.com>

* [NOD-805] Fix merge errors.

* [NOD-805] Fix a comment.

* [NOD-805] Don't return virtualTxsAcceptanceData from applyDAGChanges.

* [NOD-805] Add missing error handling in TestAcceptanceDataIndexRecover.

* [NOD-805] Rename blockDAG to dag in indexers/manager.go.

* [NOD-805] Defer cursor.Close() everywhere.

* [NOD-805] Rename scanFlatFiles to findCurrentLocation.

* [NOD-805] Extract crc32ChecksumLength and dataLengthLength to constants.

* [NOD-805] Handle open files properly in rollback.go.

* [NOD-805] Remove unnecessary func wrapper.

* [NOD-805] Remove unnecessary trimming in initialize.

* [NOD-805] Made StoreBlock accept only TxContext.

* [NOD-805] Changed the log level of an error message to Error.

* [NOD-805] Add a note about holding mutexes over deleteFile.

* [NOD-805] Remove a false comment.

* [NOD-805] Fix a comment.

* [NOD-805] Rename blk to block.

* [NOD-805] Extract utxoKey to a separate function.

* [NOD-805] Move dbaccess.xxxKey functions to the tops of their respective files.

* [NOD-805] Fix grammar in dbaccess/db.go.

* [NOD-805] Wrap a failed database corruption recovery error.

* [NOD-805] Split lines with WithStack in them.

* [NOD-805] Fix the comment over initialize.

* [NOD-805] Rename ffdb to flatFileDB and ldb to levelDB.

* [NOD-805] Fix a comment.

* [NOD-805] Fix a comment.

* [NOD-805] Use s.writeCursor instead of cursor.

* [NOD-805] Embed file in lockableFile.

* [NOD-805] the the -> the

* [NOD-805] openDB -> db

* [NOD-805] Use TxContext in all flushToDB functions.

* [NOD-805] Rename context -> dbContext.

* [NOD-805] Reword the comment at the beginning on initDAGState.

* [NOD-805] Explain cursor key trimming.

* [NOD-805] Remove Error from Cursor.

* [NOD-805] Return ErrNotFound from done Cursor Key and Value.

* [NOD-805] Add missing error handling.

* [NOD-805] Fix a comment.

* [NOD-805] Fix a variable name.

* [NOD-805] Remove pointless underscore.

* [NOD-805] Fix a comment.

* [NOD-805] Fix a variable name.

Co-authored-by: Mike Zak <feanorr@gmail.com>
Co-authored-by: Ori Newman <orinewman1@gmail.com>
2020-04-02 13:56:32 +03:00

1120 lines
38 KiB
Go

// Copyright (c) 2013-2017 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package blockdag
import (
"fmt"
"github.com/kaspanet/kaspad/dbaccess"
"github.com/pkg/errors"
"os"
"path/filepath"
"testing"
"time"
"github.com/kaspanet/kaspad/dagconfig"
"github.com/kaspanet/kaspad/txscript"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/kaspanet/kaspad/util/subnetworkid"
"github.com/kaspanet/kaspad/wire"
)
func TestBlockCount(t *testing.T) {
// Load up blocks such that there is a fork in the DAG.
// (genesis block) -> 1 -> 2 -> 3 -> 4
// \-> 3b
testFiles := []string{
"blk_0_to_4.dat",
"blk_3B.dat",
}
var blocks []*util.Block
for _, file := range testFiles {
blockTmp, err := LoadBlocks(filepath.Join("testdata/", file))
if err != nil {
t.Fatalf("Error loading file: %v\n", err)
}
blocks = append(blocks, blockTmp...)
}
// Create a new database and DAG instance to run tests against.
dag, teardownFunc, err := DAGSetup("TestBlockCount", true, Config{
DAGParams: &dagconfig.SimnetParams,
})
if err != nil {
t.Fatalf("Failed to setup DAG instance: %v", err)
}
defer teardownFunc()
// Since we're not dealing with the real block DAG, set the coinbase
// maturity to 0.
dag.TestSetCoinbaseMaturity(0)
for i := 1; i < len(blocks); i++ {
isOrphan, isDelayed, err := dag.ProcessBlock(blocks[i], BFNone)
if err != nil {
t.Fatalf("ProcessBlock fail on block %v: %v\n", i, err)
}
if isDelayed {
t.Fatalf("ProcessBlock: block %d "+
"is too far in the future", i)
}
if isOrphan {
t.Fatalf("ProcessBlock incorrectly returned block %v "+
"is an orphan\n", i)
}
}
expectedBlockCount := uint64(6)
if dag.BlockCount() != expectedBlockCount {
t.Errorf("TestBlockCount: BlockCount expected to return %v but got %v", expectedBlockCount, dag.BlockCount())
}
}
// TestIsKnownBlock tests the IsKnownBlock API to ensure proper functionality.
func TestIsKnownBlock(t *testing.T) {
// Load up blocks such that there is a fork in the DAG.
// (genesis block) -> 1 -> 2 -> 3 -> 4
// \-> 3b
testFiles := []string{
"blk_0_to_4.dat",
"blk_3B.dat",
}
var blocks []*util.Block
for _, file := range testFiles {
blockTmp, err := LoadBlocks(filepath.Join("testdata/", file))
if err != nil {
t.Fatalf("Error loading file: %v\n", err)
}
blocks = append(blocks, blockTmp...)
}
// Create a new database and DAG instance to run tests against.
dag, teardownFunc, err := DAGSetup("haveblock", true, Config{
DAGParams: &dagconfig.SimnetParams,
})
if err != nil {
t.Fatalf("Failed to setup DAG instance: %v", err)
}
defer teardownFunc()
// Since we're not dealing with the real block DAG, set the coinbase
// maturity to 0.
dag.TestSetCoinbaseMaturity(0)
for i := 1; i < len(blocks); i++ {
isOrphan, isDelayed, err := dag.ProcessBlock(blocks[i], BFNone)
if err != nil {
t.Fatalf("ProcessBlock fail on block %v: %v\n", i, err)
}
if isDelayed {
t.Fatalf("ProcessBlock: block %d "+
"is too far in the future", i)
}
if isOrphan {
t.Fatalf("ProcessBlock incorrectly returned block %v "+
"is an orphan\n", i)
}
}
// Test a block with related parents
testFiles = []string{
"blk_3C.dat",
}
for _, file := range testFiles {
blockTmp, err := LoadBlocks(filepath.Join("testdata/", file))
if err != nil {
t.Fatalf("Error loading file: %v\n", err)
}
blocks = append(blocks, blockTmp...)
}
isOrphan, isDelayed, err := dag.ProcessBlock(blocks[6], BFNone)
// Block 3C should fail to connect since its parents are related. (It points to 1 and 2, and 1 is the parent of 2)
if err == nil {
t.Fatalf("ProcessBlock for block 3C has no error when expected to have an error\n")
}
if isDelayed {
t.Fatalf("ProcessBlock: block 3C " +
"is too far in the future")
}
if isOrphan {
t.Fatalf("ProcessBlock incorrectly returned block 3C " +
"is an orphan\n")
}
// Test a block with the same input twice
testFiles = []string{
"blk_3D.dat",
}
for _, file := range testFiles {
blockTmp, err := LoadBlocks(filepath.Join("testdata/", file))
if err != nil {
t.Fatalf("Error loading file: %v\n", err)
}
blocks = append(blocks, blockTmp...)
}
isOrphan, isDelayed, err = dag.ProcessBlock(blocks[7], BFNone)
// Block 3D should fail to connect since it has a transaction with the same input twice
if err == nil {
t.Fatalf("ProcessBlock for block 3D has no error when expected to have an error\n")
}
var ruleErr RuleError
ok := errors.As(err, &ruleErr)
if !ok {
t.Fatalf("ProcessBlock for block 3D expected a RuleError, but got %v\n", err)
}
if !ok || ruleErr.ErrorCode != ErrDuplicateTxInputs {
t.Fatalf("ProcessBlock for block 3D expected error code %s but got %s\n", ErrDuplicateTxInputs, ruleErr.ErrorCode)
}
if isDelayed {
t.Fatalf("ProcessBlock: block 3D " +
"is too far in the future")
}
if isOrphan {
t.Fatalf("ProcessBlock incorrectly returned block 3D " +
"is an orphan\n")
}
// Insert an orphan block.
isOrphan, isDelayed, err = dag.ProcessBlock(util.NewBlock(&Block100000), BFNoPoWCheck)
if err != nil {
t.Fatalf("Unable to process block 100000: %v", err)
}
if isDelayed {
t.Fatalf("ProcessBlock incorrectly returned that block 100000 " +
"has a delay")
}
if !isOrphan {
t.Fatalf("ProcessBlock indicated block is an not orphan when " +
"it should be\n")
}
tests := []struct {
hash string
want bool
}{
// Genesis block should be present.
{hash: dagconfig.SimnetParams.GenesisHash.String(), want: true},
// Block 3b should be present (as a second child of Block 2).
{hash: "216301e3fc03cf89973b9192b4ecdd732bf3b677cf1ca4f6c340a56f1533fb4f", want: true},
// Block 100000 should be present (as an orphan).
{hash: "65b20b048a074793ebfd1196e49341c8d194dabfc6b44a4fd0c607406e122baf", want: true},
// Random hashes should not be available.
{hash: "123", want: false},
}
for i, test := range tests {
hash, err := daghash.NewHashFromStr(test.hash)
if err != nil {
t.Fatalf("NewHashFromStr: %v", err)
}
result := dag.IsKnownBlock(hash)
if result != test.want {
t.Fatalf("IsKnownBlock #%d got %v want %v", i, result,
test.want)
}
}
}
// TestCalcSequenceLock tests the LockTimeToSequence function, and the
// CalcSequenceLock method of a DAG instance. The tests exercise several
// combinations of inputs to the CalcSequenceLock function in order to ensure
// the returned SequenceLocks are correct for each test instance.
func TestCalcSequenceLock(t *testing.T) {
netParams := &dagconfig.SimnetParams
blockVersion := int32(0x10000000)
// Generate enough synthetic blocks for the rest of the test
dag := newTestDAG(netParams)
node := dag.selectedTip()
blockTime := node.Header().Timestamp
numBlocksToGenerate := 5
for i := 0; i < numBlocksToGenerate; i++ {
blockTime = blockTime.Add(time.Second)
node = newTestNode(dag, blockSetFromSlice(node), blockVersion, 0, blockTime)
dag.index.AddNode(node)
dag.virtual.SetTips(blockSetFromSlice(node))
}
// Create a utxo view with a fake utxo for the inputs used in the
// transactions created below. This utxo is added such that it has an
// age of 4 blocks.
msgTx := wire.NewNativeMsgTx(wire.TxVersion, nil, []*wire.TxOut{{ScriptPubKey: nil, Value: 10}})
targetTx := util.NewTx(msgTx)
utxoSet := NewFullUTXOSet()
blueScore := uint64(numBlocksToGenerate) - 4
if isAccepted, err := utxoSet.AddTx(targetTx.MsgTx(), blueScore); err != nil {
t.Fatalf("AddTx unexpectedly failed. Error: %s", err)
} else if !isAccepted {
t.Fatalf("AddTx unexpectedly didn't add tx %s", targetTx.ID())
}
// Create a utxo that spends the fake utxo created above for use in the
// transactions created in the tests. It has an age of 4 blocks. Note
// that the sequence lock heights are always calculated from the same
// point of view that they were originally calculated from for a given
// utxo. That is to say, the height prior to it.
utxo := wire.Outpoint{
TxID: *targetTx.ID(),
Index: 0,
}
prevUtxoBlueScore := uint64(numBlocksToGenerate) - 4
// Obtain the past median time from the PoV of the input created above.
// The past median time for the input is the past median time from the PoV
// of the block *prior* to the one that included it.
medianTime := node.RelativeAncestor(5).PastMedianTime(dag).Unix()
// The median time calculated from the PoV of the best block in the
// test DAG. For unconfirmed inputs, this value will be used since
// the MTP will be calculated from the PoV of the yet-to-be-mined
// block.
nextMedianTime := node.PastMedianTime(dag).Unix()
nextBlockBlueScore := int32(numBlocksToGenerate) + 1
// Add an additional transaction which will serve as our unconfirmed
// output.
unConfTx := wire.NewNativeMsgTx(wire.TxVersion, nil, []*wire.TxOut{{ScriptPubKey: nil, Value: 5}})
unConfUtxo := wire.Outpoint{
TxID: *unConfTx.TxID(),
Index: 0,
}
if isAccepted, err := utxoSet.AddTx(unConfTx, UnacceptedBlueScore); err != nil {
t.Fatalf("AddTx unexpectedly failed. Error: %s", err)
} else if !isAccepted {
t.Fatalf("AddTx unexpectedly didn't add tx %s", unConfTx.TxID())
}
tests := []struct {
name string
tx *wire.MsgTx
utxoSet UTXOSet
mempool bool
want *SequenceLock
}{
// A transaction with a single input with max sequence number.
// This sequence number has the high bit set, so sequence locks
// should be disabled.
{
name: "single input, max sequence number",
tx: wire.NewNativeMsgTx(1, []*wire.TxIn{{PreviousOutpoint: utxo, Sequence: wire.MaxTxInSequenceNum}}, nil),
utxoSet: utxoSet,
want: &SequenceLock{
Seconds: -1,
BlockBlueScore: -1,
},
},
// A transaction with a single input whose lock time is
// expressed in seconds. However, the specified lock time is
// below the required floor for time based lock times since
// they have time granularity of 512 seconds. As a result, the
// seconds lock-time should be just before the median time of
// the targeted block.
{
name: "single input, seconds lock time below time granularity",
tx: wire.NewNativeMsgTx(1, []*wire.TxIn{{PreviousOutpoint: utxo, Sequence: LockTimeToSequence(true, 2)}}, nil),
utxoSet: utxoSet,
want: &SequenceLock{
Seconds: medianTime - 1,
BlockBlueScore: -1,
},
},
// A transaction with a single input whose lock time is
// expressed in seconds. The number of seconds should be 1023
// seconds after the median past time of the last block in the
// chain.
{
name: "single input, 1023 seconds after median time",
tx: wire.NewNativeMsgTx(1, []*wire.TxIn{{PreviousOutpoint: utxo, Sequence: LockTimeToSequence(true, 1024)}}, nil),
utxoSet: utxoSet,
want: &SequenceLock{
Seconds: medianTime + 1023,
BlockBlueScore: -1,
},
},
// A transaction with multiple inputs. The first input has a
// lock time expressed in seconds. The second input has a
// sequence lock in blocks with a value of 4. The last input
// has a sequence number with a value of 5, but has the disable
// bit set. So the first lock should be selected as it's the
// latest lock that isn't disabled.
{
name: "multiple varied inputs",
tx: wire.NewNativeMsgTx(1,
[]*wire.TxIn{{
PreviousOutpoint: utxo,
Sequence: LockTimeToSequence(true, 2560),
}, {
PreviousOutpoint: utxo,
Sequence: LockTimeToSequence(false, 4),
}, {
PreviousOutpoint: utxo,
Sequence: LockTimeToSequence(false, 5) |
wire.SequenceLockTimeDisabled,
}},
nil),
utxoSet: utxoSet,
want: &SequenceLock{
Seconds: medianTime + (5 << wire.SequenceLockTimeGranularity) - 1,
BlockBlueScore: int64(prevUtxoBlueScore) + 3,
},
},
// Transaction with a single input. The input's sequence number
// encodes a relative lock-time in blocks (3 blocks). The
// sequence lock should have a value of -1 for seconds, but a
// height of 2 meaning it can be included at height 3.
{
name: "single input, lock-time in blocks",
tx: wire.NewNativeMsgTx(1, []*wire.TxIn{{PreviousOutpoint: utxo, Sequence: LockTimeToSequence(false, 3)}}, nil),
utxoSet: utxoSet,
want: &SequenceLock{
Seconds: -1,
BlockBlueScore: int64(prevUtxoBlueScore) + 2,
},
},
// A transaction with two inputs with lock times expressed in
// seconds. The selected sequence lock value for seconds should
// be the time further in the future.
{
name: "two inputs, lock-times in seconds",
tx: wire.NewNativeMsgTx(1, []*wire.TxIn{{
PreviousOutpoint: utxo,
Sequence: LockTimeToSequence(true, 5120),
}, {
PreviousOutpoint: utxo,
Sequence: LockTimeToSequence(true, 2560),
}}, nil),
utxoSet: utxoSet,
want: &SequenceLock{
Seconds: medianTime + (10 << wire.SequenceLockTimeGranularity) - 1,
BlockBlueScore: -1,
},
},
// A transaction with two inputs with lock times expressed in
// blocks. The selected sequence lock value for blocks should
// be the height further in the future, so a height of 10
// indicating it can be included at height 11.
{
name: "two inputs, lock-times in blocks",
tx: wire.NewNativeMsgTx(1,
[]*wire.TxIn{{
PreviousOutpoint: utxo,
Sequence: LockTimeToSequence(false, 1),
}, {
PreviousOutpoint: utxo,
Sequence: LockTimeToSequence(false, 11),
}},
nil),
utxoSet: utxoSet,
want: &SequenceLock{
Seconds: -1,
BlockBlueScore: int64(prevUtxoBlueScore) + 10,
},
},
// A transaction with multiple inputs. Two inputs are time
// based, and the other two are block based. The lock lying
// further into the future for both inputs should be chosen.
{
name: "four inputs, two lock-times in time, two lock-times in blocks",
tx: wire.NewNativeMsgTx(1,
[]*wire.TxIn{{
PreviousOutpoint: utxo,
Sequence: LockTimeToSequence(true, 2560),
}, {
PreviousOutpoint: utxo,
Sequence: LockTimeToSequence(true, 6656),
}, {
PreviousOutpoint: utxo,
Sequence: LockTimeToSequence(false, 3),
}, {
PreviousOutpoint: utxo,
Sequence: LockTimeToSequence(false, 9),
}},
nil),
utxoSet: utxoSet,
want: &SequenceLock{
Seconds: medianTime + (13 << wire.SequenceLockTimeGranularity) - 1,
BlockBlueScore: int64(prevUtxoBlueScore) + 8,
},
},
// A transaction with a single unconfirmed input. As the input
// is confirmed, the height of the input should be interpreted
// as the height of the *next* block. So, a 2 block relative
// lock means the sequence lock should be for 1 block after the
// *next* block height, indicating it can be included 2 blocks
// after that.
{
name: "single input, unconfirmed, lock-time in blocks",
tx: wire.NewNativeMsgTx(1, []*wire.TxIn{{PreviousOutpoint: unConfUtxo, Sequence: LockTimeToSequence(false, 2)}}, nil),
utxoSet: utxoSet,
mempool: true,
want: &SequenceLock{
Seconds: -1,
BlockBlueScore: int64(nextBlockBlueScore) + 1,
},
},
// A transaction with a single unconfirmed input. The input has
// a time based lock, so the lock time should be based off the
// MTP of the *next* block.
{
name: "single input, unconfirmed, lock-time in seoncds",
tx: wire.NewNativeMsgTx(1, []*wire.TxIn{{PreviousOutpoint: unConfUtxo, Sequence: LockTimeToSequence(true, 1024)}}, nil),
utxoSet: utxoSet,
mempool: true,
want: &SequenceLock{
Seconds: nextMedianTime + 1023,
BlockBlueScore: -1,
},
},
}
t.Logf("Running %v SequenceLock tests", len(tests))
for _, test := range tests {
utilTx := util.NewTx(test.tx)
seqLock, err := dag.CalcSequenceLock(utilTx, utxoSet, test.mempool)
if err != nil {
t.Fatalf("test '%s', unable to calc sequence lock: %v", test.name, err)
}
if seqLock.Seconds != test.want.Seconds {
t.Fatalf("test '%s' got %v seconds want %v seconds",
test.name, seqLock.Seconds, test.want.Seconds)
}
if seqLock.BlockBlueScore != test.want.BlockBlueScore {
t.Fatalf("test '%s' got blue score of %v want blue score of %v ",
test.name, seqLock.BlockBlueScore, test.want.BlockBlueScore)
}
}
}
func TestCalcPastMedianTime(t *testing.T) {
netParams := &dagconfig.SimnetParams
blockVersion := int32(0x10000000)
dag := newTestDAG(netParams)
numBlocks := uint32(300)
nodes := make([]*blockNode, numBlocks)
nodes[0] = dag.genesis
blockTime := dag.genesis.Header().Timestamp
for i := uint32(1); i < numBlocks; i++ {
blockTime = blockTime.Add(time.Second)
nodes[i] = newTestNode(dag, blockSetFromSlice(nodes[i-1]), blockVersion, 0, blockTime)
dag.index.AddNode(nodes[i])
}
tests := []struct {
blockNumber uint32
expectedSecondsSinceGenesis int64
}{
{
blockNumber: 262,
expectedSecondsSinceGenesis: 130,
},
{
blockNumber: 270,
expectedSecondsSinceGenesis: 138,
},
{
blockNumber: 240,
expectedSecondsSinceGenesis: 108,
},
{
blockNumber: 5,
expectedSecondsSinceGenesis: 0,
},
}
for _, test := range tests {
secondsSinceGenesis := nodes[test.blockNumber].PastMedianTime(dag).Unix() - dag.genesis.Header().Timestamp.Unix()
if secondsSinceGenesis != test.expectedSecondsSinceGenesis {
t.Errorf("TestCalcPastMedianTime: expected past median time of block %v to be %v seconds from genesis but got %v", test.blockNumber, test.expectedSecondsSinceGenesis, secondsSinceGenesis)
}
}
}
func TestNew(t *testing.T) {
tempDir := os.TempDir()
dbPath := filepath.Join(tempDir, "TestNew")
_ = os.RemoveAll(dbPath)
err := dbaccess.Open(dbPath)
if err != nil {
t.Fatalf("error creating db: %s", err)
}
defer func() {
dbaccess.Close()
os.RemoveAll(dbPath)
}()
config := &Config{
DAGParams: &dagconfig.SimnetParams,
TimeSource: NewTimeSource(),
SigCache: txscript.NewSigCache(1000),
}
_, err = New(config)
if err != nil {
t.Fatalf("failed to create dag instance: %s", err)
}
config.SubnetworkID = &subnetworkid.SubnetworkID{0xff}
_, err = New(config)
expectedErrorMessage := fmt.Sprintf("Cannot start kaspad with subnetwork ID %s because"+
" its database is already built with subnetwork ID <nil>. If you"+
" want to switch to a new database, please reset the"+
" database by starting kaspad with --reset-db flag", config.SubnetworkID)
if err.Error() != expectedErrorMessage {
t.Errorf("Unexpected error. Expected error '%s' but got '%s'", expectedErrorMessage, err)
}
}
// TestAcceptingInInit makes sure that blocks that were stored but not
// yet fully processed do get correctly processed on DAG init. This may
// occur when the node shuts down improperly while a block is being
// validated.
func TestAcceptingInInit(t *testing.T) {
tempDir := os.TempDir()
// Create a test database
dbPath := filepath.Join(tempDir, "TestAcceptingInInit")
_ = os.RemoveAll(dbPath)
err := dbaccess.Open(dbPath)
if err != nil {
t.Fatalf("error creating db: %s", err)
}
defer func() {
dbaccess.Close()
os.RemoveAll(dbPath)
}()
// Create a DAG to add the test block into
config := &Config{
DAGParams: &dagconfig.SimnetParams,
TimeSource: NewTimeSource(),
SigCache: txscript.NewSigCache(1000),
}
dag, err := New(config)
if err != nil {
t.Fatalf("failed to create dag instance: %s", err)
}
// Load the test block
blocks, err := LoadBlocks("testdata/blk_0_to_4.dat")
if err != nil {
t.Fatalf("Error loading file: %v\n", err)
}
genesisBlock := blocks[0]
testBlock := blocks[1]
// Create a test blockNode with an unvalidated status
genesisNode := dag.index.LookupNode(genesisBlock.Hash())
testNode, _ := dag.newBlockNode(&testBlock.MsgBlock().Header, blockSetFromSlice(genesisNode))
testNode.status = statusDataStored
// Manually add the test block to the database
dbTx, err := dbaccess.NewTx()
if err != nil {
t.Fatalf("Failed to open database "+
"transaction: %s", err)
}
defer dbTx.RollbackUnlessClosed()
err = storeBlock(dbTx, testBlock)
if err != nil {
t.Fatalf("Failed to store block: %s", err)
}
dbTestNode, err := serializeBlockNode(testNode)
if err != nil {
t.Fatalf("Failed to serialize blockNode: %s", err)
}
key := blockIndexKey(testNode.hash, testNode.blueScore)
err = dbaccess.StoreIndexBlock(dbTx, key, dbTestNode)
if err != nil {
t.Fatalf("Failed to update block index: %s", err)
}
err = dbTx.Commit()
if err != nil {
t.Fatalf("Failed to commit database "+
"transaction: %s", err)
}
// Create a new DAG. We expect this DAG to process the
// test node
dag, err = New(config)
if err != nil {
t.Fatalf("failed to create dag instance: %s", err)
}
// Make sure that the test node's status is valid
testNode = dag.index.LookupNode(testBlock.Hash())
if testNode.status&statusValid == 0 {
t.Fatalf("testNode is unexpectedly invalid")
}
}
func TestConfirmations(t *testing.T) {
// Create a new database and DAG instance to run tests against.
params := dagconfig.SimnetParams
params.K = 1
dag, teardownFunc, err := DAGSetup("TestConfirmations", true, Config{
DAGParams: &params,
})
if err != nil {
t.Fatalf("Failed to setup DAG instance: %v", err)
}
defer teardownFunc()
dag.TestSetCoinbaseMaturity(0)
// Check that the genesis block of a DAG with only the genesis block in it has confirmations = 1.
genesisConfirmations, err := dag.blockConfirmations(dag.genesis)
if err != nil {
t.Fatalf("TestConfirmations: confirmations for genesis block unexpectedly failed: %s", err)
}
if genesisConfirmations != 0 {
t.Fatalf("TestConfirmations: unexpected confirmations for genesis block. Want: 1, got: %d", genesisConfirmations)
}
// Add a chain of blocks
chainBlocks := make([]*wire.MsgBlock, 5)
chainBlocks[0] = dag.dagParams.GenesisBlock
for i := uint32(1); i < 5; i++ {
chainBlocks[i] = prepareAndProcessBlock(t, dag, chainBlocks[i-1])
}
// Make sure that each one of the chain blocks has the expected confirmations number
for i, block := range chainBlocks {
confirmations, err := dag.BlockConfirmationsByHash(block.BlockHash())
if err != nil {
t.Fatalf("TestConfirmations: confirmations for block unexpectedly failed: %s", err)
}
expectedConfirmations := uint64(len(chainBlocks) - i - 1)
if confirmations != expectedConfirmations {
t.Fatalf("TestConfirmations: unexpected confirmations for block. "+
"Want: %d, got: %d", expectedConfirmations, confirmations)
}
}
branchingBlocks := make([]*wire.MsgBlock, 2)
// Add two branching blocks
branchingBlocks[0] = prepareAndProcessBlock(t, dag, chainBlocks[1])
branchingBlocks[1] = prepareAndProcessBlock(t, dag, branchingBlocks[0])
// Check that the genesis has a confirmations number == len(chainBlocks)
genesisConfirmations, err = dag.blockConfirmations(dag.genesis)
if err != nil {
t.Fatalf("TestConfirmations: confirmations for genesis block unexpectedly failed: %s", err)
}
expectedGenesisConfirmations := uint64(len(chainBlocks)) - 1
if genesisConfirmations != expectedGenesisConfirmations {
t.Fatalf("TestConfirmations: unexpected confirmations for genesis block. "+
"Want: %d, got: %d", expectedGenesisConfirmations, genesisConfirmations)
}
// Check that each of the tips has a 0 confirmations
tips := dag.virtual.tips()
for tip := range tips {
tipConfirmations, err := dag.blockConfirmations(tip)
if err != nil {
t.Fatalf("TestConfirmations: confirmations for tip unexpectedly failed: %s", err)
}
expectedConfirmations := uint64(0)
if tipConfirmations != expectedConfirmations {
t.Fatalf("TestConfirmations: unexpected confirmations for tip. "+
"Want: %d, got: %d", expectedConfirmations, tipConfirmations)
}
}
// Generate 100 blocks to force the "main" chain to become red
branchingChainTip := branchingBlocks[1]
for i := uint32(0); i < 100; i++ {
nextBranchingChainTip := prepareAndProcessBlock(t, dag, branchingChainTip)
branchingChainTip = nextBranchingChainTip
}
// Make sure that a red block has confirmation number = 0
redChainBlock := chainBlocks[3]
redChainBlockConfirmations, err := dag.BlockConfirmationsByHash(redChainBlock.BlockHash())
if err != nil {
t.Fatalf("TestConfirmations: confirmations for red chain block unexpectedly failed: %s", err)
}
if redChainBlockConfirmations != 0 {
t.Fatalf("TestConfirmations: unexpected confirmations for red chain block. "+
"Want: 0, got: %d", redChainBlockConfirmations)
}
// Make sure that the red tip has confirmation number = 0
redChainTip := chainBlocks[len(chainBlocks)-1]
redChainTipConfirmations, err := dag.BlockConfirmationsByHash(redChainTip.BlockHash())
if err != nil {
t.Fatalf("TestConfirmations: confirmations for red chain tip unexpectedly failed: %s", err)
}
if redChainTipConfirmations != 0 {
t.Fatalf("TestConfirmations: unexpected confirmations for red tip block. "+
"Want: 0, got: %d", redChainTipConfirmations)
}
}
func TestAcceptingBlock(t *testing.T) {
// Create a new database and DAG instance to run tests against.
params := dagconfig.SimnetParams
params.K = 3
dag, teardownFunc, err := DAGSetup("TestAcceptingBlock", true, Config{
DAGParams: &params,
})
if err != nil {
t.Fatalf("Failed to setup DAG instance: %v", err)
}
defer teardownFunc()
dag.TestSetCoinbaseMaturity(0)
acceptingBlockByMsgBlock := func(block *wire.MsgBlock) (*blockNode, error) {
node := nodeByMsgBlock(t, dag, block)
return dag.acceptingBlock(node)
}
// Check that the genesis block of a DAG with only the genesis block in it is accepted by the virtual.
genesisAcceptingBlock, err := dag.acceptingBlock(dag.genesis)
if err != nil {
t.Fatalf("TestAcceptingBlock: acceptingBlock for genesis block unexpectedly failed: %s", err)
}
if genesisAcceptingBlock != nil {
t.Fatalf("TestAcceptingBlock: unexpected acceptingBlock for genesis block. "+
"Want: nil, got: %s", genesisAcceptingBlock.hash)
}
numChainBlocks := uint32(10)
chainBlocks := make([]*wire.MsgBlock, numChainBlocks)
chainBlocks[0] = dag.dagParams.GenesisBlock
for i := uint32(1); i <= numChainBlocks-1; i++ {
chainBlocks[i] = prepareAndProcessBlock(t, dag, chainBlocks[i-1])
}
// Make sure that each chain block (including the genesis) is accepted by its child
for i, chainBlockNode := range chainBlocks[:len(chainBlocks)-1] {
expectedAcceptingBlockNode := nodeByMsgBlock(t, dag, chainBlocks[i+1])
chainAcceptingBlockNode, err := acceptingBlockByMsgBlock(chainBlockNode)
if err != nil {
t.Fatalf("TestAcceptingBlock: acceptingBlock for chain block %d unexpectedly failed: %s", i, err)
}
if expectedAcceptingBlockNode != chainAcceptingBlockNode {
t.Fatalf("TestAcceptingBlock: unexpected acceptingBlock for chain block. "+
"Want: %s, got: %s", expectedAcceptingBlockNode.hash, chainAcceptingBlockNode.hash)
}
}
// Make sure that the selected tip doesn't have an accepting
tipAcceptingBlock, err := acceptingBlockByMsgBlock(chainBlocks[len(chainBlocks)-1])
if err != nil {
t.Fatalf("TestAcceptingBlock: acceptingBlock for tip unexpectedly failed: %s", err)
}
if tipAcceptingBlock != nil {
t.Fatalf("TestAcceptingBlock: unexpected acceptingBlock for tip. "+
"Want: nil, got: %s", tipAcceptingBlock.hash)
}
// Generate a chain tip that will be in the anticone of the selected tip and
// in dag.virtual.blues.
branchingChainTip := prepareAndProcessBlock(t, dag, chainBlocks[len(chainBlocks)-3])
// Make sure that branchingChainTip is not in the selected parent chain
isBranchingChainTipInSelectedParentChain, err := dag.IsInSelectedParentChain(branchingChainTip.BlockHash())
if err != nil {
t.Fatalf("TestAcceptingBlock: IsInSelectedParentChain unexpectedly failed: %s", err)
}
if isBranchingChainTipInSelectedParentChain {
t.Fatalf("TestAcceptingBlock: branchingChainTip wasn't expected to be in the selected parent chain")
}
// Make sure that branchingChainTip is in the virtual blues
isVirtualBlue := false
for _, virtualBlue := range dag.virtual.blues {
if branchingChainTip.BlockHash().IsEqual(virtualBlue.hash) {
isVirtualBlue = true
break
}
}
if !isVirtualBlue {
t.Fatalf("TestAcceptingBlock: redChainBlock was expected to be a virtual blue")
}
// Make sure that a block that is in the anticone of the selected tip and
// in the blues of the virtual doesn't have an accepting block
branchingChainTipAcceptionBlock, err := acceptingBlockByMsgBlock(branchingChainTip)
if err != nil {
t.Fatalf("TestAcceptingBlock: acceptingBlock for red chain block unexpectedly failed: %s", err)
}
if branchingChainTipAcceptionBlock != nil {
t.Fatalf("TestAcceptingBlock: unexpected acceptingBlock for branchingChainTipAcceptionBlock. "+
"Want: nil, got: %s", branchingChainTipAcceptionBlock.hash)
}
// Add shorter side-chain
intersectionBlock := chainBlocks[1]
sideChainTip := intersectionBlock
for i := 0; i < len(chainBlocks)-3; i++ {
sideChainTip = prepareAndProcessBlock(t, dag, sideChainTip)
}
// Make sure that the accepting block of the parent of the branching block didn't change
expectedAcceptingBlock := nodeByMsgBlock(t, dag, chainBlocks[2])
intersectionAcceptingBlock, err := acceptingBlockByMsgBlock(intersectionBlock)
if err != nil {
t.Fatalf("TestAcceptingBlock: acceptingBlock for intersection block unexpectedly failed: %s", err)
}
if expectedAcceptingBlock != intersectionAcceptingBlock {
t.Fatalf("TestAcceptingBlock: unexpected acceptingBlock for intersection block. "+
"Want: %s, got: %s", expectedAcceptingBlock.hash, intersectionAcceptingBlock.hash)
}
// Make sure that a block that is found in the red set of the selected tip
// doesn't have an accepting block
prepareAndProcessBlock(t, dag, sideChainTip, chainBlocks[len(chainBlocks)-1])
sideChainTipAcceptingBlock, err := acceptingBlockByMsgBlock(sideChainTip)
if err != nil {
t.Fatalf("TestAcceptingBlock: acceptingBlock for sideChainTip unexpectedly failed: %s", err)
}
if sideChainTipAcceptingBlock != nil {
t.Fatalf("TestAcceptingBlock: unexpected acceptingBlock for sideChainTip. "+
"Want: nil, got: %s", intersectionAcceptingBlock.hash)
}
}
func TestFinalizeNodesBelowFinalityPoint(t *testing.T) {
testFinalizeNodesBelowFinalityPoint(t, true)
testFinalizeNodesBelowFinalityPoint(t, false)
}
func testFinalizeNodesBelowFinalityPoint(t *testing.T, deleteDiffData bool) {
params := dagconfig.SimnetParams
params.K = 1
dag, teardownFunc, err := DAGSetup("testFinalizeNodesBelowFinalityPoint", true, Config{
DAGParams: &params,
})
if err != nil {
t.Fatalf("Failed to setup DAG instance: %v", err)
}
defer teardownFunc()
blockVersion := int32(0x10000000)
blockTime := dag.genesis.Header().Timestamp
flushUTXODiffStore := func() {
dbTx, err := dbaccess.NewTx()
if err != nil {
t.Fatalf("Failed to open database transaction: %s", err)
}
defer dbTx.RollbackUnlessClosed()
err = dag.utxoDiffStore.flushToDB(dbTx)
if err != nil {
t.Fatalf("Error flushing utxoDiffStore data to DB: %s", err)
}
dag.utxoDiffStore.clearDirtyEntries()
err = dbTx.Commit()
if err != nil {
t.Fatalf("Failed to commit database transaction: %s", err)
}
}
addNode := func(parent *blockNode) *blockNode {
blockTime = blockTime.Add(time.Second)
node := newTestNode(dag, blockSetFromSlice(parent), blockVersion, 0, blockTime)
node.updateParentsChildren()
dag.index.AddNode(node)
// Put dummy diff data in dag.utxoDiffStore
err := dag.utxoDiffStore.setBlockDiff(node, NewUTXODiff())
if err != nil {
t.Fatalf("setBlockDiff: %s", err)
}
flushUTXODiffStore()
return node
}
finalityInterval := dag.dagParams.FinalityInterval
nodes := make([]*blockNode, 0, finalityInterval)
currentNode := dag.genesis
nodes = append(nodes, currentNode)
for i := uint64(0); i <= finalityInterval*2; i++ {
currentNode = addNode(currentNode)
nodes = append(nodes, currentNode)
}
// Manually set the last finality point
dag.lastFinalityPoint = nodes[finalityInterval-1]
dag.finalizeNodesBelowFinalityPoint(deleteDiffData)
flushUTXODiffStore()
for _, node := range nodes[:finalityInterval-1] {
if !node.isFinalized {
t.Errorf("Node with blue score %d expected to be finalized", node.blueScore)
}
if _, ok := dag.utxoDiffStore.loaded[*node.hash]; deleteDiffData && ok {
t.Errorf("The diff data of node with blue score %d should have been unloaded if deleteDiffData is %T", node.blueScore, deleteDiffData)
} else if !deleteDiffData && !ok {
t.Errorf("The diff data of node with blue score %d shouldn't have been unloaded if deleteDiffData is %T", node.blueScore, deleteDiffData)
}
_, err := dag.utxoDiffStore.diffDataFromDB(node.hash)
exists := !dbaccess.IsNotFoundError(err)
if exists && err != nil {
t.Errorf("diffDataFromDB: %s", err)
continue
}
if deleteDiffData && exists {
t.Errorf("The diff data of node with blue score %d should have been deleted from the database if deleteDiffData is %T", node.blueScore, deleteDiffData)
continue
}
if !deleteDiffData && !exists {
t.Errorf("The diff data of node with blue score %d shouldn't have been deleted from the database if deleteDiffData is %T", node.blueScore, deleteDiffData)
continue
}
}
for _, node := range nodes[finalityInterval-1:] {
if node.isFinalized {
t.Errorf("Node with blue score %d wasn't expected to be finalized", node.blueScore)
}
if _, ok := dag.utxoDiffStore.loaded[*node.hash]; !ok {
t.Errorf("The diff data of node with blue score %d shouldn't have been unloaded", node.blueScore)
}
if diffData, err := dag.utxoDiffStore.diffDataFromDB(node.hash); err != nil {
t.Errorf("diffDataFromDB: %s", err)
} else if diffData == nil {
t.Errorf("The diff data of node with blue score %d shouldn't have been deleted from the database", node.blueScore)
}
}
}
func TestDAGIndexFailedStatus(t *testing.T) {
params := dagconfig.SimnetParams
dag, teardownFunc, err := DAGSetup("TestDAGIndexFailedStatus", true, Config{
DAGParams: &params,
})
if err != nil {
t.Fatalf("Failed to setup DAG instance: %v", err)
}
defer teardownFunc()
invalidCbTx := wire.NewSubnetworkMsgTx(wire.TxVersion, []*wire.TxIn{}, []*wire.TxOut{}, subnetworkid.SubnetworkIDCoinbase, 0, []byte{})
txs := []*util.Tx{util.NewTx(invalidCbTx)}
hashMerkleRoot := BuildHashMerkleTreeStore(txs).Root()
invalidMsgBlock := wire.NewMsgBlock(
wire.NewBlockHeader(
1,
[]*daghash.Hash{params.GenesisHash}, hashMerkleRoot,
&daghash.Hash{},
&daghash.Hash{},
dag.genesis.bits,
0),
)
invalidMsgBlock.AddTransaction(invalidCbTx)
invalidBlock := util.NewBlock(invalidMsgBlock)
isOrphan, isDelayed, err := dag.ProcessBlock(invalidBlock, BFNoPoWCheck)
if !errors.As(err, &RuleError{}) {
t.Fatalf("ProcessBlock: expected a rule error but got %s instead", err)
}
if isDelayed {
t.Fatalf("ProcessBlock: invalidBlock " +
"is too far in the future")
}
if isOrphan {
t.Fatalf("ProcessBlock incorrectly returned invalidBlock " +
"is an orphan\n")
}
invalidBlockNode := dag.index.LookupNode(invalidBlock.Hash())
if invalidBlockNode == nil {
t.Fatalf("invalidBlockNode wasn't added to the block index as expected")
}
if invalidBlockNode.status&statusValidateFailed != statusValidateFailed {
t.Fatalf("invalidBlockNode status to have %b flags raised (got: %b)", statusValidateFailed, invalidBlockNode.status)
}
invalidMsgBlockChild := wire.NewMsgBlock(
wire.NewBlockHeader(1, []*daghash.Hash{
invalidBlock.Hash(),
}, hashMerkleRoot, &daghash.Hash{}, &daghash.Hash{}, dag.genesis.bits, 0),
)
invalidMsgBlockChild.AddTransaction(invalidCbTx)
invalidBlockChild := util.NewBlock(invalidMsgBlockChild)
isOrphan, isDelayed, err = dag.ProcessBlock(invalidBlockChild, BFNoPoWCheck)
var ruleErr RuleError
if ok := errors.As(err, &ruleErr); !ok || ruleErr.ErrorCode != ErrInvalidAncestorBlock {
t.Fatalf("ProcessBlock: expected a rule error but got %s instead", err)
}
if isDelayed {
t.Fatalf("ProcessBlock: invalidBlockChild " +
"is too far in the future")
}
if isOrphan {
t.Fatalf("ProcessBlock incorrectly returned invalidBlockChild " +
"is an orphan\n")
}
invalidBlockChildNode := dag.index.LookupNode(invalidBlockChild.Hash())
if invalidBlockChildNode == nil {
t.Fatalf("invalidBlockChild wasn't added to the block index as expected")
}
if invalidBlockChildNode.status&statusInvalidAncestor != statusInvalidAncestor {
t.Fatalf("invalidBlockNode status to have %b flags raised (got %b)", statusInvalidAncestor, invalidBlockChildNode.status)
}
invalidMsgBlockGrandChild := wire.NewMsgBlock(
wire.NewBlockHeader(1, []*daghash.Hash{
invalidBlockChild.Hash(),
}, hashMerkleRoot, &daghash.Hash{}, &daghash.Hash{}, dag.genesis.bits, 0),
)
invalidMsgBlockGrandChild.AddTransaction(invalidCbTx)
invalidBlockGrandChild := util.NewBlock(invalidMsgBlockGrandChild)
isOrphan, isDelayed, err = dag.ProcessBlock(invalidBlockGrandChild, BFNoPoWCheck)
if ok := errors.As(err, &ruleErr); !ok || ruleErr.ErrorCode != ErrInvalidAncestorBlock {
t.Fatalf("ProcessBlock: expected a rule error but got %s instead", err)
}
if isDelayed {
t.Fatalf("ProcessBlock: invalidBlockGrandChild " +
"is too far in the future")
}
if isOrphan {
t.Fatalf("ProcessBlock incorrectly returned invalidBlockGrandChild " +
"is an orphan\n")
}
invalidBlockGrandChildNode := dag.index.LookupNode(invalidBlockGrandChild.Hash())
if invalidBlockGrandChildNode == nil {
t.Fatalf("invalidBlockGrandChild wasn't added to the block index as expected")
}
if invalidBlockGrandChildNode.status&statusInvalidAncestor != statusInvalidAncestor {
t.Fatalf("invalidBlockGrandChildNode status to have %b flags raised (got %b)", statusInvalidAncestor, invalidBlockGrandChildNode.status)
}
}
func TestIsDAGCurrentMaxDiff(t *testing.T) {
netParams := []*dagconfig.Params{
&dagconfig.MainnetParams,
&dagconfig.TestnetParams,
&dagconfig.DevnetParams,
&dagconfig.RegressionNetParams,
&dagconfig.SimnetParams,
}
for _, params := range netParams {
if params.TargetTimePerBlock*time.Duration(params.FinalityInterval) < isDAGCurrentMaxDiff {
t.Errorf("in %s, a DAG can be considered current even if it's below the finality point", params.Name)
}
}
}