Choosing a type for blockchain height (beware of unsigned integers)
This article was originally published on Medium.
Before this pull request,
it was a mess. ABCI was using a uint64
height on EndBlock. Tendermint Core
was using the same type for TxResult. Block and BlockStore, however, were using
int
heights (remember that int
is different depending on the processor
architecture: int32
for x86, int64
for amd64). There was no single standard
across our repositories as for what type to use for blockchain height.
There was a need [1]. If you have different types everywhere, you need to cast them, which can lead to information loss:
- value — when casting bigger to smaller type
- sign — when casting signed to unsigned type
Users should be able to build upon Tendermint without worrying that some type is going to change overnight.
So one day we sat down and decided to do a small research. Choosing between
uint64
and int64
was not an obvious choice. At least not back then.
Blockchain height should only go up, so it is logical to suggest uint64
. And
with a maximum value being 18446744073709551615, a chain can live up to 584
million years (assuming one sec. blocks). Wow. That’s life! What about other
projects? Parity and
chain use uint64
. Sounds like an obvious
choice? But if you look deep enough, there are dragons.
Ruling out int type
int
was ruled out almost immediately due to size (32 bits on x86) and
inconsistency between nodes with different architectures.
Assuming a one second block time this means on a 32-bit architecture a Tendermint chain can only live 68 years, which is just under the worldwide mean life expectancy for a male. Surely a Tendermint chain should live longer!
uint64 vs int64
Benefits of uint64
- good for bit-wise arithmetic (encryption algorithms, etc.)
- a form of self-documentation. The type indicates that the value which the unsigned int was intended to hold is never supposed to be negative.
Downsides of uint64
- you have to be careful with arithmetic. There are cases where we subtract
heights or subtract 1 like getting the age of some piece of evidence or logic
around checkpointing. With signed integers, we can just assert for
< 0
. With unsigned integers, we are risking to get a super huge value if we forget to check for underflow or make a mistake at some point.
I am just saying that if we decrement an uninitialized height uint64(0), all of a sudden that’s a quick mess up. if we have code that can check if
height <= 0
then we are set withint64
. but withuint64
only code Jesus can help us

package main
import (
"fmt"
)
func main() {
var a int64
a = 10
a -= 29
if a < 0 {
fmt.Println("panic")
}
fmt.Printf("a=%v\n", a)
var b uint64
b = 10
if (29 - b) > 10 {
fmt.Println("underflow")
}
b -= 29
fmt.Printf("b=%v\n", b)
}
https://play.golang.org/p/YvQgJ6Bm2Xu
Note on uint64 and Java
Java does not support unsigned ints. It means ABCI apps, that are written in
Java won’t be able to go beyond int64
anyway. If you are curious about why read
Why doesn’t Java support unsigned ints?
Benefits of int64
- less chance of fuck up
Downsides of int64
- smaller max value (although, 292 million years are more than enough)
In conclusion
Given above reasoning we decided to go with int64
. “value should never be less
than zero” should not be a primary reason for picking uint64
as a type for
blockchain height. If you are gonna use uint64
, be extra careful with
arithmetic.
Resources: