github.com/spacemeshos/merkle-tree@v0.2.2/merkle_test.go (about) 1 package merkle_test 2 3 import ( 4 "encoding/binary" 5 "encoding/hex" 6 "fmt" 7 "testing" 8 "time" 9 10 "github.com/stretchr/testify/require" 11 12 "github.com/spacemeshos/merkle-tree" 13 "github.com/spacemeshos/merkle-tree/cache" 14 ) 15 16 var ( 17 NewTree = merkle.NewTree 18 NewTreeBuilder = merkle.NewTreeBuilder 19 NewProvingTree = merkle.NewProvingTree 20 NewCachingTree = merkle.NewCachingTree 21 GenerateProof = merkle.GenerateProof 22 ValidatePartialTree = merkle.ValidatePartialTree 23 ValidatePartialTreeWithParkingSnapshots = merkle.ValidatePartialTreeWithParkingSnapshots 24 GetSha256Parent = merkle.GetSha256Parent 25 GetNode = merkle.GetNode 26 setOf = merkle.SetOf 27 newSparseBoolStack = merkle.NewSparseBoolStack 28 emptyNode = merkle.EmptyNode 29 NodeSize = merkle.NodeSize 30 ) 31 32 type ( 33 set = merkle.Set 34 position = merkle.Position 35 validator = merkle.Validator 36 leafIterator = merkle.LeafIterator 37 CacheReader = cache.CacheReader 38 ) 39 40 /* 41 42 8-leaf tree (1st 2 bytes of each node): 43 44 +--------------------------------------------------+ 45 | 89a0 | 46 | ba94 633b | 47 | cb59 0094 bd50 fa67 | 48 | 0000 0100 0200 0300 0400 0500 0600 0700 | 49 +--------------------------------------------------+ 50 51 */ 52 53 func TestNewTree(t *testing.T) { 54 r := require.New(t) 55 tree, err := NewTree() 56 r.NoError(err) 57 for i := uint64(0); i < 8; i++ { 58 err := tree.AddLeaf(NewNodeFromUint64(i)) 59 r.NoError(err) 60 } 61 expectedRoot, _ := NewNodeFromHex("89a0f1577268cc19b0a39c7a69f804fd140640c699585eb635ebb03c06154cce") 62 root := tree.Root() 63 r.Equal(expectedRoot, root) 64 } 65 66 func concatLeaves(_, lChild, rChild []byte) []byte { 67 if len(lChild) == NodeSize { 68 lChild = lChild[:1] 69 } 70 if len(rChild) == NodeSize { 71 rChild = rChild[:1] 72 } 73 return append(lChild, rChild...) 74 } 75 76 func TestNewTreeWithMinHeightEqual(t *testing.T) { 77 r := require.New(t) 78 tree, err := NewTreeBuilder().WithHashFunc(concatLeaves).WithMinHeight(3).Build() 79 r.NoError(err) 80 for i := uint64(0); i < 8; i++ { 81 err := tree.AddLeaf(NewNodeFromUint64(i)) 82 r.NoError(err) 83 } 84 expectedRoot, _ := NewNodeFromHex("0001020304050607") 85 root := tree.Root() 86 r.Equal(expectedRoot, root) 87 } 88 89 func TestNewTreeWithMinHeightGreater(t *testing.T) { 90 r := require.New(t) 91 tree, err := NewTreeBuilder().WithHashFunc(concatLeaves).WithMinHeight(4).Build() 92 r.NoError(err) 93 for i := uint64(0); i < 8; i++ { 94 err := tree.AddLeaf(NewNodeFromUint64(i)) 95 r.NoError(err) 96 } 97 // An 8-leaf tree is 3 layers high, so setting a minHeight of 4 means we need to add a "padding node" to the root. 98 expectedRoot, _ := NewNodeFromHex("000102030405060700") 99 root := tree.Root() 100 r.Equal(expectedRoot, root) 101 } 102 103 func TestNewTreeWithMinHeightGreater2(t *testing.T) { 104 r := require.New(t) 105 tree, err := NewTreeBuilder().WithHashFunc(concatLeaves).WithMinHeight(5).Build() 106 r.NoError(err) 107 for i := uint64(0); i < 8; i++ { 108 err := tree.AddLeaf(NewNodeFromUint64(i)) 109 r.NoError(err) 110 } 111 // An 8-leaf tree is 3 layers high, so setting a minHeight of 5 means we need to add two "padding nodes" to the root. 112 expectedRoot, _ := NewNodeFromHex("00010203040506070000") 113 root := tree.Root() 114 r.Equal(expectedRoot, root) 115 } 116 117 func TestNewTreeUnbalanced(t *testing.T) { 118 r := require.New(t) 119 tree, err := NewTree() 120 r.NoError(err) 121 for i := uint64(0); i < 9; i++ { 122 err := tree.AddLeaf(NewNodeFromUint64(i)) 123 r.NoError(err) 124 } 125 expectedRoot, _ := NewNodeFromHex("cb71c80ee780788eedb819ec125a41e0cde57bd0955cdd3157ca363193ab5ff1") 126 root := tree.Root() 127 r.Equal(expectedRoot, root) 128 } 129 130 func TestNewTreeUnbalanced2(t *testing.T) { 131 r := require.New(t) 132 tree, err := NewTree() 133 r.NoError(err) 134 for i := uint64(0); i < 10; i++ { 135 err := tree.AddLeaf(NewNodeFromUint64(i)) 136 r.NoError(err) 137 } 138 expectedRoot, _ := NewNodeFromHex("59f32a43534fe4c4c0966421aef624267cdf65bd11f74998c60f27c7caccb12d") 139 root := tree.Root() 140 r.Equal(expectedRoot, root) 141 } 142 143 func TestNewTreeUnbalanced3(t *testing.T) { 144 r := require.New(t) 145 tree, err := NewTree() 146 r.NoError(err) 147 for i := uint64(0); i < 15; i++ { 148 err := tree.AddLeaf(NewNodeFromUint64(i)) 149 r.NoError(err) 150 } 151 expectedRoot, _ := NewNodeFromHex("b9746fb884ed07041c5cbb3bb5526e1383928e832a8385e08db995966889b5a8") 152 root := tree.Root() 153 r.Equal(expectedRoot, root) 154 } 155 156 func TestNewTreeUnbalancedProof(t *testing.T) { 157 r := require.New(t) 158 159 leavesToProve := setOf(0, 4, 7) 160 161 cacheWriter := cache.NewWriter(cache.MinHeightPolicy(0), cache.MakeSliceReadWriterFactory()) 162 163 tree, err := NewTreeBuilder(). 164 WithLeavesToProve(leavesToProve). 165 WithCacheWriter(cacheWriter). 166 Build() 167 r.NoError(err) 168 for i := uint64(0); i < 10; i++ { 169 err := tree.AddLeaf(NewNodeFromUint64(i)) 170 r.NoError(err) 171 } 172 expectedRoot, _ := NewNodeFromHex("59f32a43534fe4c4c0966421aef624267cdf65bd11f74998c60f27c7caccb12d") 173 root := tree.Root() 174 r.Equal(expectedRoot, root) 175 176 cacheReader, err := cacheWriter.GetReader() 177 r.NoError(err) 178 179 assertWidth(r, 10, cacheReader.GetLayerReader(0)) 180 assertWidth(r, 5, cacheReader.GetLayerReader(1)) 181 assertWidth(r, 2, cacheReader.GetLayerReader(2)) 182 assertWidth(r, 1, cacheReader.GetLayerReader(3)) 183 184 cacheRoot, err := cacheReader.GetLayerReader(3).ReadNext() 185 r.NoError(err) 186 r.NotEqual(cacheRoot, expectedRoot) 187 188 expectedProof := make([][]byte, 5) 189 expectedProof[0], _ = NewNodeFromHex("0100000000000000000000000000000000000000000000000000000000000000") 190 expectedProof[1], _ = NewNodeFromHex("0094579cfc7b716038d416a311465309bea202baa922b224a7b08f01599642fb") 191 expectedProof[2], _ = NewNodeFromHex("0500000000000000000000000000000000000000000000000000000000000000") 192 expectedProof[3], _ = NewNodeFromHex("0600000000000000000000000000000000000000000000000000000000000000") 193 expectedProof[4], _ = NewNodeFromHex("bc68417a8495de6e22d95b980fca5a1183f29eff0e2a9b7ddde91ed5bcbea952") 194 195 proof := tree.Proof() 196 r.EqualValues(expectedProof, proof) 197 } 198 199 func assertWidth(r *require.Assertions, expectedWidth int, layerReader cache.LayerReader) { 200 r.NotNil(layerReader) 201 width, err := layerReader.Width() 202 r.NoError(err) 203 r.Equal(uint64(expectedWidth), width) 204 } 205 206 func BenchmarkNewTree(b *testing.B) { 207 var size uint64 = 1 << 28 208 tree, _ := NewTree() 209 for i := uint64(0); i < size; i++ { 210 _ = tree.AddLeaf(NewNodeFromUint64(i)) 211 } 212 /* 213 goos: darwin 214 goarch: amd64 215 pkg: github.com/spacemeshos/merkle-tree 216 BenchmarkNewTree-8 1 125055682774 ns/op 217 PASS 218 */ 219 } 220 221 func BenchmarkNewTreeSmall(b *testing.B) { 222 var size uint64 = 1 << 23 223 start := time.Now() 224 tree, _ := NewTree() 225 for i := uint64(0); i < size; i++ { 226 _ = tree.AddLeaf(NewNodeFromUint64(i)) 227 } 228 b.Log(time.Since(start)) 229 /* 230 merkle_test.go:72: 3.700763631s 231 */ 232 } 233 234 func BenchmarkNewTreeNoHashing(b *testing.B) { 235 var size uint64 = 1 << 28 236 tree, _ := NewTree() 237 for i := uint64(0); i < size; i++ { 238 _ = tree.AddLeaf(NewNodeFromUint64(i)) 239 } 240 /* 241 goos: darwin 242 goarch: amd64 243 pkg: github.com/spacemeshos/merkle-tree 244 BenchmarkNewTreeNoHashing-8 1 14668889972 ns/op 245 BenchmarkNewTreeNoHashing-8 1 15791579912 ns/op 246 PASS 247 */ 248 } 249 250 /* 251 28 layer tree takes 125 seconds to construct. Overhead (no hashing) is 15.5 seconds. Net: 109.5 seconds. 252 (8.5GB @ 32b leaves) => x30 256GB => 55 minutes for hashing, 8 minutes overhead. 253 254 Reading 256GB from a magnetic disk should take ~30 minutes. 255 */ 256 257 // Proving tree tests 258 259 func TestNewProvingTree(t *testing.T) { 260 r := require.New(t) 261 tree, err := NewProvingTree(setOf(4)) 262 r.NoError(err) 263 for i := uint64(0); i < 8; i++ { 264 err := tree.AddLeaf(NewNodeFromUint64(i)) 265 r.NoError(err) 266 } 267 expectedRoot, _ := NewNodeFromHex("89a0f1577268cc19b0a39c7a69f804fd140640c699585eb635ebb03c06154cce") 268 root := tree.Root() 269 r.Equal(expectedRoot, root) 270 271 expectedProof := make([][]byte, 3) 272 expectedProof[0], _ = NewNodeFromHex("0500000000000000000000000000000000000000000000000000000000000000") 273 expectedProof[1], _ = NewNodeFromHex("fa670379e5c2212ed93ff09769622f81f98a91e1ec8fb114d607dd25220b9088") 274 expectedProof[2], _ = NewNodeFromHex("ba94ffe7edabf26ef12736f8eb5ce74d15bedb6af61444ae2906e926b1a95084") 275 276 proof := tree.Proof() 277 r.EqualValues(expectedProof, proof) 278 279 /*************************************************** 280 | 89a0 | 281 | .ba94. 633b | 282 | cb59 0094 bd50 .fa67. | 283 | 0000 0100 0200 0300 =0400=.0500. 0600 0700 | 284 ***************************************************/ 285 } 286 287 func TestNewProvingTreeMultiProof(t *testing.T) { 288 r := require.New(t) 289 tree, err := NewProvingTree(setOf(1, 4)) 290 r.NoError(err) 291 for i := uint64(0); i < 8; i++ { 292 err := tree.AddLeaf(NewNodeFromUint64(i)) 293 r.NoError(err) 294 } 295 expectedRoot, _ := NewNodeFromHex("89a0f1577268cc19b0a39c7a69f804fd140640c699585eb635ebb03c06154cce") 296 root := tree.Root() 297 r.Equal(expectedRoot, root) 298 299 expectedProof := make([][]byte, 4) 300 expectedProof[0], _ = NewNodeFromHex("0000000000000000000000000000000000000000000000000000000000000000") 301 expectedProof[1], _ = NewNodeFromHex("0094579cfc7b716038d416a311465309bea202baa922b224a7b08f01599642fb") 302 expectedProof[2], _ = NewNodeFromHex("0500000000000000000000000000000000000000000000000000000000000000") 303 expectedProof[3], _ = NewNodeFromHex("fa670379e5c2212ed93ff09769622f81f98a91e1ec8fb114d607dd25220b9088") 304 305 proof := tree.Proof() 306 r.EqualValues(expectedProof, proof) 307 308 /*************************************************** 309 | 89a0 | 310 | ba94 633b | 311 | cb59 .0094. bd50 .fa67. | 312 | .0000.=0100= 0200 0300 =0400=.0500. 0600 0700 | 313 ***************************************************/ 314 } 315 316 // TestNewProvingTreeMultiProofReuseLeafBytes verifies if the user of Tree 317 // can safely reuse the memory passed into Tree::AddLeaf. 318 func TestNewProvingTreeMultiProofReuseLeafBytes(t *testing.T) { 319 r := require.New(t) 320 tree, err := NewProvingTree(setOf(1, 4)) 321 r.NoError(err) 322 var leaf [32]byte 323 for i := uint64(0); i < 8; i++ { 324 binary.LittleEndian.PutUint64(leaf[:], i) 325 r.NoError(tree.AddLeaf(leaf[:])) 326 } 327 expectedRoot, _ := NewNodeFromHex("89a0f1577268cc19b0a39c7a69f804fd140640c699585eb635ebb03c06154cce") 328 root := tree.Root() 329 r.Equal(expectedRoot, root) 330 331 expectedProof := make([][]byte, 4) 332 expectedProof[0], _ = NewNodeFromHex("0000000000000000000000000000000000000000000000000000000000000000") 333 expectedProof[1], _ = NewNodeFromHex("0094579cfc7b716038d416a311465309bea202baa922b224a7b08f01599642fb") 334 expectedProof[2], _ = NewNodeFromHex("0500000000000000000000000000000000000000000000000000000000000000") 335 expectedProof[3], _ = NewNodeFromHex("fa670379e5c2212ed93ff09769622f81f98a91e1ec8fb114d607dd25220b9088") 336 337 proof := tree.Proof() 338 r.EqualValues(expectedProof, proof) 339 340 /*************************************************** 341 | 89a0 | 342 | ba94 633b | 343 | cb59 .0094. bd50 .fa67. | 344 | .0000.=0100= 0200 0300 =0400=.0500. 0600 0700 | 345 ***************************************************/ 346 } 347 348 func TestNewProvingTreeMultiProof2(t *testing.T) { 349 r := require.New(t) 350 tree, err := NewProvingTree(setOf(0, 1, 4)) 351 r.NoError(err) 352 for i := uint64(0); i < 8; i++ { 353 err := tree.AddLeaf(NewNodeFromUint64(i)) 354 r.NoError(err) 355 } 356 expectedRoot, _ := NewNodeFromHex("89a0f1577268cc19b0a39c7a69f804fd140640c699585eb635ebb03c06154cce") 357 root := tree.Root() 358 r.Equal(expectedRoot, root) 359 360 expectedProof := make([][]byte, 3) 361 expectedProof[0], _ = NewNodeFromHex("0094579cfc7b716038d416a311465309bea202baa922b224a7b08f01599642fb") 362 expectedProof[1], _ = NewNodeFromHex("0500000000000000000000000000000000000000000000000000000000000000") 363 expectedProof[2], _ = NewNodeFromHex("fa670379e5c2212ed93ff09769622f81f98a91e1ec8fb114d607dd25220b9088") 364 365 proof := tree.Proof() 366 r.EqualValues(expectedProof, proof) 367 368 /*************************************************** 369 | 89a0 | 370 | ba94 633b | 371 | cb59 .0094. bd50 .fa67. | 372 | =0000==0100= 0200 0300 =0400=.0500. 0600 0700 | 373 ***************************************************/ 374 } 375 376 func NewNodeFromUint64(i uint64) []byte { 377 b := make([]byte, NodeSize) 378 binary.LittleEndian.PutUint64(b, i) 379 return b 380 } 381 382 func NewNodeFromHex(s string) ([]byte, error) { 383 return hex.DecodeString(s) 384 } 385 386 // Caching tests: 387 388 func TestNewCachingTree(t *testing.T) { 389 r := require.New(t) 390 cacheWriter := cache.NewWriter(cache.MinHeightPolicy(0), cache.MakeSliceReadWriterFactory()) 391 tree, err := NewCachingTree(cacheWriter) 392 r.NoError(err) 393 for i := uint64(0); i < 8; i++ { 394 err := tree.AddLeaf(NewNodeFromUint64(i)) 395 r.NoError(err) 396 } 397 expectedRoot, _ := NewNodeFromHex("89a0f1577268cc19b0a39c7a69f804fd140640c699585eb635ebb03c06154cce") 398 root := tree.Root() 399 r.Equal(expectedRoot, root) 400 401 cacheReader, err := cacheWriter.GetReader() 402 r.NoError(err) 403 404 assertWidth(r, 8, cacheReader.GetLayerReader(0)) 405 assertWidth(r, 4, cacheReader.GetLayerReader(1)) 406 assertWidth(r, 2, cacheReader.GetLayerReader(2)) 407 assertWidth(r, 1, cacheReader.GetLayerReader(3)) 408 cacheRoot, err := cacheReader.GetLayerReader(3).ReadNext() 409 r.NoError(err) 410 r.Equal(cacheRoot, expectedRoot) 411 412 // cacheWriter.Print(0 , 3) 413 } 414 415 func BenchmarkNewCachingTreeSmall(b *testing.B) { 416 var size uint64 = 1 << 23 417 cacheWriter := cache.NewWriter(cache.MinHeightPolicy(7), cache.MakeSliceReadWriterFactory()) 418 start := time.Now() 419 tree, _ := NewCachingTree(cacheWriter) 420 for i := uint64(0); i < size; i++ { 421 _ = tree.AddLeaf(NewNodeFromUint64(i)) 422 } 423 b.Log(time.Since(start)) 424 /* 425 merkle_test.go:242: 3.054842184s 426 */ 427 } 428 429 func TestSparseBoolStack(t *testing.T) { 430 r := require.New(t) 431 432 allFalse := newSparseBoolStack(make(set)) 433 for i := 0; i < 1000; i++ { 434 r.False(allFalse.Pop()) 435 } 436 437 allTrue := newSparseBoolStack(setOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)) 438 for i := 0; i < 10; i++ { 439 r.True(allTrue.Pop()) 440 } 441 442 rounds := make(set) 443 for i := uint64(0); i < 1000; i += 10 { 444 rounds[i] = true 445 } 446 roundsTrue := newSparseBoolStack(rounds) 447 for i := 0; i < 1000; i++ { 448 if i%10 == 0 { 449 r.True(roundsTrue.Pop()) 450 } else { 451 r.False(roundsTrue.Pop()) 452 } 453 } 454 } 455 456 func TestEmptyNode(t *testing.T) { 457 r := require.New(t) 458 459 r.True(emptyNode.IsEmpty()) 460 r.False(emptyNode.OnProvenPath) 461 } 462 463 func TestTree_GetParkedNodes(t *testing.T) { 464 r := require.New(t) 465 466 tree, err := NewTreeBuilder().Build() 467 r.NoError(err) 468 469 r.NoError(tree.AddLeaf([]byte{0})) 470 r.EqualValues( 471 [][]byte{{0}}, 472 tree.GetParkedNodes(nil)) 473 474 r.NoError(tree.AddLeaf([]byte{1})) 475 r.EqualValues( 476 [][]byte{{}, decode(r, "b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2")}, 477 tree.GetParkedNodes(nil)) 478 479 r.NoError(tree.AddLeaf([]byte{2})) 480 r.EqualValues( 481 [][]byte{{2}, decode(r, "b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2")}, 482 tree.GetParkedNodes(nil)) 483 484 r.NoError(tree.AddLeaf([]byte{3})) 485 r.EqualValues( 486 [][]byte{{}, {}, decode(r, "7699a4fdd6b8b6908a344f73b8f05c8e1400f7253f544602c442ff5c65504b24")}, 487 tree.GetParkedNodes(nil)) 488 } 489 490 func TestTree_SetParkedNodes(t *testing.T) { 491 r := require.New(t) 492 493 tree, err := NewTreeBuilder().Build() 494 r.NoError(err) 495 r.NoError(tree.SetParkedNodes([][]byte{{0}})) 496 r.NoError(tree.AddLeaf([]byte{1})) 497 parkedNodes := [][]byte{{}, decode(r, "b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2")} 498 r.EqualValues(parkedNodes, tree.GetParkedNodes(nil)) 499 500 tree, err = NewTreeBuilder().Build() 501 r.NoError(err) 502 r.NoError(tree.SetParkedNodes(parkedNodes)) 503 r.NoError(tree.AddLeaf([]byte{2})) 504 parkedNodes = [][]byte{{2}, decode(r, "b413f47d13ee2fe6c845b2ee141af81de858df4ec549a58b7970bb96645bc8d2")} 505 r.EqualValues(parkedNodes, tree.GetParkedNodes(nil)) 506 507 tree, err = NewTreeBuilder().Build() 508 r.NoError(err) 509 r.NoError(tree.SetParkedNodes(parkedNodes)) 510 r.NoError(tree.AddLeaf([]byte{3})) 511 parkedNodes = [][]byte{{}, {}, decode(r, "7699a4fdd6b8b6908a344f73b8f05c8e1400f7253f544602c442ff5c65504b24")} 512 r.EqualValues(parkedNodes, tree.GetParkedNodes(nil)) 513 } 514 515 func decode(r *require.Assertions, hexString string) []byte { 516 hash, err := hex.DecodeString(hexString) 517 r.NoError(err) 518 return hash 519 } 520 521 // Annotated example explaining how to use this package 522 func ExampleTree() { 523 // First, we create a cache writer with caching policy and layer read-writer factory: 524 cacheWriter := cache.NewWriter(cache.MinHeightPolicy(0), cache.MakeSliceReadWriterFactory()) 525 526 // We then initialize the tree: 527 tree, err := NewTreeBuilder().WithCacheWriter(cacheWriter).Build() 528 if err != nil { 529 fmt.Println("Error while building the tree:", err.Error()) 530 return 531 } 532 533 // We add the leaves one-by-one: 534 for i := uint64(0); i < 8; i++ { 535 err := tree.AddLeaf(NewNodeFromUint64(i)) 536 if err != nil { 537 fmt.Println("Error while adding a leaf:", err.Error()) 538 return 539 } 540 } 541 542 // After adding some leaves we can access the root of the tree: 543 fmt.Println(tree.Root()) // 89a0f1577268cc19b0a39c7a69f804fd140640c699585eb635ebb03c06154cce 544 545 // If we need to generate a proof, we could derive the proven leaves from the root. Here we create a static set: 546 leavesToProve := setOf(0, 4, 7) 547 548 // We get a cache reader from the cache writer: 549 cacheReader, err := cacheWriter.GetReader() 550 if err != nil { 551 fmt.Println("Error while getting cache reader:", err.Error()) 552 return 553 } 554 555 // We pass the cache into GenerateProof along with the set of leaves to prove: 556 sortedProvenLeafIndices, provenLeaves, proof, err := GenerateProof(leavesToProve, cacheReader) 557 if err != nil { 558 fmt.Println("Error while getting generating proof:", err.Error()) 559 return 560 } 561 562 // We now have access to a sorted list of proven leaves, the values of those leaves and the Merkle proof for them: 563 fmt.Println(sortedProvenLeafIndices) // 0 4 7 564 fmt.Println(nodes(provenLeaves)) // 0000 0400 0700 565 fmt.Println(nodes(proof)) // 0100 0094 0500 0600 566 567 // We can validate these values using ValidatePartialTree: 568 valid, err := ValidatePartialTree(sortedProvenLeafIndices, provenLeaves, proof, tree.Root(), GetSha256Parent) 569 if err != nil { 570 fmt.Println("Error while validating proof:", err.Error()) 571 return 572 } 573 fmt.Println(valid) // true 574 575 /*************************************************** 576 | 89a0 | 577 | ba94 633b | 578 | cb59 .0094. bd50 fa67 | 579 | =0000=.0100. 0200 0300 =0400=.0500..0600.=0700= | 580 ***************************************************/ 581 }