-
Notifications
You must be signed in to change notification settings - Fork 119
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
proof: add code for stitching together failed proof suffixes
- Loading branch information
Showing
1 changed file
with
182 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
package proof | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"encoding/hex" | ||
"errors" | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/btcsuite/btcd/rpcclient" | ||
"github.com/btcsuite/btcd/wire" | ||
"github.com/lightninglabs/taproot-assets/commitment" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestProofStitching(t *testing.T) { | ||
// This code makes a lot of assumptions on what files need to be present | ||
// and is really only meant for manual use to stitch together the proofs | ||
// of a failed transfer. Comment out the t.Skip line to run it. | ||
t.Skip("Code only for manual use.") | ||
|
||
const ( | ||
rpcUser = "lightning" | ||
rpcPassword = "lightning" | ||
rpcHost = "localhost:8332" | ||
startIndex = 0 | ||
endIndex = 12 | ||
mindedBlock = 868229 | ||
) | ||
|
||
client, err := bitcoinClient(rpcHost, rpcUser, rpcPassword) | ||
require.NoError(t, err) | ||
|
||
verifier := &bitcoindVerifier{client: client} | ||
|
||
tplBytes, err := os.ReadFile(filepath.Join( | ||
testDataFileName, "template.proof", | ||
)) | ||
require.NoError(t, err) | ||
|
||
f := &File{} | ||
err = f.Decode(bytes.NewReader(tplBytes)) | ||
require.NoError(t, err) | ||
|
||
// We want the template to valid, otherwise all other steps are | ||
// meaningless. | ||
_, err = f.Verify( | ||
context.Background(), verifier.VerifyHeader, MockMerkleVerifier, | ||
MockGroupVerifier, MockChainLookup, | ||
) | ||
require.NoError(t, err) | ||
|
||
// The template proof is the last proof in the file, that was the only | ||
// one fully written. We take the last proof to find out the on-chain | ||
// TX that was involved in the transfer. | ||
tplProof, err := f.LastProof() | ||
require.NoError(t, err) | ||
|
||
// Fetch the transaction to make sure it is actually known. | ||
txid := tplProof.AnchorTx.TxHash() | ||
_, err = client.GetRawTransaction(&txid) | ||
require.NoError(t, err) | ||
|
||
// Now fetch the full block the TX was mined in. | ||
blockHash, err := client.GetBlockHash(int64(mindedBlock)) | ||
require.NoError(t, err) | ||
block, err := client.GetBlock(blockHash) | ||
require.NoError(t, err) | ||
|
||
// What's the transaction's index in the block? | ||
txIndex := -1 | ||
for i, tx := range block.Transactions { | ||
if tx.TxHash() == txid { | ||
txIndex = i | ||
break | ||
} | ||
} | ||
require.NotEqual(t, -1, txIndex) | ||
|
||
// And with that, we can create the merkle proof for the transaction. | ||
merkleProof, err := NewTxMerkleProof(block.Transactions, txIndex) | ||
require.NoError(t, err) | ||
|
||
for i := startIndex; i <= endIndex; i++ { | ||
brokenProofHex, err := os.ReadFile(filepath.Join( | ||
testDataFileName, fmt.Sprintf("suffix-%d.hex", i), | ||
)) | ||
require.NoError(t, err) | ||
|
||
brokenProofBytes, err := hex.DecodeString( | ||
strings.TrimSpace(string(brokenProofHex)), | ||
) | ||
require.NoError(t, err) | ||
|
||
brokenProof := &Proof{} | ||
err = brokenProof.Decode(bytes.NewReader(brokenProofBytes)) | ||
require.NoError(t, err) | ||
|
||
// We can now fill in the block information for the broken | ||
// proof. | ||
brokenProof.TxMerkleProof = *merkleProof | ||
brokenProof.BlockHeight = mindedBlock | ||
brokenProof.BlockHeader = block.Header | ||
|
||
// We now should have a fully valid proof that we can write out | ||
// to a file, by replacing the last one in the template. | ||
err = f.ReplaceLastProof(*brokenProof) | ||
require.NoError(t, err) | ||
|
||
// We now need to find out if this is one of the proofs that | ||
// can't be fixed because of the script key usage. So if we | ||
// get an "invalid exclusion proof" error when validating, we | ||
// need to skip this proof. | ||
_, err = f.Verify( | ||
context.Background(), verifier.VerifyHeader, | ||
DefaultMerkleVerifier, MockGroupVerifier, | ||
MockChainLookup, | ||
) | ||
switch { | ||
case errors.Is(err, commitment.ErrInvalidTaprootProof): | ||
t.Logf("Proof %d is invalid and can't be rescued: %v", | ||
i, err) | ||
|
||
continue | ||
|
||
case err != nil: | ||
require.NoError(t, err) | ||
} | ||
|
||
// If the proof is valid, we can write it out to a file. | ||
outFile, err := os.Create(filepath.Join( | ||
testDataFileName, fmt.Sprintf("fixed-%d.proof", i), | ||
)) | ||
require.NoError(t, err) | ||
|
||
err = f.Encode(outFile) | ||
require.NoError(t, err) | ||
|
||
err = outFile.Close() | ||
require.NoError(t, err) | ||
} | ||
|
||
} | ||
|
||
func bitcoinClient(rpcHost, rpcUser, | ||
rpcPass string) (*rpcclient.Client, error) { | ||
|
||
rpcCfg := rpcclient.ConnConfig{ | ||
Host: rpcHost, | ||
User: rpcUser, | ||
Pass: rpcPass, | ||
DisableConnectOnNew: true, | ||
DisableAutoReconnect: false, | ||
DisableTLS: true, | ||
HTTPPostMode: true, | ||
} | ||
|
||
return rpcclient.New(&rpcCfg, nil) | ||
} | ||
|
||
type bitcoindVerifier struct { | ||
client *rpcclient.Client | ||
} | ||
|
||
func (b *bitcoindVerifier) VerifyHeader(header wire.BlockHeader, | ||
height uint32) error { | ||
|
||
targetHash, err := b.client.GetBlockHash(int64(height)) | ||
if err != nil { | ||
return fmt.Errorf("unable to get block hash: %w", err) | ||
} | ||
|
||
if *targetHash != header.BlockHash() { | ||
return fmt.Errorf("block hash mismatch") | ||
} | ||
|
||
return nil | ||
} |