### Install Trillian Development Tools Locally Source: https://github.com/google/trillian/blob/master/README.md Installs essential development tools for Trillian locally using 'go install'. Ensure you are in the Trillian directory before running these commands. ```bash cd $(go list -f '{{ .Dir }}' github.com/google/trillian); \ go install github.com/golang/mock/mockgen; \ go install google.golang.org/protobuf/proto; \ go install google.golang.org/protobuf/cmd/protoc-gen-go; \ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc; \ go install github.com/pseudomuto/protoc-gen-doc/cmd/protoc-gen-doc; \ go install golang.org/x/tools/cmd/stringer ``` -------------------------------- ### Storage.Begin() Source: https://github.com/google/trillian/blob/master/docs/storage/commit_log/commit_log_based_storage_design.md Starts a Trillian transaction by reading and verifying the current state from the database and Kafka. ```APIDOC ## Storage.Begin() ### Description Starts a Trillian transaction. This operation reads the current STH, treeRevision, and sthOffset from the database and verifies them against the corresponding entry in Kafka. It returns a LogTX struct containing these values. ### Method `Begin` (method on `CQComboStorage`) ### Endpoint N/A (This is a method call within the Trillian system) ### Parameters None ### Request Body None ### Request Example ```golang // Example usage (conceptual) logTX, err := storage.Begin() ``` ### Response #### Success Response - **LogTX** (LogTX) - A transaction object containing the initial state. - **error** (error) - An error if the transaction could not be started. #### Response Example ```golang // Conceptual success response // logTX object would be returned ``` #### Error Handling - Returns an error if the local DB has data ahead of the STHs topic. - Returns an error if the local DB committed to an invalid STH from the topic. - Returns an error if the local DB has different data than the STHs topic. ``` -------------------------------- ### Install golangci-lint Source: https://github.com/google/trillian/blob/master/README.md Installs the golangci-lint linter from source to version v2.10.1. This tool is used for running code linters as part of the codebase checks. ```bash go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.10.1 ``` -------------------------------- ### Start Trillian Servers with Etcd Quotas Source: https://github.com/google/trillian/blob/master/quota/etcd/README.md Ensure logserver and logsigner are started with the necessary flags to enable etcd quotas. The `--etcd_servers` flag specifies the etcd connection, and `--quota_system=etcd` activates the etcd quota manager. ```bash trillian_log_server \ --etcd_servers=... \ --rpc_endpoint=localhost:8090 \ --quota_system=etcd trillian_log_signer --etcd_servers=... --quota_system=etcd ``` -------------------------------- ### Start Trillian Transaction with Storage.Begin() Source: https://github.com/google/trillian/blob/master/docs/storage/commit_log/commit_log_based_storage_design.md Initiates a Trillian transaction by reading and verifying the latest STH from the database and Kafka. The HBase implementation buffers writes locally until `Commit` is called. ```golang func (ls *CQComboStorage) Begin() (LogTX, error) { // create db and cq "TX" objects tx := &splitTX{...} // read `dbSTH` (containing `treeRevision` and `sthOffset`) from local DB tx.dbSTH, tx.treeRevision, tx.stdOffset := dbTX.latestSTH() // Sanity check that the STH table has what we already know. ourSTH := cqTX.GetSTHAt(tx.sthOffset) if ourSTH == nil { return nil, fmt.Errorf("should not happen - local DB has data ahead of STHs topic") } if ourSTH.expectedOffset != dbSTH.sthOffset { return nil, fmt.Errorf("should not happen - local DB committed to invalid STH from topic") } if ourSTH.timestamp != dbSTH.timestamp || ourSTH.tree_size != dbSTH.tree_size { return nil, fmt.Errorf("should not happen - local DB has different data than STHs topic") } ... return tx, nil } ``` -------------------------------- ### Create a Sequencing-Based Global Write Quota Source: https://github.com/google/trillian/blob/master/quota/etcd/README.md Use grpcurl to create a new quota configuration. This example sets up a global write quota with a max_tokens value implying a 4-hour backlog based on an expected 50 QPS sequencing performance. The quota state is set to ENABLED. ```bash grpcurl -plaintext -d @ localhost:8090 v1beta1/quotas/global/write/config < cd cmd/trillian_log_server && go build && ls -sh trillian_log_server 62M trillian_log_server* ``` -------------------------------- ### Get Inclusion Proof API Source: https://github.com/google/trillian/blob/master/docs/api.md Retrieves an inclusion proof for a specific leaf index within a log. Includes charge-to information. ```APIDOC ## POST /trillian.Trillian/GetInclusionProof ### Description Retrieves an inclusion proof for a specific leaf index. The `proof` field may be empty if the requested `tree_size` is larger than the server's current tree size, indicating potential server instance skew. In such cases, `signed_log_root` will reflect the server's known tree size. ### Method POST ### Endpoint /trillian.Trillian/GetInclusionProof ### Parameters #### Request Body - **log_id** (int64) - Required - The ID of the log. - **leaf_index** (int64) - Required - The index of the leaf for which to retrieve the proof. - **tree_size** (int64) - Required - The target tree size for the proof. - **charge_to** (ChargeTo) - Optional - Information about who to charge for the operation. ### Response #### Success Response (200) - **proof** (Proof) - The inclusion proof. - **signed_log_root** (SignedLogRoot) - The signed root of the log at the time of the proof. #### Response Example ```json { "proof": { "leaf_index": "123", "leaf_hash": "...", "audit_path": ["...", "..."] }, "signed_log_root": { "log_id": "456", "timestamp": "1678886400", "root_hash": "...", "tree_size": "1000", "signature": { "sig": "..." } } } ``` ``` -------------------------------- ### Download Proto Definitions using wget Source: https://github.com/google/trillian/blob/master/third_party/googleapis/README.md This script downloads specific proto definitions from the Googleapis repository using wget. Ensure the GA_VERSION environment variable is set to the desired commit hash. ```sh export GA_VERSION=c81bb701eb53991d6faf74b2656eaf539261a122 mkdir -p google/api wget https://raw.githubusercontent.com/googleapis/googleapis/$GA_VERSION/google/api/annotations.proto -O google/api/annotations.proto wget https://raw.githubusercontent.com/googleapis/googleapis/$GA_VERSION/google/api/http.proto -O google/api/http.proto mkdir -p google/rpc wget https://raw.githubusercontent.com/googleapis/googleapis/$GA_VERSION/google/rpc/code.proto -O google/rpc/code.proto wget https://raw.githubusercontent.com/googleapis/googleapis/$GA_VERSION/google/rpc/status.proto -O google/rpc/status.proto ``` -------------------------------- ### Initialize and Apply Terraform Infrastructure Source: https://github.com/google/trillian/blob/master/deployment/README.md Use this command to initialize Terraform and apply the infrastructure for Trillian deployment. Replace PROJECT_ID with your actual Google Cloud Project ID. ```shell terraform init && terraform apply -var="gcp_project=PROJECT_ID" ``` -------------------------------- ### Build Trillian Log Server with MySQL and PostgreSQL Source: https://github.com/google/trillian/blob/master/storage/README.md Builds the `trillian_log_server` binary including both MySQL and PostgreSQL storage and their associated quota implementations. This results in a moderate binary size. ```bash > cd cmd/trillian_log_server && go build -tags=mysql,postgresql && ls -sh trillian_log_server 40M trillian_log_server* ``` -------------------------------- ### Prepare MySQL Database Source: https://github.com/google/trillian/blob/master/examples/deployment/kubernetes/mysql/README.md Run the script to prepare the MySQL database for Trillian usage after the cluster has been provisioned. This script initializes the database schema and settings. ```shell $GOPATH/src/github.com/google/trillian/storage/mysql/kubernetes/resetdb.sh ``` -------------------------------- ### List All Configured Quotas Source: https://github.com/google/trillian/blob/master/quota/etcd/README.md Retrieve a full list of all currently configured quotas using grpcurl. The `view: "FULL"` option ensures all details of each quota are returned. ```bash grpcurl -plaintext -d '{"view": "FULL"}' localhost:8090 v1beta1/quotas ``` -------------------------------- ### Get Inclusion Proof by Hash API Source: https://github.com/google/trillian/blob/master/docs/api.md Retrieves an inclusion proof for a given leaf hash within a specified tree size. Supports ordering by sequence and includes charge-to information. ```APIDOC ## POST /trillian.Trillian/GetInclusionProofByHash ### Description Retrieves an inclusion proof for a given leaf hash. The `proof` field in the response can be repeated if multiple leaves have the same hash. If a leaf's index is beyond the requested `tree_size`, its corresponding proof entry will be missing. ### Method POST ### Endpoint /trillian.Trillian/GetInclusionProofByHash ### Parameters #### Request Body - **log_id** (int64) - Required - The ID of the log. - **leaf_hash** (bytes) - Required - The Merkle tree hash of the leaf entry to be retrieved. - **tree_size** (int64) - Required - The target tree size for the proof. - **order_by_sequence** (bool) - Optional - If true, results are ordered by sequence. - **charge_to** (ChargeTo) - Optional - Information about who to charge for the operation. ### Response #### Success Response (200) - **proof** (Proof) - Repeated - The inclusion proof(s). - **signed_log_root** (SignedLogRoot) - The signed root of the log at the time of the proof. #### Response Example ```json { "proof": [ { "leaf_index": "123", "leaf_hash": "...", "audit_path": ["...", "..."] } ], "signed_log_root": { "log_id": "456", "timestamp": "1678886400", "root_hash": "...", "tree_size": "1000", "signature": { "sig": "..." } } } ``` ``` -------------------------------- ### Build and Fetch Trillian Code Source: https://github.com/google/trillian/blob/master/README.md Clone the Trillian repository, navigate into the directory, and build all Go packages. This command fetches dependencies and compiles the project. ```bash git clone https://github.com/google/trillian.git cd trillian go build ./... ``` -------------------------------- ### Get Latest Signed Log Root API Source: https://github.com/google/trillian/blob/master/docs/api.md Retrieves the latest signed root of a log. Optionally, it can return a consistency proof between a specified previous tree size and the latest size. ```APIDOC ## POST /trillian.Trillian/GetLatestSignedLogRoot ### Description Retrieves the latest signed root of a log. If `first_tree_size` is provided and non-zero, the response will include a consistency proof between `first_tree_size` and the new tree size, provided `first_tree_size` is not greater than the available tree size on the server. ### Method POST ### Endpoint /trillian.Trillian/GetLatestSignedLogRoot ### Parameters #### Request Body - **log_id** (int64) - Required - The ID of the log. - **charge_to** (ChargeTo) - Optional - Information about who to charge for the operation. - **first_tree_size** (int64) - Optional - If non-zero, a consistency proof will be included if this size is valid. ### Response #### Success Response (200) - **signed_log_root** (SignedLogRoot) - The latest signed root of the log. - **proof** (Proof) - A consistency proof, if `first_tree_size` was requested and valid. #### Response Example ```json { "signed_log_root": { "log_id": "456", "timestamp": "1678886400", "root_hash": "...", "tree_size": "1000", "signature": { "sig": "..." } }, "proof": { "leaf_index": "0", "leaf_hash": "...", "audit_path": ["...", "..."] } } ``` ``` -------------------------------- ### Build Experimental Map with Beam Go Source: https://github.com/google/trillian/blob/master/experimental/batchmap/README.md Use this command to run the Beam Go pipeline for map generation. Adjust output directory and runner as needed. Be mindful of file system inodes for large key counts or strata. ```bash go run ./cmd/build/mapdemo.go --output=/tmp/mapv1 --runner=PrismRunner ``` -------------------------------- ### Set Up AWS Deployment Variables Source: https://github.com/google/trillian/blob/master/examples/deployment/aws/README.md Before deploying, set environment variables for the database password and network access CIDR. Ensure your AWS keys are accessible to Terraform. ```shell cd examples/deployment/aws/ # Set a random password export TF_VAR_DB_PASSWORD "$(openssl rand -hex 16)" # Substitute this variable with a block you'll be accessing from export TF_VAR_WHITELIST_CIDR="0.0.0.0/0" ``` -------------------------------- ### Test Map Hasher Scheme: Empty Subtree Hashing Source: https://github.com/google/trillian/blob/master/docs/MapHashers.md Recursively defines the hash of an empty subtree at a given level 'n' in the test MapHasher scheme. It starts with the hash of an empty leaf (E0) and applies HashChildren. ```go n = 0: E0 := HashLeaf(nil) n = 1: E1 := HashChildren(E0, E0) n = 2: E2 := HashChildren(E1, E1) ... ``` -------------------------------- ### Run Trillian Deployment Script Source: https://github.com/google/trillian/blob/master/docs/CloudSpanner.md Execute the deployment script after configuring environment variables. This script initiates the Trillian deployment on Google Cloud. ```bash ./scripts/deploy_gce.sh ``` -------------------------------- ### Update Trillian Dependencies Source: https://github.com/google/trillian/blob/master/README.md Commands for managing Trillian's dependencies using Go modules. Use 'go get' to fetch specific versions or the latest commit, and 'go mod tidy' to clean up unused dependencies. ```bash go get package/path # Fetch the latest published version go get package/path@X.Y.Z # Fetch a specific published version go get package/path@HEAD # Fetch the latest commit go get -u go mod tidy ``` -------------------------------- ### Build Trillian Log Server with CRDB Implementation Source: https://github.com/google/trillian/blob/master/storage/README.md Builds the `trillian_log_server` binary with only the CockroachDB (crdb) storage and associated quota implementation. This significantly reduces the binary size. ```bash > cd cmd/trillian_log_server && go build -tags=crdb && ls -sh trillian_log_server 37M trillian_log_server* ``` -------------------------------- ### Render Domain Model to Full Model Source: https://github.com/google/trillian/blob/master/docs/claimantmodel/experimental/cmd/render/README.md Use this command to render a domain-level Claimant Model into a full model, outputting snippets to stdout. Iterate on the input YAML based on the output. ```bash go run ./docs/claimantmodel/experimental/cmd/render --domain_model_file /tmp/cmfun/model.yaml ``` -------------------------------- ### Provision a Trillian Tree Source: https://github.com/google/trillian/blob/master/examples/deployment/kubernetes/README.md Use this script to provision a new tree into the Trillian log. It utilizes kubectl to forward requests to the Trillian Log's admin API. Ensure you have the example-config.sh file available. ```bash ./provision_tree.sh example-config.sh ``` -------------------------------- ### Highlight Inclusion Proof in Merkle Tree Source: https://github.com/google/trillian/blob/master/docs/merkletree/treetex/README.md Visualize an inclusion proof by specifying the leaf index with the --inclusion flag. This highlights the path from the specified leaf to the root. ```bash go run github.com/google/trillian/docs/merkletree/treetex --tree_size=23 --inclusion=13 ``` -------------------------------- ### InitLog API Source: https://github.com/google/trillian/blob/master/docs/api.md Initializes a new log within the Trillian system. This operation requires a Log ID and optionally accepts charge-to information. ```APIDOC ## POST /trillian/InitLog ### Description Initializes a new log. The server will assign a new Log ID if one is not provided. ### Method POST ### Endpoint /trillian/InitLog ### Request Body - **log_id** (int64) - Optional - The ID to assign to the new log. If not provided, the server will generate one. - **charge_to** (ChargeTo) - Optional - Information about who is to be charged for the log creation. ### Request Example ```json { "log_id": 12345, "charge_to": { "name": "example-user" } } ``` ### Response #### Success Response (200) - **created** (SignedLogRoot) - The signed root of the newly created log. ``` -------------------------------- ### Verify Experimental Map Source: https://github.com/google/trillian/blob/master/experimental/batchmap/README.md Verify a specific key's inclusion and commitment within the generated map tiles. Ensure map directory, logtostderr flag, and other parameters match those used during map construction. ```bash go run cmd/verify/verify.go --logtostderr --map_dir=/tmp/mapv1 --key=5 ``` -------------------------------- ### Create Kubernetes Secret for Spanner Key Source: https://github.com/google/trillian/blob/master/docs/CloudSpanner.md Use this command to create a Kubernetes secret from a downloaded service account key file. Ensure the key is properly restricted. ```bash kubectl create secret generic spanner-key --from-file=key.json=service-key.json ``` -------------------------------- ### Abstract Signer Process in Go Source: https://github.com/google/trillian/blob/master/docs/storage/commit_log/commit_log_based_storage_design.md This Go code outlines the abstract process for a Trillian signer. It handles reading from local DB, sanity checks against the Kafka STHs topic, and either updating the STHs topic as master or catching up with existing STHs. Ensure idempotency for retries. ```go func SignerRun() { // if any of the below operations fail, just bail and retry // read `dbSTH` (containing `treeRevision` and `sthOffset`) from local DB dbSTH.treeRevision, dbSTH.sthOffset = tx.LatestSTH() // Sanity check that the STH table has what we already know. ourSTH := kafka.Read("STHs/", dbSTH.sthOffset) if ourSTH == nil { klog.Errorf("should not happen - local DB has data ahead of STHs topic") return } if ourSTH.expectedOffset != dbSTH.sthOffset { klog.Errorf("should not happen - local DB committed to invalid STH from topic") return } if ourSTH.timestamp != dbSTH.timestamp || ourSTH.tree_size != dbSTH.tree_size { klog.Errorf("should not happen - local DB has different data than STHs topic") return } // Look to see if anyone else has already stored data just ahead of our STH. nextOffset := dbSTH.sthOffset nextSTH := nil for { nextOffset++ nextSTH = kafka.Read("STHs/", nextOffset) if nextSTH == nil { break } if nextSTH.expectedOffset != nextOffset { // Someone's been writing STHs when they weren't supposed to be, skip // this one until we find another which is in-sync. klog.Warning("skipping unexpected STH") continue } if nextSTH.timestamp < ourSTH.timestamp || nextSTH.tree_size < ourSTH.tree_size { klog.Fatal("should not happen - earlier STH with later offset") return } } if nextSTH == nil { // We're up-to-date with the STHs topic (as of a moment ago) ... if !IsMaster() { // ... but we're not allowed to create fresh STHs. return } // ... and we're the master. Move the STHs topic along to encompass any unincorporated leaves. offset := dbSTH.tree_size batch := kafka.Read("Leaves", offset, batchSize) for b := range batch { db.Put("//leaves/", b.contents) } root := UpdateMerkleTreeAndBufferNodes(batch, treeRevision+1) newSTH := STH{root, ...} newSTH.treeRevision = dbSTH.treeRevision + 1 newSTH.expectedOffset = nextOffset actualOffset := kafka.Append("STHs/", newSTH) if actualOffset != nextOffset { klog.Warning("someone else wrote an STH while we were master") tx.Abort() return } newSTH.sthOffset = actualOffset tx.BufferNewSTHForDB(newSTH) tx.Commit() // flush writes } else { // There is an STH one ahead of us that we're not caught up with yet. // Read the leaves between what we have in our DB, and that STH... leafRange := InclusiveExclusive(dbSTH.tree_size, nextSTH.tree_size) batch := kafka.Read("Leaves", leafRange) // ... and store them in our local DB for b := range batch { db.Put("/leaves/", b.contents) } newRoot := tx.UpdateMerkleTreeAndBufferNodes(batch, treeRevision+1) if newRoot != nextSTH.root { klog.Warning("calculated root hash != expected root hash, corrupt DB?") tx.Abort() return } tx.BufferNewSTHForDB(nextSTH) tx.Commit() // flush writes // We may still not be caught up, but that's for the next time around. } } ``` -------------------------------- ### Render Full Claimant Model Source: https://github.com/google/trillian/blob/master/docs/claimantmodel/experimental/cmd/render/README.md After resolving TODOs in the full model YAML, use this command to generate markdown snippets and a mermaid sequence diagram. This command assumes the full model file has been saved. ```bash go run ./docs/claimantmodel/experimental/cmd/render --full_model_file /tmp/cmfun/full.yaml ``` -------------------------------- ### Monitor Log Integration Metrics Source: https://github.com/google/trillian/blob/master/docs/howto/freeze_a_ct_log.md Fetch and filter metrics from the signer to monitor the integration status of queued leaves for a specific log ID. This helps confirm that the log has finished processing all entries before freezing. ```bash curl ${METRICS_URI} | grep ${LOG_ID} | grep -v delay | grep -v latency | grep -v quota ```