diff --git a/btclightclient/btc_header_bytes.go b/btclightclient/btc_header_bytes.go index 2f1448f..7f0ca02 100644 --- a/btclightclient/btc_header_bytes.go +++ b/btclightclient/btc_header_bytes.go @@ -7,14 +7,23 @@ import ( "github.com/btcsuite/btcd/wire" ) -// We don't want to make it compilicated yet :D -// type BTCHeaderBytes []byte -// const BTCHeaderSize = 80 +const BTCHeaderSize = 80 // 80 bytes +// Utils for converting hex string to header +// The input must be 80 bytes hex string type func BlockHeaderFromHex(hexStr string) (wire.BlockHeader, error) { - data, _ := hex.DecodeString(hexStr) var header wire.BlockHeader + + if len(hexStr) != BTCHeaderSize*2 { + return header, ErrInvalidHeaderSize + } + + data, err := hex.DecodeString(hexStr) + if err != nil { + return header, err + } + reader := bytes.NewReader(data) - err := header.Deserialize(reader) + err = header.Deserialize(reader) return header, err } diff --git a/btclightclient/btc_header_bytes_test.go b/btclightclient/btc_header_bytes_test.go new file mode 100644 index 0000000..afbb947 --- /dev/null +++ b/btclightclient/btc_header_bytes_test.go @@ -0,0 +1,55 @@ +package btclightclient + +import ( + "encoding/hex" + "testing" + + "gotest.tools/assert" +) + +func TestBTCHeaderFromHex(t *testing.T) { + type testCase struct { + name string + headerHex string + expectedBlockHash string + expectedError error + } + + run := func(t *testing.T, tc testCase) { + header, err := BlockHeaderFromHex(tc.headerHex) + blockHash := header.BlockHash() + if tc.expectedError != nil { + assert.Error(t, err, tc.expectedError.Error()) + } else { + assert.NilError(t, err) + assert.Equal(t, tc.expectedBlockHash, blockHash.String()) + } + } + + testCases := []testCase{ + { + name: "happy test case", + headerHex: "020000004cdba1415b2c6e7808c1b3c18df1374238454f7104203475bf01000000000000c17ea9d06015dc83902911cd24837a8ba4bdc0c1d72b873f906d921e06e48d2f984a8250ef75051a72a8061a", + expectedError: nil, + expectedBlockHash: "0000000000000363d7f5f3341fb0b5b69949103e2d681591c9f737e4ea67e2a7", + }, + { + name: "invalid header length", + headerHex: "020000004cdba1415b2c6e7808c1b3c18df1374238454f7104203475bf01000000000000c17ea9d06015dc83902911cd24837a8ba4bdc0c1d72b873f906d921e06e48d2f984a8250ef75051a72a8061ab", + expectedError: ErrInvalidHeaderSize, + expectedBlockHash: "0000000000000363d7f5f3341fb0b5b69949103e2d681591c9f737e4ea67e2a7", + }, + { + name: "non-hex character", + headerHex: "020000004cdba1415b2c6e7808c1b3c18df1374238454f7104203475bf01000000000000c17ea9d06015dc83902911cd24837a8ba4bdc0c1d72b873f906d921e06e48d2f984a8250ef75051a72a8061Q", + expectedError: hex.InvalidByteError('Q'), + expectedBlockHash: "", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + run(t, tc) + }) + } +} diff --git a/btclightclient/btc_light_client.go b/btclightclient/btc_light_client.go index 6db8883..ef5f195 100644 --- a/btclightclient/btc_light_client.go +++ b/btclightclient/btc_light_client.go @@ -1,7 +1,6 @@ package btclightclient import ( - "errors" "fmt" "math/big" "time" @@ -53,13 +52,13 @@ func (lc *BTCLightClient) FindPreviousCheckpoint() (blockchain.HeaderCtx, error) func (lc *BTCLightClient) InsertHeader(header wire.BlockHeader) error { if lb := lc.btcStore.LightBlockByHash(header.BlockHash()); lb != nil { - return errors.New("parent block not found") + return ErrBlockNotInChain } parentHash := header.PrevBlock parent := lc.btcStore.LightBlockByHash(parentHash) if parent == nil { - return errors.New("block doesn't belong to any fork") + return ErrParentBlockNotInChain } // we need to handle 2 cases: @@ -89,7 +88,7 @@ func (lc *BTCLightClient) forkOfBlockhash(bh chainhash.Hash) ([]*LightBlock, err for count := 0; count <= MaxForkAge; count++ { curr := lc.btcStore.LightBlockByHash(bh) if checkpoint.Height > curr.Height { - return nil, errors.New("fork too old") + return nil, ErrForkTooOld } fork = append(fork, curr) if bh.IsEqual(&checkpointHash) { @@ -98,7 +97,7 @@ func (lc *BTCLightClient) forkOfBlockhash(bh chainhash.Hash) ([]*LightBlock, err bh = curr.Header.PrevBlock } - return nil, errors.New("block not found or too old") + return nil, ErrForkTooOld } // We follow: @@ -163,15 +162,15 @@ func (lc *BTCLightClient) CreateNewFork(parent *LightBlock, header wire.BlockHea func (lc *BTCLightClient) ForkAge(bh chainhash.Hash) (int32, error) { lb := lc.btcStore.LightBlockByHash(bh) - checkpoint := lc.btcStore.LatestCheckPoint() if lb == nil { - return 0, errors.New("hash doesn't belong to db") + return 0, ErrBlockNotInChain } if !lc.btcStore.IsForkHead(bh) { - return 0, errors.New("hash not a latest block in forks") + return 0, ErrBlockIsNotForkHead } + checkpoint := lc.btcStore.LatestCheckPoint() age := lb.Height - checkpoint.Height return age, nil } diff --git a/btclightclient/btc_light_client_store.go b/btclightclient/btc_light_client_store.go index efab72f..ffdc1e5 100644 --- a/btclightclient/btc_light_client_store.go +++ b/btclightclient/btc_light_client_store.go @@ -121,7 +121,6 @@ func (s *MemStore) SetIsNotHead(bh chainhash.Hash) { delete(s.latestBlockHashOfFork, bh) } -// TODO: convert to iterator rather than returning a map func (s *MemStore) LatestBlockHashOfFork() []chainhash.Hash { hashes := []chainhash.Hash{} for h := range s.latestBlockHashOfFork { diff --git a/btclightclient/btc_light_client_test.go b/btclightclient/btc_light_client_test.go index 02d3990..2eeec1e 100644 --- a/btclightclient/btc_light_client_test.go +++ b/btclightclient/btc_light_client_test.go @@ -1,14 +1,10 @@ package btclightclient import ( - "errors" - "fmt" - - "testing" - "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/wire" "gotest.tools/assert" + "testing" ) type commonTestCaseData struct { @@ -19,43 +15,43 @@ type commonTestCaseData struct { type testCaseMap map[string]commonTestCaseData var HEADERS = []string{ - "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4adae5494dffff7f20020000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000", - "0000002006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f2fe76e709f3031b5ed684f098b5cd35a09633943d141a6d0525f34a1643dcf44e0b23c67ffff7f200200000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff025100ffffffff0200f2052a01000000160014f1734bfdd31f315548e1a654e686d3b6367ecec70000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", - "000000205dcd36bceabfc1816ab17503f753e8de66be7ceddee2b8b806b85e61cf9fdc68ce1efdd1cb457e408bc2984151f5ba8e7efcf8a64c2ca9f23de07a3e718a7e9de1b23c67ffff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff025200ffffffff0200f2052a01000000160014f1734bfdd31f315548e1a654e686d3b6367ecec70000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", - "00000020866a367051ccd8849186173fe851392406fc1ac4b444776466260a34b58820351ab2820d880bf70bfe937525e3e7cdd48142186d7fefbef16c42f235f923aa70e1b23c67ffff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff025300ffffffff0200f2052a01000000160014f1734bfdd31f315548e1a654e686d3b6367ecec70000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", - "00000020849665c354e016f4ada91e1bb4d77cfe85e8c40b466641455bd3df70d1e80d363862bfea99b4b68243fcc88e8166a89ace00f5a10a8720e9603684bed07a6278e2b23c67ffff7f200200000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff025400ffffffff0200f2052a01000000160014f1734bfdd31f315548e1a654e686d3b6367ecec70000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", - "000000203b05a05cb26319f421e41c1391ce11d94d7648396c74096560951c4bf412d7174339e31618fee9e9d0acad931c64d6a0785b66cea24a3ef630962f3bd272530be2b23c67ffff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff025500ffffffff0200f2052a01000000160014f1734bfdd31f315548e1a654e686d3b6367ecec70000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", - "000000202cbb8366b0051b44757d626cb859631994adc82399ad37ce0b04edf01e829924ac2b58bde3db81c8aea571e4c90d4f638164ab8debd192c44767575bfc28edbbe2b23c67ffff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff025600ffffffff0200f2052a01000000160014f1734bfdd31f315548e1a654e686d3b6367ecec70000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", - "00000020f8557187addf502f0bbf3fa3a7baa9c5021166315c5b62c7c5ab63f69726ce646e4393f90540542998e3934f0181df08d07afb0daa8e0bedb23e603523739aaae2b23c67ffff7f200400000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff025700ffffffff0200f2052a01000000160014f1734bfdd31f315548e1a654e686d3b6367ecec70000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", - "000000203890e056da2ec78018dc491b61cfbb3134c88ee0f76e838db758b7ce7a237327f97921cc0471669591969f0eb960a67d72ff58d19496a58c9bf48c477fb80242e3b23c67ffff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff025800ffffffff0200f2052a01000000160014f1734bfdd31f315548e1a654e686d3b6367ecec70000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", - "000000204716da4899c77d5c64a0f17ff69c568f31d5c5429939ade626c6ef58cf972437ad21b69fc615dee7c8c940526a690bb81205b634ab0992ec465e7a2fb39df4a2e3b23c67ffff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff025900ffffffff0200f2052a01000000160014f1734bfdd31f315548e1a654e686d3b6367ecec70000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", - "000000207b58138803dfe75a1a035a8519489acefde0768e1b3295fb988ea93dd8199926f6a63660b9a502ed33eacf7130b980208e73c63c623ca27cb6f4185550d0a262e3b23c67ffff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff025a00ffffffff0200f2052a01000000160014f1734bfdd31f315548e1a654e686d3b6367ecec70000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", - "000000209e26cb5b513b048b423dbdd6fed32620feb1bf8dba1e069473d3ad83a0c3477ae4e0efa71fc24467985b923badf291bb5cd62b81bfa4aeec335b86e091f964bae3b23c67ffff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff025b00ffffffff0200f2052a01000000160014f1734bfdd31f315548e1a654e686d3b6367ecec70000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", - "00000020e78f4506e5f9f958b41da8b467c84377efd02bad4ff7c214f98971bab4fc936399c8a0513e6e0f2c9f804d1644a779789f0b0a9b47c0b1e26f08ae08e5ff967ae3b23c67ffff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff025c00ffffffff0200f2052a01000000160014f1734bfdd31f315548e1a654e686d3b6367ecec70000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", - "00000020b084bde47ad161583492f9d397d6cfc87950bef4d80b5720684fe952fe189327d60d5179f5512c82cd32a46176260dee781e0c30f499f81ce7e2fcda8a50bfa9e3b23c67ffff7f200300000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff025d00ffffffff0200f2052a01000000160014f1734bfdd31f315548e1a654e686d3b6367ecec70000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", - "0000002054775e67825f7a4caf14b3fabe95af99a4204f5c3960b935aa429966e1e143498af4ff5bc198b3ab10e9d542be5e0c0e0e8d249e7d4bfa19eb2517c978f06737e4b23c67ffff7f200700000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff025e00ffffffff0200f2052a01000000160014f1734bfdd31f315548e1a654e686d3b6367ecec70000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", - "00000020f972e238a280d6a3f0eb7560deae346c29fdd0203722510e0a82f792f1020933a5f456f5967ce558ac2da01200814edd3b045f9f7f1a6f8235086260d3ea4a80e4b23c67ffff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff025f00ffffffff0200f2052a01000000160014f1734bfdd31f315548e1a654e686d3b6367ecec70000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", - "00000020b27998bce45300e48a19d64f57fb219c82e7edc283da74b73eeb4125db49d51bee3a55f8afeef128522c5a0ef633840e098d5288d077eb9c1bd6a1e01bf333b1e4b23c67ffff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff026000ffffffff0200f2052a01000000160014f1734bfdd31f315548e1a654e686d3b6367ecec70000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", - "0000002018c821aeb4f94b3847dccc7946ff82a4d022a9e87162bc0601992b7dbaf12b432237b251c5f3473a3ece242cb340c6c7d69ec9db9579d57c38069341dfabcf98bdb43c67ffff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03011100ffffffff0200f2052a010000001600145bda3166f8ae9ff21d249197b66acf08326af84b0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4adae5494dffff7f2002000000", + "0000002006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f2fe76e709f3031b5ed684f098b5cd35a09633943d141a6d0525f34a1643dcf44e0b23c67ffff7f2002000000", + "000000205dcd36bceabfc1816ab17503f753e8de66be7ceddee2b8b806b85e61cf9fdc68ce1efdd1cb457e408bc2984151f5ba8e7efcf8a64c2ca9f23de07a3e718a7e9de1b23c67ffff7f2001000000", + "00000020866a367051ccd8849186173fe851392406fc1ac4b444776466260a34b58820351ab2820d880bf70bfe937525e3e7cdd48142186d7fefbef16c42f235f923aa70e1b23c67ffff7f2000000000", + "00000020849665c354e016f4ada91e1bb4d77cfe85e8c40b466641455bd3df70d1e80d363862bfea99b4b68243fcc88e8166a89ace00f5a10a8720e9603684bed07a6278e2b23c67ffff7f2002000000", + "000000203b05a05cb26319f421e41c1391ce11d94d7648396c74096560951c4bf412d7174339e31618fee9e9d0acad931c64d6a0785b66cea24a3ef630962f3bd272530be2b23c67ffff7f2000000000", + "000000202cbb8366b0051b44757d626cb859631994adc82399ad37ce0b04edf01e829924ac2b58bde3db81c8aea571e4c90d4f638164ab8debd192c44767575bfc28edbbe2b23c67ffff7f2000000000", + "00000020f8557187addf502f0bbf3fa3a7baa9c5021166315c5b62c7c5ab63f69726ce646e4393f90540542998e3934f0181df08d07afb0daa8e0bedb23e603523739aaae2b23c67ffff7f2004000000", + "000000203890e056da2ec78018dc491b61cfbb3134c88ee0f76e838db758b7ce7a237327f97921cc0471669591969f0eb960a67d72ff58d19496a58c9bf48c477fb80242e3b23c67ffff7f2000000000", + "000000204716da4899c77d5c64a0f17ff69c568f31d5c5429939ade626c6ef58cf972437ad21b69fc615dee7c8c940526a690bb81205b634ab0992ec465e7a2fb39df4a2e3b23c67ffff7f2001000000", + "000000207b58138803dfe75a1a035a8519489acefde0768e1b3295fb988ea93dd8199926f6a63660b9a502ed33eacf7130b980208e73c63c623ca27cb6f4185550d0a262e3b23c67ffff7f2000000000", + "000000209e26cb5b513b048b423dbdd6fed32620feb1bf8dba1e069473d3ad83a0c3477ae4e0efa71fc24467985b923badf291bb5cd62b81bfa4aeec335b86e091f964bae3b23c67ffff7f2001000000", + "00000020e78f4506e5f9f958b41da8b467c84377efd02bad4ff7c214f98971bab4fc936399c8a0513e6e0f2c9f804d1644a779789f0b0a9b47c0b1e26f08ae08e5ff967ae3b23c67ffff7f2000000000", + "00000020b084bde47ad161583492f9d397d6cfc87950bef4d80b5720684fe952fe189327d60d5179f5512c82cd32a46176260dee781e0c30f499f81ce7e2fcda8a50bfa9e3b23c67ffff7f2003000000", + "0000002054775e67825f7a4caf14b3fabe95af99a4204f5c3960b935aa429966e1e143498af4ff5bc198b3ab10e9d542be5e0c0e0e8d249e7d4bfa19eb2517c978f06737e4b23c67ffff7f2007000000", + "00000020f972e238a280d6a3f0eb7560deae346c29fdd0203722510e0a82f792f1020933a5f456f5967ce558ac2da01200814edd3b045f9f7f1a6f8235086260d3ea4a80e4b23c67ffff7f2000000000", + "00000020b27998bce45300e48a19d64f57fb219c82e7edc283da74b73eeb4125db49d51bee3a55f8afeef128522c5a0ef633840e098d5288d077eb9c1bd6a1e01bf333b1e4b23c67ffff7f2001000000", + "0000002018c821aeb4f94b3847dccc7946ff82a4d022a9e87162bc0601992b7dbaf12b432237b251c5f3473a3ece242cb340c6c7d69ec9db9579d57c38069341dfabcf98bdb43c67ffff7f2000000000", } func CommonTestCases() testCaseMap { tcs := map[string]commonTestCaseData{ "Append a fork": { headers: HEADERS, - header: "000000201dc5f8e7cdb2dda12a307615bd0b9847c60f813baff61a591cb15c44f6a242205abc34309a00ac2983bb4cf1bc76ebc8800ec9965d423508fba603eb763b9215bdb43c67ffff7f200200000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03011200ffffffff0200f2052a010000001600145bda3166f8ae9ff21d249197b66acf08326af84b0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + header: "000000201dc5f8e7cdb2dda12a307615bd0b9847c60f813baff61a591cb15c44f6a242205abc34309a00ac2983bb4cf1bc76ebc8800ec9965d423508fba603eb763b9215bdb43c67ffff7f2002000000", }, "Create fork": { headers: HEADERS, - header: "0000002018c821aeb4f94b3847dccc7946ff82a4d022a9e87162bc0601992b7dbaf12b43b275a58e2107b29a5735d8c6bd63d674ef01d4596c78961f338e5a364693b03502b53c67ffff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03011100ffffffff0200f2052a01000000160014c6aee90d5d3120a43a090b1e3720aea464248c5f0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + header: "0000002018c821aeb4f94b3847dccc7946ff82a4d022a9e87162bc0601992b7dbaf12b43b275a58e2107b29a5735d8c6bd63d674ef01d4596c78961f338e5a364693b03502b53c67ffff7f2000000000", }, "Insert failed because fork too old": { headers: HEADERS, - header: "0000002006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f91bf7fc009e51a44f6c7b063e64d80b36af5cb8bc9879b9dadc7eebec779a70b437f3c67ffff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff025100ffffffff0200f2052a01000000160014db62d5ead43bde6defb99c188151bf9c9d37f6c30000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + header: "0000002006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f91bf7fc009e51a44f6c7b063e64d80b36af5cb8bc9879b9dadc7eebec779a70b437f3c67ffff7f2001000000", }, "Block doesn't belong to any fork!": { headers: HEADERS, - header: "0000002018c8213eb4f94b3847dccc7946ff82a4d022a9e87162bc0601992b7dbaf12b43b275a58e2107b29a5735d8c6bd63d674ef01d4596c78961f338e5a364693b03502b53c67ffff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03011100ffffffff0200f2052a01000000160014c6aee90d5d3120a43a090b1e3720aea464248c5f0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + header: "0000002018c8213eb4f94b3847dccc7946ff82a4d022a9e87162bc0601992b7dbaf12b43b275a58e2107b29a5735d8c6bd63d674ef01d4596c78961f338e5a364693b03502b53c67ffff7f2000000000", }, } @@ -63,14 +59,12 @@ func CommonTestCases() testCaseMap { } func TestInsertHeader(t *testing.T) { - commonTestCase := CommonTestCases() - testCases := map[string]error{ "Append a fork": nil, "Create fork": nil, - "Insert failed because fork too old": errors.New("fork too old"), - "Block doesn't belong to any fork!": errors.New("Block doesn't belong to any fork!"), + "Insert failed because fork too old": ErrForkTooOld, + "Block doesn't belong to any fork!": ErrParentBlockNotInChain, } run := func(t *testing.T, testcase string) { @@ -84,14 +78,12 @@ func TestInsertHeader(t *testing.T) { } lc := NewBTCLightClientWithData(&chaincfg.RegressionNetParams, decodedHeader, 0) lcErr := lc.InsertHeader(btcHeader) - fmt.Println(lcErr) - - err := testCases[testcase] - if err == nil { - assert.Assert(t, lcErr == nil) - // TODO: fix bug can't compare 2 errors - } else if errors.Is(lcErr, err) { - t.Fatalf("Error not match") + + expectedErr := testCases[testcase] + if expectedErr == nil { + assert.NilError(t, lcErr) + } else { + assert.Error(t, lcErr, expectedErr.Error()) } } @@ -118,24 +110,16 @@ func TestCleanup(t *testing.T) { // b1 <-b2 <- b3 .... b9 // run cleanup // b2 <- b3 .... b9 - data := tcs["Append a fork"] btcHeader, _ := BlockHeaderFromHex(data.header) - beforeCheckpoint := lc.btcStore.LatestCheckPoint() - lcErr := lc.InsertHeader(btcHeader) - - // we ignore this common test if lcErr != nil { t.Fatal("Should not return error when insert header") } - err = lc.CleanUpFork() assert.NilError(t, err) - afterCheckpoint := lc.btcStore.LatestCheckPoint() - assert.Assert(t, beforeCheckpoint.Height+1 == afterCheckpoint.Height) // notthing change @@ -145,16 +129,13 @@ func TestCleanup(t *testing.T) { // run cleanup // b3 ... b8 <- b9 <- b10 // \- c10 + err = lc.CleanUpFork() assert.NilError(t, err) - notupdateCheckpoint := lc.btcStore.LatestCheckPoint() - assert.Assert(t, notupdateCheckpoint == afterCheckpoint) - data = tcs["Create fork"] btcHeader, _ = BlockHeaderFromHex(data.header) - err = lc.InsertHeader(btcHeader) assert.NilError(t, err) @@ -165,35 +146,25 @@ func TestCleanup(t *testing.T) { // run cleanup // b2 <- b3 .... b9 // \- c3 - headers := []string{ - "000000201bb3e1c443436f66b4cd58bad75748ceddd9d5737cf8c28bfffe3be786e21f6df83b5f5af28fbbac9795e543e2ef0f97bf9137305483e6c2fe59771a59303d89e1bd3d67ffff7f200200000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03011300ffffffff0200f2052a01000000160014c2adc16152275bed2e0c7f3c86852d9e15ea31680000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", - "0000002029539cf7f719b9ab72fccace6bdf9429fa9e5b3a34338f674e82c697822ee0724dbbfdc4c44cccd0a450db68a4223e47c8ecd859c00759e98b7fd227d6ad7e21e1bd3d67ffff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03011400ffffffff0200f2052a01000000160014c2adc16152275bed2e0c7f3c86852d9e15ea31680000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", - "0000002008f3a2e5297fc16ac46bb17ff9f39d1f631836f9d83de21ed6cbecdf26334f15341daa558da062aa4af3020410d94df326a7e674a85b4201a8524e9098a5bdfbe1bd3d67ffff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03011500ffffffff0200f2052a01000000160014c2adc16152275bed2e0c7f3c86852d9e15ea31680000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", - "000000202d71344e38568cc35604fec0421107143482fafc945b3291eaab4758bcb4db11c0d28e3d5b868addec8bd15b6feaa883b867f672062f5099b720f6390565df3de1bd3d67ffff7f200500000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03011600ffffffff0200f2052a01000000160014c2adc16152275bed2e0c7f3c86852d9e15ea31680000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", - "00000020f004fcfd82e23c77592bb920a817a9fc3c93854203b906ca728d240f986e6d4d9266200adb7bdef93d07e9c050a7b3e2d33f0d8324389999b21405d3e2ec77b7e1bd3d67ffff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03011700ffffffff0200f2052a01000000160014c2adc16152275bed2e0c7f3c86852d9e15ea31680000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", - "00000020f07faecc9a8a61cee690a7756f8112df5f5bfb94e5c64f39a3e61e4f8b8db35bbd276e146642e535fccf8bee242895bbdfefe92f2efcb5069e339366bf2c59e5e1bd3d67ffff7f200a00000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03011800ffffffff0200f2052a01000000160014c2adc16152275bed2e0c7f3c86852d9e15ea31680000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", - "000000203325626e050e9f17884eb04c08f89d4c689879e86bae5518ba0e77535de63976e9413b0e69182999433ea49fc52e8bb29c8d3ff7ff69c58da5f3d7978e23ea5fe2bd3d67ffff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03011900ffffffff0200f2052a01000000160014c2adc16152275bed2e0c7f3c86852d9e15ea31680000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000", + "000000201bb3e1c443436f66b4cd58bad75748ceddd9d5737cf8c28bfffe3be786e21f6df83b5f5af28fbbac9795e543e2ef0f97bf9137305483e6c2fe59771a59303d89e1bd3d67ffff7f2002000000", + "0000002029539cf7f719b9ab72fccace6bdf9429fa9e5b3a34338f674e82c697822ee0724dbbfdc4c44cccd0a450db68a4223e47c8ecd859c00759e98b7fd227d6ad7e21e1bd3d67ffff7f2000000000", + "0000002008f3a2e5297fc16ac46bb17ff9f39d1f631836f9d83de21ed6cbecdf26334f15341daa558da062aa4af3020410d94df326a7e674a85b4201a8524e9098a5bdfbe1bd3d67ffff7f2000000000", + "000000202d71344e38568cc35604fec0421107143482fafc945b3291eaab4758bcb4db11c0d28e3d5b868addec8bd15b6feaa883b867f672062f5099b720f6390565df3de1bd3d67ffff7f2005000000", + "00000020f004fcfd82e23c77592bb920a817a9fc3c93854203b906ca728d240f986e6d4d9266200adb7bdef93d07e9c050a7b3e2d33f0d8324389999b21405d3e2ec77b7e1bd3d67ffff7f2000000000", + "00000020f07faecc9a8a61cee690a7756f8112df5f5bfb94e5c64f39a3e61e4f8b8db35bbd276e146642e535fccf8bee242895bbdfefe92f2efcb5069e339366bf2c59e5e1bd3d67ffff7f200a000000", + "000000203325626e050e9f17884eb04c08f89d4c689879e86bae5518ba0e77535de63976e9413b0e69182999433ea49fc52e8bb29c8d3ff7ff69c58da5f3d7978e23ea5fe2bd3d67ffff7f2000000000", } - - // lc.btcStore.SetLatestCheckPoint() - - for id, h := range headers { + for _, h := range headers { btcHeader, _ := BlockHeaderFromHex(h) lcErr := lc.InsertHeader(btcHeader) - // we ignore this common test if lcErr != nil { - fmt.Println(lcErr, id) t.Fatal("Should not return error when insert header") } err := lc.CleanUpFork() assert.NilError(t, err) - fmt.Println(btcHeader.BlockHash()) } - listFork := lc.btcStore.LatestBlockHashOfFork() - assert.Assert(t, len(listFork) == 1) - } diff --git a/btclightclient/errors.go b/btclightclient/errors.go new file mode 100644 index 0000000..af4cbe9 --- /dev/null +++ b/btclightclient/errors.go @@ -0,0 +1,15 @@ +package btclightclient + +import "errors" + +// Light client errors +var ErrForkTooOld = errors.New("fork too old") +var ErrBlockNotInChain = errors.New("block not in the chain") +var ErrInvalidHeaderSize = errors.New("invalid header size, must be 80 bytes") +var ErrParentBlockNotInChain = errors.New("parent block not in chain") +var ErrBlockIsNotForkHead = errors.New("block is not a fork head") + +// SPV errors +var ErrValueIsNotMerkleLeaf = errors.New("value doesn't exist in merkle tree") +var ErrMerkleDecodeOutbound = errors.New("out-bound of vHash") +var ErrMerkleDecodeHashNumberInvalid = errors.New("number of hashes reach to limit") diff --git a/btclightclient/partial_merkle_tree.go b/btclightclient/partial_merkle_tree.go index c37cdbd..8a67e8b 100644 --- a/btclightclient/partial_merkle_tree.go +++ b/btclightclient/partial_merkle_tree.go @@ -4,7 +4,6 @@ import ( "bytes" "encoding/binary" "encoding/hex" - "errors" "fmt" "io" "math" @@ -94,7 +93,7 @@ func decodePartialMerkleTreeData(buf []byte) (partialMerkleTreeData, error) { return pmt, err } if numberOfHashes*chainhash.HashSize > maxAllowBytes { - return pmt, errors.New("number of hashes is too big") + return pmt, ErrMerkleDecodeHashNumberInvalid } bytes := make([]byte, numberOfHashes*chainhash.HashSize) @@ -143,7 +142,7 @@ func (pmtd *partialMerkleTreeData) calcTreeWidth(height uint32) uint32 { func (pmtd *partialMerkleTreeData) nextBit() (bool, error) { if int(pmtd.nBitUsed) >= len(pmtd.vBits) { - return false, fmt.Errorf("Out-bound of vBits") + return false, fmt.Errorf("out-bound of vBits") } bit := pmtd.vBits[pmtd.nBitUsed] pmtd.nBitUsed++ @@ -152,7 +151,7 @@ func (pmtd *partialMerkleTreeData) nextBit() (bool, error) { func (pmtd *partialMerkleTreeData) nextHash() (*chainhash.Hash, error) { if int(pmtd.nHashUsed) >= len(pmtd.vHash) { - return nil, fmt.Errorf("Out-bound of vHash") + return nil, fmt.Errorf("out-bound of vHash") } hash := pmtd.vHash[pmtd.nHashUsed] pmtd.nHashUsed++ @@ -171,7 +170,7 @@ func (pmtd *partialMerkleTreeData) buildTreeRecursive(height, pos uint32, merkle return nil, err } - // handle leaf + // Handle leaf if height == 0 || !fParentOfMatch { hash, err := pmtd.nextHash() if err != nil { @@ -182,20 +181,21 @@ func (pmtd *partialMerkleTreeData) buildTreeRecursive(height, pos uint32, merkle } // handle internal node - // we go to left of the tree + + // Traverse to the left node in the tree. left, err := pmtd.buildTreeRecursive(height-1, pos*2, merkleTree) if err != nil { return nil, err } var right *chainhash.Hash - // Check right node exists and jump to this + // Check if right node exists and traverse to this node if it exists if pos*2+1 < pmtd.calcTreeWidth(height-1) { right, err = pmtd.buildTreeRecursive(height-1, pos*2+1, merkleTree) if err != nil { return nil, err } if left.IsEqual(right) { - return nil, fmt.Errorf("Right node never identical with left node") + return nil, fmt.Errorf("right node never identical with left node") } } else { // Right node doesn't exist, it's assigned value by left node. @@ -230,7 +230,7 @@ func (mk PartialMerkleTree) getLeafNodeIndex(txID *chainhash.Hash) (uint32, erro return leafIndex, nil } } - return 0, errors.New("Node value doesn't exist in merkle tree") + return 0, ErrValueIsNotMerkleLeaf } @@ -255,13 +255,14 @@ func (mk PartialMerkleTree) GetProof(txID string) (*MerkleProof, error) { for i := 0; i < h-1; i++ { var siblingHash chainhash.Hash if position%2 == 0 { - // current node is left node, push right node + // A current node is a left children of parent node + // We need the node on the right to compute parent node siblingHash = mk.nodesAtHeight[i][position+1] } else { - //currect node is right node push left node + // A current node is a right children of parent node + // We need the node on the left to compute parent node siblingHash = mk.nodesAtHeight[i][position-1] } - merklePath = append(merklePath, siblingHash) position = position / 2 } diff --git a/btclightclient/partial_merkle_tree_test.go b/btclightclient/partial_merkle_tree_test.go index d5c241f..8e234bf 100644 --- a/btclightclient/partial_merkle_tree_test.go +++ b/btclightclient/partial_merkle_tree_test.go @@ -8,29 +8,29 @@ import ( func TestPartialMerkleTree(t *testing.T) { type testCase struct { - name string - txoutproof string - txID string - expectedErrorMessage string - root string + name string + txoutproof string + txID string + expectedError error + root string } run := func(t *testing.T, tc testCase) { pmt, err := PartialMerkleTreeFromHex(tc.txoutproof[160:]) if err != nil { - assert.Error(t, err, tc.expectedErrorMessage) + assert.Error(t, err, tc.expectedError.Error()) return } merkleProof, err := pmt.GetProof(tc.txID) if err != nil { - assert.Error(t, err, tc.expectedErrorMessage) + assert.Error(t, err, tc.expectedError.Error()) return } - if len(tc.expectedErrorMessage) > 0 { - t.Fatalf("Should return error") + if tc.expectedError != nil { + t.Fatalf("should return error") } assert.Equal(t, merkleProof.merkleRoot.String(), tc.root) @@ -40,26 +40,25 @@ func TestPartialMerkleTree(t *testing.T) { { name: "Happy test case", // data from chainstack example: https://docs.chainstack.com/reference/bitcoin-gettxoutproof - txoutproof: "00e0002000471175ec71a72541c100f21bb79f9da0e5ca98259a000000000000000000004769eae15b51056127304c5dec6d94c7840f8f922c0b65bc32177cb46ce05de9b8c10866d36203175dae051fdc0a00000d625aa7b5510f7c003624338259d21544e61ccb3666792dde9734b7621d2cf80bb81ffa45657310bdc47ad3b3f5e5346c150d4fc1b98a5446cc560c6f38f7156138761aab058be861e51fe52ea7cf7b4914a1e1b159ecebe46b51db0ec5cfd4c2324ae9c132169d1f133981632895c216a8e3c3d3a9cea545fade4c0ab8b626a2791862728b657abbdb06dedcc3faabee9d72ce6b8252b45fc99d6fe0f79cec401e1a431774d8830b962e5dee97fc96f4f85f84a6e50b986a37b35318537a81f3f8c604554e5b4f5ca4b4437caa3b0723896396532c1985d52f42f915084534c6bedb4ded1238781d23be0173b94ca25d7faff2832ac99fa16b2f9b219ff276062f100d4b7ce774ba405fbad36b65165e2e5aece3e0b9718886d7b24708be5ae72d10911e9301811b19fcb218ce7dfee31729f4ef56a3d8f31670865a039b3678b34fcb47f12bd157a064339c3e91a960c5a14b9e8da9c8ce211a02bb94e7165a1668d8a17663e95adcdacdbc8e8ab793e8796fda9b270ca957e67aa33dc95cff158cb2ff6882064942ef545612a8eceb3c60415d677d170f4351ede1f7a8807504ff1f0000", - txID: "0bf82c1d62b73497de2d796636cb1ce64415d25982332436007c0f51b5a75a62", - expectedErrorMessage: "", - root: "e95de06cb47c1732bc650b2c928f0f84c7946dec5d4c30276105515be1ea6947", + txoutproof: "00e0002000471175ec71a72541c100f21bb79f9da0e5ca98259a000000000000000000004769eae15b51056127304c5dec6d94c7840f8f922c0b65bc32177cb46ce05de9b8c10866d36203175dae051fdc0a00000d625aa7b5510f7c003624338259d21544e61ccb3666792dde9734b7621d2cf80bb81ffa45657310bdc47ad3b3f5e5346c150d4fc1b98a5446cc560c6f38f7156138761aab058be861e51fe52ea7cf7b4914a1e1b159ecebe46b51db0ec5cfd4c2324ae9c132169d1f133981632895c216a8e3c3d3a9cea545fade4c0ab8b626a2791862728b657abbdb06dedcc3faabee9d72ce6b8252b45fc99d6fe0f79cec401e1a431774d8830b962e5dee97fc96f4f85f84a6e50b986a37b35318537a81f3f8c604554e5b4f5ca4b4437caa3b0723896396532c1985d52f42f915084534c6bedb4ded1238781d23be0173b94ca25d7faff2832ac99fa16b2f9b219ff276062f100d4b7ce774ba405fbad36b65165e2e5aece3e0b9718886d7b24708be5ae72d10911e9301811b19fcb218ce7dfee31729f4ef56a3d8f31670865a039b3678b34fcb47f12bd157a064339c3e91a960c5a14b9e8da9c8ce211a02bb94e7165a1668d8a17663e95adcdacdbc8e8ab793e8796fda9b270ca957e67aa33dc95cff158cb2ff6882064942ef545612a8eceb3c60415d677d170f4351ede1f7a8807504ff1f0000", + txID: "0bf82c1d62b73497de2d796636cb1ce64415d25982332436007c0f51b5a75a62", + expectedError: nil, + root: "e95de06cb47c1732bc650b2c928f0f84c7946dec5d4c30276105515be1ea6947", }, { name: "Error b/c txID not exist in merkle tree", // data from chainstack example: https://docs.chainstack.com/reference/bitcoin-gettxoutproof - txoutproof: "00e0002000471175ec71a72541c100f21bb79f9da0e5ca98259a000000000000000000004769eae15b51056127304c5dec6d94c7840f8f922c0b65bc32177cb46ce05de9b8c10866d36203175dae051fdc0a00000d625aa7b5510f7c003624338259d21544e61ccb3666792dde9734b7621d2cf80bb81ffa45657310bdc47ad3b3f5e5346c150d4fc1b98a5446cc560c6f38f7156138761aab058be861e51fe52ea7cf7b4914a1e1b159ecebe46b51db0ec5cfd4c2324ae9c132169d1f133981632895c216a8e3c3d3a9cea545fade4c0ab8b626a2791862728b657abbdb06dedcc3faabee9d72ce6b8252b45fc99d6fe0f79cec401e1a431774d8830b962e5dee97fc96f4f85f84a6e50b986a37b35318537a81f3f8c604554e5b4f5ca4b4437caa3b0723896396532c1985d52f42f915084534c6bedb4ded1238781d23be0173b94ca25d7faff2832ac99fa16b2f9b219ff276062f100d4b7ce774ba405fbad36b65165e2e5aece3e0b9718886d7b24708be5ae72d10911e9301811b19fcb218ce7dfee31729f4ef56a3d8f31670865a039b3678b34fcb47f12bd157a064339c3e91a960c5a14b9e8da9c8ce211a02bb94e7165a1668d8a17663e95adcdacdbc8e8ab793e8796fda9b270ca957e67aa33dc95cff158cb2ff6882064942ef545612a8eceb3c60415d677d170f4351ede1f7a8807504ff1f0000", - txID: "e95de06cb47c1732bc650b2c928f0f84c7946dec5d4c30276105515be1ea6947", - expectedErrorMessage: "Node value doesn't exist in merkle tree", - root: "", + txoutproof: "00e0002000471175ec71a72541c100f21bb79f9da0e5ca98259a000000000000000000004769eae15b51056127304c5dec6d94c7840f8f922c0b65bc32177cb46ce05de9b8c10866d36203175dae051fdc0a00000d625aa7b5510f7c003624338259d21544e61ccb3666792dde9734b7621d2cf80bb81ffa45657310bdc47ad3b3f5e5346c150d4fc1b98a5446cc560c6f38f7156138761aab058be861e51fe52ea7cf7b4914a1e1b159ecebe46b51db0ec5cfd4c2324ae9c132169d1f133981632895c216a8e3c3d3a9cea545fade4c0ab8b626a2791862728b657abbdb06dedcc3faabee9d72ce6b8252b45fc99d6fe0f79cec401e1a431774d8830b962e5dee97fc96f4f85f84a6e50b986a37b35318537a81f3f8c604554e5b4f5ca4b4437caa3b0723896396532c1985d52f42f915084534c6bedb4ded1238781d23be0173b94ca25d7faff2832ac99fa16b2f9b219ff276062f100d4b7ce774ba405fbad36b65165e2e5aece3e0b9718886d7b24708be5ae72d10911e9301811b19fcb218ce7dfee31729f4ef56a3d8f31670865a039b3678b34fcb47f12bd157a064339c3e91a960c5a14b9e8da9c8ce211a02bb94e7165a1668d8a17663e95adcdacdbc8e8ab793e8796fda9b270ca957e67aa33dc95cff158cb2ff6882064942ef545612a8eceb3c60415d677d170f4351ede1f7a8807504ff1f0000", + txID: "e95de06cb47c1732bc650b2c928f0f84c7946dec5d4c30276105515be1ea6947", + expectedError: ErrValueIsNotMerkleLeaf, + root: "", }, { - name: "Can't decode txoutproof", - // data from chainstack example: https://docs.chainstack.com/reference/bitcoin-gettxoutproof - txoutproof: "00e0002000471175ec71a72541c100f21bb79f9da0e5ca98259a000000000000000000004769eae15b51056127304c5edec6d94c7840f8f922c0b65bc32177cb46ce05de9b8c10866d36203175dae051fdc0a00000d625aa7b5510f7c003624338259d21544e61ccb3666792dde9734b7621d2cf80bb81ffa45657310bdc47ad3b3f5e5346c150d4fc1b98a5446cc560c6f38f7156138761aab058be861e51fe52ea7cf7b4914a1e1b159ecebe46b51db0ec5cfd4c2324ae9c132169d1f133981632895c216a8e3c3d3a9cea545fade4c0ab8b626a2791862728b657abbdb06dedcc3faabee9d72ce6b8252b45fc99d6fe0f79cec401e1a431774d8830b962e5dee97fc96f4f85f84a6e50b986a37b35318537a81f3f8c604554e5b4f5ca4b4437caa3b0723896396532c1985d52f42f915084534c6bedb4ded1238781d23be0173b94ca25d7faff2832ac99fa16b2f9b219ff276062f100d4b7ce774ba405fbad36b65165e2e5aece3e0b9718886d7b24708be5ae72d10911e9301811b19fcb218ce7dfee31729f4ef56a3d8f31670865a039b3678b34fcb47f12bd157a064339c3e91a960c5a14b9e8da9c8ce211a02bb94e7165a1668d8a17663e95adcdacdbc8e8ab793e8796fda9b270ca957e67aa33dc95cff158cb2ff6882064942ef545612a8eceb3c60415d677d170f4351ede1f7a8807504ff1f0000e", - txID: "", - expectedErrorMessage: "Out-bound of vHash", - root: "", + name: "Can't decode txoutproof", + txoutproof: "00e0002000471175ec71a72541c100f21bb79f9da0e5ca98259a000000000000000000004769eae15b51056127304c5edec6d94c7840f8f922c0b65bc32177cb46ce05de9b8c10866d36203175dae051fdc0a00000d625aa7b5510f7c003624338259d21544e61ccb3666792dde9734b7621d2cf80bb81ffa45657310bdc47ad3b3f5e5346c150d4fc1b98a5446cc560c6f38f7156138761aab058be861e51fe52ea7cf7b4914a1e1b159ecebe46b51db0ec5cfd4c2324ae9c132169d1f133981632895c216a8e3c3d3a9cea545fade4c0ab8b626a2791862728b657abbdb06dedcc3faabee9d72ce6b8252b45fc99d6fe0f79cec401e1a431774d8830b962e5dee97fc96f4f85f84a6e50b986a37b35318537a81f3f8c604554e5b4f5ca4b4437caa3b0723896396532c1985d52f42f915084534c6bedb4ded1238781d23be0173b94ca25d7faff2832ac99fa16b2f9b219ff276062f100d4b7ce774ba405fbad36b65165e2e5aece3e0b9718886d7b24708be5ae72d10911e9301811b19fcb218ce7dfee31729f4ef56a3d8f31670865a039b3678b34fcb47f12bd157a064339c3e91a960c5a14b9e8da9c8ce211a02bb94e7165a1668d8a17663e95adcdacdbc8e8ab793e8796fda9b270ca957e67aa33dc95cff158cb2ff6882064942ef545612a8eceb3c60415d677d170f4351ede1f7a8807504ff1f0000e", + txID: "", + expectedError: ErrMerkleDecodeOutbound, + root: "", }, }