diff --git a/common/address.go b/common/address.go
index 33412fab616..a83e12287af 100644
--- a/common/address.go
+++ b/common/address.go
@@ -130,21 +130,10 @@ func (a Address) Format(s fmt.State, c rune) {
// SetBytes sets the address to the value of b.
// If b is larger than len(a), b will be cropped from the left.
-func (a *Address) SetBytes(b []byte) {
- if len(b) > len(a) {
- b = b[len(b)-length.Addr:]
- }
- copy(a[length.Addr-len(b):], b)
-}
+func (a *Address) SetBytes(b []byte) { fixedSetBytes(a[:], b) }
// MarshalText returns the hex representation of a.
-func (a Address) MarshalText() ([]byte, error) {
- b := a[:]
- result := make([]byte, len(b)*2+2)
- copy(result, hexutil.HexPrefix)
- hex.Encode(result[2:], b)
- return result, nil
-}
+func (a Address) MarshalText() ([]byte, error) { return hexutil.Bytes(a[:]).MarshalText() }
// UnmarshalText parses a hash in hex syntax.
func (a *Address) UnmarshalText(input []byte) error {
diff --git a/common/bytes4.go b/common/bytes4.go
index 04a3345b500..1f2041f28d4 100644
--- a/common/bytes4.go
+++ b/common/bytes4.go
@@ -17,9 +17,7 @@
package common
import (
- "bytes"
"database/sql/driver"
- "encoding/hex"
"fmt"
"math/rand"
"reflect"
@@ -48,76 +46,29 @@ func (b *Bytes4) UnmarshalText(input []byte) error {
}
// MarshalText returns the hex representation of a.
-func (b Bytes4) MarshalText() ([]byte, error) {
- bl := b[:]
- result := make([]byte, len(b)*2+2)
- copy(result, hexutil.HexPrefix)
- hex.Encode(result[2:], bl)
- return result, nil
-}
+func (b Bytes4) MarshalText() ([]byte, error) { return hexutil.Bytes(b[:]).MarshalText() }
// Format implements fmt.Formatter.
// Hash supports the %v, %s, %v, %x, %X and %d format verbs.
-func (b Bytes4) Format(s fmt.State, c rune) {
- hexb := make([]byte, 2+len(b)*2)
- copy(hexb, "0x")
- hex.Encode(hexb[2:], b[:])
-
- switch c {
- case 'x', 'X':
- if !s.Flag('#') {
- hexb = hexb[2:]
- }
- if c == 'X' {
- hexb = bytes.ToUpper(hexb)
- }
- fallthrough
- case 'v', 's':
- s.Write(hexb)
- case 'q':
- q := []byte{'"'}
- s.Write(q)
- s.Write(hexb)
- s.Write(q)
- case 'd':
- fmt.Fprint(s, ([len(b)]byte)(b))
- default:
- fmt.Fprintf(s, "%%!%c(hash=%x)", c, b)
- }
-}
+func (b Bytes4) Format(s fmt.State, c rune) { fixedFormat(s, c, "hash", b[:]) }
// String implements the stringer interface and is used also by the logger when
// doing full logging into a file.
-func (b Bytes4) String() string {
- return b.Hex()
-}
+func (b Bytes4) String() string { return b.Hex() }
// SetBytes sets the hash to the value of i.
// If b is larger than len(h), b will be cropped from the left.
-func (b *Bytes4) SetBytes(i []byte) {
- if len(i) > len(b) {
- i = i[len(i)-length.Hash:]
- }
-
- copy(b[length.Hash-len(i):], i)
-}
+func (b *Bytes4) SetBytes(i []byte) { fixedSetBytes(b[:], i) }
// Generate implements testing/quick.Generator.
func (b Bytes4) Generate(rand *rand.Rand, size int) reflect.Value {
- m := rand.Intn(len(b))
- for i := len(b) - 1; i > m; i-- {
- b[i] = byte(rand.Uint32())
- }
+ fixedGenerate(b[:], rand.Intn, rand.Uint32)
return reflect.ValueOf(b)
}
// Value implements valuer for database/sql.
-func (b Bytes4) Value() (driver.Value, error) {
- return b[:], nil
-}
+func (b Bytes4) Value() (driver.Value, error) { return b[:], nil }
// TerminalString implements log.TerminalStringer, formatting a string for console
// output during logging.
-func (b Bytes4) TerminalString() string {
- return fmt.Sprintf("%x", b)
-}
+func (b Bytes4) TerminalString() string { return fmt.Sprintf("%x", b) }
diff --git a/common/bytes48.go b/common/bytes48.go
index b6b27e734fb..53a82126f2d 100644
--- a/common/bytes48.go
+++ b/common/bytes48.go
@@ -17,9 +17,7 @@
package common
import (
- "bytes"
"database/sql/driver"
- "encoding/hex"
"fmt"
"math/rand"
"reflect"
@@ -48,76 +46,29 @@ func (b *Bytes48) UnmarshalText(input []byte) error {
}
// MarshalText returns the hex representation of a.
-func (b Bytes48) MarshalText() ([]byte, error) {
- bl := b[:]
- result := make([]byte, len(b)*2+2)
- copy(result, hexutil.HexPrefix)
- hex.Encode(result[2:], bl)
- return result, nil
-}
+func (b Bytes48) MarshalText() ([]byte, error) { return hexutil.Bytes(b[:]).MarshalText() }
// Format implements fmt.Formatter.
// Hash supports the %v, %s, %v, %x, %X and %d format verbs.
-func (b Bytes48) Format(s fmt.State, c rune) {
- hexb := make([]byte, 2+len(b)*2)
- copy(hexb, "0x")
- hex.Encode(hexb[2:], b[:])
-
- switch c {
- case 'x', 'X':
- if !s.Flag('#') {
- hexb = hexb[2:]
- }
- if c == 'X' {
- hexb = bytes.ToUpper(hexb)
- }
- fallthrough
- case 'v', 's':
- s.Write(hexb)
- case 'q':
- q := []byte{'"'}
- s.Write(q)
- s.Write(hexb)
- s.Write(q)
- case 'd':
- fmt.Fprint(s, ([len(b)]byte)(b))
- default:
- fmt.Fprintf(s, "%%!%c(hash=%x)", c, b)
- }
-}
+func (b Bytes48) Format(s fmt.State, c rune) { fixedFormat(s, c, "hash", b[:]) }
// String implements the stringer interface and is used also by the logger when
// doing full logging into a file.
-func (b Bytes48) String() string {
- return b.Hex()
-}
+func (b Bytes48) String() string { return b.Hex() }
// SetBytes sets the hash to the value of i.
// If b is larger than len(h), b will be cropped from the left.
-func (b *Bytes48) SetBytes(i []byte) {
- if len(i) > len(b) {
- i = i[len(i)-length.Hash:]
- }
-
- copy(b[length.Hash-len(i):], i)
-}
+func (b *Bytes48) SetBytes(i []byte) { fixedSetBytes(b[:], i) }
// Generate implements testing/quick.Generator.
func (b Bytes48) Generate(rand *rand.Rand, size int) reflect.Value {
- m := rand.Intn(len(b))
- for i := len(b) - 1; i > m; i-- {
- b[i] = byte(rand.Uint32())
- }
+ fixedGenerate(b[:], rand.Intn, rand.Uint32)
return reflect.ValueOf(b)
}
// Value implements valuer for database/sql.
-func (b Bytes48) Value() (driver.Value, error) {
- return b[:], nil
-}
+func (b Bytes48) Value() (driver.Value, error) { return b[:], nil }
// TerminalString implements log.TerminalStringer, formatting a string for console
// output during logging.
-func (b Bytes48) TerminalString() string {
- return fmt.Sprintf("%x…%x", b[:3], b[len(b)-3:])
-}
+func (b Bytes48) TerminalString() string { return fixedTerminalString(b[:]) }
diff --git a/common/bytes64.go b/common/bytes64.go
index 40da3a4ca75..725527dc505 100644
--- a/common/bytes64.go
+++ b/common/bytes64.go
@@ -17,9 +17,7 @@
package common
import (
- "bytes"
"database/sql/driver"
- "encoding/hex"
"fmt"
"reflect"
@@ -47,67 +45,23 @@ func (b *Bytes64) UnmarshalText(input []byte) error {
}
// MarshalText returns the hex representation of a.
-func (b Bytes64) MarshalText() ([]byte, error) {
- bl := b[:]
- result := make([]byte, len(b)*2+2)
- copy(result, hexutil.HexPrefix)
- hex.Encode(result[2:], bl)
- return result, nil
-}
+func (b Bytes64) MarshalText() ([]byte, error) { return hexutil.Bytes(b[:]).MarshalText() }
// Format implements fmt.Formatter.
// Hash supports the %v, %s, %v, %x, %X and %d format verbs.
-func (b Bytes64) Format(s fmt.State, c rune) {
- hexb := make([]byte, 2+len(b)*2)
- copy(hexb, "0x")
- hex.Encode(hexb[2:], b[:])
-
- switch c {
- case 'x', 'X':
- if !s.Flag('#') {
- hexb = hexb[2:]
- }
- if c == 'X' {
- hexb = bytes.ToUpper(hexb)
- }
- fallthrough
- case 'v', 's':
- s.Write(hexb)
- case 'q':
- q := []byte{'"'}
- s.Write(q)
- s.Write(hexb)
- s.Write(q)
- case 'd':
- fmt.Fprint(s, ([len(b)]byte)(b))
- default:
- fmt.Fprintf(s, "%%!%c(hash=%x)", c, b)
- }
-}
+func (b Bytes64) Format(s fmt.State, c rune) { fixedFormat(s, c, "hash", b[:]) }
// String implements the stringer interface and is used also by the logger when
// doing full logging into a file.
-func (b Bytes64) String() string {
- return b.Hex()
-}
+func (b Bytes64) String() string { return b.Hex() }
// SetBytes sets the hash to the value of i.
// If b is larger than len(h), b will be cropped from the left.
-func (b *Bytes64) SetBytes(i []byte) {
- if len(i) > len(b) {
- i = i[len(i)-length.Hash:]
- }
-
- copy(b[length.Hash-len(i):], i)
-}
+func (b *Bytes64) SetBytes(i []byte) { fixedSetBytes(b[:], i) }
// Value implements valuer for database/sql.
-func (b Bytes64) Value() (driver.Value, error) {
- return b[:], nil
-}
+func (b Bytes64) Value() (driver.Value, error) { return b[:], nil }
// TerminalString implements log.TerminalStringer, formatting a string for console
// output during logging.
-func (b Bytes64) TerminalString() string {
- return fmt.Sprintf("%x…%x", b[:3], b[len(b)-3:])
-}
+func (b Bytes64) TerminalString() string { return fixedTerminalString(b[:]) }
diff --git a/common/bytes96.go b/common/bytes96.go
index ee5a92241be..73da73cee98 100644
--- a/common/bytes96.go
+++ b/common/bytes96.go
@@ -17,9 +17,7 @@
package common
import (
- "bytes"
"database/sql/driver"
- "encoding/hex"
"fmt"
"math/rand"
"reflect"
@@ -48,76 +46,29 @@ func (b *Bytes96) UnmarshalText(input []byte) error {
}
// MarshalText returns the hex representation of a.
-func (b Bytes96) MarshalText() ([]byte, error) {
- bl := b[:]
- result := make([]byte, len(b)*2+2)
- copy(result, hexutil.HexPrefix)
- hex.Encode(result[2:], bl)
- return result, nil
-}
+func (b Bytes96) MarshalText() ([]byte, error) { return hexutil.Bytes(b[:]).MarshalText() }
// Format implements fmt.Formatter.
// Hash supports the %v, %s, %v, %x, %X and %d format verbs.
-func (b Bytes96) Format(s fmt.State, c rune) {
- hexb := make([]byte, 2+len(b)*2)
- copy(hexb, "0x")
- hex.Encode(hexb[2:], b[:])
-
- switch c {
- case 'x', 'X':
- if !s.Flag('#') {
- hexb = hexb[2:]
- }
- if c == 'X' {
- hexb = bytes.ToUpper(hexb)
- }
- fallthrough
- case 'v', 's':
- s.Write(hexb)
- case 'q':
- q := []byte{'"'}
- s.Write(q)
- s.Write(hexb)
- s.Write(q)
- case 'd':
- fmt.Fprint(s, ([len(b)]byte)(b))
- default:
- fmt.Fprintf(s, "%%!%c(hash=%x)", c, b)
- }
-}
+func (b Bytes96) Format(s fmt.State, c rune) { fixedFormat(s, c, "hash", b[:]) }
// String implements the stringer interface and is used also by the logger when
// doing full logging into a file.
-func (b Bytes96) String() string {
- return b.Hex()
-}
+func (b Bytes96) String() string { return b.Hex() }
// SetBytes sets the hash to the value of i.
// If b is larger than len(h), b will be cropped from the left.
-func (b *Bytes96) SetBytes(i []byte) {
- if len(i) > len(b) {
- i = i[len(i)-length.Hash:]
- }
-
- copy(b[length.Hash-len(i):], i)
-}
+func (b *Bytes96) SetBytes(i []byte) { fixedSetBytes(b[:], i) }
// Generate implements testing/quick.Generator.
func (b Bytes96) Generate(rand *rand.Rand, size int) reflect.Value {
- m := rand.Intn(len(b))
- for i := len(b) - 1; i > m; i-- {
- b[i] = byte(rand.Uint32())
- }
+ fixedGenerate(b[:], rand.Intn, rand.Uint32)
return reflect.ValueOf(b)
}
// Value implements valuer for database/sql.
-func (b Bytes96) Value() (driver.Value, error) {
- return b[:], nil
-}
+func (b Bytes96) Value() (driver.Value, error) { return b[:], nil }
// TerminalString implements log.TerminalStringer, formatting a string for console
// output during logging.
-func (b Bytes96) TerminalString() string {
- return fmt.Sprintf("%x…%x", b[:3], b[len(b)-3:])
-}
+func (b Bytes96) TerminalString() string { return fixedTerminalString(b[:]) }
diff --git a/common/bytesn.go b/common/bytesn.go
new file mode 100644
index 00000000000..6ab360da38e
--- /dev/null
+++ b/common/bytesn.go
@@ -0,0 +1,73 @@
+// Copyright 2026 The Erigon Authors
+// This file is part of Erigon.
+//
+// Erigon is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Erigon is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with Erigon. If not, see .
+
+package common
+
+import (
+ "bytes"
+ "encoding/hex"
+ "fmt"
+)
+
+func fixedFormat(s fmt.State, c rune, typeName string, b []byte) {
+ hexb := make([]byte, 2+len(b)*2)
+ copy(hexb, "0x")
+ hex.Encode(hexb[2:], b)
+
+ switch c {
+ case 'x', 'X':
+ if !s.Flag('#') {
+ hexb = hexb[2:]
+ }
+ if c == 'X' {
+ hexb = bytes.ToUpper(hexb)
+ }
+ fallthrough
+ case 'v', 's':
+ s.Write(hexb)
+ case 'q':
+ q := []byte{'"'}
+ s.Write(q)
+ s.Write(hexb)
+ s.Write(q)
+ case 'd':
+ fmt.Fprint(s, b)
+ default:
+ fmt.Fprintf(s, "%%!%c(%s=%x)", c, typeName, b)
+ }
+}
+
+func fixedSetBytes(dst, src []byte) {
+ if len(src) > len(dst) {
+ src = src[len(src)-len(dst):]
+ }
+ copy(dst[len(dst)-len(src):], src)
+}
+
+func fixedTerminalString(b []byte) string {
+ return fmt.Sprintf("%x…%x", b[:3], b[len(b)-3:])
+}
+
+// fixedGenerate fills a random-length tail of b with random bytes, the shared
+// body of every fixed-size type's testing/quick Generator. intn and u32 are
+// supplied by the caller so this works with both math/rand and math/rand/v2,
+// whose Rand types differ only in Intn vs IntN.
+func fixedGenerate(b []byte, intn func(int) int, u32 func() uint32) {
+ m := intn(len(b))
+ for i := len(b) - 1; i > m; i-- {
+ b[i] = byte(u32())
+ }
+}
diff --git a/common/bytesn_golden_test.go b/common/bytesn_golden_test.go
new file mode 100644
index 00000000000..0f769f8877a
--- /dev/null
+++ b/common/bytesn_golden_test.go
@@ -0,0 +1,377 @@
+// Copyright 2026 The Erigon Authors
+// This file is part of Erigon.
+//
+// Erigon is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Erigon is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with Erigon. If not, see .
+
+package common
+
+import (
+ "database/sql/driver"
+ "encoding"
+ "encoding/json"
+ "errors"
+ "fmt"
+ mrand "math/rand"
+ randv2 "math/rand/v2"
+ "testing"
+)
+
+var fixedBytesGoldens = map[string]map[string]string{
+ "Bytes4": {
+ "String": "0x01020304",
+ "MarshalText": "0x01020304",
+ "Value": "01020304",
+ "%v": "0x01020304",
+ "%s": "0x01020304",
+ "%q": "\"0x01020304\"",
+ "%x": "01020304",
+ "%X": "01020304",
+ "%#x": "0x01020304",
+ "%#X": "0X01020304",
+ "%d": "[1 2 3 4]",
+ "%t": "%!t(hash=01020304)",
+ "Hex": "0x01020304",
+ "TerminalString": "01020304",
+ "SetBytesShort": "00deadbe",
+ "SetBytesLong": "06070809",
+ "UnmarshalTextErr": "hex string has length 4, want 8 for Bytes4",
+ "UnmarshalJSONErrType": "common.Bytes4",
+ "Generate": "010203dd",
+ },
+ "Bytes48": {
+ "String": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30",
+ "MarshalText": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30",
+ "Value": "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30",
+ "%v": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30",
+ "%s": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30",
+ "%q": "\"0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30\"",
+ "%x": "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30",
+ "%X": "0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F30",
+ "%#x": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30",
+ "%#X": "0X0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F30",
+ "%d": "[1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48]",
+ "%t": "%!t(hash=0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30)",
+ "Hex": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30",
+ "TerminalString": "010203…2e2f30",
+ "SetBytesShort": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000deadbe",
+ "SetBytesLong": "060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435",
+ "UnmarshalTextErr": "hex string has length 4, want 96 for Bytes48",
+ "UnmarshalJSONErrType": "common.Bytes48",
+ "Generate": "0102030405060708090a0b0c0d0e0ff6b16be9bf8ac39fba6a4f2665389c9b5ce0b0eee5f5a2f97870cc80e9897f5add",
+ },
+ "Bytes64": {
+ "String": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40",
+ "MarshalText": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40",
+ "Value": "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40",
+ "%v": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40",
+ "%s": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40",
+ "%q": "\"0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40\"",
+ "%x": "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40",
+ "%X": "0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F40",
+ "%#x": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40",
+ "%#X": "0X0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F40",
+ "%d": "[1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64]",
+ "%t": "%!t(hash=0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40)",
+ "Hex": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f40",
+ "TerminalString": "010203…3e3f40",
+ "SetBytesShort": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000deadbe",
+ "SetBytesLong": "060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445",
+ "UnmarshalTextErr": "hex string has length 4, want 128 for Bytes64",
+ "UnmarshalJSONErrType": "common.Bytes64",
+ },
+ "Bytes96": {
+ "String": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f60",
+ "MarshalText": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f60",
+ "Value": "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f60",
+ "%v": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f60",
+ "%s": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f60",
+ "%q": "\"0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f60\"",
+ "%x": "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f60",
+ "%X": "0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F60",
+ "%#x": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f60",
+ "%#X": "0X0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F60",
+ "%d": "[1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96]",
+ "%t": "%!t(hash=0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f60)",
+ "Hex": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f60",
+ "TerminalString": "010203…5e5f60",
+ "SetBytesShort": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000deadbe",
+ "SetBytesLong": "060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465",
+ "UnmarshalTextErr": "hex string has length 4, want 192 for BLSSignature",
+ "UnmarshalJSONErrType": "common.Bytes96",
+ "Generate": "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3ff6b16be9bf8ac39fba6a4f2665389c9b5ce0b0eee5f5a2f97870cc80e9897f5add",
+ },
+ "Hash": {
+ "String": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20",
+ "MarshalText": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20",
+ "Value": "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20",
+ "%v": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20",
+ "%s": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20",
+ "%q": "\"0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20\"",
+ "%x": "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20",
+ "%X": "0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20",
+ "%#x": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20",
+ "%#X": "0X0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20",
+ "%d": "[1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32]",
+ "%t": "%!t(hash=0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20)",
+ "Hex": "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20",
+ "TerminalString": "010203…1e1f20",
+ "SetBytesShort": "0000000000000000000000000000000000000000000000000000000000deadbe",
+ "SetBytesLong": "060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425",
+ "UnmarshalTextErr": "hex string has length 4, want 64 for Hash",
+ "UnmarshalJSONErrType": "common.Hash",
+ "Generate": "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f80",
+ },
+ "Address": {
+ "String": "0x0102030405060708090a0B0c0d0e0f1011121314",
+ "MarshalText": "0x0102030405060708090a0b0c0d0e0f1011121314",
+ "Value": "0102030405060708090a0b0c0d0e0f1011121314",
+ "%v": "0x0102030405060708090a0B0c0d0e0f1011121314",
+ "%s": "0x0102030405060708090a0B0c0d0e0f1011121314",
+ "%q": "\"0x0102030405060708090a0B0c0d0e0f1011121314\"",
+ "%x": "0102030405060708090a0b0c0d0e0f1011121314",
+ "%X": "0102030405060708090A0B0C0D0E0F1011121314",
+ "%#x": "0x0102030405060708090a0b0c0d0e0f1011121314",
+ "%#X": "0X0102030405060708090A0B0C0D0E0F1011121314",
+ "%d": "[1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20]",
+ "%t": "%!t(address=0102030405060708090a0b0c0d0e0f1011121314)",
+ "Hex": "0x0102030405060708090a0B0c0d0e0f1011121314",
+ "HexEIP55": "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
+ "SetBytesShort": "0000000000000000000000000000000000deadbe",
+ "SetBytesLong": "060708090a0b0c0d0e0f10111213141516171819",
+ "UnmarshalTextErr": "hex string has length 4, want 40 for Address",
+ "UnmarshalJSONErrType": "common.Address",
+ },
+}
+
+// TestFixedBytesGolden pins the exact formatting, marshalling, and SetBytes
+// output of every fixed-size byte type.
+func TestFixedBytesGolden(t *testing.T) {
+ for _, tc := range fixedBytesGoldenCases(t) {
+ t.Run(tc.name, func(t *testing.T) {
+ want := fixedBytesGoldens[tc.name]
+ if len(tc.rows) != len(want) {
+ t.Errorf("have %d rows, want %d goldens", len(tc.rows), len(want))
+ }
+ for _, r := range tc.rows {
+ w, ok := want[r.name]
+ if !ok {
+ t.Errorf("%s: no golden", r.name)
+ continue
+ }
+ if r.got != w {
+ t.Errorf("%s = %q, want %q", r.name, r.got, w)
+ }
+ }
+ })
+ }
+}
+
+type goldenRow struct {
+ name string
+ got string
+}
+
+type goldenCase struct {
+ name string
+ rows []goldenRow
+}
+
+func fixedBytesGoldenCases(t *testing.T) []goldenCase {
+ t.Helper()
+ return []goldenCase{
+ {"Bytes4", bytes4Goldens(t)},
+ {"Bytes48", bytes48Goldens(t)},
+ {"Bytes64", bytes64Goldens(t)},
+ {"Bytes96", bytes96Goldens(t)},
+ {"Hash", hashGoldens(t)},
+ {"Address", addressGoldens(t)},
+ }
+}
+
+func goldenSeq(n int) []byte {
+ b := make([]byte, n)
+ for i := range b {
+ b[i] = byte(i + 1)
+ }
+ return b
+}
+
+type fixedBytesValue interface {
+ fmt.Stringer
+ encoding.TextMarshaler
+ driver.Valuer
+}
+
+func formatGoldens(t *testing.T, v fixedBytesValue) []goldenRow {
+ t.Helper()
+ text, err := v.MarshalText()
+ if err != nil {
+ t.Fatalf("MarshalText: %v", err)
+ }
+ value, err := v.Value()
+ if err != nil {
+ t.Fatalf("Value: %v", err)
+ }
+ valueBytes, ok := value.([]byte)
+ if !ok {
+ t.Fatalf("Value returned %T, want []byte", value)
+ }
+ rows := []goldenRow{
+ {"String", v.String()},
+ {"MarshalText", string(text)},
+ {"Value", fmt.Sprintf("%x", valueBytes)},
+ }
+ for _, verb := range []string{"%v", "%s", "%q", "%x", "%X", "%#x", "%#X", "%d", "%t"} {
+ rows = append(rows, goldenRow{verb, fmt.Sprintf(verb, v)})
+ }
+ return rows
+}
+
+func errString(err error) string {
+ if err == nil {
+ return ""
+ }
+ return err.Error()
+}
+
+func jsonErrType(t *testing.T, err error) string {
+ t.Helper()
+ var typeErr *json.UnmarshalTypeError
+ if !errors.As(err, &typeErr) {
+ t.Fatalf("expected *json.UnmarshalTypeError, got %v", err)
+ }
+ return typeErr.Type.String()
+}
+
+func bytes4Goldens(t *testing.T) []goldenRow {
+ t.Helper()
+ var b Bytes4
+ b.SetBytes(goldenSeq(4))
+
+ var short, long, u, j Bytes4
+ short.SetBytes([]byte{0xde, 0xad, 0xbe})
+ long.SetBytes(goldenSeq(9))
+ gen := b.Generate(mrand.New(mrand.NewSource(7)), 10).Interface().(Bytes4)
+
+ return append(formatGoldens(t, b),
+ goldenRow{"Hex", b.Hex()},
+ goldenRow{"TerminalString", b.TerminalString()},
+ goldenRow{"SetBytesShort", fmt.Sprintf("%x", short)},
+ goldenRow{"SetBytesLong", fmt.Sprintf("%x", long)},
+ goldenRow{"UnmarshalTextErr", errString(u.UnmarshalText([]byte("0x1234")))},
+ goldenRow{"UnmarshalJSONErrType", jsonErrType(t, j.UnmarshalJSON([]byte("5")))},
+ goldenRow{"Generate", fmt.Sprintf("%x", gen)},
+ )
+}
+
+func bytes48Goldens(t *testing.T) []goldenRow {
+ t.Helper()
+ var b Bytes48
+ b.SetBytes(goldenSeq(48))
+
+ var short, long, u, j Bytes48
+ short.SetBytes([]byte{0xde, 0xad, 0xbe})
+ long.SetBytes(goldenSeq(53))
+ gen := b.Generate(mrand.New(mrand.NewSource(7)), 10).Interface().(Bytes48)
+
+ return append(formatGoldens(t, b),
+ goldenRow{"Hex", b.Hex()},
+ goldenRow{"TerminalString", b.TerminalString()},
+ goldenRow{"SetBytesShort", fmt.Sprintf("%x", short)},
+ goldenRow{"SetBytesLong", fmt.Sprintf("%x", long)},
+ goldenRow{"UnmarshalTextErr", errString(u.UnmarshalText([]byte("0x1234")))},
+ goldenRow{"UnmarshalJSONErrType", jsonErrType(t, j.UnmarshalJSON([]byte("5")))},
+ goldenRow{"Generate", fmt.Sprintf("%x", gen)},
+ )
+}
+
+func bytes64Goldens(t *testing.T) []goldenRow {
+ t.Helper()
+ var b Bytes64
+ b.SetBytes(goldenSeq(64))
+
+ var short, long, u, j Bytes64
+ short.SetBytes([]byte{0xde, 0xad, 0xbe})
+ long.SetBytes(goldenSeq(69))
+
+ return append(formatGoldens(t, b),
+ goldenRow{"Hex", b.Hex()},
+ goldenRow{"TerminalString", b.TerminalString()},
+ goldenRow{"SetBytesShort", fmt.Sprintf("%x", short)},
+ goldenRow{"SetBytesLong", fmt.Sprintf("%x", long)},
+ goldenRow{"UnmarshalTextErr", errString(u.UnmarshalText([]byte("0x1234")))},
+ goldenRow{"UnmarshalJSONErrType", jsonErrType(t, j.UnmarshalJSON([]byte("5")))},
+ )
+}
+
+func bytes96Goldens(t *testing.T) []goldenRow {
+ t.Helper()
+ var b Bytes96
+ b.SetBytes(goldenSeq(96))
+
+ var short, long, u, j Bytes96
+ short.SetBytes([]byte{0xde, 0xad, 0xbe})
+ long.SetBytes(goldenSeq(101))
+ gen := b.Generate(mrand.New(mrand.NewSource(7)), 10).Interface().(Bytes96)
+
+ return append(formatGoldens(t, b),
+ goldenRow{"Hex", b.Hex()},
+ goldenRow{"TerminalString", b.TerminalString()},
+ goldenRow{"SetBytesShort", fmt.Sprintf("%x", short)},
+ goldenRow{"SetBytesLong", fmt.Sprintf("%x", long)},
+ goldenRow{"UnmarshalTextErr", errString(u.UnmarshalText([]byte("0x1234")))},
+ goldenRow{"UnmarshalJSONErrType", jsonErrType(t, j.UnmarshalJSON([]byte("5")))},
+ goldenRow{"Generate", fmt.Sprintf("%x", gen)},
+ )
+}
+
+func hashGoldens(t *testing.T) []goldenRow {
+ t.Helper()
+ var h Hash
+ h.SetBytes(goldenSeq(32))
+
+ var short, long, u, j Hash
+ short.SetBytes([]byte{0xde, 0xad, 0xbe})
+ long.SetBytes(goldenSeq(37))
+ gen := h.Generate(randv2.New(randv2.NewPCG(7, 7)), 10).Interface().(Hash)
+
+ return append(formatGoldens(t, h),
+ goldenRow{"Hex", h.Hex()},
+ goldenRow{"TerminalString", h.TerminalString()},
+ goldenRow{"SetBytesShort", fmt.Sprintf("%x", short)},
+ goldenRow{"SetBytesLong", fmt.Sprintf("%x", long)},
+ goldenRow{"UnmarshalTextErr", errString(u.UnmarshalText([]byte("0x1234")))},
+ goldenRow{"UnmarshalJSONErrType", jsonErrType(t, j.UnmarshalJSON([]byte("5")))},
+ goldenRow{"Generate", fmt.Sprintf("%x", gen)},
+ )
+}
+
+func addressGoldens(t *testing.T) []goldenRow {
+ t.Helper()
+ var a Address
+ a.SetBytes(goldenSeq(20))
+
+ var short, long, u, j Address
+ short.SetBytes([]byte{0xde, 0xad, 0xbe})
+ long.SetBytes(goldenSeq(25))
+
+ return append(formatGoldens(t, a),
+ goldenRow{"Hex", a.Hex()},
+ goldenRow{"HexEIP55", HexToAddress("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed").Hex()},
+ goldenRow{"SetBytesShort", fmt.Sprintf("%x", short)},
+ goldenRow{"SetBytesLong", fmt.Sprintf("%x", long)},
+ goldenRow{"UnmarshalTextErr", errString(u.UnmarshalText([]byte("0x1234")))},
+ goldenRow{"UnmarshalJSONErrType", jsonErrType(t, j.UnmarshalJSON([]byte("5")))},
+ )
+}
diff --git a/common/bytesn_test.go b/common/bytesn_test.go
new file mode 100644
index 00000000000..7e3ba10e59c
--- /dev/null
+++ b/common/bytesn_test.go
@@ -0,0 +1,92 @@
+// Copyright 2026 The Erigon Authors
+// This file is part of Erigon.
+//
+// Erigon is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Erigon is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with Erigon. If not, see .
+
+package common
+
+import (
+ "bytes"
+ "testing"
+)
+
+// SetBytes on all fixed-size byte types must follow Hash.SetBytes semantics:
+// inputs longer than the type keep the last N bytes, shorter inputs are
+// right-aligned with zero padding.
+func TestBytesNSetBytes(t *testing.T) {
+ seq := func(n int) []byte {
+ b := make([]byte, n)
+ for i := range b {
+ b[i] = byte(i + 1)
+ }
+ return b
+ }
+
+ check := func(t *testing.T, got, in []byte) {
+ t.Helper()
+ n := len(got)
+ want := make([]byte, n)
+ if len(in) >= n {
+ copy(want, in[len(in)-n:])
+ } else {
+ copy(want[n-len(in):], in)
+ }
+ if !bytes.Equal(got, want) {
+ t.Fatalf("SetBytes(%d bytes) = %x, want %x", len(in), got, want)
+ }
+ }
+
+ t.Run("Bytes4", func(t *testing.T) {
+ for _, n := range []int{2, 4, 6} {
+ var b Bytes4
+ b.SetBytes(seq(n))
+ check(t, b[:], seq(n))
+ }
+ })
+ t.Run("Bytes48", func(t *testing.T) {
+ for _, n := range []int{40, 48, 60} {
+ var b Bytes48
+ b.SetBytes(seq(n))
+ check(t, b[:], seq(n))
+ }
+ })
+ t.Run("Bytes64", func(t *testing.T) {
+ for _, n := range []int{40, 64, 70} {
+ var b Bytes64
+ b.SetBytes(seq(n))
+ check(t, b[:], seq(n))
+ }
+ })
+ t.Run("Bytes96", func(t *testing.T) {
+ for _, n := range []int{40, 96, 100} {
+ var b Bytes96
+ b.SetBytes(seq(n))
+ check(t, b[:], seq(n))
+ }
+ })
+ t.Run("Hash", func(t *testing.T) {
+ for _, n := range []int{20, 32, 40} {
+ var h Hash
+ h.SetBytes(seq(n))
+ check(t, h[:], seq(n))
+ }
+ })
+ t.Run("Address", func(t *testing.T) {
+ for _, n := range []int{12, 20, 28} {
+ var a Address
+ a.SetBytes(seq(n))
+ check(t, a[:], seq(n))
+ }
+ })
+}
diff --git a/common/hash.go b/common/hash.go
index 45b124ba05c..fc0b95ff530 100644
--- a/common/hash.go
+++ b/common/hash.go
@@ -19,7 +19,6 @@ package common
import (
"bytes"
"database/sql/driver"
- "encoding/hex"
"fmt"
"math/big"
"math/rand/v2"
@@ -71,9 +70,7 @@ func (h Hash) Hex() string { return hexutil.Encode(h[:]) }
// TerminalString implements log.TerminalStringer, formatting a string for console
// output during logging.
-func (h Hash) TerminalString() string {
- return fmt.Sprintf("%x…%x", h[:3], h[29:])
-}
+func (h Hash) TerminalString() string { return fixedTerminalString(h[:]) }
// String implements the stringer interface and is used also by the logger when
// doing full logging into a file.
@@ -83,33 +80,7 @@ func (h Hash) String() string {
// Format implements fmt.Formatter.
// Hash supports the %v, %s, %v, %x, %X and %d format verbs.
-func (h Hash) Format(s fmt.State, c rune) {
- hexb := make([]byte, 2+len(h)*2)
- copy(hexb, "0x")
- hex.Encode(hexb[2:], h[:])
-
- switch c {
- case 'x', 'X':
- if !s.Flag('#') {
- hexb = hexb[2:]
- }
- if c == 'X' {
- hexb = bytes.ToUpper(hexb)
- }
- fallthrough
- case 'v', 's':
- s.Write(hexb)
- case 'q':
- q := []byte{'"'}
- s.Write(q)
- s.Write(hexb)
- s.Write(q)
- case 'd':
- fmt.Fprint(s, ([len(h)]byte)(h))
- default:
- fmt.Fprintf(s, "%%!%c(hash=%x)", c, h)
- }
-}
+func (h Hash) Format(s fmt.State, c rune) { fixedFormat(s, c, "hash", h[:]) }
// UnmarshalText parses a hash in hex syntax.
func (h *Hash) UnmarshalText(input []byte) error {
@@ -128,20 +99,11 @@ func (h Hash) MarshalText() ([]byte, error) {
// SetBytes sets the hash to the value of b.
// If b is larger than len(h), b will be cropped from the left.
-func (h *Hash) SetBytes(b []byte) {
- if len(b) > len(h) {
- b = b[len(b)-length.Hash:]
- }
-
- copy(h[length.Hash-len(b):], b)
-}
+func (h *Hash) SetBytes(b []byte) { fixedSetBytes(h[:], b) }
// Generate implements testing/quick.Generator.
func (h Hash) Generate(rand *rand.Rand, size int) reflect.Value {
- m := rand.IntN(len(h))
- for i := len(h) - 1; i > m; i-- {
- h[i] = byte(rand.Uint32())
- }
+ fixedGenerate(h[:], rand.IntN, rand.Uint32)
return reflect.ValueOf(h)
}