package main
import (
"context"
"crypto"
"encoding/json"
"flag"
"fmt"
"os"
"path/filepath"
"reflect"
"github.com/apache/beam/sdks/v2/go/pkg/beam"
"github.com/apache/beam/sdks/v2/go/pkg/beam/io/filesystem/local"
"github.com/apache/beam/sdks/v2/go/pkg/beam/x/beamx"
"k8s.io/klog/v2"
"github.com/google/trillian/experimental/batchmap"
"github.com/google/trillian/merkle/coniks"
"github.com/google/trillian/merkle/smt/node"
)
const hash = crypto.SHA512_256
var (
output = flag.String("output", "", "Output directory in which the tiles will be written.")
valueSalt = flag.String("value_salt", "v1", "Some string that will be smooshed in with the generated value before hashing. Allows generated values to be deterministic but variable.")
startKey = flag.Int64("start_key", 0, "Keys will be generated starting with this index.")
keyCount = flag.Int64("key_count", 1<<5, "The number of keys that will be placed in the map.")
treeID = flag.Int64("tree_id", 12345, "The ID of the tree. Used as a salt in hashing.")
prefixStrata = flag.Int("prefix_strata", 1, "The number of strata of 8-bit strata before the final strata. 3 is optimal for trees up to 2^30. 10 is required to import into Trillian.")
)
func init() {
beam.RegisterType(reflect.TypeOf((*mapEntryFn)(nil)).Elem())
beam.RegisterType(reflect.TypeOf((*writeTileFn)(nil)).Elem())
}
func main() {
klog.InitFlags(nil)
flag.Parse()
beam.Init()
output := filepath.Clean(*output)
if output == "" {
klog.Exitf("No output provided")
}
if _, err := os.Stat(output); os.IsNotExist(err) {
if err = os.Mkdir(output, 0o700); err != nil {
klog.Fatalf("couldn't find or create directory %s, %v", output, err)
}
}
p, s := beam.NewPipelineWithRoot()
entries := beam.ParDo(s, &mapEntryFn{*valueSalt, *treeID}, createRange(s, *startKey, *keyCount))
allTiles, err := batchmap.Create(s, entries, *treeID, hash, *prefixStrata)
if err != nil {
klog.Fatalf("Failed to create pipeline: %v", err)
}
beam.ParDo0(s, &writeTileFn{output}, allTiles)
if err := beamx.Run(context.Background(), p); err != nil {
klog.Fatalf("Failed to execute job: %v", err)
}
}
type mapEntryFn struct {
Salt string
TreeID int64
}
func (fn *mapEntryFn) ProcessElement(i int64) *batchmap.Entry {
h := hash.New()
h.Write([]byte(fmt.Sprintf("%d", i)))
kbs := h.Sum(nil)
leafID := node.NewID(string(kbs), uint(len(kbs)*8))
data := []byte(fmt.Sprintf("[%s]%d", fn.Salt, i))
return &batchmap.Entry{
HashKey: kbs,
HashValue: coniks.Default.HashLeaf(fn.TreeID, leafID, data),
}
}
type writeTileFn struct {
Directory string
}
func (fn *writeTileFn) ProcessElement(ctx context.Context, t *batchmap.Tile) error {
fs := local.New(ctx)
w, err := fs.OpenWrite(ctx, fmt.Sprintf("%s/path_%x", fn.Directory, t.Path))
if err != nil {
return err
}
defer func() {
if err := w.Close(); err != nil {
klog.Errorf("Close(): %v", err)
}
}()
bs, err := json.Marshal(t)
if err != nil {
return err
}
_, err = w.Write(bs)
return err
}
func createRange(s beam.Scope, start, count int64) beam.PCollection {
values := make([]int64, count)
for i := int64(0); i < count; i++ {
values[i] = start + i
}
return beam.CreateList(s, values)
}