Skip to content

Commit 7a7b5a9

Browse files
feat: add stacktrace to SSZ (#1017)
Co-authored-by: Tomás Grüner <[email protected]>
1 parent 5a53ee2 commit 7a7b5a9

File tree

7 files changed

+240
-45
lines changed

7 files changed

+240
-45
lines changed

lib/ssz_ex/decode.ex

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ defmodule SszEx.Decode do
5555
{:error,
5656
%Error{
5757
message:
58-
"Invalid binary length while decoding uint.\nExpected size: #{size}.\nFound:#{bit_size(binary)}\n"
58+
"Invalid binary length while decoding uint.\nExpected size: #{size}.\nFound:#{bit_size(binary)}"
5959
}}
6060

6161
defp decode_uint(binary, size) do
@@ -68,7 +68,7 @@ defmodule SszEx.Decode do
6868
{:error,
6969
%Error{
7070
message:
71-
"Invalid binary length while decoding bool.\nExpected size: 1.\nFound:#{byte_size(binary)}\n"
71+
"Invalid binary length while decoding bool.\nExpected size: 1.\nFound:#{byte_size(binary)}"
7272
}}
7373

7474
defp decode_bool("\x01"), do: {:ok, true}
@@ -86,7 +86,7 @@ defmodule SszEx.Decode do
8686
do:
8787
{:error,
8888
%Error{
89-
message: "Invalid binary value while decoding BitList.\nEmpty binary found.\n"
89+
message: "Invalid binary value while decoding BitList.\nEmpty binary found."
9090
}}
9191

9292
defp decode_bitlist(bit_list, max_size) do
@@ -98,14 +98,14 @@ defmodule SszEx.Decode do
9898
match?(<<_::binary-size(num_bytes - 1), 0>>, bit_list) ->
9999
{:error,
100100
%Error{
101-
message: "Invalid binary value while decoding BitList.\nMissing sentinel bit.\n"
101+
message: "Invalid binary value while decoding BitList.\nMissing sentinel bit."
102102
}}
103103

104104
len > max_size ->
105105
{:error,
106106
%Error{
107107
message:
108-
"Invalid binary length while decoding BitList. \nExpected max_size: #{max_size}. Found: #{len}.\n"
108+
"Invalid binary length while decoding BitList. \nExpected max_size: #{max_size}. Found: #{len}."
109109
}}
110110

111111
true ->
@@ -125,7 +125,7 @@ defmodule SszEx.Decode do
125125
_ ->
126126
{:error,
127127
%Error{
128-
message: "Invalid binary length while decoding BitVector. \nExpected size: #{size}.\n"
128+
message: "Invalid binary length while decoding BitVector. \nExpected size: #{size}."
129129
}}
130130
end
131131
end
@@ -162,7 +162,7 @@ defmodule SszEx.Decode do
162162
{:error,
163163
%Error{
164164
message:
165-
"Invalid binary length while decoding list of #{inspect(inner_type)}.\nExpected max_size: #{max_size}.\nFound: #{byte_length}\n"
165+
"Invalid binary length while decoding list of #{inspect(inner_type)}.\nExpected max_size: #{max_size}.\nFound: #{byte_length}"
166166
}}
167167

168168
defp check_valid_fixed_list_size(_byte_length, _inner_type, _inner_type_size, _max_size),
@@ -174,7 +174,7 @@ defmodule SszEx.Decode do
174174
{:error,
175175
%Error{
176176
message:
177-
"Invalid binary length while decoding vector of #{inspect(inner_type)}.\nExpected size #{inner_type_size * size} bytes.\nFound: #{byte_length}.\n"
177+
"Invalid binary length while decoding vector of #{inspect(inner_type)}.\nExpected size #{inner_type_size * size} bytes.\nFound: #{byte_length}."
178178
}}
179179

180180
defp check_valid_vector_size(_byte_length, _inner_type, _inner_type_size, _size),
@@ -310,7 +310,7 @@ defmodule SszEx.Decode do
310310
{:error,
311311
%Error{
312312
message:
313-
"Invalid binary length while decoding #{module}. \nExpected #{expected_length}. \nFound #{size}.\n"
313+
"Invalid binary length while decoding #{module}. \nExpected #{expected_length}. \nFound #{size}."
314314
}}
315315

316316
defp check_fixed_container_size(_module, _expected_length, _size),
@@ -322,7 +322,7 @@ defmodule SszEx.Decode do
322322
{:error,
323323
%Error{
324324
message:
325-
"First offset does not point to the first variable byte.\nExpected index: #{items_index}.\nOffset: #{offset}. "
325+
"First offset does not point to the first variable byte.\nExpected index: #{items_index}.\nOffset: #{offset}."
326326
}}
327327

328328
defp check_first_offset(_offsets, _items_index, _binary_size),
@@ -337,14 +337,17 @@ defmodule SszEx.Decode do
337337
:ok ->
338338
size = next_offset - offset
339339
<<chunk::binary-size(size), rest::bitstring>> = rest_bytes
340-
{:cont, {rest, [{key, decode(chunk, schema)} | acc_variable_parts]}}
340+
341+
{:cont,
342+
{rest, [{key, decode(chunk, schema) |> Error.add_trace(key)} | acc_variable_parts]}}
341343

342344
error ->
343345
{:halt, {<<>>, [{key, error} | acc_variable_parts]}}
344346
end
345347

346348
[{_offset, {key, schema}}], {rest_bytes, acc_variable_parts} ->
347-
{:cont, {<<>>, [{key, decode(rest_bytes, schema)} | acc_variable_parts]}}
349+
{:cont,
350+
{<<>>, [{key, decode(rest_bytes, schema) |> Error.add_trace(key)} | acc_variable_parts]}}
348351
end)
349352
|> then(fn {<<>>, variable_parts} ->
350353
flatten_container_results(variable_parts)
@@ -363,7 +366,9 @@ defmodule SszEx.Decode do
363366
else
364367
ssz_fixed_len = Utils.get_fixed_size(schema)
365368
<<chunk::binary-size(ssz_fixed_len), rest::bitstring>> = binary
366-
{rest, [{key, decode(chunk, schema)} | fixed_parts], offsets, items_index + ssz_fixed_len}
369+
370+
{rest, [{key, decode(chunk, schema) |> Error.add_trace(key)} | fixed_parts], offsets,
371+
items_index + ssz_fixed_len}
367372
end
368373
end)
369374
|> then(fn {_rest_bytes, fixed_parts, offsets, items_index} ->
@@ -441,7 +446,7 @@ defmodule SszEx.Decode do
441446
{:error,
442447
%Error{
443448
message:
444-
"Invalid binary length while decoding collection. \nInner type size: #{chunk_size} bytes. Binary length: #{byte_size(binary)} bytes.\n"
449+
"Invalid binary length while decoding collection. \nInner type size: #{chunk_size} bytes. Binary length: #{byte_size(binary)} bytes."
445450
}}
446451
| results
447452
]
@@ -455,7 +460,7 @@ defmodule SszEx.Decode do
455460
case Enum.group_by(results, fn {_, {type, _}} -> type end, fn {key, {_, result}} ->
456461
{key, result}
457462
end) do
458-
%{error: errors} -> {:error, errors}
463+
%{error: [first_error | _rest]} -> {:error, first_error}
459464
summary -> {:ok, Map.get(summary, :ok, [])}
460465
end
461466
end

