Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Storage API uses partial width rather than logSize #393

Merged
merged 12 commits into from
Dec 9, 2024
8 changes: 4 additions & 4 deletions api/layout/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,19 @@ func ExampleNodeCoordsToTileAddress() {
}

func ExampleTilePath() {
tilePath := layout.TilePath(0, 1234067, 315921160)
tilePath := layout.TilePath(0, 1234067, 8)
fmt.Printf("tile path: %s", tilePath)
// Output: tile path: tile/0/x001/x234/067.p/8
}

func ExampleEntriesPath() {
entriesPath := layout.EntriesPath(1234067, 315921160)
entriesPath := layout.EntriesPath(1234067, 8)
fmt.Printf("entries path: %s", entriesPath)
// Output: entries path: tile/entries/x001/x234/067.p/8
}

func ExampleParseTileLevelIndexWidth() {
level, index, width, _ := layout.ParseTileLevelIndexWidth("0", "x001/x234/067.p/8")
func ExampleParseTileLevelIndexPartial() {
level, index, width, _ := layout.ParseTileLevelIndexPartial("0", "x001/x234/067.p/8")
fmt.Printf("level: %d, index: %d, width: %d", level, index, width)
// Output: level: 0, index: 1234067, width: 8
}
34 changes: 18 additions & 16 deletions api/layout/paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,27 +37,28 @@ const (
// The logSize is required so that a partial qualifier can be appended to tiles that
// would contain fewer than 256 entries.
func EntriesPathForLogIndex(seq, logSize uint64) string {
return EntriesPath(seq/256, logSize)
return EntriesPath(seq/EntryBundleWidth, PartialTileSize(0, seq, logSize))
}

// NWithSuffix returns a tiles-spec "N" path, with a partial suffix if applicable.
func NWithSuffix(l, n, logSize uint64) string {
// NWithSuffix returns a tiles-spec "N" path, with a partial suffix if p > 0.
func NWithSuffix(l, n uint64, p uint8) string {
suffix := ""
if p := partialTileSize(l, n, logSize); p > 0 {
if p > 0 {
suffix = fmt.Sprintf(".p/%d", p)
}
return fmt.Sprintf("%s%s", fmtN(n), suffix)
}

// EntriesPath returns the local path for the nth entry bundle. p denotes the partial
// tile size, or 0 if the tile is complete.
func EntriesPath(n, logSize uint64) string {
return fmt.Sprintf("tile/entries/%s", NWithSuffix(0, n, logSize))
func EntriesPath(n uint64, p uint8) string {
return fmt.Sprintf("tile/entries/%s", NWithSuffix(0, n, p))
}

// TilePath builds the path to the subtree tile with the given level and index in tile space.
func TilePath(tileLevel, tileIndex, logSize uint64) string {
return fmt.Sprintf("tile/%d/%s", tileLevel, NWithSuffix(tileLevel, tileIndex, logSize))
// If p > 0 the path represents a partial tile.
func TilePath(tileLevel, tileIndex uint64, p uint8) string {
return fmt.Sprintf("tile/%d/%s", tileLevel, NWithSuffix(tileLevel, tileIndex, p))
}

// fmtN returns the "N" part of a Tiles-spec path.
Expand All @@ -83,13 +84,13 @@ func fmtN(N uint64) string {
// Examples:
// "/tile/0/x001/x234/067" means level 0 and index 1234067 of a full tile.
// "/tile/0/x001/x234/067.p/8" means level 0, index 1234067 and width 8 of a partial tile.
func ParseTileLevelIndexWidth(level, index string) (uint64, uint64, uint64, error) {
func ParseTileLevelIndexPartial(level, index string) (uint64, uint64, uint8, error) {
l, err := ParseTileLevel(level)
if err != nil {
return 0, 0, 0, err
}

i, w, err := ParseTileIndexWidth(index)
i, w, err := ParseTileIndexPartial(index)
if err != nil {
return 0, 0, 0, err
}
Expand All @@ -107,17 +108,18 @@ func ParseTileLevel(level string) (uint64, error) {
return l, err
}

// ParseTileIndexWidth takes index in string, validates and returns the index and width in uint64.
func ParseTileIndexWidth(index string) (uint64, uint64, error) {
w := uint64(256)
// ParseTileIndexPartial takes index in string, validates and returns the index and width in uint64.
func ParseTileIndexPartial(index string) (uint64, uint8, error) {
w := uint8(0)
indexPaths := strings.Split(index, "/")

if strings.Contains(index, ".p") {
var err error
w, err = strconv.ParseUint(indexPaths[len(indexPaths)-1], 10, 64)
if err != nil || w < 1 || w > 255 {
return 0, 0, fmt.Errorf("failed to parse tile index")
w64, err := strconv.ParseUint(indexPaths[len(indexPaths)-1], 10, 64)
if err != nil || w64 < 1 || w64 >= TileWidth {
return 0, 0, fmt.Errorf("failed to parse tile width")
}
w = uint8(w64)
indexPaths[len(indexPaths)-2] = strings.TrimSuffix(indexPaths[len(indexPaths)-2], ".p")
indexPaths = indexPaths[:len(indexPaths)-1]
}
Expand Down
109 changes: 25 additions & 84 deletions api/layout/paths_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ package layout

import (
"fmt"
"math"
"testing"
)

Expand Down Expand Up @@ -65,39 +64,30 @@ func TestEntriesPathForLogIndex(t *testing.T) {
func TestEntriesPath(t *testing.T) {
for _, test := range []struct {
N uint64
logSize uint64
p uint8
wantPath string
wantErr bool
}{
{
N: 0,
logSize: 289,
wantPath: "tile/entries/000",
},
{
N: 0,
logSize: 8,
p: 8,
wantPath: "tile/entries/000.p/8",
}, {
N: 255,
logSize: 256 * 256,
wantPath: "tile/entries/255",
}, {
N: 255,
logSize: 255*256 - 3,
p: 253,
wantPath: "tile/entries/255.p/253",
}, {
N: 256,
logSize: 257 * 256,
wantPath: "tile/entries/256",
}, {
N: 123456789000,
logSize: math.MaxUint64,
wantPath: "tile/entries/x123/x456/x789/000",
},
} {
desc := fmt.Sprintf("N %d", test.N)
t.Run(desc, func(t *testing.T) {
gotPath := EntriesPath(test.N, test.logSize)
gotPath := EntriesPath(test.N, test.p)
if gotPath != test.wantPath {
t.Errorf("got file %q want %q", gotPath, test.wantPath)
}
Expand All @@ -109,59 +99,37 @@ func TestTilePath(t *testing.T) {
for _, test := range []struct {
level uint64
index uint64
logSize uint64
p uint8
wantPath string
}{
{
level: 0,
index: 0,
logSize: 256,
wantPath: "tile/0/000",
}, {
level: 0,
index: 0,
logSize: 0,
wantPath: "tile/0/000",
}, {
level: 0,
index: 0,
logSize: 255,
p: 255,
wantPath: "tile/0/000.p/255",
}, {
level: 1,
index: 0,
logSize: math.MaxUint64,
wantPath: "tile/1/000",
}, {
level: 1,
index: 0,
logSize: 256,
wantPath: "tile/1/000.p/1",
}, {
level: 1,
index: 0,
logSize: 1024,
wantPath: "tile/1/000.p/4",
}, {
level: 15,
index: 455667,
logSize: math.MaxUint64,
p: 0,
wantPath: "tile/15/x455/667",
}, {
level: 3,
index: 1234567,
logSize: math.MaxUint64,
wantPath: "tile/3/x001/x234/567",
}, {
level: 15,
index: 123456789,
logSize: math.MaxUint64,
wantPath: "tile/15/x123/x456/789",
p: 41,
wantPath: "tile/15/x123/x456/789.p/41",
},
} {
desc := fmt.Sprintf("level %x index %x", test.level, test.index)
t.Run(desc, func(t *testing.T) {
gotPath := TilePath(test.level, test.index, test.logSize)
gotPath := TilePath(test.level, test.index, test.p)
if gotPath != test.wantPath {
t.Errorf("Got path %q want %q", gotPath, test.wantPath)
}
Expand All @@ -173,102 +141,75 @@ func TestNWithSuffix(t *testing.T) {
for _, test := range []struct {
level uint64
index uint64
logSize uint64
p uint8
wantPath string
}{
{
level: 0,
index: 0,
logSize: 256,
wantPath: "000",
}, {
level: 0,
index: 0,
logSize: 0,
wantPath: "000",
}, {
level: 0,
index: 0,
logSize: 255,
p: 255,
wantPath: "000.p/255",
}, {
level: 1,
index: 0,
logSize: math.MaxUint64,
wantPath: "000",
}, {
level: 1,
index: 0,
logSize: 256,
wantPath: "000.p/1",
}, {
level: 1,
index: 0,
logSize: 1024,
wantPath: "000.p/4",
}, {
level: 15,
index: 455667,
logSize: math.MaxUint64,
wantPath: "x455/667",
}, {
level: 3,
index: 1234567,
logSize: math.MaxUint64,
wantPath: "x001/x234/567",
}, {
level: 15,
index: 123456789,
logSize: math.MaxUint64,
wantPath: "x123/x456/789",
p: 65,
wantPath: "x123/x456/789.p/65",
},
} {
desc := fmt.Sprintf("level %x index %x", test.level, test.index)
t.Run(desc, func(t *testing.T) {
gotPath := NWithSuffix(test.level, test.index, test.logSize)
gotPath := NWithSuffix(test.level, test.index, test.p)
if gotPath != test.wantPath {
t.Errorf("Got path %q want %q", gotPath, test.wantPath)
}
})
}
}

func TestParseTileLevelIndexWidth(t *testing.T) {
func TestParseTileLevelIndexPartial(t *testing.T) {
for _, test := range []struct {
pathLevel string
pathIndex string
wantLevel uint64
wantIndex uint64
wantWidth uint64
wantP uint8
wantErr bool
}{
{
pathLevel: "0",
pathIndex: "x001/x234/067",
wantLevel: 0,
wantIndex: 1234067,
wantWidth: 256,
wantP: 0,
},
{
pathLevel: "0",
pathIndex: "x001/x234/067.p/89",
wantLevel: 0,
wantIndex: 1234067,
wantWidth: 89,
wantP: 89,
},
{
pathLevel: "63",
pathIndex: "x999/x999/x999/x999/x999/999.p/255",
wantLevel: 63,
wantIndex: 999999999999999999,
wantWidth: 255,
wantP: 255,
},
{
pathLevel: "0",
pathIndex: "001",
wantLevel: 0,
wantIndex: 1,
wantWidth: 256,
wantP: 0,
},
{
pathLevel: "0",
Expand Down Expand Up @@ -348,15 +289,15 @@ func TestParseTileLevelIndexWidth(t *testing.T) {
} {
desc := fmt.Sprintf("pathLevel: %q, pathIndex: %q", test.pathLevel, test.pathIndex)
t.Run(desc, func(t *testing.T) {
gotLevel, gotIndex, gotWidth, err := ParseTileLevelIndexWidth(test.pathLevel, test.pathIndex)
gotLevel, gotIndex, gotWidth, err := ParseTileLevelIndexPartial(test.pathLevel, test.pathIndex)
if gotLevel != test.wantLevel {
t.Errorf("got level %d want %d", gotLevel, test.wantLevel)
}
if gotIndex != test.wantIndex {
t.Errorf("got index %d want %d", gotIndex, test.wantIndex)
}
if gotWidth != test.wantWidth {
t.Errorf("got width %d want %d", gotWidth, test.wantWidth)
if gotWidth != test.wantP {
t.Errorf("got width %d want %d", gotWidth, test.wantP)
}
gotErr := err != nil
if gotErr != test.wantErr {
Expand Down
27 changes: 19 additions & 8 deletions api/layout/tile.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,35 @@

package layout

// partialTileSize returns the expected number of leaves in a tile at the given location within
const (
// TileHeight is the maximum number of levels Merkle tree levels a tile represents.
// This is fixed at 8 by tlog-tile spec.
TileHeight = 8
// TileWidth is the maximum number of hashes which can be present in the bottom row of a tile.
TileWidth = 1 << TileHeight
// EntryBundleWidth is the maximum number of entries which can be present in an EntryBundle.
// This is defined to be the same as the width of the node tiles by tlog-tile spec.
EntryBundleWidth = TileWidth
)

// PartialTileSize returns the expected number of leaves in a tile at the given location within
// a tree of the specified logSize, or 0 if the tile is expected to be fully populated.
func partialTileSize(level, index, logSize uint64) uint64 {
sizeAtLevel := logSize >> (level * 8)
fullTiles := sizeAtLevel / 256
func PartialTileSize(level, index, logSize uint64) uint8 {
sizeAtLevel := logSize >> (level * TileHeight)
fullTiles := sizeAtLevel / TileWidth
if index < fullTiles {
return 0
}
return sizeAtLevel % 256
return uint8(sizeAtLevel % TileWidth)
}

// NodeCoordsToTileAddress returns the (TileLevel, TileIndex) in tile-space, and the
// (NodeLevel, NodeIndex) address within that tile of the specified tree node co-ordinates.
func NodeCoordsToTileAddress(treeLevel, treeIndex uint64) (uint64, uint64, uint, uint64) {
tileRowWidth := uint64(1 << (8 - treeLevel%8))
tileLevel := treeLevel / 8
tileRowWidth := uint64(1 << (TileHeight - treeLevel%TileHeight))
tileLevel := treeLevel / TileHeight
tileIndex := treeIndex / tileRowWidth
nodeLevel := uint(treeLevel % 8)
nodeLevel := uint(treeLevel % TileHeight)
nodeIndex := uint64(treeIndex % tileRowWidth)

return tileLevel, tileIndex, nodeLevel, nodeIndex
Expand Down
Loading
Loading