diff --git a/Dockerfile b/Dockerfile index 57e415df..97e48c0f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,7 +10,7 @@ # To run container: make docker_image_run # # This will place you into the container where you can run zcashd, zcash-cli, - # lightwalletd ingester, and lightwalletd server etc.. + # lightwalletd server etc.. # # First you need to get zcashd sync to current height on testnet, from outside container: # make docker_img_run_zcashd @@ -18,8 +18,7 @@ # Sometimes you need to manually start zcashd for the first time, from insdie the container: # zcashd -printtoconsole # - # Once the block height is atleast 280,000 you can go ahead and start lightwalletd components - # make docker_img_run_lightwalletd_ingest + # Once the block height is atleast 280,000 you can go ahead and start lightwalletd # make docker_img_run_lightwalletd_insecure_server # # If you need a random bash session in the container, use: @@ -57,7 +56,7 @@ RUN git clone ${LIGHTWALLETD_URL} #ADD . /home RUN cd ./lightwalletd && make -RUN /usr/bin/install -c /home/lightwalletd/ingest /home/lightwalletd/server /usr/bin/ +RUN /usr/bin/install -c /home/lightwalletd/server /usr/bin/ # Setup layer for zcashd and zcash-cli binary FROM golang:1.11 AS zcash_builder @@ -95,8 +94,8 @@ RUN mkdir -p /home/$ZCASHD_USER/.zcash/ && \ USER $ZCASHD_USER WORKDIR /home/$ZCASHD_USER/ -# Use lightwallet server and ingest binaries from prior layer -COPY --from=lightwalletd_base /usr/bin/ingest /usr/bin/server /usr/bin/ +# Use lightwallet server binary from prior layer +COPY --from=lightwalletd_base /usr/bin/server /usr/bin/ COPY --from=zcash_builder /usr/bin/zcashd /usr/bin/zcash-cli /usr/bin/ COPY --from=zcash_builder /root/.zcash-params/ /home/$ZCASHD_USER/.zcash-params/ diff --git a/Makefile b/Makefile index 8dc77391..b1526747 100644 --- a/Makefile +++ b/Makefile @@ -75,7 +75,7 @@ docker_img_stop_zcashd: # Start the lightwalletd server in the zcashdlwd container docker_img_run_lightwalletd_insecure_server: - docker exec -i zcashdlwd server --very-insecure=true --conf-file /home/zcash/.zcash/zcash.conf --db-path /db/sql.db --log-file /logs/server.log --bind-addr 127.0.0.1:18232 + docker exec -i zcashdlwd server --no-tls-very-insecure=true --conf-file /home/zcash/.zcash/zcash.conf --log-file /logs/server.log --bind-addr 127.0.0.1:18232 # Remove and delete ALL images and containers in Docker; assumes containers are stopped docker_remove_all: @@ -98,4 +98,4 @@ install: clean: @echo "clean project..." - #rm -f $(PROJECT_NAME) \ No newline at end of file + #rm -f $(PROJECT_NAME) diff --git a/README.md b/README.md index c78e8cc6..3f73fe53 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ This will build the server binary, where you can use the below commands to confi Assuming you used `make` to build SERVER: ``` -./server --very-insecure=true --conf-file /home/zcash/.zcash/zcash.conf --log-file /logs/server.log --bind-addr 127.0.0.1:18232 +./server --no-tls-very-insecure=true --conf-file /home/zcash/.zcash/zcash.conf --log-file /logs/server.log --bind-addr 127.0.0.1:18232 ``` # Production Usage diff --git a/cmd/server/main.go b/cmd/server/main.go index 0e472284..df09b805 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -194,27 +194,20 @@ func main() { } // Get the sapling activation height from the RPC - saplingHeight, blockHeight, chainName, branchID, err := common.GetSaplingInfo(rpcClient) - if err != nil { - log.WithFields(logrus.Fields{ - "error": err, - }).Warn("Unable to get sapling activation height") - } - + // (this first RPC also verifies that we can communicate with zcashd) + saplingHeight, blockHeight, chainName, branchID := common.GetSaplingInfo(rpcClient, log) log.Info("Got sapling height ", saplingHeight, " chain ", chainName, " branchID ", branchID) // Initialize the cache cache := common.NewBlockCache(opts.cacheSize) - stopChan := make(chan bool, 1) - // Start the block cache importer at cacheSize blocks before current height cacheStart := blockHeight - opts.cacheSize if cacheStart < saplingHeight { cacheStart = saplingHeight } - go common.BlockIngestor(rpcClient, cache, log, stopChan, cacheStart) + go common.BlockIngestor(rpcClient, cache, log, cacheStart) // Compact transaction service initialization service, err := frontend.NewLwdStreamer(rpcClient, cache, log) @@ -244,8 +237,7 @@ func main() { log.WithFields(logrus.Fields{ "signal": s.String(), }).Info("caught signal, stopping gRPC server") - // Stop the block ingestor - stopChan <- true + os.Exit(1) }() log.Infof("Starting gRPC server on %s", opts.bindAddr) diff --git a/common/cache.go b/common/cache.go index 4d9e5a94..a318220b 100644 --- a/common/cache.go +++ b/common/cache.go @@ -37,24 +37,13 @@ func (c *BlockCache) Add(height int, block *walletrpc.CompactBlock) (error, bool c.mutex.Lock() defer c.mutex.Unlock() - if c.FirstBlock == -1 && c.LastBlock == -1 { - // If this is the first block, prep the data structure - c.FirstBlock = height - c.LastBlock = height - 1 - } - - // If we're adding a block in the middle of the cache, remove all - // blocks after it, since this might be a reorg, and we don't want - // Any outdated blocks returned - if height >= c.FirstBlock && height <= c.LastBlock { - for i := height; i <= c.LastBlock; i++ { - delete(c.m, i) - } - c.LastBlock = height - 1 + // If we already have this block or any higher blocks, a reorg + // must have occurred; these must be re-added + for i := height; i <= c.LastBlock; i++ { + delete(c.m, i) } - // Don't allow out-of-order blocks. This is more of a sanity check than anything - // If there is a reorg, then the ingestor needs to handle it. + // Detect reorg, ingestor needs to handle it if c.m[height-1] != nil && !bytes.Equal(block.PrevHash, c.m[height-1].hash) { return nil, true } @@ -72,11 +61,14 @@ func (c *BlockCache) Add(height int, block *walletrpc.CompactBlock) (error, bool } c.LastBlock = height + if c.FirstBlock < 0 || c.FirstBlock > height { + c.FirstBlock = height + } - // If the cache is full, remove the oldest block - if c.LastBlock-c.FirstBlock+1 > c.MaxEntries { + // remove any blocks that are older than the capacity of the cache + for c.FirstBlock <= c.LastBlock-c.MaxEntries { delete(c.m, c.FirstBlock) - c.FirstBlock = c.FirstBlock + 1 + c.FirstBlock++ } return nil, false @@ -86,11 +78,7 @@ func (c *BlockCache) Get(height int) *walletrpc.CompactBlock { c.mutex.RLock() defer c.mutex.RUnlock() - if c.LastBlock == -1 || c.FirstBlock == -1 { - return nil - } - - if height < c.FirstBlock || height > c.LastBlock { + if c.m[height] == nil { return nil } diff --git a/common/common.go b/common/common.go index 2202030a..74182932 100644 --- a/common/common.go +++ b/common/common.go @@ -3,7 +3,6 @@ package common import ( "encoding/hex" "encoding/json" - "fmt" "strconv" "strings" "time" @@ -15,28 +14,34 @@ import ( "github.com/zcash-hackworks/lightwalletd/walletrpc" ) -func GetSaplingInfo(rpcClient *rpcclient.Client) (int, int, string, string, error) { - result, rpcErr := rpcClient.RawRequest("getblockchaininfo", make([]json.RawMessage, 0)) - - var err error - var errCode int64 - - // For some reason, the error responses are not JSON - if rpcErr != nil { - errParts := strings.SplitN(rpcErr.Error(), ":", 2) - errCode, err = strconv.ParseInt(errParts[0], 10, 32) - if err == nil && errCode == -8 { - return -1, -1, "", "", nil - } - return -1, -1, "", "", errors.Wrap(rpcErr, "error requesting block") - } - +func GetSaplingInfo(rpcClient *rpcclient.Client, log *logrus.Entry) (int, int, string, string) { + // This request must succeed or we can't go on; give zcashd time to start up var f interface{} - err = json.Unmarshal(result, &f) - if err != nil { - return -1, -1, "", "", errors.Wrap(err, "error reading JSON response") + retryCount := 0 + for { + result, rpcErr := rpcClient.RawRequest("getblockchaininfo", make([]json.RawMessage, 0)) + if rpcErr == nil { + if retryCount > 0 { + log.Warn("getblockchaininfo RPC successful") + } + err := json.Unmarshal(result, &f) + if err != nil { + log.Fatalf("error parsing JSON getblockchaininfo response: %v", err) + } + break + } + retryCount++ + if retryCount > 10 { + log.WithFields(logrus.Fields{ + "timeouts": retryCount, + }).Fatal("unable to issue getblockchaininfo RPC call to zcashd node") + } + log.WithFields(logrus.Fields{ + "error": rpcErr.Error(), + "retry": retryCount, + }).Warn("error with getblockchaininfo rpc, retrying...") + time.Sleep(time.Duration(10+retryCount*5) * time.Second) // backoff } - chainName := f.(map[string]interface{})["chain"].(string) upgradeJSON := f.(map[string]interface{})["upgrades"] @@ -48,7 +53,7 @@ func GetSaplingInfo(rpcClient *rpcclient.Client) (int, int, string, string, erro consensus := f.(map[string]interface{})["consensus"] branchID := consensus.(map[string]interface{})["nextblock"].(string) - return int(saplingHeight), int(blockHeight), chainName, branchID, nil + return int(saplingHeight), int(blockHeight), chainName, branchID } func getBlockFromRPC(rpcClient *rpcclient.Client, height int) (*walletrpc.CompactBlock, error) { @@ -57,13 +62,10 @@ func getBlockFromRPC(rpcClient *rpcclient.Client, height int) (*walletrpc.Compac params[1] = json.RawMessage("0") result, rpcErr := rpcClient.RawRequest("getblock", params) - var err error - var errCode int64 - // For some reason, the error responses are not JSON if rpcErr != nil { errParts := strings.SplitN(rpcErr.Error(), ":", 2) - errCode, err = strconv.ParseInt(errParts[0], 10, 32) + errCode, err := strconv.ParseInt(errParts[0], 10, 32) // Check to see if we are requesting a height the zcashd doesn't have yet if err == nil && errCode == -8 { return nil, nil @@ -72,7 +74,7 @@ func getBlockFromRPC(rpcClient *rpcclient.Client, height int) (*walletrpc.Compac } var blockDataHex string - err = json.Unmarshal(result, &blockDataHex) + err := json.Unmarshal(result, &blockDataHex) if err != nil { return nil, errors.Wrap(err, "error reading JSON response") } @@ -94,77 +96,61 @@ func getBlockFromRPC(rpcClient *rpcclient.Client, height int) (*walletrpc.Compac return block.ToCompact(), nil } -func BlockIngestor(rpcClient *rpcclient.Client, cache *BlockCache, log *logrus.Entry, - stopChan chan bool, startHeight int) { +func BlockIngestor(rpcClient *rpcclient.Client, cache *BlockCache, log *logrus.Entry, startHeight int) { reorgCount := 0 height := startHeight - timeoutCount := 0 // Start listening for new blocks + retryCount := 0 for { - select { - case <-stopChan: - break - - case <-time.After(15 * time.Second): - for { - if reorgCount > 0 { - height -= 10 - } - - if reorgCount > 10 { - log.Error("Reorg exceeded max of 100 blocks! Help!") - return - } - - block, err := getBlockFromRPC(rpcClient, height) - - if err != nil { + block, err := getBlockFromRPC(rpcClient, height) + if block == nil || err != nil { + if err != nil { + log.WithFields(logrus.Fields{ + "height": height, + "error": err, + }).Warn("error with getblock rpc") + retryCount++ + if retryCount > 10 { log.WithFields(logrus.Fields{ - "height": height, - "error": err, - }).Warn("error with getblock") - - timeoutCount++ - if timeoutCount == 3 { - log.WithFields(logrus.Fields{ - "timeouts": timeoutCount, - }).Warn("unable to issue RPC call to zcashd node 3 times") - break - } + "timeouts": retryCount, + }).Fatal("unable to issue RPC call to zcashd node") } + } + // We're up to date in our polling; wait for a new block + time.Sleep(10 * time.Second) + continue + } + retryCount = 0 - if block == nil { - break - } - if timeoutCount > 0 { - timeoutCount-- - } - - log.Info("Ingestor adding block to cache: ", height) - err, reorg := cache.Add(height, block) - - if err != nil { - log.Error("Error adding block to cache: ", err) - continue - } + log.Info("Ingestor adding block to cache: ", height) + err, reorg := cache.Add(height, block) - // Check for reorgs once we have inital block hash from startup - if reorg { - reorgCount++ + if err != nil { + // It's unclear how this will recover, but we certainly + // don't want to loop full-speed + log.Error("Error adding block to cache: ", err) + time.Sleep(10 * time.Second) + continue + } - log.WithFields(logrus.Fields{ - "height": height, - "hash": displayHash(block.Hash), - "phash": displayHash(block.PrevHash), - "reorg": reorgCount, - }).Warn("REORG") - } else { - reorgCount = 0 - height++ - } + // Check for reorgs once we have inital block hash from startup + if reorg { + height -= 10 + reorgCount++ + if reorgCount > 10 { + log.Fatal("Reorg exceeded max of 100 blocks! Help!") } + log.WithFields(logrus.Fields{ + "height": height, + "hash": displayHash(block.Hash), + "phash": displayHash(block.PrevHash), + "reorg": reorgCount, + }).Warn("REORG") + continue } + reorgCount = 0 + height++ } } @@ -175,18 +161,15 @@ func GetBlock(rpcClient *rpcclient.Client, cache *BlockCache, height int) (*wall return block, nil } - // If a block was not found, make sure user is requesting a historical block - if height > cache.GetLatestBlock() { - return nil, errors.New( - fmt.Sprintf( - "Block requested is newer than latest block. Requested: %d Latest: %d", - height, cache.GetLatestBlock())) - } - + // Not in the cache, ask zcashd block, err := getBlockFromRPC(rpcClient, height) if err != nil { return nil, err } + if block == nil { + // Block height is too large + return nil, errors.New("block requested is newer than latest block") + } return block, nil } diff --git a/frontend/service.go b/frontend/service.go index c1c1dd01..ebbabf25 100644 --- a/frontend/service.go +++ b/frontend/service.go @@ -202,19 +202,12 @@ func (s *LwdStreamer) GetTransaction(ctx context.Context, txf *walletrpc.TxFilte // GetLightdInfo gets the LightWalletD (this server) info func (s *LwdStreamer) GetLightdInfo(ctx context.Context, in *walletrpc.Empty) (*walletrpc.LightdInfo, error) { - saplingHeight, blockHeight, chainName, consensusBranchId, err := common.GetSaplingInfo(s.client) - - if err != nil { - s.log.WithFields(logrus.Fields{ - "error": err, - }).Warn("Unable to get sapling activation height") - return nil, err - } + saplingHeight, blockHeight, chainName, consensusBranchId := common.GetSaplingInfo(s.client, s.log) // TODO these are called Error but they aren't at the moment. // A success will return code 0 and message txhash. return &walletrpc.LightdInfo{ - Version: "0.2.0", + Version: "0.2.1", Vendor: "ECC LightWalletD", TaddrSupport: true, ChainName: chainName,