lib/ssz_ex/encode.ex

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ defmodule SszEx.Encode do
5555
{:error,
5656
%Error{
5757
message:
58-
"Invalid binary length while encoding list of #{inspect(inner_type)}.\nExpected max_size: #{max_size}.\nFound: #{size}\n"
58+
"Invalid binary length while encoding list of #{inspect(inner_type)}.\nExpected max_size: #{max_size}.\nFound: #{size}"
5959
}}
6060
else
6161
list
@@ -71,7 +71,7 @@ defmodule SszEx.Encode do
7171
{:error,
7272
%Error{
7373
message:
74-
"Invalid binary length while encoding BitList.\nExpected max_size: #{max_size}. Found: #{len}.\n"
74+
"Invalid binary length while encoding BitList.\nExpected max_size: #{max_size}. Found: #{len}."
7575
}}
7676
else
7777
{:ok, BitList.to_bytes(bit_list)}
@@ -96,7 +96,7 @@ defmodule SszEx.Encode do
9696
{:error,
9797
%Error{
9898
message:
99-
"Invalid binary length while encoding list of #{inspect(inner_type)}.\nExpected max_size: #{max_size}.\nFound: #{size}\n"
99+
"Invalid binary length while encoding list of #{inspect(inner_type)}.\nExpected max_size: #{max_size}.\nFound: #{size}"
100100
}}
101101
else
102102
fixed_lengths = @bytes_per_length_offset * length(list)
@@ -147,8 +147,7 @@ defmodule SszEx.Encode do
147147
Enum.reduce(variable_parts, 0, fn part, acc -> byte_size(part) + acc end),
148148
:ok <- check_length(fixed_length, variable_length),
149149
{:ok, fixed_parts} <-
150-
replace_offsets(fixed_size_values, offsets)
151-
|> encode_schemas() do
150+
replace_offsets(fixed_size_values, offsets) |> encode_schemas() do
152151
(fixed_parts ++ variable_parts)
153152
|> Enum.join()
154153
|> then(&{:ok, &1})
@@ -163,23 +162,25 @@ defmodule SszEx.Encode do
163162

