blockchain_test, fullblocktests: add test to check for utxo

existance/non-existance

New test instance BlockDisconnectExpectUTXO tests that a utxo
exists/doesn't exist after a specific block has been disconnected.
This commit is contained in:
Calvin Kim 2024-02-21 18:14:26 +09:00
parent 13152b35e1
commit 5a91ea23ca
2 changed files with 141 additions and 2 deletions

View File

@ -146,6 +146,70 @@ func TestFullBlocks(t *testing.T) {
}
defer teardownFunc()
testBlockDisconnectExpectUTXO := func(item fullblocktests.BlockDisconnectExpectUTXO) {
expectedCallBack := func(notification *blockchain.Notification) {
switch notification.Type {
case blockchain.NTBlockDisconnected:
block, ok := notification.Data.(*btcutil.Block)
if !ok {
t.Fatalf("expected a block")
}
// Return early if the block we get isn't the relevant
// block.
if !block.Hash().IsEqual(&item.BlockHash) {
return
}
entry, err := chain.FetchUtxoEntry(item.OutPoint)
if err != nil {
t.Fatal(err)
}
if entry == nil || entry.IsSpent() {
t.Logf("expected utxo %v to exist but it's "+
"nil or spent\n", item.OutPoint.String())
t.Fatalf("expected utxo %v to exist but it's "+
"nil or spent", item.OutPoint.String())
}
}
}
unexpectedCallBack := func(notification *blockchain.Notification) {
switch notification.Type {
case blockchain.NTBlockDisconnected:
block, ok := notification.Data.(*btcutil.Block)
if !ok {
t.Fatalf("expected a block")
}
// Return early if the block we get isn't the relevant
// block.
if !block.Hash().IsEqual(&item.BlockHash) {
return
}
entry, err := chain.FetchUtxoEntry(item.OutPoint)
if err != nil {
t.Fatal(err)
}
if entry != nil && !entry.IsSpent() {
t.Logf("unexpected utxo %v to exist but it's "+
"not nil and not spent", item.OutPoint.String())
t.Fatalf("unexpected utxo %v exists but it's "+
"not nil and not spent\n", item.OutPoint.String())
}
}
}
if item.Expected {
chain.Subscribe(expectedCallBack)
} else {
chain.Subscribe(unexpectedCallBack)
}
}
// testAcceptedBlock attempts to process the block in the provided test
// instance and ensures that it was accepted according to the flags
// specified in the test.
@ -300,6 +364,8 @@ func TestFullBlocks(t *testing.T) {
testOrphanOrRejectedBlock(item)
case fullblocktests.ExpectedTip:
testExpectedTip(item)
case fullblocktests.BlockDisconnectExpectUTXO:
testBlockDisconnectExpectUTXO(item)
default:
t.Fatalf("test #%d, item #%d is not one of "+
"the supported test instance types -- "+

View File

@ -150,6 +150,21 @@ type RejectedNonCanonicalBlock struct {
// This implements the TestInstance interface.
func (b RejectedNonCanonicalBlock) FullBlockTestInstance() {}
// BlockDisconnectExpectUTXO defines a test instance that tests an utxo to exist or not
// exist after a specified block has been disconnected.
type BlockDisconnectExpectUTXO struct {
Name string
Expected bool
BlockHash chainhash.Hash
OutPoint wire.OutPoint
}
// FullBlockTestInstance only exists to allow BlockDisconnectExpectUTXO to be treated as
// a TestInstance.
//
// This implements the TestInstance interface.
func (b BlockDisconnectExpectUTXO) FullBlockTestInstance() {}
// spendableOut represents a transaction output that is spendable along with
// additional metadata such as the block its in and how much it pays.
type spendableOut struct {
@ -878,6 +893,9 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
//
// orphanedOrRejected creates and appends a single orphanOrRejectBlock
// test instance for the current tip.
//
// blockDisconnectExpectUTXO creates and appends a BlockDisconnectExpectUTXO test
// instance with the passed in values.
accepted := func() {
tests = append(tests, []TestInstance{
acceptBlock(g.tipName, g.tip, true, false),
@ -904,6 +922,12 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
orphanOrRejectBlock(g.tipName, g.tip),
})
}
blockDisconnectExpectUTXO := func(name string, expected bool, op wire.OutPoint,
hash chainhash.Hash) {
tests = append(tests, []TestInstance{
BlockDisconnectExpectUTXO{name, expected, hash, op},
})
}
// ---------------------------------------------------------------------
// Generate enough blocks to have mature coinbase outputs to work with.
@ -2044,6 +2068,55 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
}
accepted()
// Create a chain where the utxo created in b82a is spent in b83a.
//
// b81() -> b82a(28) -> b83a(b82.tx[1].out[0])
//
g.nextBlock("b82a", outs[28])
accepted()
b82aTx1Out0 := makeSpendableOut(g.tip, 1, 0)
g.nextBlock("b83a", &b82aTx1Out0)
accepted()
// Now we'll build a side-chain where we don't spend any of the outputs.
//
// b81() -> b82a(28) -> b83a(b82.tx[1].out[0])
// \-> b82() -> b83()
//
g.setTip("b81")
g.nextBlock("b82", nil)
acceptedToSideChainWithExpectedTip("b83a")
g.nextBlock("b83", nil)
acceptedToSideChainWithExpectedTip("b83a")
// At this point b83a is still the tip. When we add block 84, the tip
// will change. Pre-load up the expected utxos test before the reorganization.
//
// We expect b82a output to now be a utxo since b83a was spending it and it was
// removed from the main chain.
blockDisconnectExpectUTXO("b82aTx1Out0",
true, b82aTx1Out0.prevOut, g.blocksByName["b83a"].BlockHash())
// We expect the output from b82 to not exist once b82a itself has been removed
// from the main chain.
blockDisconnectExpectUTXO("b82aTx1Out0",
false, b82aTx1Out0.prevOut, g.blocksByName["b82a"].BlockHash())
// The output that was being spent in b82a should exist after the removal of
// b82a.
blockDisconnectExpectUTXO("outs[28]",
true, outs[28].prevOut, g.blocksByName["b82a"].BlockHash())
// Create block 84 and reorg out the sidechain with b83a as the tip.
//
// b81() -> b82a(28) -> b83a(b82.tx[1].out[0])
// \-> b82() -> b83() -> b84()
//
g.nextBlock("b84", nil)
accepted()
// ---------------------------------------------------------------------
// Large block re-org test.
// ---------------------------------------------------------------------
@ -2054,8 +2127,8 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
// Ensure the tip the re-org test builds on is the best chain tip.
//
// ... -> b81(27) -> ...
g.setTip("b81")
// ... -> b84() -> ...
g.setTip("b84")
// Collect all of the spendable coinbase outputs from the previous
// collection point up to the current tip.