Skip to content

netsync : compare header hash at assume utreexo height instead of chain tip - #418

Open
4xvgal wants to merge 1 commit into
utreexo:mainfrom
4xvgal:fix/assumeutreexo-header-hash-check
Open

4xvgal wants to merge 1 commit into
utreexo:mainfrom
4xvgal:fix/assumeutreexo-header-hash-check

Conversation

@4xvgal

@4xvgal 4xvgal commented Jun 20, 2026

Copy link
Copy Markdown

BestHeader() returns the tip hash of header chain, which is can be at a height pass the assume utreexo point when headers are downloaded in a single batch.
this causes a false mismatch and os.Exit(1).

BestHeader() = chain tip = 954,553
00000000000000000001c86c3e8ddbffa6a572724f66fe9df46ba84f51fe70ef

hard coded = 943,013
00000000000000000001c595730bd4a5fb0e2b35af70882962ce7ae602f48aff

@4xvgal

4xvgal commented Jun 20, 2026

Copy link
Copy Markdown
Author
026-06-20 20:48:45.715 [INF] SYNC: Downloading headers from 1 to 954553 from peer 1.228.21.110:8333
2026-06-20 20:48:45.754 [WRN] SYNC: The node had hash 00000000000000000001c595730bd4a5fb0e2b35af70882962ce7ae602f48aff
 hardcoded in but the valid proof-of-work chain has the hash
 00000000000000000001c86c3e8ddbffa6a572724f66fe9df46ba84f51fe70ef at height 954553.
 The user should not trust this software as genuine and there may be attempts to steal funds.
 The user should delete the datadir
 

@4xvgal
4xvgal marked this pull request as ready for review June 20, 2026 16:32
@4xvgal

4xvgal commented Jun 20, 2026

Copy link
Copy Markdown
Author

stopHas is fine. This just makes the check more forgiving.
it compares the hash at the exact assume height, so it's ok if peer sends extra headers.

Comment thread netsync/manager.go Outdated
assumeUtreexoHash := sm.chain.AssumeUtreexoHash()
if !bestHash.IsEqual(&assumeUtreexoHash) {
headerHash, err := sm.chain.HeaderHashByHeight(assumeHeight)
if err != nil || !headerHash.IsEqual(&assumeUtreexoHash) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of handling this err here and making editing hashStr, can we just fail if we're unable to fetch the hash for the height?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. split into two separate failure cases.

@kcalvinalvin

kcalvinalvin commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Good catch! I do wanna add a unit test so that we never have the same bug again. If you'd like to work on that that'd be cool. Otherwise I'll just do it in a follow up!

Ah also, per Go standards, the git commit should be:

netsync: compare header hash at assume utreexo height instead of chain tip

@4xvgal

4xvgal commented Jun 26, 2026

Copy link
Copy Markdown
Author

Good catch! I do wanna add a unit test so that we never have the same bug again. If you'd like to work on that that'd be cool. Otherwise I'll just do it in a follow up!

Ah also, per Go standards, the git commit should be:

netsync: compare header hash at assume utreexo height instead of chain tip

Sounds gooood. I'm planning to edit commit msg and add three cases:

  1. bestHeight > assumeHeight - peer sends headers past the assume height in one batch the hash at exaclty assumeHeight matches -> should pass

  2. hash mismatch at assumeHeight - hardcoded hash doesn't match the actual chain -> should return an error

  3. fetch error -> HeaderHashByHeight returns an error (e.g. assumeHeight not yet in the heawder index)

Does that cover what you had in mind, or is there specific scenario you'd want to test? happy to add more if need.

@kcalvinalvin

Copy link
Copy Markdown
Contributor

Good catch! I do wanna add a unit test so that we never have the same bug again. If you'd like to work on that that'd be cool. Otherwise I'll just do it in a follow up!
Ah also, per Go standards, the git commit should be:

netsync: compare header hash at assume utreexo height instead of chain tip

Sounds gooood. I'm planning to edit commit msg and add three cases:

  1. bestHeight > assumeHeight - peer sends headers past the assume height in one batch the hash at exaclty assumeHeight matches -> should pass
  2. hash mismatch at assumeHeight - hardcoded hash doesn't match the actual chain -> should return an error
  3. fetch error -> HeaderHashByHeight returns an error (e.g. assumeHeight not yet in the heawder index)

Does that cover what you had in mind, or is there specific scenario you'd want to test? happy to add more if need.

Honestly I'd need to see the code heh. Ping me when you're ready!

@4xvgal
4xvgal force-pushed the fix/assumeutreexo-header-hash-check branch from 384e7d6 to b2c34a1 Compare June 27, 2026 16:57
@4xvgal

4xvgal commented Jun 27, 2026

Copy link
Copy Markdown
Author

Done. Split the check into a checkAssumeUtreexoHash() error helper so the failure paths are unit testable. commit updated, ready for review!

@4xvgal
4xvgal force-pushed the fix/assumeutreexo-header-hash-check branch from b2c34a1 to c4eff4b Compare June 27, 2026 17:12
Comment thread netsync/manager.go Outdated
}
}

func (sm *SyncManager) checkUtreexoHash() error {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should say checkAssumeUtreexoHash(). Also we should add a comment on what the function is doing.

Comment thread netsync/manager_test.go Outdated
"mempool should have removed the double-spending tx on block connect")
}

func TestCheckUtreexoHash(t *testing.T) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test should be table-driven.

