You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
} else{ // only other possible value for a u8 here is 255
35
35
letmutbuffer= [0; 8];
36
36
transaction_bytes.read(&mutbuffer).unwrap();
37
37
u64::from_le_bytes(buffer)
38
-
} else {
39
-
panic!("invalid compact size");
40
38
}
41
39
}
42
40
```
43
41
44
42
A few things to point out here:
45
-
1.`1..253` syntax is a [range type](https://doc.rust-lang.org/std/ops/struct.Range.html#), which has a method called `contains` to check if a value is in the given range.
43
+
1.`0..253` syntax is a [range type](https://doc.rust-lang.org/std/ops/struct.Range.html#), which has a method called `contains` to check if a value is in the given range.
46
44
2. The number of bytes read match the integer type. For example, 2 bytes give us a `u16` type. 4 bytes give us a `u32` type.
47
45
3. We **cast** each type into a `u64`. We can convert between primitive types in Rust using the [`as` keyword](https://doc.rust-lang.org/std/keyword.as.html).
48
46
4. Notice how there are are no semicolons for each ending line, such as `u32::from_le_bytes(buffer) as u64`. This is the equivalent of returning that value from the function. We could also write it as `return u32::from_le_bytes(buffer) as u64;` but implicit return without semicolon is more idiomatic.
49
-
5. We will call `panic!` and crash our program if the compact size number does not match the specification. This is exactly what happens when `unwrap` is called on a failed result. Technically, the only way this code will be reached is if the size is `0`. All other `u8` values are captured and anything that is not a valid `u8` will not enter this function.
50
47
51
48
We're going to make one more change. While standard if/else statements work fine, Rust provides pattern matching via the `match` keyword and this is a good opportunity to use it as it is commonly used in Rust codebases. https://doc.rust-lang.org/book/ch06-02-match.html
What do you think? The `match` looks nicer doesn't it? Take a moment to get familiar with the syntax. Each of the `arm`'s has a pattern to match followed by `=>` and then some code to return for that given pattern. The `_` at the end is a catchall for any pattern not specified above. It's also nice that we don't have to repeat calling `compact_size[0]` for each scenario.
76
+
What do you think? The `match` looks nicer doesn't it? Take a moment to get familiar with the syntax. Each of the `arm`'s has a pattern to match followed by `=>` and then some code to return for that given pattern.
77
+
78
+
We sometimes see an arm with the underscore symbol (`_` ) as the pattern to match. This represents a catchall pattern that will capture any value not already covered by the previous arms. However, in our case, this is not needed since the previous arms are exhaustive and capture all the possible scenarios. Remember a `u8` can only have a value between `0` and `255`.
85
79
86
80
Now all we have to do is update our `main` function to call this and return the number of inputs.
87
81
@@ -105,7 +99,7 @@ Version: 1
105
99
Input Length: 2
106
100
```
107
101
108
-
Pretty neat! We're making good progress. But even though our code compiles, how can we be sure we've written it correctly and that this function will return the appropriate number of inputs for different transactions? We want to test it with different inputs and ensure it is returning the appropriate outputs. We can do this with unit testing. So let's look into setting up our first unit test in the next section.
102
+
Pretty neat! We're making good progress. But even though our code compiles, how can we be sure we've written it correctly and that this function will return the appropriate number of inputs for different transactions? We want to test it with different arguments and ensure it is returning the correct compactSize. We can do this with unit testing. So let's look into setting up our first unit test in the next section.
109
103
110
104
### Quiz
111
105
*How do nodes know whether the transaction is a legacy or a segwit transaction as they read it? How do they know whether to view the next field after the version as an input length encoded as compactSize or as the marker and flag for a Segwit transaction?*
That checks out! Neat. Let's add another test to account for the scenario in which the program `panic!`s. Since a `panic!` will cause the program to crash, we need a way of catching that when it happens and then checking the result.
204
-
205
-
One way we can do this is with `std::panic::catch_unwind` which will take the code we want to run as a `closure` and return an `Err``Result` if a `panic!` is reached. We'll talk more about the `Result` enum later on. For now just know that there's a method, `is_err`, that will check if the `Result` is the `Err` type. And if you're not familiar with closures, you can read more about them [here](https://doc.rust-lang.org/book/ch13-01-closures.html). These are essentially anonymous functions that we can pass into another function as an argument.
206
-
207
-
```rust
208
-
letresult=std::panic::catch_unwind(|| {
209
-
letmutbytes= [0_u8].as_slice();
210
-
read_compact_size(&mutbytes);
211
-
});
212
-
assert!(result.is_err());
213
-
```
214
-
215
-
This is the only scenario in which the code will panic. We won't be able to pass in a number greater than 255 because we've ensured that the input type is a `u8` and the maximum number for a `u8` is 255. So the only value that will actually cause the program to crash is 0.
216
-
217
203
Run this with `cargo test` and all the tests should pass!
218
204
219
205
Great! We've learned about unit testing. We'll keep this in mind as we write more functions with complex logic. Let's keep it moving and keep reading the transaction.
220
206
221
207
### Additional Reading
222
208
* Test Organization: https://doc.rust-lang.org/book/ch11-03-test-organization.html
0 commit comments