164163
if Utils.variable_size?(schema) do
165164
{[:offset | acc_fixed_size_values], @bytes_per_length_offset + acc_fixed_length,
166-
[{value, schema} | acc_variable_values]}
165+
[{value, key, schema} | acc_variable_values]}
167166
else
168-
{[{value, schema} | acc_fixed_size_values],
167+
{[{value, key, schema} | acc_fixed_size_values],
169168
acc_fixed_length + Utils.get_fixed_size(schema), acc_variable_values}
170169
end
171170
end)
172171
end
173172

174173
defp encode_schemas(tuple_values) do
175-
Enum.map(tuple_values, fn {value, schema} -> encode(value, schema) end)
174+
Enum.map(tuple_values, fn {value, key, schema} ->
175+
encode(value, schema) |> Error.add_trace(key)
176+
end)
176177
|> Utils.flatten_results()
177178
end
178179

179180
defp calculate_offsets(variable_parts, fixed_length) do
180181
{offsets, _} =
181182
Enum.reduce(variable_parts, {[], fixed_length}, fn element, {res, acc} ->
182-
{[{acc, {:int, 32}} | res], byte_size(element) + acc}
183+
{[{acc, :offset, {:int, 32}} | res], byte_size(element) + acc}
183184
end)
184185

185186
offsets

