# Miscellaneous Features

Jigu comes preconfigured with some sensible defaults, but you may want to override behavior if you find that Jigu is doing something that is not compatible with your process flow.

WARNING

The below features are quite advanced and messing around with them may lead to unexpected behavior. Use with caution.

# LCD Middlewares

You can alter how requests are made and responses are handled to the LCD server, or add your own side-effects during request generation and response handling.

# Request Middlewares

Request middlewares determine how the request is built, and mainly involve preparing HTTP headers. The goal is to transform an LcdRequest by changing its URL, Method, or Keyword Arguments (which get turned into HTTP headers).

# from jigu.client.lcd.middlewares

def set_timeout(client, request: LcdRequest):
    kwargs = request.kwargs
    kwargs["timeout"] = kwargs.get("timeout", client.timeout)
    return LcdRequest(request.method, request.url, kwargs)


def set_query(client, request: LcdRequest):
    """Add `params` to GET requests to make queries, and `data` to POST requests for
    submitting JSON data. Applies serialization strategy to JSON post requests."""

    method = request.method
    kwargs = request.kwargs
    kwargs["data"] = kwargs.get("data", None)
    kwargs["headers"] = kwargs.get("headers", {})

    if kwargs["data"] and method == "get":
        kwargs["params"] = kwargs["data"]
        del kwargs["data"]

    if method == "post":
        kwargs["headers"]["content-type"] = "application/json"
        kwargs["data"] = serialize_to_json(kwargs["data"])  # apply custom serialization
    return LcdRequest(method, request.url, kwargs)

# Response Middlewares

Response middlewares get activated after a response has been received from the LCD. Mainly, they serve to parse errors and raise more specific errors in the Terra domain.

# from jigu.client.lcd.middlewares

def handle_codespace_errors(client, response: requests.Response):
    """Try to extract more specific codespace errors."""
    error_type = str(response.status_code)[0]
    if error_type in ["4", "5"]:
        try:
            res = response.json()
            error = res.get("error", "")
            if isinstance(error, dict):
                error_msg = error["message"]
            else:
                error_msg = error
        except (ValueError, TypeError, KeyError):
            error_msg = response.content
        try:
            e = json.loads(error_msg)
            if isinstance(e, dict) and "codespace" in e:
                raise get_codespace_error(e["codespace"], e["code"], e["message"])
        except json.decoder.JSONDecodeError:
            pass  # continue normally
    return response


def handle_general_errors(client, response: requests.Response):
    """Handles 4xx and 5xx errors. Checks first if they originally from an RPC error."""
    error_type = str(response.status_code)[0]
    if error_type in ["4", "5"]:
        try:
            res = response.json()
            error = res.get("error", {})
            if isinstance(error, dict):
                error_msg = error["message"]
            else:
                error_msg = error
        except (ValueError, TypeError, KeyError):
            error_msg = response.content
        match = re.match(r"^.*RPC error (\-?\d+) - (.*)$", str(error_msg))
        if match:
            raise get_rpc_error(int(match.group(1)), match.group(2), response.request)
        if error_type == "4":
            raise BadRequest(response.status_code, error_msg, response.request)
        if error_type == "5":
            raise LcdInternalError(response.status_code, error_msg, response.request)
    return response

# Installing your own LCD middleware

You can alter the middleware stack by adding your middleware before, in the middle, or after the existing middlewares. You can even replace the entire default middleware stack to customize Jigu's default request-building and response error-handling behavior.

# use middleware at the end
terra.lcd.request_middlewares.append(your_req_middleware)

# use middleware in the begining
terra.lcd.response_middlewares.insert(0, your_req_middleware)

# replace the middlewares altogether
terra.lcd.response_middlewares = [mw1, mw2, ...]

# LCD and WebSocket Clients

The Terra instance holds 2 clients that are used to make and manage requests to the LCD and RPC WebSocket servers, respectively.

# Changing LCD timeout settings

You can adjust the tolerated time that a single request is allowed to take.

terra.lcd.timeout = 20 # if no response after 20 seconds, raise IOError

# Use multiple threads for LCD

The LCD client can be configured to use multiple threads, which is useful for fetching TxInfo in parallel for the default behavior of the block transformer.

terra.lcd.threads = 40 # use 40 threads to fetch TxInfos

# Changing the LCD / WS URL

If you need to change your LCD / WS URL during runtime, you don't need to create a new Terra instance.

terra.lcd.url = "<new-lcd-url>"
terra.ws.url = "<new-ws-url>"

# Using an SSL context for WebSockets

If you are using a wss:// endpoint for WebSockets, you can provide an SSLContext for managing settings and certificates.

terra.ws.ssl_context = SSLContext(...)

# Block Transformer

By default, when you fetch a block via the Terra instance, the transaction data is hashed and then looked up with terra.tx_info. This means that if you are fetching a block with 47 transactions, after fetching the initial block data, the Terra instance will automatically look up each of the 47 transactions and populate the block with a TxInfosQuery over the ensemble transaction data.

This is because it is currently difficult to decode the Amino-encoded StdTx directly within Python. We are currently working on a Python solution to remedy this, but if you need to replace this standard behavior, you can do so by altering the block transformer settings.

A block transformer is a post-procesing function that is applied to the block after it is fetched.

# Disabling the block transformer

You can shut off block post-processing altogether by turning it off:

terra.blocks.transform_after_fetch = False

# Writing your own block transformer

You can easily replace the default block transformer by setting your own. The transformer function accepts one argument, the Block data structure post-fetch from the blockchain. A point of interest is that instead of TxInfo in the .txs attribute, the raw block you are provided will be populated rather with Amino-encoded text blobs representing StdTx. You can alter any attributes, and return the transformed block.

def my_block_transformer(block):
    # change block here ...
    return block

terra.blocks.transformer = my_block_transformer

The default block transformer is provided as a reference below:

class FetchTxInfoBlockTransformer:
    """Fetches TxInfo from data in `block.txs`, which it expects to contain Amino-encoded
    TX data."""

    def __init__(self, terra):
        self.terra = terra  # need terra instance to fetch txinfo

    def __call__(self, block: Block) -> Block:
        txhashes = [hash_amino(txdata) for txdata in block.txs]
        txs = self.terra.tx._tx_info_threaded(txhashes)
        block.txs = TxInfosQuery(txs)
        return block

This transformer is loaded when the Terra instance instantiates its BlockQuery.

# jigu.client.object_query.blocks
...
class BlocksQuery(object):
    """Manages how blocks are retrieved and parsed from the blockchain."""

    def __init__(self, terra):
        self.terra = terra
        self.transform_after_fetch = True
        self.transformer = FetchTxInfoBlockTransformer(terra)
...
Updated on: 3/11/2020, 9:08:33 PM