diff --git a/common/cache.go b/common/cache.go index d4c68d15..ec353c0e 100644 --- a/common/cache.go +++ b/common/cache.go @@ -15,8 +15,8 @@ import ( "path/filepath" "sync" - "github.com/golang/protobuf/proto" "github.com/zcash/lightwalletd/walletrpc" + "google.golang.org/protobuf/proto" ) // BlockCache contains a consecutive set of recent compact blocks in marshalled form. diff --git a/common/common.go b/common/common.go index c8a1a92b..89c273ea 100644 --- a/common/common.go +++ b/common/common.go @@ -349,8 +349,7 @@ func BlockIngestor(c *BlockCache, rep int) { if err != nil { Log.Fatal("bad getbestblockhash return:", err, result) } - lastBestBlockHash := []byte{} - lastBestBlockHash, err = hex.DecodeString(hashHex) + lastBestBlockHash, err := hex.DecodeString(hashHex) if err != nil { Log.Fatal("error decoding getbestblockhash", err, hashHex) } diff --git a/common/common_test.go b/common/common_test.go index 1ff26d3e..d618822e 100644 --- a/common/common_test.go +++ b/common/common_test.go @@ -25,16 +25,10 @@ import ( // be useful across multiple tests. var ( - testT *testing.T - - // The various stub callbacks need to sequence through states - step int - - getblockchaininfoReply []byte - logger = logrus.New() - - blocks [][]byte // four test blocks - + testT *testing.T + step int // The various stub callbacks need to sequence through states + logger = logrus.New() + blocks [][]byte // four test blocks testcache *BlockCache ) @@ -199,11 +193,14 @@ func blockIngestorStub(method string, params []json.RawMessage) (json.RawMessage // height 380640 return blocks[0], nil case 3: + checkSleepMethod(0, 0, "getblock", method) + return []byte("{\"Tx\": [\"00\"]}"), nil + case 4: checkSleepMethod(0, 0, "getbestblockhash", method) // This hash doesn't matter, won't match anything r, _ := json.Marshal("010101") return r, nil - case 4: + case 5: checkSleepMethod(0, 0, "getblock", method) var height string err := json.Unmarshal(params[0], &height) @@ -215,24 +212,27 @@ func blockIngestorStub(method string, params []json.RawMessage) (json.RawMessage } // height 380641 return blocks[1], nil - case 5: + case 6: + checkSleepMethod(0, 0, "getblock", method) + return []byte("{\"Tx\": [\"00\"]}"), nil + case 7: // Return the expected block hash, so we're synced, should // then sleep for 2 seconds, then another getbestblockhash checkSleepMethod(0, 0, "getbestblockhash", method) r, _ := json.Marshal(displayHash(testcache.GetLatestHash())) return r, nil - case 6: + case 8: // Simulate still no new block, still synced, should // sleep for 2 seconds, then another getbestblockhash checkSleepMethod(1, 2, "getbestblockhash", method) r, _ := json.Marshal(displayHash(testcache.GetLatestHash())) return r, nil - case 7: + case 9: // Simulate new block (any non-matching hash will do) checkSleepMethod(2, 4, "getbestblockhash", method) r, _ := json.Marshal("aabb") return r, nil - case 8: + case 10: checkSleepMethod(2, 4, "getblock", method) var height string err := json.Unmarshal(params[0], &height) @@ -244,20 +244,23 @@ func blockIngestorStub(method string, params []json.RawMessage) (json.RawMessage } // height 380642 return blocks[2], nil - case 9: + case 11: + checkSleepMethod(2, 4, "getblock", method) + return []byte("{\"Tx\": [\"00\"]}"), nil + case 12: // Simulate still no new block, still synced, should // sleep for 2 seconds, then another getbestblockhash checkSleepMethod(2, 4, "getbestblockhash", method) r, _ := json.Marshal(displayHash(testcache.GetLatestHash())) return r, nil - case 10: + case 13: // There are 3 blocks in the cache (380640-642), so let's // simulate a 1-block reorg, new version (replacement) of 380642 checkSleepMethod(3, 6, "getbestblockhash", method) // hash doesn't matter, just something that doesn't match r, _ := json.Marshal("4545") return r, nil - case 11: + case 14: // It thinks there may simply be a new block, but we'll say // there is no block at this height (380642 was replaced). checkSleepMethod(3, 6, "getblock", method) @@ -270,13 +273,13 @@ func blockIngestorStub(method string, params []json.RawMessage) (json.RawMessage testT.Fatal("incorrect height requested") } return nil, errors.New("-8: Block height out of range") - case 12: + case 15: // It will re-ask the best hash (let's make no change) checkSleepMethod(3, 6, "getbestblockhash", method) // hash doesn't matter, just something that doesn't match r, _ := json.Marshal("4545") return r, nil - case 13: + case 16: // It should have backed up one block checkSleepMethod(3, 6, "getblock", method) var height string @@ -289,14 +292,17 @@ func blockIngestorStub(method string, params []json.RawMessage) (json.RawMessage } // height 380642 return blocks[2], nil - case 14: + case 17: + checkSleepMethod(3, 6, "getblock", method) + return []byte("{\"Tx\": [\"00\"]}"), nil + case 18: // We're back to the same state as case 9, and this time // we'll make it back up 2 blocks (rather than one) checkSleepMethod(3, 6, "getbestblockhash", method) // XXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXX // hash doesn't matter, just something that doesn't match r, _ := json.Marshal("5656") return r, nil - case 15: + case 19: // It thinks there may simply be a new block, but we'll say // there is no block at this height (380642 was replaced). checkSleepMethod(3, 6, "getblock", method) @@ -309,12 +315,12 @@ func blockIngestorStub(method string, params []json.RawMessage) (json.RawMessage testT.Fatal("incorrect height requested") } return nil, errors.New("-8: Block height out of range") - case 16: + case 20: checkSleepMethod(3, 6, "getbestblockhash", method) // hash doesn't matter, just something that doesn't match r, _ := json.Marshal("5656") return r, nil - case 17: + case 21: // Like case 13, it should have backed up one block, but // this time we'll make it back up one more checkSleepMethod(3, 6, "getblock", method) @@ -327,12 +333,12 @@ func blockIngestorStub(method string, params []json.RawMessage) (json.RawMessage testT.Fatal("incorrect height requested") } return nil, errors.New("-8: Block height out of range") - case 18: + case 22: checkSleepMethod(3, 6, "getbestblockhash", method) // hash doesn't matter, just something that doesn't match r, _ := json.Marshal("5656") return r, nil - case 19: + case 23: // It should have backed up one more checkSleepMethod(3, 6, "getblock", method) var height string @@ -344,6 +350,9 @@ func blockIngestorStub(method string, params []json.RawMessage) (json.RawMessage testT.Fatal("incorrect height requested") } return blocks[1], nil + case 24: + checkSleepMethod(3, 6, "getblock", method) + return []byte("{\"Tx\": [\"00\"]}"), nil } testT.Error("blockIngestorStub called too many times") return nil, nil @@ -357,7 +366,7 @@ func TestBlockIngestor(t *testing.T) { os.RemoveAll(unitTestPath) testcache = NewBlockCache(unitTestPath, unitTestChain, 380640, -1) BlockIngestor(testcache, 11) - if step != 19 { + if step != 24 { t.Error("unexpected final step", step) } step = 0 @@ -389,19 +398,23 @@ func getblockStub(method string, params []json.RawMessage) (json.RawMessage, err // Sunny-day return blocks[0], nil case 2: + return []byte("{\"Tx\": [\"00\"]}"), nil + case 3: if height != "380641" { testT.Error("unexpected height") } // Sunny-day return blocks[1], nil - case 3: + case 4: + return []byte("{\"Tx\": [\"00\"]}"), nil + case 5: if height != "380642" { testT.Error("unexpected height", height) } // Simulate that we're synced (caught up); // this should cause one 10s sleep (then retry). return nil, errors.New("-8: Block height out of range") - case 4: + case 6: if sleepCount != 1 || sleepDuration != 2*time.Second { testT.Error("unexpected sleeps", sleepCount, sleepDuration) } @@ -411,7 +424,7 @@ func getblockStub(method string, params []json.RawMessage) (json.RawMessage, err // Simulate that we're still caught up; this should cause a 1s // wait then a check for reorg to shorter chain (back up one). return nil, errors.New("-8: Block height out of range") - case 5: + case 7: if sleepCount != 1 || sleepDuration != 2*time.Second { testT.Error("unexpected sleeps", sleepCount, sleepDuration) } @@ -422,7 +435,9 @@ func getblockStub(method string, params []json.RawMessage) (json.RawMessage, err // Return the expected block (as normally happens, no actual reorg), // ingestor will immediately re-request the next block (42). return blocks[1], nil - case 6: + case 8: + return []byte("{\"Tx\": [\"00\"]}"), nil + case 9: if sleepCount != 1 || sleepDuration != 2*time.Second { testT.Error("unexpected sleeps", sleepCount, sleepDuration) } @@ -431,7 +446,9 @@ func getblockStub(method string, params []json.RawMessage) (json.RawMessage, err } // Block 42 has now finally appeared, it will immediately ask for 43. return blocks[2], nil - case 7: + case 10: + return []byte("{\"Tx\": [\"00\"]}"), nil + case 11: if sleepCount != 1 || sleepDuration != 2*time.Second { testT.Error("unexpected sleeps", sleepCount, sleepDuration) } @@ -442,7 +459,9 @@ func getblockStub(method string, params []json.RawMessage) (json.RawMessage, err // this causes a 1s sleep and then back up one block (to 42). blocks[3][9]++ // first byte of the prevhash return blocks[3], nil - case 8: + case 12: + return []byte("{\"Tx\": [\"00\"]}"), nil + case 13: blocks[3][9]-- // repair first byte of the prevhash if sleepCount != 1 || sleepDuration != 2*time.Second { testT.Error("unexpected sleeps", sleepCount, sleepDuration) @@ -451,7 +470,9 @@ func getblockStub(method string, params []json.RawMessage) (json.RawMessage, err testT.Error("unexpected height ", height) } return blocks[2], nil - case 9: + case 14: + return []byte("{\"Tx\": [\"00\"]}"), nil + case 15: if sleepCount != 1 || sleepDuration != 2*time.Second { testT.Error("unexpected sleeps", sleepCount, sleepDuration) } @@ -461,7 +482,7 @@ func getblockStub(method string, params []json.RawMessage) (json.RawMessage, err // Instead of returning expected (43), simulate block unmarshal // failure, should cause 10s sleep, retry return nil, nil - case 10: + case 16: if sleepCount != 2 || sleepDuration != 12*time.Second { testT.Error("unexpected sleeps", sleepCount, sleepDuration) } @@ -470,7 +491,9 @@ func getblockStub(method string, params []json.RawMessage) (json.RawMessage, err } // Back to sunny-day return blocks[3], nil - case 11: + case 17: + return []byte("{\"Tx\": [\"00\"]}"), nil + case 18: if sleepCount != 2 || sleepDuration != 12*time.Second { testT.Error("unexpected sleeps", sleepCount, sleepDuration) } @@ -522,7 +545,7 @@ func TestGetBlockRange(t *testing.T) { if err.Error() != "block requested is newer than latest block" { t.Fatal("unexpected error:", err) } - case _ = <-blockChan: + case <-blockChan: t.Fatal("reading height 22 should have failed") } @@ -547,17 +570,23 @@ func getblockStubReverse(method string, params []json.RawMessage) (json.RawMessa // Sunny-day return blocks[2], nil case 2: + return []byte("{\"Tx\": [\"00\"]}"), nil + case 3: if height != "380641" { testT.Error("unexpected height") } // Sunny-day return blocks[1], nil - case 3: + case 4: + return []byte("{\"Tx\": [\"00\"]}"), nil + case 5: if height != "380640" { testT.Error("unexpected height") } // Sunny-day return blocks[0], nil + case 6: + return []byte("{\"Tx\": [\"00\"]}"), nil } testT.Error("getblockStub called too many times") return nil, nil diff --git a/common/darkside.go b/common/darkside.go index 70608d37..b26c9f8f 100644 --- a/common/darkside.go +++ b/common/darkside.go @@ -167,7 +167,7 @@ func DarksideApplyStaged(height int) error { } } if len(state.activeBlocks) == 0 { - return errors.New("No active blocks after applying staged blocks") + return errors.New("no active blocks after applying staged blocks") } // Add staged transactions into blocks. Note we're not trying to diff --git a/common/generatecerts.go b/common/generatecerts.go index 0dddbc99..deb02052 100644 --- a/common/generatecerts.go +++ b/common/generatecerts.go @@ -19,6 +19,9 @@ import ( func GenerateCerts() *tls.Certificate { privKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + Log.Fatal("Failed to generate key") + } publicKey := &privKey.PublicKey serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) diff --git a/frontend/frontend_test.go b/frontend/frontend_test.go index 38199df1..6c04b064 100644 --- a/frontend/frontend_test.go +++ b/frontend/frontend_test.go @@ -115,7 +115,7 @@ func TestGetTransaction(t *testing.T) { if err == nil { testT.Fatal("GetTransaction unexpectedly succeeded") } - if err.Error() != "Please call GetTransaction with txid" { + if err.Error() != "please call GetTransaction with txid" { testT.Fatal("GetTransaction unexpected error message") } if rawtx != nil { @@ -127,7 +127,7 @@ func TestGetTransaction(t *testing.T) { if err == nil { testT.Fatal("GetTransaction unexpectedly succeeded") } - if err.Error() != "Can't GetTransaction with a blockhash+num. Please call GetTransaction with txid" { + if err.Error() != "can't GetTransaction with a blockhash+num, please call GetTransaction with txid" { testT.Fatal("GetTransaction unexpected error message") } if rawtx != nil { @@ -151,7 +151,10 @@ func getblockStub(method string, params []json.RawMessage) (json.RawMessage, err case 1: return blocks[0], nil case 2: - return nil, errors.New("getblock test error") + // verbose mode (getblock height 1), return transaction list + return []byte("{\"Tx\": [\"00\"]}"), nil + case 3: + return nil, errors.New("getblock test error, too many requests") } testT.Fatal("unexpected call to getblockStub") return nil, nil @@ -169,7 +172,7 @@ func TestGetLatestBlock(t *testing.T) { if err == nil { t.Fatal("GetLatestBlock should have failed, empty cache") } - if err.Error() != "Cache is empty. Server is probably not yet ready" { + if err.Error() != "cache is empty, server is probably not yet ready" { t.Fatal("GetLatestBlock incorrect error", err) } if blockID != nil { @@ -288,7 +291,7 @@ func TestGetTaddressTxids(t *testing.T) { if err == nil { t.Fatal("GetTaddressTxids should have failed on bad address, case", i) } - if err.Error() != "Invalid address" { + if err.Error() != "invalid address" { t.Fatal("GetTaddressTxids incorrect error on bad address, case", i) } } @@ -363,7 +366,7 @@ func TestGetBlock(t *testing.T) { if err == nil { t.Fatal("GetBlock should have failed") } - if err.Error() != "GetBlock by Hash is not yet implemented" { + if err.Error() != "gRPC GetBlock by Hash is not yet implemented" { t.Fatal("GetBlock hash unimplemented error message failed") } @@ -525,7 +528,7 @@ func TestNewZRPCFromConf(t *testing.T) { } // can't pass an integer - connCfg, err = connFromConf(10) + _, err = connFromConf(10) if err == nil { t.Fatal("connFromConf unexpected success") } @@ -570,8 +573,8 @@ func TestMempoolFilter(t *testing.T) { } for i := 0; i < len(actual); i++ { if actual[i] != expected[i] { - t.Fatal(fmt.Sprintf("mempool: expected: %s actual: %s", - expected[i], actual[i])) + t.Fatalf("mempool: expected: %s actual: %s", + expected[i], actual[i]) } } // If the exclude list is empty, return the entire mempool. @@ -589,8 +592,8 @@ func TestMempoolFilter(t *testing.T) { } for i := 0; i < len(actual); i++ { if actual[i] != expected[i] { - t.Fatal(fmt.Sprintf("mempool: expected: %s actual: %s", - expected[i], actual[i])) + t.Fatalf("mempool: expected: %s actual: %s", + expected[i], actual[i]) } } diff --git a/frontend/service.go b/frontend/service.go index 7ea7ae79..1a8a034f 100644 --- a/frontend/service.go +++ b/frontend/service.go @@ -50,7 +50,7 @@ func NewDarksideStreamer(cache *common.BlockCache) (walletrpc.DarksideStreamerSe func checkTaddress(taddr string) error { match, err := regexp.Match("\\At[a-zA-Z0-9]{34}\\z", []byte(taddr)) if err != nil || !match { - return errors.New("Invalid address") + return errors.New("invalid address") } return nil } @@ -61,7 +61,7 @@ func (s *lwdStreamer) GetLatestBlock(ctx context.Context, placeholder *walletrpc latestHash := s.cache.GetLatestHash() if latestBlock == -1 { - return nil, errors.New("Cache is empty. Server is probably not yet ready") + return nil, errors.New("cache is empty, server is probably not yet ready") } return &walletrpc.BlockID{Height: uint64(latestBlock), Hash: latestHash}, nil @@ -75,13 +75,13 @@ func (s *lwdStreamer) GetTaddressTxids(addressBlockFilter *walletrpc.Transparent } if addressBlockFilter.Range == nil { - return errors.New("Must specify block range") + return errors.New("must specify block range") } if addressBlockFilter.Range.Start == nil { - return errors.New("Must specify a start block height") + return errors.New("must specify a start block height") } if addressBlockFilter.Range.End == nil { - return errors.New("Must specify an end block height") + return errors.New("must specify an end block height") } params := make([]json.RawMessage, 1) request := &common.ZcashdRpcRequestGetaddresstxids{ @@ -135,7 +135,7 @@ func (s *lwdStreamer) GetBlock(ctx context.Context, id *walletrpc.BlockID) (*wal // Precedence: a hash is more specific than a height. If we have it, use it first. if id.Hash != nil { // TODO: Get block by hash - return nil, errors.New("GetBlock by Hash is not yet implemented") + return nil, errors.New("gRPC GetBlock by Hash is not yet implemented") } cBlock, err := common.GetBlock(s.cache, int(id.Height)) @@ -153,7 +153,7 @@ func (s *lwdStreamer) GetBlockRange(span *walletrpc.BlockRange, resp walletrpc.C blockChan := make(chan *walletrpc.CompactBlock) errChan := make(chan error) if span.Start == nil || span.End == nil { - return errors.New("Must specify start and end heights") + return errors.New("must specify start and end heights") } go common.GetBlockRange(s.cache, blockChan, errChan, int(span.Start.Height), int(span.End.Height)) @@ -237,7 +237,7 @@ func (s *lwdStreamer) GetTreeState(ctx context.Context, id *walletrpc.BlockID) ( func (s *lwdStreamer) GetTransaction(ctx context.Context, txf *walletrpc.TxFilter) (*walletrpc.RawTransaction, error) { if txf.Hash != nil { if len(txf.Hash) != 32 { - return nil, errors.New("Transaction ID has invalid length") + return nil, errors.New("transaction ID has invalid length") } leHashStringJSON, err := json.Marshal(hex.EncodeToString(parser.Reverse(txf.Hash))) if err != nil { @@ -270,9 +270,9 @@ func (s *lwdStreamer) GetTransaction(ctx context.Context, txf *walletrpc.TxFilte } if txf.Block != nil && txf.Block.Hash != nil { - return nil, errors.New("Can't GetTransaction with a blockhash+num. Please call GetTransaction with txid") + return nil, errors.New("can't GetTransaction with a blockhash+num, please call GetTransaction with txid") } - return nil, errors.New("Please call GetTransaction with txid") + return nil, errors.New("please call GetTransaction with txid") } // GetLightdInfo gets the LightWalletD (this server) info, and includes information @@ -292,7 +292,7 @@ func (s *lwdStreamer) SendTransaction(ctx context.Context, rawtx *walletrpc.RawT // Verify rawtx if rawtx == nil || rawtx.Data == nil { - return nil, errors.New("Bad transaction data") + return nil, errors.New("bad transaction data") } // Construct raw JSON-RPC params @@ -311,14 +311,14 @@ func (s *lwdStreamer) SendTransaction(ctx context.Context, rawtx *walletrpc.RawT if rpcErr != nil { errParts := strings.SplitN(rpcErr.Error(), ":", 2) if len(errParts) < 2 { - return nil, errors.New("SendTransaction couldn't parse error code") + return nil, errors.New("sendTransaction couldn't parse error code") } errMsg = strings.TrimSpace(errParts[1]) errCode, err = strconv.ParseInt(errParts[0], 10, 32) if err != nil { // This should never happen. We can't panic here, but it's that class of error. // This is why we need integration testing to work better than regtest currently does. TODO. - return nil, errors.New("SendTransaction couldn't parse error code") + return nil, errors.New("sendTransaction couldn't parse error code") } } else { errMsg = string(result) @@ -401,7 +401,7 @@ var mempoolList []string var lastMempool time.Time func (s *lwdStreamer) GetMempoolTx(exclude *walletrpc.Exclude, resp walletrpc.CompactTxStreamer_GetMempoolTxServer) error { - if time.Now().Sub(lastMempool).Seconds() >= 2 { + if time.Since(lastMempool).Seconds() >= 2 { lastMempool = time.Now() // Refresh our copy of the mempool. params := make([]json.RawMessage, 0) @@ -449,6 +449,9 @@ func (s *lwdStreamer) GetMempoolTx(exclude *walletrpc.Exclude, resp walletrpc.Co } tx := parser.NewTransaction() txdata, err := tx.ParseFromSlice(txBytes) + if err != nil { + return err + } if len(txdata) > 0 { return errors.New("extra data deserializing transaction") } @@ -607,7 +610,7 @@ func (s *lwdStreamer) Ping(ctx context.Context, in *walletrpc.Duration) (*wallet // concurrent threads, which could run the server out of resources, // so only allow if explicitly enabled. if !s.pingEnable { - return nil, errors.New("Ping not enabled, start lightwalletd with --ping-very-insecure") + return nil, errors.New("ping not enabled, start lightwalletd with --ping-very-insecure") } var response walletrpc.PingResponse response.Entry = atomic.AddInt64(&concurrent, 1) @@ -620,12 +623,12 @@ func (s *lwdStreamer) Ping(ctx context.Context, in *walletrpc.Duration) (*wallet func (s *DarksideStreamer) Reset(ctx context.Context, ms *walletrpc.DarksideMetaState) (*walletrpc.Empty, error) { match, err := regexp.Match("\\A[a-fA-F0-9]+\\z", []byte(ms.BranchID)) if err != nil || !match { - return nil, errors.New("Invalid branch ID") + return nil, errors.New("invalid branch ID") } match, err = regexp.Match("\\A[a-zA-Z0-9]+\\z", []byte(ms.ChainName)) if err != nil || !match { - return nil, errors.New("Invalid chain name") + return nil, errors.New("invalid chain name") } err = common.DarksideReset(int(ms.SaplingActivation), ms.BranchID, ms.ChainName) if err != nil { diff --git a/testdata/compact_blocks.json b/testdata/compact_blocks.json index 8b2abe3f..e44f6132 100644 --- a/testdata/compact_blocks.json +++ b/testdata/compact_blocks.json @@ -11,7 +11,7 @@ "hash": "000645395496d1c3110908ee307402eddc5ea44aa122ec3e21500bd5788431c2", "prev": "0005df175723aeb1a766d2fefa2ad91e7e1c2dc3a11977ebcb7119b5f734f24f", "full": "040000004ff234f7b51971cbeb7719a1c32d1c7e1ed92afafed266a7b1ae235717df0500b91aff605bc02e5174b4f3ebf7a1f56d7f9867e863d9c82c12dc4e6757a405d11630c6a555b8fe7bf021772c3bd6896f74bd99684bfc0d35684c07dec83744633b10985b0622231f45002e7cbedd0cfdc19b81d84c33f5050eadbe7599c1f3be7abde31c87aa0000fd4005006988d13e997f0de4960617df32dac6bf627c680705604b61cfe1e499f90fa4f1535605a1b39a7c8f42018e235ef9158aa56f8b70f0f07506d08ab8edcc5d32d66efc2c18d357365c14163864b2d15dd0911fca0659d89916046fc8391f55fe22f1deaad953f7956749662c2cdce82175f69d47f828c6502e5f3b3c33bc07b10540688ae16be560c277f4a9bf2e096496ae482a7216df1fe4e1df9ca783fb055d4c7e8aadf8fce303547f29fadb67e5057d56723efc286e617ab34cc42f6fae7db254901d2c1c258b225c007e1952f5cf9b06ca0be9d9c3ccaffa2803f51b42ceae1ada345a9b1c81ff6d16e27c6d34cc826465943880bca4af5ae003cc0998a56a52c7c7de31eadd66d4ad3734b5bced114e4f03d21834c114e883e9b23a2e890c1dd642d622cfbdfe3dc9eede6a91439041e1769d1d739bf8112471648b0523d0399d12ca752aea1bdf6a10de8d4c00c62c2ddbca631136c2d30b69eb9014fb23297999136fab479a0c598109eff92fa55ea94e5b497fb5d314b58982e1968088bde7c30462ce92e586fc58b5973291841d93d52cf3ac1916af855054134f80fab55102f00fb4efd6bb0d6847839286d43926548f1d92e329f33cf880a2ea17c8c086ce08f0a899e1105fbfd711dcc58e5fd6d406f776f431cbae5fcd705d4d1cfd239b9e1840968bd78e79299de7e07326b430bf19b501e9148ffd8d9bf55099519da31750fcc422f808821aabf407c05e2d037d5531e5b9a75f2e047371820a1a8ebcf79172d937fb82ea2be77e751b771b7cab5a27f90ef9d516b66fd54ab73b1ddfba77f22e7fd98f17476bf847093583d320a184a9358a197cabd03661213492a6282b1d4f958916ccb9edab7a610c1cd09a174a738b34aad5237057d248577681ad586fd10b5a4c15fb1bc6d483395e8457d965d4611ba4223e0d47006d62f643c126f530e704b5d46d3e6ec99c5fa2a50db047277f8d35efbd41f30dc9adb8c5cd36aff1f80594c333b361a703b24673d3c57e5549e758b053360dc67dafae91af30c78bc3bef9584a297a62ed3292133bb575c84b68cee80a4146c35280b5c4eff4afbc22c6e11774b9c767e8cbc43a50cd40e568708ee55515724efea6481cf487d6f511ab46c6a2f5307a45145f461df487b2e9bd9c49785b98d3c45f8c2cbd55b705353c037c1061a556ae932f19a67a36d7de1f00d80b893f55c88ca7135bb10598957041aeed7437dc1811e9820b9337d5a9bf64216c777f99a870b8f33b33201db2f441eee1079db5c66ddc5953b9e9c97bc0d318595dbbe46d1611b97cf1de6fe97e24a93975640b2a545c54045182b2c32425f31b4a1e965e35155c4420fe4bee224f3d40a912698b2ce1b9a7049ffdadae3a4837be4420140b6389c5a16d4d95b24bb51d161c01f5096fdbee0125d2d5f0718d537c6c4f37a3162024b14dcaf0cb1d31e8da43d75b64672171c099ee5d029ab75a84218041715c517f7d3105734fa91d578d6eb54ceb5038cbbbd51eea1e8a785a8b73ff3c98090422335c7b854ced448090ba6e31cefa498b5a1828139a6f7cfcce45d9960083de6766a84572465bac8f102c049b8cdc54ddac46f5866c3676b6403056ad4a25e79b2a80cc21011fbd6983d3e4c3c6db487ade8a03cc8edb0785ecccd6730283f8fb28393ecf32fb8317861b3b100646a19c98825ce53531f2d981198c5a0e5e61e4f658d2e2cf2bd0f03c9350acbf4da840863479d7128e9765a7ab208c113c651dcb385dbb12db046ff8fbcdd05035597786d33ff0654b49b2fda10704a9255e8d9283394f3cb17780f62189be79cf80e9088c0be16bc6d42dc7e8d2c1e21aefd0c6885d989154efd36ead563cf98ef999ea53d09c3efea07cfdca020400008085202f89010000000000000000000000000000000000000000000000000000000000000000ffffffff0603b56a04010cffffffff0210f19a3b000000001976a91403d2f7d8a28eaf20a969cb4d66b389131200f2fb88ac80b2e60e0000000017a914fd79e0c84acac223142cf503dc6afde24acac50e87000000000000000000000000000000000000000400008085202f89000000000000c96a04001027000000000000012bbecf02e9884204deacd3a5bf2a8887a7aa6555bc2e5f50736236b84efbb2bf2a3c543dde10b5b096adaae51ab337c79d7879257045a7c00809eedecbfc093244132eb8e6242bfc572e6204d412125d628136510da7a27b9b1606994b04fc3228f1bc9c3bd663d857131f54e62a83daa1195e99a51e2404764f28dd3302db5a90f41a2bc4ca7c683d912860b6c6f979d43c3a7c5f5df79f09b80ba42c9d6bf0b12236c313ce3c6b29f28038ed7c571f89eeb4c4a3826e9d7b4564ad118eaef483a3ffbf5cf1671d71b29df08bf7b8963e343a224011d5a5283ec6a6fc8164880e80e50d2ba60d8979e8cfd412dbf08f5344746afa64d1cd434ef8e2d371924cf43274743f6da55e93b0e22ab64d059ba6c2ab4dd96ea5976ce21dad904790a08a5bab281b93115dd8357bb89b39b2a6d8e80d0b17290643fefb86f4188996ec68e7632e31a50d84375e8199d7cc81b9db363ac4c684118a18a9874fe893ead945789c509e358b430168000910a00865ba0dc4ce1862d07fe631adbeeda85a08024537c5776c925886ed7e662443c5504212b56c2a23b001f2a278e78aa8b8e38aa2ac2ff0be1d017763d092a76ec6a39c5c1d84da4a08fd972dbd129179665431d71bd2c81733dc51fc14cebdd70c53d2f4ac57278d09ef379a32f6b3477f8a9e8ae40ecf5a4d90fb0d6257766fed5b29bb0a006cf9c20cc4fb1f77c3fe6433c4dc70b5dad0fc7dead0fc84b54018b74f2941d2e0bab01e90e4d2ea031579784f2a79bd555a1784f162b8985be740c255aede37fd255ded20b6982838065e51c9a966f8eff25b99fcb4d526e0dd7874c9c65f908cfc5de8f78082acc6abf3029096ff20711f52f9d59c1fca03c4e465ac8f6b5a9659663b5e3f09d66e93f34612e17a0a88db1709c199edd76934927264697519b253251101b2a839f983f8d1a90bbec1c3879267be14f6e3c736b508bc653cb0f16be10e519c7ac2bb4cc425be8e780f2832737c72b43e0ad20481666bab6592443538b904fb63048835e9e8d62e4d6e2ad292d34b7e526c46ba19004e8d18a58505d2e7662e8c93d391ab5e8d3e2c1e4e31288346299c89b6feab0b1d37cd507b6ca2172aa4a60f1f2ef94934be1af83db7ac2142aba794f20fd35c43461e44b8b45c276a7966c15c4beb4c61fa6879c9e650541e90f3453b48af63c1cec143a6fb236b709ecfcd9a015da46b376021668637e4132c398b4709cea784fdafe9b68272ac268a176c5bdeb011fc45e554e3c8e7a0ea743f19d80ecbd1443e2a0f7178538c359cfa7e9b597d5b7d6d369092e8870f7dd48c4362a6b3fac9a9b338c684d1f19991295b069cdfae7a0c57fe03b5491adf252077a1a97053567c7ba624cfe0bb7482e512b4db5449b237c84a68cb198bad17522c0305897760619e2d637bb9d4d529586713d8058314a347fac5edea6a9c01077fa5775a76e96ac5c2c24ffd4fe4bc727278dc16945c4eabb3496d03e596c1aadd7d587bdb149937b179fe7ab54fafd9f91f3574178dd1549254764589fa39e5a1bd515c688694678d2ece174c778440d96770cc5e3ed9a234c43880d234d52d17cf645c5babd8c06479a1d01b1c65da5414d28081091ad7f5981ace80de78325fb19b835c9ed5fba6344141ac7d505b97bfdd77b39397bcb6c7a3412d31edda61436e7af90e9539b3f8cb9cf2234f229dfc5567dc346a3d3489389e8b0b31643eff4281c735a55799e012843c5f3e43777a3112d921c791de62f2dde1a9240011de6ce69c9152b61030838a0da722a76f42ab26b002d8d52cd18a7e2b2801af33ac6a806ebb75cb6352986ebf1c12f15c1ce14acbbaf45fa07547f6d349e60399424596027dc1c62739d2d09076264f0c85e63ceb2da927ced65811e0e42f003ccb010a2e9eca1b0438d46fb5f40a90ffeda68a5c4ec45b8ace240590cad9adfc131d96b8f8ebb69c63df8c541b19510e241951ff9059ccce9a9ec2bd815de5eab071099dff1cafb9d6ddde020dfbcbeb6946be9d7679dd9f57ca4f8dca08dbbd34864347cbbc230d39d90af8ae2a23db7220f12cba958d197f45e084e24819bd4a2df52c911ba9b115d3669f94240d2a62fbf05f1c0b7842903d7af57b8820608c02e6606d3a3b684d7a5b9337285509af4f3d2902ad44ef37471c14890e5d0c3e02562349ed722a1504bdb4402d3ab85f4f123d8fdec98daad20d7d2c8056bc9c6438819db884af86cdcbca754236366aa6f1adff93bf7d09d0875a0fd1b0fe64dc7cc49187e19a5de6ef33e1e0f90e86b21b14cce8394616e17c1a40e3b0e4c84e1ffe668b140a32276c844085f5a446da618c7a8b7bbba4b804c40d2818d6b10c0064f0da847903669f9a1f8f1c345f3d0e1a95cfd4203e340aa9b7c4f2a6e7dbd2e9085acfb11bf9736454d96b7ecd030c79d635b72ccae5a87e008b940ff6e09d7797ff106958d671a9a005fdf0f972f5a1e1aaa407530f6165d35cb0d04eb11cc212e5442b94b5490d0676b354ea2299dfbd802725983eaeebd7ce48da283905ef2985ff3967144aa5232f742bad7a798462e5b4e098c21193f1c8967311a773f8342fec92030d0a11c9c60eb9a1d8d5f383bcf2732024673e1dd51c3ddf941f3e3af5ea0315433ebf714672ce4482816c369570c5a47f0f8b3895c3e8d5c757fa8f47b8136a9de4f1a07fec28c5ddec4fd5fb2f98e87863a32420dea5024a4b989639cc46f33194fe7027d939c3e382b69d4521dc592025579c5524642e365b6f7be30af8658c5fab6e3ef8d7bbb395017391c34d6371b119c0cb4301db4fe0f19e6143af9747149ebbb1b8a990c2d56e1ee600e44c4b5daaee1036186321d5474b2e8342a6169241a13c6dacd7304ae8eff41279c797242a42f986267600213ee54747dc2497d6908f3c1d74be8b3d6cde9db799aa2bd0f40d90675f36d2e6474a2a58d3b7d00f8e431a8e7c255f0aa386fe24b4ace7422c90bfab42f24a7eaf77e9379866e3e2c694ab6802e0908c5b13f725037b53e358f292fc0b98e3deca31161f0ab19a0bdbb6164b8505343709634b22bd4de37dcdc610023538ae44dd479d34fde3aada60cb23dc6adb87750b72e00cc141e74ff6cba2c13ab5befc3eb960b04f27c179def5b6308612a7693f99ebe3c603cf9d198b979bf998662ec5c1e3dc6e106ff241ad17168a10b89ff770b3cf43dd3c28c7f5005cdb070a57c72832655fac7243034fa51f5bf8f91e1145a55017cf276d408f36b0f719c5448995a4e3e178b003c8fc12daaad0d946f5703149ae83cec332e807", - "compact": "10b5d5111a20c2318478d50b50213eec22a14aa45edced027430ee080911c3d196543945060022204ff234f7b51971cbeb7719a1c32d1c7e1ed92afafed266a7b1ae235717df050028bba0e0dc053ac00208011220b3f18eb6f495a265142e58f7dc7d5a8cccba77f3e262ccacf5f5e3ae6cb4917122220a2044132eb8e6242bfc572e6204d412125d628136510da7a27b9b1606994b04fc322a7a0a20a2ac2ff0be1d017763d092a76ec6a39c5c1d84da4a08fd972dbd1291796654311220d71bd2c81733dc51fc14cebdd70c53d2f4ac57278d09ef379a32f6b3477f8a9e1a348ae40ecf5a4d90fb0d6257766fed5b29bb0a006cf9c20cc4fb1f77c3fe6433c4dc70b5dad0fc7dead0fc84b54018b74f2941d2e02a7a0a20d46fb5f40a90ffeda68a5c4ec45b8ace240590cad9adfc131d96b8f8ebb69c631220df8c541b19510e241951ff9059ccce9a9ec2bd815de5eab071099dff1cafb9d61a34ddde020dfbcbeb6946be9d7679dd9f57ca4f8dca08dbbd34864347cbbc230d39d90af8ae2a23db7220f12cba958d197f45e084e2" + "compact": "10b5d5111a20c2318478d50b50213eec22a14aa45edced027430ee080911c3d196543945060022204ff234f7b51971cbeb7719a1c32d1c7e1ed92afafed266a7b1ae235717df050028bba0e0dc053a9e02080122220a2044132eb8e6242bfc572e6204d412125d628136510da7a27b9b1606994b04fc322a7a0a20a2ac2ff0be1d017763d092a76ec6a39c5c1d84da4a08fd972dbd1291796654311220d71bd2c81733dc51fc14cebdd70c53d2f4ac57278d09ef379a32f6b3477f8a9e1a348ae40ecf5a4d90fb0d6257766fed5b29bb0a006cf9c20cc4fb1f77c3fe6433c4dc70b5dad0fc7dead0fc84b54018b74f2941d2e02a7a0a20d46fb5f40a90ffeda68a5c4ec45b8ace240590cad9adfc131d96b8f8ebb69c631220df8c541b19510e241951ff9059ccce9a9ec2bd815de5eab071099dff1cafb9d61a34ddde020dfbcbeb6946be9d7679dd9f57ca4f8dca08dbbd34864347cbbc230d39d90af8ae2a23db7220f12cba958d197f45e084e2" }, { "block": 289462, @@ -39,6 +39,6 @@ "hash": "0008b8f322d97b873b4cb777119da821c358d9d5635b32b46593fe6fa424f196", "prev": "0009c34ed4923e1878ff25cd91033036c9caed094ec4e52581810c16202bbb77", "full": "0400000077bb2b20160c818125e5c44e09edcac936300391cd25ff78183e92d44ec309009062581bd8d6d60ecf6abe9511f0f691b59e2f7e54a14bbadd2d6429e3c6913cd630b8f176589fe56e1d77e450eedca615c8355115a8ae2d81c309eab17e22126011985b8bb1211f0a00ef46a149b52bfc0e797b2d2a0d4177c84767443d3220ec0631579cd20000fd400500827a3e6d996ec4d29d51a893a7df55753fd77ea6160a66880f2dc121ed5c1a131d6b323a98a61e87ea1b78f4e53b10d814afbc843de0d5fa4679d1b6fb0f36f3f52798f111198d60a503f87b81eea06b97e9ed051d07cb008b6965736321e8cc1a00de3ebef2dc560ed35ba073474ccb27b19461a7e253e7a4fc1e46601a72611a5b16446f6ce82336ee3b8394f58f8f1df7242fbc9befa676339041626dfd683504feb6d0476c014a88583db4521db0ad65312941e7026764197d0f0427789c1e6036839f0352c826d35765c909f6ec1c0b523c436a26b9eb83a744ffb0e8956589a61758da3681bdcc0c56f15ef1a5e533dcdc19b387911d626513a71cff8c95977b6a3ad480c3ff016e6f08ffaf68216ace20da720a3fa66fa5ce8ef4ad1d75bdf92f2825f24fa3f09317b7925d874553e15b15e1321245e15e4fff9445181a85e2d9362ffce9d102542237ef1c0144c7621e41047dc05618da5e4ea2aa3f9138bb870727f94c8fd9648f6cfb238e1956c3edfa80357ef6177d73991d8fc20f08daf4e0e8baa9dd9f77d7e79e1f5a16991f4864feccb4a5cd2ffa3319e359b1e5580a1ea1932b67de59cf58284b514c071e8b64bb7ebe115f2bbedd11a5ff06f2c6a8db7fedae997b1ab1e7261c47907254d498e6b6b39c99b049fdef25b78562474555380f52ad15fb63bac721f87db6beebf9c284082559f35086153da3cba30fa165a174e4345343515204cc6cf5172c53ea02c602f13373c98d9e8e441d0f71dc46a665aaa5f780958c05e211f6664313b12f3869a371bb19d54f7a9a060fe0b22b059cd0ba39fa1113d895a044f18eef01d248bca70044c94d390d7e22491ddeb9f45edfa5a3c42c6ce5d152d85479c54a1c50036489976d9f4d3e1319f1aa9dce3bc4d83cd3268457fd65378f15dc32f32448509feeba713e81f902578b99f95ecf778e7c40b20433184df0a979ce8c075d2cf17ac38ef9a1f4c1330cab1ec608d7b98bba183afc8d391bef31fa81b3178c3e2e617db73f3947201c466b8dcbd214c759c2b3b31c9cbad067dc8ac712624ec1328e8901fe6f5831f242c6fecfc7df4e4334ccdf12f4d3df4111f6536c4d4162569c8e596352319a628d62e67fdbe4bc33cffd3cd3bedbf63a66e93e43dfd63eb107e7b42f04ed484320e5d05bfd8c3b0370fdc49fecdeabacd374d88168c97da3ecd6b117089716b283a11fb56b909194d3c13d09a6b613af1d0544108f63839ea55e2f328c0bdc1506ae9558578b178c2bc38bc6e9d5c201b20622deafa12376fffda50b384d038d43ffe291070273d71688cd8b893264f13e761afce7e088f99549a4453840ad56da305b0e6820d612b20d68cbcb81fef9054074d31be9cfdfe7e16bd32f317b1d54e116b62b87efe8356f490affbe74046ac36f31441ee11839c3ad95ce44c2050e1e8a59142a696029a0c975fa16880f2c46e512ba28399bfb060856836ef3f141a2dcb210ab95ee2e507519eb6709bf6db40bc842b85bbe60e6803d12597f073ca81e081ffb93ed8acdd51b9ca497eccca771325a36bc4a3f1cad4744282aa7ab4964176b332506f827ffc3d6248b0bbd085a3e43ceb8c8aa01fca85b1ca9de88db26a9a47192df00211003d6c6a2bd1eb1e1fa5d6cd0062792ae039e01fb0da43073090c47b0aa661cb5f21850b4ebe3aa1ecf8de032ffa89f89558625fd9a0c15f112036da19d15ca1942e021d45f59742f2cdfde77505750c3692fddcca4dc1f4f70313f4cebdab415077ee49863cc3a8aaa2381246db80051b3854e42b93b063f92bc2141eb2798b59de9c29c2a39fcf4b7e2081df56c2a5eeb0b3c3245724ed41996b07c18289733df3765d055df572caea686d1ebf3d299a499dc60020400008085202f89010000000000000000000000000000000000000000000000000000000000000000ffffffff0603b96a040102ffffffff0210f19a3b000000001976a9143b2d60c6d3d8a18583de8d723dc0036d544b0e8488ac80b2e60e0000000017a914fd79e0c84acac223142cf503dc6afde24acac50e87000000000000000000000000000000000000000400008085202f89000000000000cc6a0400102700000000000001630ad5032d5648064da4cf53c003ed8fe71b1be3ab26c5993ff4185f621d57681630c6a555b8fe7bf021772c3bd6896f74bd99684bfc0d35684c07dec837446347c023875cdda8633c4dfc209f9a5d6787106f63fcb4a341c5301bb229cf4d2f3d507e00025128ddc1f6b2d91b8d552b1984a3d972a960dda4e2d93c26a5a25aaacce61ae7c00dfa9f56918dec51978b0981cbcdedae489c276a8b390bab530bbec805f5fdc7abbe2284c8203d5d41fb8e4f9f671e51f05aa333aedb02b183ec15970dfb52b409593489dd30965593fcba24edbd9d5c9ac431b2e106c265a3f91541d2d72949bec96e94aa189c0414e3fa06670024f537d77b953bca0c9d666ff78f0bbb1d37cd62e2a9bc31bbf37b2eaa84787dbda458ac7f92167656b635ef797d46385b42c733c780f6c0be13408c5cc06b9cf4abdc1aef5d11e89476a8f9db986f35e2aa1b900a1cb0e9fd0b5ee8fc70be345d74e1eb293716e6dc640abc3f7a8d98375296700590b4d0f2ad4f5cb3eef75532ba47e0bd42630dd2308e010242c7688bc516fe883e7e027e6f80f15c9b7eef15d642d3d69e2eef8bb0c3d05553771c0f7a6ca726ead81d3fa0fd06a1cc8583999760f0bdbcdca8a0fe99bf399d2034b583ffe8ec4d906702902a6906377218c70f3c0158b8ae363ec4f6fc9694eed9a250041474562ac2787376614e2dcafe7729ba6384ec5890d77e05a23d22f83fcf342efcb1c3860525a3fe39b6e83c6b2f2ffa8b5c63ab47e07666c83c215e32f862d0ea427d9e484658053fed881744b1e2797d254ea3593ca96f6ec66868524ec3e26ccf5a6fc12b54412fd6cb35a7e663421382435acc18f1b279d2880d898cc1f00401bb34812ba5b1c9a2ef96cca0894b3762939a10b8f15523c46a44159fffe3954f4b66475b624f726feb364b952696cd0e3a0a5b522e69c2aa7c4371d392c365a32a6086a6fb752893bbfbfaf5095567e47a3c5e50baf7a00b32381a28ce9e4e38a113a449788be08d742bb3c14264282680f6255ff74063af37dca5d3f7b6ebb1695d30d6b939035d590e44d55678a512ff2ee47526eb3150a6b4e399647a288a74362aa460e17e5d2ef6a0e9c5dd8f66a9af6276ef39de8c45b6c65b345fe14a0f2694983958f7d45179db2c661cf983d88351798084b49f08ea504aef3e51125fb89b76c81bbebffce381985ed4e45d4b4a68562f4bd3769b1c2744c0c1d188a35e2c204a22fc13442e36d802825ab8e2efafa241ef8b39aab41450d6d0d9cef754d8643312bf1fb8b134852cd038f0751566054f307424795186a1990827c75cb430ab7a1d1f67b7bff918ddd27e3b32743fd6cb02b69087a81033d2b3c0a1b0575e3e34b330c81974bf9861f27f10605eb60ab7545ff97ed7a0111e13586b31d693e962b9efd31b6d889b8c2f34bbd6ca4acc9b94bc0066bc9a78d3974a8ed84081b0c7c151579598a9633b298bdceb3b420f79ceef668d31b2144f9ec9464d54bbcc489018713327d4bdad2dafe4a29d025650091da5c6ccc179086cce95448d82be410ceb0a62674e67af1162db99ce2520c690c257dd0306960891fbeb469df7ec9616e03d7af442888873db5b0b93ab5892704541e6aa4fd2ac1c025dc180c0a3ca12a3b72cb728e88345860577f7ffc51a8ea0067781aa12a4ebb156e940403bc672de25909f96f83e2aaa1fb03d70569613d19acdd44404f6d2a0efcbd3f39df91e7ce4e037eeb30d94f0eaebacbb78faaf4963e6e8c85090032496d1e2abbba461c8571817e3e51aa6bce4a9934e7b9f7b220fa139d1828977f0f17d383fc5505087883d1a41afc6d1e09a7eb568b3881fd5abbdc333a09165c58b8db8d4d767d3eb8c8618040f533ed8baec0d01d2969c1c676ee50ec6622bf0f764cb12ec1c66e189c8bdbe5db1c899c2556040df204a67e6549327c520db8adca1b1a0b2f71d60923dd9b445e0641930a344f802387f29b8078698f3d81aa7a554189c6382926fdf6c0b416fc53006546990ec2708f4fcab680ab582c308bdfef5eec949dbd381358e94f1f11a994ba2283b2e2d6c85c4a33a2c08f815a68ee42b27bacf7255212c186dd2873034cfbc09e586af4860a99e2f734a15378cb3a8295881bbd2d73c4f9ab7f43c8a245dd96c4a48517207d825469c161b633491b3f92b522296d1ed99b9c7b732842e4befe2904f0080103e99d73cf0ca98c96a8529dc5d36ee9aa2a43dc3752f856599ca9824cf866e8b1ccccfe9faf4bd017a14272b164cb86d1d3764f853f20cf4e504f550cea6ae30b19d25f91fb02e700e5e57c2ae1c18bdcd8a7de001e348cee633f55bc07528bdb3840cfed6d912dd2f4ace84c8588453cd15b3c0b7781303f1bf7f783bb17528782a5bd101989f3661bd8cab8330c81c77d55968e9ac57c3b9cec14ddb1bde277af34ebf5c1cb9e6bd507afb88f121cb53c5df6249f3001b4044356329182bfeae056ccc10533dcff9e22000358a7d0b6e7746dd301410028df6fab8262ac080c087d1c289ddf42ccf2b980d6d04dfd9130a5826434174b775dfa361c519716aa7a9279ce844543309cf92841b1e93fac3010ff420392ad748315db76f0e072b8a6655490c3ab1da6b1ab935033575d51ca002807554b86aee8ec8a5fd7446709891cc079e3c98873f4825c3b9a9bf9a43e61169b3112343bda5b5608e6530c2cba8f31c3cca7396478562d7be45da72b9cac1ce757d263884c1616b31a478d8d8aa2eba15139abdd307f55e9d597d6a9ce17cb0351e573f35429dd10843883f39f4d6be6bd6b71779ac80a1e3a236c7f5e71989440f1a614ff6269642af874b504bba295f1eef47f435d3d95fe913a19ec47732b1c4cf974a12d259ec722b444978d7ca4596d73b3b2c60d4c3dd9c831595889fa2133e99279b3307374c1f9e57f430827330c8bc8ab181021d285fc2662da517c670fc2a910b791e4af4144c834709f1682de71905482970400152ae6e9e2e51efd75f93a498a585f397640a3171148ed4543a3024dc8e811d2fdf781e49babeadc41fcd33177099bc93baea144f0f52ad44db787401013a9da2ef15a5f78560e6d3fd89781f758eb8d348d1b4fe160741280fa7019aff9dbeb7d56456bff4faa345de2484c5809ef845754ea5e423978bf4178936ac239b00c3077842fc4301583684ae7376cfb868ee915acde84fe0e2cd5d68708500ec5966647cf2fcdbf00ea0083826d32279fe667c5aaf46f2c539dddd9ce8d60bd4cf46debae7e6d507d8695e208bdef4d4c546dc37240c8f92ff8a6acebb5b03", - "compact": "10b9d5111a2096f124a46ffe9365b4325b63d5d958c321a89d1177b74c3b877bd922f3b80800222077bb2b20160c818125e5c44e09edcac936300391cd25ff78183e92d44ec3090028e0a2e0dc053ac0020801122000d55fcd89706a88a673d73dc17a799b8aa94a1af42ffb9d7ef9152d28c0c41e22220a2047c023875cdda8633c4dfc209f9a5d6787106f63fcb4a341c5301bb229cf4d2f2a7a0a2053771c0f7a6ca726ead81d3fa0fd06a1cc8583999760f0bdbcdca8a0fe99bf3912209d2034b583ffe8ec4d906702902a6906377218c70f3c0158b8ae363ec4f6fc961a3494eed9a250041474562ac2787376614e2dcafe7729ba6384ec5890d77e05a23d22f83fcf342efcb1c3860525a3fe39b6e83c6b2f2a7a0a20b1c899c2556040df204a67e6549327c520db8adca1b1a0b2f71d60923dd9b4451220e0641930a344f802387f29b8078698f3d81aa7a554189c6382926fdf6c0b416f1a34c53006546990ec2708f4fcab680ab582c308bdfef5eec949dbd381358e94f1f11a994ba2283b2e2d6c85c4a33a2c08f815a68ee4" + "compact": "10b9d5111a2096f124a46ffe9365b4325b63d5d958c321a89d1177b74c3b877bd922f3b80800222077bb2b20160c818125e5c44e09edcac936300391cd25ff78183e92d44ec3090028e0a2e0dc053a9e02080122220a2047c023875cdda8633c4dfc209f9a5d6787106f63fcb4a341c5301bb229cf4d2f2a7a0a2053771c0f7a6ca726ead81d3fa0fd06a1cc8583999760f0bdbcdca8a0fe99bf3912209d2034b583ffe8ec4d906702902a6906377218c70f3c0158b8ae363ec4f6fc961a3494eed9a250041474562ac2787376614e2dcafe7729ba6384ec5890d77e05a23d22f83fcf342efcb1c3860525a3fe39b6e83c6b2f2a7a0a20b1c899c2556040df204a67e6549327c520db8adca1b1a0b2f71d60923dd9b4451220e0641930a344f802387f29b8078698f3d81aa7a554189c6382926fdf6c0b416f1a34c53006546990ec2708f4fcab680ab582c308bdfef5eec949dbd381358e94f1f11a994ba2283b2e2d6c85c4a33a2c08f815a68ee4" } ]