lib/ssz_ex/error.ex

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,52 @@ defmodule SszEx.Error do
33
Error messages for SszEx domain.
44
"""
55
alias SszEx.Error
6-
defstruct [:message]
7-
@type t :: %__MODULE__{message: String.t()}
6+
defstruct [:message, stacktrace: []]
7+
@type t :: %__MODULE__{message: String.t(), stacktrace: list()}
88

9-
def format(%Error{message: message}) do
10-
"#{message}"
9+
def format(%Error{message: message, stacktrace: []}), do: "#{message}\n"
10+
11+
def format(%Error{message: message, stacktrace: stacktrace}) do
12+
formatted_stacktrace = stacktrace |> Enum.join(".")
13+
"#{message}\nStacktrace: #{formatted_stacktrace}"
14+
end
15+
16+
def add_container(%Error{message: message, stacktrace: stacktrace}, value)
17+
when is_struct(value) do
18+
new_trace =
19+
value.__struct__ |> Module.split() |> List.last()
20+
21+
%Error{message: message, stacktrace: [new_trace | stacktrace]}
22+
end
23+
24+
def add_container(%Error{} = error, :bool), do: error
25+
26+
def add_container(%Error{message: message, stacktrace: stacktrace}, value)
27+
when is_atom(value) do
28+
new_trace =
29+
value |> Module.split() |> List.last()
30+
31+
%Error{message: message, stacktrace: [new_trace | stacktrace]}
1132
end
1233

34+
def add_container(%Error{message: message, stacktrace: stacktrace}, new_trace) do
35+
%Error{message: message, stacktrace: [new_trace | stacktrace]}
36+
end
37+
38+
def add_container({:error, %Error{} = error}, new_trace),
39+
do: {:error, Error.add_container(error, new_trace)}
40+
41+
def add_container(value, _module), do: value
42+
43+
def add_trace(%Error{message: message, stacktrace: stacktrace}, new_trace) do
44+
%Error{message: message, stacktrace: [new_trace | stacktrace]}
45+
end
46+
47+
def add_trace({:error, %Error{} = error}, new_trace),
48+
do: {:error, Error.add_trace(error, new_trace)}
49+
50+
def add_trace(value, _module), do: value
51+
1352
defimpl String.Chars, for: __MODULE__ do
1453
def to_string(error), do: Error.format(error)
1554
end

lib/ssz_ex/merkleization.ex

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@ defmodule SszEx.Merkleization do
3939

4040
@spec hash_tree_root(binary, {:byte_vector, non_neg_integer}) ::
4141
{:ok, Types.root()}
42+
def hash_tree_root(value, {:byte_vector, size}) when byte_size(value) != size,
43+
do:
44+
{:error,
45+
%Error{
46+
message:
47+
"Invalid binary length while merkleizing byte_vector.\nExpected size: #{size}.\nFound: #{byte_size(value)}"
48+
}}
49+
4250
def hash_tree_root(value, {:byte_vector, _size}) do
4351
packed_chunks = pack_bytes(value)
4452
leaf_count = packed_chunks |> get_chunks_len() |> next_pow_of_two()
@@ -73,7 +81,7 @@ defmodule SszEx.Merkleization do
7381
{:error,
7482
%Error{
7583
message:
76-
"Invalid binary length while merkleizing list of #{inspect(type)}.\nExpected max_size: #{max_size}.\nFound: #{len}\n"
84+
"Invalid binary length while merkleizing list of #{inspect(type)}.\nExpected max_size: #{max_size}.\nFound: #{len}"
7785
}}
7886

7987
Utils.basic_type?(type) ->
@@ -91,7 +99,7 @@ defmodule SszEx.Merkleization do
9199
{:error,
92100
%Error{
93101
message:
94-
"Invalid binary length while merkleizing vector of #{inspect(inner_type)}.\nExpected size: #{size}.\nFound: #{length(vector)}\n"
102+
"Invalid binary length while merkleizing vector of #{inspect(inner_type)}.\nExpected size: #{size}.\nFound: #{length(vector)}"
95103
}}
96104

97105
def hash_tree_root(vector, {:vector, type, _size} = schema) do
@@ -118,7 +126,7 @@ defmodule SszEx.Merkleization do
118126

119127
case hash_tree_root(value, schema) do
120128
{:ok, root} -> {:cont, {:ok, acc_root <> root}}
121-
{:error, %Error{}} = error -> {:halt, error}
129+
{:error, %Error{} = error} -> {:halt, {:error, Error.add_trace(error, key)}}
122130
end
123131
end)
124132

lib/ssz_ex/ssz_ex.ex

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,17 @@ defmodule SszEx do
6262

6363
@spec encode(struct()) ::
6464
{:ok, binary()} | {:error, Error.t()}
65-
def encode(%name{} = value), do: encode(value, name)
65+
def encode(%name{} = value), do: encode(value, name) |> Error.add_container(value)
6666

6767
@spec encode(any(), schema()) ::
6868
{:ok, binary()} | {:error, Error.t()}
6969
def encode(value, schema), do: Encode.encode(value, schema)
7070

71+
@spec decode(binary(), schema()) ::
72+
{:ok, any()} | {:error, Error.t()}
73+
def decode(value, module) when is_atom(module),
74+
do: Decode.decode(value, module) |> Error.add_container(module)
75+
7176
@spec decode(binary(), schema()) ::
7277
{:ok, any()} | {:error, Error.t()}
7378
def decode(value, schema), do: Decode.decode(value, schema)
@@ -78,6 +83,10 @@ defmodule SszEx do
7883
@spec hash_tree_root!(any, any) :: Types.root()
7984
def hash_tree_root!(value, schema), do: Merkleization.hash_tree_root!(value, schema)
8085

86+
@spec hash_tree_root(struct()) :: {:ok, Types.root()} | {:error, Error.t()}
87+
def hash_tree_root(%name{} = value),
88+
do: hash_tree_root(value, name) |> Error.add_container(value)
89+
8190
@spec hash_tree_root(any, any) :: {:ok, Types.root()} | {:error, Error.t()}
8291
def hash_tree_root(value, schema), do: Merkleization.hash_tree_root(value, schema)
8392

lib/ssz_ex/utils.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ defmodule SszEx.Utils do
6767

6868
def flatten_results_by(results, fun) do
6969
case Enum.group_by(results, fn {type, _} -> type end, fn {_, result} -> result end) do
70-
%{error: errors} -> {:error, errors}
70+
%{error: [first_error | _rest]} -> {:error, first_error}
7171
summary -> {:ok, fun.(Map.get(summary, :ok, []))}
7272
end
7373
end

0 commit comments

Comments
 (0)