Skip to content

Commit d84c886

Browse files
Add remote_calendar{_test}.go
Allows creation of pending timestamps and updating them using opentimestamps servers.
1 parent 6ae8d88 commit d84c886

File tree

2 files changed

+215
-0
lines changed

2 files changed

+215
-0
lines changed

opentimestamps/remote_calendar.go

+146
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
package opentimestamps
2+
3+
import (
4+
"bytes"
5+
"encoding/hex"
6+
"fmt"
7+
"io/ioutil"
8+
"net/http"
9+
"net/http/httputil"
10+
"strings"
11+
12+
"github.com/Sirupsen/logrus"
13+
)
14+
15+
const userAgent = "go-opentimestamps"
16+
17+
const dumpResponse = false
18+
19+
type RemoteCalendar struct {
20+
baseURL string
21+
client *http.Client
22+
log *logrus.Logger
23+
}
24+
25+
func NewRemoteCalendar(baseURL string) (*RemoteCalendar, error) {
26+
// FIXME remove this
27+
if baseURL == "localhost" {
28+
baseURL = "http://localhost:14788"
29+
}
30+
// TODO validate url
31+
if !strings.HasSuffix(baseURL, "/") {
32+
baseURL += "/"
33+
}
34+
return &RemoteCalendar{
35+
baseURL,
36+
http.DefaultClient,
37+
logrus.New(),
38+
}, nil
39+
}
40+
41+
// Check response status, return informational error message if
42+
// status is not `200 OK`.
43+
func checkStatusOK(resp *http.Response) error {
44+
if resp.StatusCode == http.StatusOK {
45+
return nil
46+
}
47+
errMsg := fmt.Sprintf("unexpected response: %q", resp.Status)
48+
if resp.Body == nil {
49+
return fmt.Errorf("%s (body=nil)", errMsg)
50+
}
51+
defer resp.Body.Close()
52+
bodyBytes, err := ioutil.ReadAll(resp.Body)
53+
if err != nil {
54+
return fmt.Errorf("%s (bodyErr=%v)", errMsg, err)
55+
} else {
56+
return fmt.Errorf("%s (body=%q)", errMsg, bodyBytes)
57+
}
58+
}
59+
60+
func (c *RemoteCalendar) do(r *http.Request) (*http.Response, error) {
61+
r.Header.Add("Accept", "application/vnd.opentimestamps.v1")
62+
r.Header.Add("User-Agent", userAgent)
63+
c.log.Debugf("> %s %s", r.Method, r.URL)
64+
resp, err := c.client.Do(r)
65+
if err != nil {
66+
c.log.Errorf("> %s %s error: %v", r.Method, r.URL, err)
67+
return resp, err
68+
}
69+
c.log.Debugf("< %s %s - %v", r.Method, r.URL, resp.Status)
70+
if dumpResponse {
71+
bytes, err := httputil.DumpResponse(resp, true)
72+
if err == nil {
73+
c.log.Debugf("response dump:%s ", bytes)
74+
}
75+
}
76+
return resp, err
77+
}
78+
79+
func (c *RemoteCalendar) url(path string) string {
80+
return c.baseURL + path
81+
}
82+
83+
func (c *RemoteCalendar) Submit(digest []byte) (*Timestamp, error) {
84+
body := bytes.NewBuffer(digest)
85+
req, err := http.NewRequest("POST", c.url("digest"), body)
86+
if err != nil {
87+
return nil, err
88+
}
89+
resp, err := c.do(req)
90+
if err != nil {
91+
return nil, err
92+
}
93+
if resp.Body != nil {
94+
defer resp.Body.Close()
95+
}
96+
if resp.StatusCode != http.StatusOK {
97+
return nil, fmt.Errorf("expected 200, got %v", resp.Status)
98+
}
99+
return NewTimestampFromReader(resp.Body, digest)
100+
}
101+
102+
func (c *RemoteCalendar) GetTimestamp(commitment []byte) (*Timestamp, error) {
103+
url := c.url("timestamp/" + hex.EncodeToString(commitment))
104+
req, err := http.NewRequest("GET", url, nil)
105+
if err != nil {
106+
return nil, err
107+
}
108+
resp, err := c.do(req)
109+
if err != nil {
110+
return nil, err
111+
}
112+
if err := checkStatusOK(resp); err != nil {
113+
return nil, err
114+
}
115+
if resp.Body != nil {
116+
defer resp.Body.Close()
117+
}
118+
return NewTimestampFromReader(resp.Body, commitment)
119+
}
120+
121+
type PendingTimestamp struct {
122+
Timestamp *Timestamp
123+
PendingAttestation *pendingAttestation
124+
}
125+
126+
func (p PendingTimestamp) Upgrade() (*Timestamp, error) {
127+
cal, err := NewRemoteCalendar(p.PendingAttestation.uri)
128+
if err != nil {
129+
return nil, err
130+
}
131+
return cal.GetTimestamp(p.Timestamp.Message)
132+
}
133+
134+
func PendingTimestamps(ts *Timestamp) (res []PendingTimestamp) {
135+
ts.Walk(func(ts *Timestamp) {
136+
for _, att := range ts.Attestations {
137+
p, ok := att.(*pendingAttestation)
138+
if !ok {
139+
continue
140+
}
141+
attCopy := *p
142+
res = append(res, PendingTimestamp{ts, &attCopy})
143+
}
144+
})
145+
return
146+
}
+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package opentimestamps
2+
3+
import (
4+
"crypto/sha256"
5+
"fmt"
6+
"os"
7+
"testing"
8+
"time"
9+
10+
"github.com/Sirupsen/logrus"
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
const calendarServerEnvvar = "GOTS_TEST_CALENDAR_SERVER"
16+
const bitcoinRegtestEnvvar = "GOTS_TEST_BITCOIN_REGTEST_SERVER"
17+
18+
func newTestCalendar(url string) *RemoteCalendar {
19+
logrus.SetLevel(logrus.DebugLevel)
20+
cal, err := NewRemoteCalendar(url)
21+
if err != nil {
22+
panic("could not create test calendar")
23+
}
24+
cal.log.Level = logrus.DebugLevel
25+
return cal
26+
}
27+
28+
func newTestDigest(in string) []byte {
29+
hash := sha256.Sum256([]byte(in))
30+
return hash[:]
31+
}
32+
33+
func TestRemoteCalendarExample(t *testing.T) {
34+
dts, err := NewDetachedTimestampFromPath(
35+
"../examples/two-calendars.txt.ots",
36+
)
37+
require.NoError(t, err)
38+
39+
pts := PendingTimestamps(dts.Timestamp)
40+
assert.Equal(t, 2, len(pts))
41+
for _, pt := range pts {
42+
ts, err := pt.Upgrade()
43+
assert.NoError(t, err)
44+
fmt.Print(ts.Dump())
45+
}
46+
}
47+
48+
func TestRemoteCalendarRoundTrip(t *testing.T) {
49+
calendarServer := os.Getenv(calendarServerEnvvar)
50+
if calendarServer == "" {
51+
t.Skipf("%q not set, skipping test", calendarServerEnvvar)
52+
}
53+
cal := newTestCalendar(calendarServer)
54+
ts, err := cal.Submit(newTestDigest("Hello, World!"))
55+
require.NoError(t, err)
56+
require.NotNil(t, ts)
57+
58+
// TODO call btcrpcclient generateblock 100
59+
60+
// FIXME possible opentimestamps-server bug?
61+
// wait until attestation has been aggregated
62+
time.Sleep(2 * time.Second)
63+
64+
for _, pts := range PendingTimestamps(ts) {
65+
ts, err := pts.Upgrade()
66+
assert.NoError(t, err)
67+
_ = ts
68+
}
69+
}

0 commit comments

Comments
 (0)