From 9e10a44be8a6bdfa8357e13c5d5b18de0b2774b7 Mon Sep 17 00:00:00 2001 From: Malone Hedges Date: Mon, 8 Aug 2022 16:43:22 -0700 Subject: [PATCH] api/www: add ltd and packedEncoding to tree response (#12) * api: use transaction when creating tree * api: add .air.toml * api: add ltd and packed to get tree response * www: update docs --- .air.toml | 32 +++++++++++++++++++++++++++++ .gitignore | 1 + api/db/queries/merkle.sql.go | 32 +++++++++++++++++------------ api/db/sql/queries/merkle.sql | 4 ++-- api/tree.go | 38 +++++++++++++++++++++++++++++------ example/src/api.ts | 21 +++++++++---------- www/pages/docs.tsx | 6 +++++- 7 files changed, 100 insertions(+), 34 deletions(-) create mode 100644 .air.toml create mode 100644 .gitignore diff --git a/.air.toml b/.air.toml new file mode 100644 index 0000000..233b0cf --- /dev/null +++ b/.air.toml @@ -0,0 +1,32 @@ +root = "." +tmp_dir = "tmp" + +[build] + bin = "./tmp/main" + cmd = "go build -o ./tmp/main ./cmd/api/main.go" + delay = 1000 + exclude_dir = ["tmp", "contracts", "example", "www"] + exclude_file = [] + exclude_regex = ["node_modules"] + exclude_unchanged = false + follow_symlink = false + full_bin = "" + include_dir = [] + include_ext = ["go", "tpl", "tmpl", "html"] + kill_delay = "0s" + log = "build-errors.log" + send_interrupt = false + stop_on_error = true + +[color] + app = "" + build = "yellow" + main = "magenta" + runner = "green" + watcher = "cyan" + +[log] + time = false + +[misc] + clean_on_exit = false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3fec32c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +tmp/ diff --git a/api/db/queries/merkle.sql.go b/api/db/queries/merkle.sql.go index b5770bc..804072c 100644 --- a/api/db/queries/merkle.sql.go +++ b/api/db/queries/merkle.sql.go @@ -56,19 +56,6 @@ func (q *Queries) InsertTree(ctx context.Context, arg InsertTreeParams) error { return err } -const selectLeaves = `-- name: SelectLeaves :one -select unhashed_leaves -from merkle_trees -where root = $1 -` - -func (q *Queries) SelectLeaves(ctx context.Context, root []byte) ([][]byte, error) { - row := q.db.QueryRow(ctx, selectLeaves, root) - var unhashed_leaves [][]byte - err := row.Scan(&unhashed_leaves) - return unhashed_leaves, err -} - const selectProofByAddress = `-- name: SelectProofByAddress :many select proof from merkle_proofs @@ -119,3 +106,22 @@ func (q *Queries) SelectProofByUnhashedLeaf(ctx context.Context, arg SelectProof err := row.Scan(&proof) return proof, err } + +const selectTree = `-- name: SelectTree :one +select unhashed_leaves, ltd, packed +from merkle_trees +where root = $1 +` + +type SelectTreeRow struct { + UnhashedLeaves [][]byte `json:"unhashedLeaves"` + Ltd []string `json:"ltd"` + Packed sql.NullBool `json:"packed"` +} + +func (q *Queries) SelectTree(ctx context.Context, root []byte) (SelectTreeRow, error) { + row := q.db.QueryRow(ctx, selectTree, root) + var i SelectTreeRow + err := row.Scan(&i.UnhashedLeaves, &i.Ltd, &i.Packed) + return i, err +} diff --git a/api/db/sql/queries/merkle.sql b/api/db/sql/queries/merkle.sql index 3126526..8f72aa7 100644 --- a/api/db/sql/queries/merkle.sql +++ b/api/db/sql/queries/merkle.sql @@ -3,8 +3,8 @@ insert into merkle_trees (root, unhashed_leaves, ltd, packed) values ($1, $2, $3, $4) on conflict (root) do nothing; --- name: SelectLeaves :one -select unhashed_leaves +-- name: SelectTree :one +select unhashed_leaves, ltd, packed from merkle_trees where root = $1; diff --git a/api/tree.go b/api/tree.go index 9d1ff01..c4cf445 100644 --- a/api/tree.go +++ b/api/tree.go @@ -99,6 +99,13 @@ func (jnb *jsonNullBool) UnmarshalJSON(d []byte) error { return nil } +func (jnb jsonNullBool) MarshalJSON() ([]byte, error) { + if jnb.Valid { + return json.Marshal(jnb.Bool) + } + return json.Marshal(nil) +} + type createTreeReq struct { Leaves []hexutil.Bytes `json:"unhashedLeaves"` Ltd []string `json:"leafTypeDescriptor"` @@ -121,12 +128,21 @@ func (s *Server) CreateTree(w http.ResponseWriter, r *http.Request) { return } + tx, err := s.db.Begin(r.Context()) + defer tx.Rollback(r.Context()) + if err != nil { + s.sendJSONError(r, w, err, http.StatusInternalServerError, "Failed to start transaction") + return + } + + q := s.dbq.WithTx(tx) + var leaves [][]byte for i := range req.Leaves { leaves = append(leaves, req.Leaves[i]) } tree := merkle.New(leaves, merkle.SortPairs) - err := s.dbq.InsertTree(r.Context(), queries.InsertTreeParams{ + err = q.InsertTree(r.Context(), queries.InsertTreeParams{ Root: tree.Root(), UnhashedLeaves: leaves, Ltd: req.Ltd, @@ -143,7 +159,7 @@ func (s *Server) CreateTree(w http.ResponseWriter, r *http.Request) { s.sendJSONError(r, w, nil, http.StatusBadRequest, "Must provide addresses that result in a proof") return } - err := s.dbq.InsertProof(r.Context(), queries.InsertProofParams{ + err := q.InsertProof(r.Context(), queries.InsertProofParams{ Root: tree.Root(), UnhashedLeaf: leaf, Address: leaf2AddrBytes(leaf, req.Ltd, req.Packed.Bool), @@ -155,6 +171,12 @@ func (s *Server) CreateTree(w http.ResponseWriter, r *http.Request) { } } + err = tx.Commit(r.Context()) + if err != nil { + s.sendJSONError(r, w, err, http.StatusInternalServerError, "Failed to persist") + return + } + s.sendJSON(r, w, createTreeResp{ MerkleRoot: fmt.Sprintf("0x%s", hex.EncodeToString(tree.Root())), }) @@ -163,6 +185,8 @@ func (s *Server) CreateTree(w http.ResponseWriter, r *http.Request) { type getTreeResp struct { UnhashedLeaves []hexutil.Bytes `json:"unhashedLeaves"` LeafCount int `json:"leafCount"` + Ltd []string `json:"leafTypeDescriptor"` + Packed jsonNullBool `json:"packedEncoding"` } func (s *Server) GetTree(w http.ResponseWriter, r *http.Request) { @@ -171,7 +195,7 @@ func (s *Server) GetTree(w http.ResponseWriter, r *http.Request) { s.sendJSONError(r, w, nil, http.StatusBadRequest, "missing root") return } - leaves, err := s.dbq.SelectLeaves(r.Context(), common.FromHex(root)) + row, err := s.dbq.SelectTree(r.Context(), common.FromHex(root)) if errors.Is(err, pgx.ErrNoRows) { s.sendJSONError(r, w, err, http.StatusNotFound, "tree not found for root") return @@ -181,11 +205,13 @@ func (s *Server) GetTree(w http.ResponseWriter, r *http.Request) { } var l []hexutil.Bytes - for i := range leaves { - l = append(l, leaves[i]) + for i := range row.UnhashedLeaves { + l = append(l, row.UnhashedLeaves[i]) } s.sendJSON(r, w, getTreeResp{ UnhashedLeaves: l, - LeafCount: len(leaves), + LeafCount: len(row.UnhashedLeaves), + Ltd: row.Ltd, + Packed: jsonNullBool{row.Packed}, }) } diff --git a/example/src/api.ts b/example/src/api.ts index bfebc88..800062f 100644 --- a/example/src/api.ts +++ b/example/src/api.ts @@ -21,13 +21,17 @@ const createTree = async ( }), }) - const encodedTree: { merkleRoot: string } = await encodedTreeRes.json() - return encodedTree + return await encodedTreeRes.json() } const getTree = async ( merkleRoot: string, -): Promise<{ unhashedLeaves: string[]; leafCount: number }> => { +): Promise<{ + unhashedLeaves: string[] + leafCount: number + leafTypeDescriptor: string[] | null + packedEncoding: boolean | null +}> => { const getTreeRes = await fetch(`${baseUrl}/api/v1/tree?root=${merkleRoot}`, { method: 'GET', headers: { @@ -36,11 +40,7 @@ const getTree = async ( }, }) - const tree: { - unhashedLeaves: string[] - leafCount: number - } = await getTreeRes.json() - return tree + return await getTreeRes.json() } const getProofForUnhashedLeaf = async ( @@ -58,10 +58,7 @@ const getProofForUnhashedLeaf = async ( }, ) - const proof: { - proof: string[] - } = await proofRes.json() - return proof + return await proofRes.json() } /** Using this endpoint is discouraged. When possible, pass `unhashedLeaf` instead */ diff --git a/www/pages/docs.tsx b/www/pages/docs.tsx index 4e20905..d9cfe16 100644 --- a/www/pages/docs.tsx +++ b/www/pages/docs.tsx @@ -31,7 +31,11 @@ GET /api/v1/tree?root={root} "0x0000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000002" ], - "leafCount": 400 + "leafCount": 2, + + // in general you can ignore the two following fields + "leafTypeDescriptor": null, // or an array of solidity types + "packedEncoding": null // or a boolean value } `.trim()