Skip to content

Commit 3298d38

Browse files
authored
api,client,www: add roots endpoint, deprecate root endpoint (#78)
1 parent 68fbad5 commit 3298d38

File tree

7 files changed

+52
-39
lines changed

7 files changed

+52
-39
lines changed

api/api.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ func (s *Server) Handler(env, gitSha string) http.Handler {
3434
mux.HandleFunc("/api/v1/tree", s.TreeHandler)
3535
mux.HandleFunc("/api/v1/proof", s.GetProof)
3636
mux.HandleFunc("/api/v1/root", s.GetRoot)
37+
mux.HandleFunc("/api/v1/roots", s.GetRoot)
3738
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
3839
fmt.Fprint(w, gitSha)
3940
})

api/root.go

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package api
22

33
import (
44
"encoding/json"
5-
"errors"
65
"net/http"
76
"strings"
87

@@ -30,6 +29,11 @@ func proofURLToDBQuery(param string) string {
3029
func (s *Server) GetRoot(w http.ResponseWriter, r *http.Request) {
3130
type rootResp struct {
3231
Root hexutil.Bytes `json:"root"`
32+
Note string `json:"note"`
33+
}
34+
35+
type rootsResp struct {
36+
Roots []hexutil.Bytes `json:"roots"`
3337
}
3438

3539
var (
@@ -45,30 +49,34 @@ func (s *Server) GetRoot(w http.ResponseWriter, r *http.Request) {
4549
const q = `
4650
SELECT root
4751
FROM trees
48-
WHERE proofs_array(proofs) <@ proofs_array($1);
52+
WHERE proofs_array(proofs) @> proofs_array($1);
4953
`
50-
rr := rootResp{}
54+
roots := make([]hexutil.Bytes, 0)
55+
rb := make(hexutil.Bytes, 0)
5156

52-
// should only return one row, using QueryFunc to verify
53-
// that's the case and return an error if not (we've had
54-
// issues with this in the past)
55-
hasRow := false
56-
_, err := s.db.QueryFunc(ctx, q, []interface{}{dbQuery}, []interface{}{&rr.Root}, func(qfr pgx.QueryFuncRow) error {
57-
if hasRow {
58-
return errors.New("multiple rows returned")
59-
}
60-
hasRow = true
57+
_, err := s.db.QueryFunc(ctx, q, []interface{}{dbQuery}, []interface{}{&rb}, func(qfr pgx.QueryFuncRow) error {
58+
roots = append(roots, rb)
6159
return nil
6260
})
6361

6462
if err != nil {
6563
s.sendJSONError(r, w, err, http.StatusInternalServerError, "selecting root")
6664
return
67-
} else if len(rr.Root) == 0 { // db.QueryFunc doesn't return pgx.ErrNoRows
65+
} else if len(roots) == 0 { // db.QueryFunc doesn't return pgx.ErrNoRows
6866
s.sendJSONError(r, w, nil, http.StatusNotFound, "root not found for proofs")
6967
return
7068
}
7169

7270
w.Header().Set("Cache-Control", "public, max-age=3600")
73-
s.sendJSON(r, w, rr)
71+
72+
if strings.HasPrefix(r.URL.Path, "/api/v1/roots") {
73+
s.sendJSON(r, w, rootsResp{Roots: roots})
74+
} else {
75+
// The original functionality of this endpoint, getting one root for
76+
// a given proof, is deprecated. This is because for smaller trees,
77+
// there are often collisions with the same root for different proofs.
78+
// This bit of code is for backwards compatibility.
79+
const note = `This endpoint is deprecated. For smaller trees, there are often collisions with the same root for different proofs. Please use the /v1/api/roots endpoint instead.`
80+
s.sendJSON(r, w, rootResp{Root: roots[0], Note: note})
81+
}
7482
}

clients/go/lanyard/client.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -260,20 +260,22 @@ func (c *Client) GetProofFromAddr(
260260
return resp, nil
261261
}
262262

263-
type RootResponse struct {
264-
Root hexutil.Bytes `json:"root"`
263+
type RootsResponse struct {
264+
Roots []hexutil.Bytes `json:"roots"`
265265
}
266266

267267
// If a Merkle tree has been published to Lanyard,
268-
// GetRootFromLeaf will return the root of the tree
268+
// GetRootsFromProof will return the root of the tree
269269
// based on a proof of a leaf. This endpoint will return
270270
// ErrNotFound if the tree associated with the
271-
// leaf has not been published.
272-
func (c *Client) GetRootFromProof(
271+
// leaf has not been published. This API response is deprecated
272+
// as there may be more than one root per proof. Use GetRootsFromProof
273+
// instead.
274+
func (c *Client) GetRootsFromProof(
273275
ctx context.Context,
274276
proof []hexutil.Bytes,
275-
) (*RootResponse, error) {
276-
resp := &RootResponse{}
277+
) (*RootsResponse, error) {
278+
resp := &RootsResponse{}
277279

278280
if len(proof) == 0 {
279281
return nil, xerrors.New("proof must not be empty")
@@ -286,7 +288,7 @@ func (c *Client) GetRootFromProof(
286288

287289
err := c.sendRequest(
288290
ctx, http.MethodGet,
289-
fmt.Sprintf("/root?proof=%s",
291+
fmt.Sprintf("/roots?proof=%s",
290292
strings.Join(pq, ","),
291293
),
292294
nil, resp,

clients/go/lanyard/client_integration_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,21 +86,21 @@ func TestGetProofFromAddr(t *testing.T) {
8686
}
8787
}
8888

89-
func TestGetRootFromProof(t *testing.T) {
89+
func TestGetRootsFromProof(t *testing.T) {
9090
p, err := client.GetProofFromLeaf(context.Background(), hexutil.MustDecode(basicRoot), basicMerkle[0])
9191

9292
if err != nil {
9393
t.Fatal(err)
9494
}
9595

96-
root, err := client.GetRootFromProof(context.Background(), p.Proof)
96+
resp, err := client.GetRootsFromProof(context.Background(), p.Proof)
9797

9898
if err != nil {
9999
t.Fatal(err)
100100
}
101101

102-
if root.Root.String() != basicRoot {
103-
t.Fatalf("expected %s, got %s", basicRoot, root.Root.String())
102+
if resp.Roots[0].String() != basicRoot {
103+
t.Fatalf("expected %s, got %s", basicRoot, resp.Roots[0].String())
104104
}
105105

106106
}

example/src/api.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,12 @@ const getProofForIndexedAddress = async (
8080
return await proofRes.json()
8181
}
8282

83-
const getRootFromProof = async (proof: string[]): Promise<string> => {
84-
const rootRes = await fetch(`${baseUrl}/api/v1/root?proof=${proof.join(',')}`)
85-
const resp: { root: string } = await rootRes.json()
86-
return resp.root
83+
const getRootsFromProof = async (proof: string[]): Promise<string> => {
84+
const rootRes = await fetch(
85+
`${baseUrl}/api/v1/roots?proof=${proof.join(',')}`,
86+
)
87+
const resp: { roots: string[] } = await rootRes.json()
88+
return resp.roots
8789
}
8890

8991
const encode = utils.defaultAbiCoder.encode.bind(utils.defaultAbiCoder)
@@ -230,6 +232,6 @@ checkProofEquality(
230232
),
231233
)
232234

233-
const root = await getRootFromProof(encodedPackedProof)
234-
console.log('root from proof', root)
235-
checkRootEquality(root, encodedPackedMerkleRoot)
235+
const root = await getRootsFromProof(encodedPackedProof)
236+
console.log('roots from proof', root[0])
237+
checkRootEquality(root[0], encodedPackedMerkleRoot)

www/components/Docs/codeSnippets.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,12 @@ GET https://lanyard.org/api/v1/proof?root={root}&unhashedLeaf={unhashedLeaf}
5050
}
5151
`.trim()
5252

53-
export const rootCode = `
53+
export const rootsCode = `
5454
// proof is 0x prefixed, comma separated values
55-
GET https://lanyard.org/api/v1/root?proof={proof}
55+
GET https://lanyard.org/api/v1/roots?proof={proof}
5656
5757
// Response body
5858
{
59-
"root": "0x0000000000000000000000000000000000000003" // returns error if not found
59+
"roots": ["0x0000000000000000000000000000000000000003"] // returns error if not found
6060
}
6161
`.trim()

www/components/Docs/index.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Link from 'next/link'
22
import { brandUnderlineClasses } from 'utils/theme'
33
import PageTitle from 'components/PageTitle'
44
import CodeBlock from 'components/CodeBlock'
5-
import { createCode, lookupCode, proofCode, rootCode } from './codeSnippets'
5+
import { createCode, lookupCode, proofCode, rootsCode } from './codeSnippets'
66
import Section from './Section'
77

88
export default function Docs() {
@@ -43,10 +43,10 @@ export default function Docs() {
4343
<CodeBlock code={proofCode} language="javascript" />
4444
</Section>
4545
<Section
46-
title="Looking up a root for a given proof"
46+
title="Looking up potential roots for a given proof"
4747
description="A more advanced use but helpful if you just have a proof."
4848
>
49-
<CodeBlock code={rootCode} language="javascript" />
49+
<CodeBlock code={rootsCode} language="javascript" />
5050
</Section>
5151
</div>
5252
</div>

0 commit comments

Comments
 (0)