diff --git a/common/common.go b/common/common.go index ba57fca..24172b4 100644 --- a/common/common.go +++ b/common/common.go @@ -38,25 +38,32 @@ func GetSaplingInfo(rpcClient *rpcclient.Client) (int, int, string, string, int, return -1, -1, "", "", -1, -1, -1, errors.Wrap(err, "error reading JSON response") } - chainName := f.(map[string]interface{})["chain"].(string) + // Assert the top-level object once, then read every field with the comma-ok + // form. These run on the block-ingestor goroutine, and nothing in this + // daemon or in grpc-go v1.24.0 recovers a panic, so one unexpected response + // shape would take the whole process down rather than fail one call. + fmap, ok := f.(map[string]interface{}) + if !ok { + return -1, -1, "", "", -1, -1, -1, errors.New("getblockchaininfo: unexpected response shape") + } + chainName, _ := fmap["chain"].(string) // DragonX has Sapling active from block 1 but sets NO_ACTIVATION_HEIGHT in // chainparams, so dragonxd omits it from the upgrades map. Fall back to // height 1 when the key is absent. saplingHeight := float64(1) - upgradeJSON, ok := f.(map[string]interface{})["upgrades"] - if ok { - if upgradesMap, ok := upgradeJSON.(map[string]interface{}); ok { - if saplingJSON, ok := upgradesMap["76b809bb"]; ok { - saplingHeight = saplingJSON.(map[string]interface{})["activationheight"].(float64) + if upgradesMap, ok := fmap["upgrades"].(map[string]interface{}); ok { + if saplingJSON, ok := upgradesMap["76b809bb"].(map[string]interface{}); ok { + if h, ok := saplingJSON["activationheight"].(float64); ok { + saplingHeight = h } } } - blockHeight := f.(map[string]interface{})["headers"].(float64) - difficulty := f.(map[string]interface{})["difficulty"].(float64) - longestchain := f.(map[string]interface{})["longestchain"].(float64) - notarized := f.(map[string]interface{})["notarized"].(float64) + blockHeight, _ := fmap["headers"].(float64) + difficulty, _ := fmap["difficulty"].(float64) + longestchain, _ := fmap["longestchain"].(float64) + notarized, _ := fmap["notarized"].(float64) // DragonX always uses Sapling consensus rules but CurrentEpochBranchId() // returns Sprout (0) for full nodes because the activation heights are diff --git a/frontend/service.go b/frontend/service.go index 280d9e4..10f932c 100644 --- a/frontend/service.go +++ b/frontend/service.go @@ -293,7 +293,26 @@ func (s *SqlStreamer) GetTransaction(ctx context.Context, txf *walletrpc.TxFilte if err != nil { return nil, err } - txHeight = txinfo.(map[string]interface{})["height"].(float64) + // dragonxd emits "height" only for a transaction that is in a block: + // rawtransaction.cpp puts it inside `if (!hashBlock.IsNull())`. Every + // mempool transaction therefore comes back WITHOUT the key, and an + // unchecked assertion on the missing value panics -- which, with no + // recover() anywhere in grpc-go v1.24.0 or in this daemon, kills the + // whole process and every wallet connected to it. Any client can reach + // this by broadcasting a transaction and immediately asking for it, and + // GetMempoolStream hands out unconfirmed txids by design. + // + // Report an unconfirmed transaction as tip+1, matching what + // GetMempoolStream already advertises (mempool.go:109). + txmap, ok := txinfo.(map[string]interface{}) + if !ok { + return nil, errors.New("getrawtransaction: unexpected response shape") + } + if h, ok := txmap["height"].(float64); ok { + txHeight = h + } else { + txHeight = float64(s.cache.GetLatestBlock() + 1) + } return &walletrpc.RawTransaction{Data: txBytes, Height: uint64(txHeight)}, nil }