Comment thread netsync/manager.go Outdated
"software as genuine and there may be attempts to steal funds. The user "+
"should delete the datadir", sm.chain.AssumeUtreexoHash().String(),
bestHash.String(), bestHeight)
if err := sm.checkUtreexoHash(); err != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're not logging the error here so or in the checkUtreexoHash() so the user wouldn't know why the binary exited.

Comment thread netsync/manager.go Outdated
headerHash, err := sm.chain.HeaderHashByHeight(assumeHeight)

if err != nil {
return fmt.Errorf("Failed to get header hash at assume utreexo height %d: %v", assumeHeight, err)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love the messaging here. If the HeaderHashByHeight() couldn't fetch the hash at the assumeUtreexoHeight, it means something is really wrong here.

This information would be useful for the end user

@4xvgal
4xvgal force-pushed the fix/assumeutreexo-header-hash-check branch from c4eff4b to 30b2990 Compare June 28, 2026 09:46
@4xvgal

4xvgal commented Jun 28, 2026

Copy link
Copy Markdown
Author

All addressed. function renamed + documented, test is table-driven, logging moved to caller. PR updated. Let me know if there's anything else.

@4xvgal 4xvgal changed the title fix: compare header hash at assume utreexo height instead of chain tip netsync : compare header hash at assume utreexo height instead of chain tip Jun 29, 2026
Comment thread netsync/manager.go
"software as genuine and there may be attempts to steal funds. The user "+
"should delete the datadir", sm.chain.AssumeUtreexoHash().String(),
bestHash.String(), bestHeight)
if err := sm.checkAssumeUtreexoHash(); err != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We left out the "why" here. Yes the user shouldn't trust the software but why? It's because the hash was hardcoded but the hardcoded hash didn't match the downloaded best PoW chain.

From the user's perspective, they'll either get:

The user should not trust this software as genuine and there may be attempts to steal funds. The user should delete the datadir: hash mismatch at height ..."

or

The user should not trust this software as genuine and there may be attempts to steal funds. The user should delete the datadir: hash not found at height ..."

The lack of information here would leave the user confused. In the current code, we explicitly state the reason. We should be doing the same here

Comment thread netsync/manager.go
assumeHeight := sm.chain.AssumeUtreexoHeight()
assumeUtreexoHash := sm.chain.AssumeUtreexoHash()
headerHash, err := sm.chain.HeaderHashByHeight(assumeHeight)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should not have this empty line here. The error assertion branch should be right after the err definition

Comment thread netsync/manager_test.go Outdated
wantErr: true,
},
{
name: "fetch error: assumeHeight not in bestHeader",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The returned error here is the same as "mismatch: hash at assumepointhHeight differs".

[I] calvin@nixos ~/b/u/u/netsync ((30b29907))> git diff
diff --git a/netsync/manager_test.go b/netsync/manager_test.go
index 5365939a..c2fd8f2d 100644
--- a/netsync/manager_test.go
+++ b/netsync/manager_test.go
@@ -1349,6 +1349,7 @@ func TestCheckUtreexoHash(t *testing.T) {
                        }
                        //assert
                        err := sm.checkAssumeUtreexoHash()
+                       fmt.Println(err)
                        if tc.wantErr {
                                require.Error(t, err)
                        } else {
[I] calvin@nixos ~/b/u/u/netsync ((30b29907))> go test -run 'TestCheckUtreexoHash/fetch_error:_assumeHeight_not_in_bestHeader'
hash mismatch at height 2: expected 0000000000000000000000000000000000000000000000000000000000000000, got 04abc77b0fceb55927320b84ae78b3de4f402f85d4a326ec236f5c17f19fe27e
PASS
ok      github.com/utreexo/utreexod/netsync     0.048s
[I] calvin@nixos ~/b/u/u/netsync ((30b29907))>

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catch. when i split the check into checkAssumeUtreexoHash(), original warn lost explicit "hardcoded vs PoW chain" framing.

I'll fix hat so the user gets the full context again.

  1. update the helper error message to explicitly mention the hardcoded assumeUtreexoHash, actual chain hash, and the height.

  2. update the caller warning

  3. remove empty line

  4. fix the "fetch error" test case

@4xvgal
4xvgal force-pushed the fix/assumeutreexo-header-hash-check branch from 30b2990 to 01a1a32 Compare July 7, 2026 13:30
@4xvgal

4xvgal commented Jul 7, 2026

Copy link
Copy Markdown
Author

The fetch error case's original purposewas to verify the error path when HeaderHashByHeight can't find the header at the assumepoint Height, but it was actually testing a hash mismatch. I fixed it by using numBlocks:1 and assumepointHeight: 10.

Thanks for catching those my mistakes.

@4xvgal
4xvgal force-pushed the fix/assumeutreexo-header-hash-check branch from 01a1a32 to 3f33b09 Compare September 16, 2026 08:36
Comment thread netsync/manager_test.go
numBlocks: 1,
assumepointHeight: 10,
hashFunc: func(b []*btcutil.Block) *chainhash.Hash { return &chainhash.Hash{} },
wantErr: true,

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kcalvinalvin
Would be better to identify each error path withe custom type (like RuleError )or use sentinel + error.Is?

I wanted to ask before edit

@4xvgal 4xvgal Sep 16, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or it could be only in Test code, match err string using ErrContains

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh Custom type and Sentinel both are over engineering and until Err is useful for other boundary or interface, i'll choose ErrContains

@4xvgal
4xvgal force-pushed the fix/assumeutreexo-header-hash-check branch from 3f33b09 to e2e33c3 Compare September 17, 2026 13:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants