Debugging
How to debug TinyChain code
There is an interactive debugger planned (tracking issue #42) but the current debugging experience relies entirely on naming and logging. Consider this example:
1
import tinychain as tc
2
3
@tc.get_op
4
def add(a: tc.Int):
5
b = tc.Int(2) + tc.Int("foo")
6
return a + b
7
8
if __name__ == "__main__":
9
cxt = tc.Context()
10
cxt.op = add
11
cxt.result = cxt.op(2)
12
13
host = tc.host.Host("http://demo.tinychain.net")
14
print(host.post("/transact/hypothetical", cxt))
Copied!
Running this produces {'/error/bad_request': 'while resolving result: while resolving Int_7f81aa963c40: not a Number: foo'}. This program is small enough that it's easy to search for "foo," but in larger programs the auto-generated names like Int_7f81aa963c40 can be very unhelpful when debugging.

Step 1: assign descriptive names

The first step to take in situations like this is to explicitly assign names in the Op context:
1
# ...
2
3
@tc.get_op
4
def add(cxt, a: tc.Int):
5
cxt.two = tc.Int(2)
6
cxt.foo = tc.Int("foo")
7
cxt.b = cxt.two + cxt.foo
8
return a + cxt.b
9
10
# ...
Copied!
Running this produces a slightly more helpful error message: {'/error/bad_request': 'while resolving result: while resolving b: not a Number: foo'}. Now you can at least tell that the error is happening with TinyChain tries to resolve state b.
Another reason why it's important to assign names to the individual states in an Op is to make sure that each step of your program is only executed once. For example:
1
# ...
2
3
@tc.post_op
4
def increment(x: tc.tensor.Tensor):
5
update = x.write(x + 1)
6
return update, update # `x` will be incremented TWICE!
7
8
# ...
Copied!
In the case above updated is a flow control, not a State, so it will be executed again every time it's referenced. Assigning a name solves this problem:
1
# ...
2
3
@tc.post_op
4
def increment(cxt, x: tc.tensor.Tensor):
5
cxt.update = x.write(x + 1)
6
return cxt.update, cxt.update # `x` will be incremented ONLY ONCE!
7
8
# ...
Copied!
In this case, we've assigned the result of x.write(x + 1) the name update, so each reference to cxt.update will resolve the result of updating x, not the update op itself.

Step 2: add validation

The simplest way to inspect the state of a running TinyChain program is to add validation checks. For example:
1
# ...
2
3
@tc.post_op
4
def percentages(x: tc.Tensor) -> tc.Number:
5
return x / x.sum()
6
7
# ...
Copied!
In this case you'll get a divide-by-zero error if x is zero-valued, but it may be confusing in the context of a larger program because you won't necessarily know where the error is happening. To get more information, you can add validation checks to your code:
1
# ...
2
ERR_ZERO_PERCENT = tc.String("error in percentages: tensor sums to {{sum}}")
3
4
@tc.post_op
5
def percentages(cxt, x: tc.tensor.Tensor) -> tc.Number:
6
# assign a name to `x.sum()` to make sure it will only be calculated once
7
cxt.sum = x.sum()
8
return tc.If(cxt.sum == 0,
9
tc.error.BadRequest(ERR_ZERO_PERCENT.render(sum=cxt.sum)),
10
x / cxt.sum)
11
# ...
Copied!

Step 3: turn on logging

In larger programs, it may unclear which Op you need to inspect to begin with. To make this determination, you can turn on debug logging:
1
import logging
2
import tinychain as tc
3
4
@tc.get_op
5
def add(cxt, a: tc.Int):
6
b = tc.Int(2) + tc.Int("foo")
7
return a + b
8
9
if __name__ == "__main__":
10
logging.basicConfig(level=logging.DEBUG) # turn on debug logging
11
# ...
Copied!
With debug logging on, you should see some helpful debug output when you run your test script:
1
$ python3 test.py
2
DEBUG:root:auto-assigning name Int_7f41de61c950 to Int(2) in execution context with data []
3
DEBUG:root:auto-assigning name Int_7f41dd7c5070 to Int(GET Op ref $Int_7f41de61c950/add (Int(foo),)) in execution context with data ['Int_7f41de61c950']
4
# ...
Copied!
As long as you have at least a few descriptive names explicitly set, this should give you a good idea where to look for more fine-grained debugging. You can also explicitly use logging.debug in your code to get information about the state of the program at compile-time.

Step 4: inspect program code as JSON

If steps 1 and 2 don't help, or you suspect you may have found a bug in the TinyChain Python client itself, the final step to try on your own is to inspect the compiled JSON that's actually executed by a TinyChain host.
1
# ...
2
3
if __name__ == "__main__":
4
cxt = tc.Context()
5
cxt.op = add
6
cxt.result = cxt.op(2)
7
8
# compile `cxt` to a JSON-encodable representation
9
json_encodable = tc.to_json(cxt)
10
tc.print_json(json_encodable) # pretty-print JSON to stdout
11
12
host = tc.host.Host("http://demo.tinychain.net")
13
14
# important! make sure to send the same JSON to the host,
15
# so the auto-generated names will be the same
16
print(host.post("/transact/hypothetical", json_encodable))
Copied!
When you run this, you'll see the structure of your program represented as JSON printed to stdout:
1
[
2
[
3
"op",
4
{
5
"/state/scalar/op/get": [
6
"a",
7
[
8
[
9
"Int_7f7ee5844950",
10
2
11
],
12
[
13
"Int_7f7ee49dec70",
14
{
15
"$Int_7f7ee5844950/add": [
16
"foo"
17
]
18
}
19
],
20
[
21
"_return",
22
{
23
"$a/add": [
24
{
25
"$Int_7f7ee49dec70": []
26
}
27
]
28
}
29
]
30
]
31
]
32
}
33
],
34
[
35
"result",
36
{
37
"$op": [
38
2
39
]
40
}
41
]
42
]
Copied!
See technical details for a description of the subset of JSON which TinyChain uses as an application language.

Step 5: ask for help

If steps 1-4 didn't solve your problem, please email [email protected], ask a question on the Discord server, or open an issue!

Step 6: run the host in debug mode

The TinyChain host supports extensive debug logging, but for performance reasons it must be compiled in debug mode in order to enable this logging. If you're running TinyChain in a Docker container, you'll have to open an interactive terminal:
1
# the `-it` flag tells Docker to open an interactive terminal
2
docker run -it <your container ID>
3
4
# now, in the container
5
$ cd host
6
7
# optional but recommended: set a filter to specify what debug logs you want
8
$ export RUST_LOG=tinychain::route,tinychain::scalar=debug
9
10
# if you're not sure what value to give RUST_LOG, leave it unset
11
# and watch for module names that you're interested in
12
# (like "tinychain::route" or "tc_tensor")
13
14
# leave `--release` out of the run command to run in debug mode
15
$ cargo run --features=tensor
16
# ...
17
HTTP server listening on 0.0.0.0:8702
Copied!
Now, whenever you make a request to your local host, you'll see the host's internal debug logs in the terminal.