diff --git a/server/public.go b/server/public.go index c9bd7ee3..8decf464 100644 --- a/server/public.go +++ b/server/public.go @@ -116,6 +116,7 @@ func (s *PublicServer) ConnectFullPublicInterface() { serveMux.HandleFunc(path+"blocks", s.htmlTemplateHandler(s.explorerBlocks)) serveMux.HandleFunc(path+"block/", s.htmlTemplateHandler(s.explorerBlock)) serveMux.HandleFunc(path+"spending/", s.htmlTemplateHandler(s.explorerSpendingTx)) + serveMux.HandleFunc(path+"sendtx", s.htmlTemplateHandler(s.explorerSendTx)) } else { // redirect to wallet requests for tx and address, possibly to external site serveMux.HandleFunc(path+"tx/", s.txRedirect) @@ -307,10 +308,12 @@ const ( addressTpl blocksTpl blockTpl + sendTransactionTpl tplCount ) +// TemplateData is used to transfer data to the templates type TemplateData struct { CoinName string CoinShortcut string @@ -329,6 +332,8 @@ type TemplateData struct { NextPage int PagingRange []int TOSLink string + SendTxHex string + Status string } func parseTemplates() []*template.Template { @@ -347,6 +352,7 @@ func parseTemplates() []*template.Template { t[addressTpl] = template.Must(template.New("address").Funcs(templateFuncMap).ParseFiles("./static/templates/address.html", "./static/templates/txdetail.html", "./static/templates/paging.html", "./static/templates/base.html")) t[blocksTpl] = template.Must(template.New("blocks").Funcs(templateFuncMap).ParseFiles("./static/templates/blocks.html", "./static/templates/paging.html", "./static/templates/base.html")) t[blockTpl] = template.Must(template.New("block").Funcs(templateFuncMap).ParseFiles("./static/templates/block.html", "./static/templates/txdetail.html", "./static/templates/paging.html", "./static/templates/base.html")) + t[sendTransactionTpl] = template.Must(template.New("block").Funcs(templateFuncMap).ParseFiles("./static/templates/sendtx.html", "./static/templates/base.html")) return t } @@ -515,6 +521,28 @@ func (s *PublicServer) explorerSearch(w http.ResponseWriter, r *http.Request) (t return errorTpl, nil, api.NewApiError(fmt.Sprintf("No matching records found for '%v'", q), true) } +func (s *PublicServer) explorerSendTx(w http.ResponseWriter, r *http.Request) (tpl, *TemplateData, error) { + s.metrics.ExplorerViews.With(common.Labels{"action": "sendtx"}).Inc() + data := s.newTemplateData() + if r.Method == http.MethodPost { + err := r.ParseForm() + if err != nil { + return sendTransactionTpl, data, err + } + hex := r.FormValue("hex") + if len(hex) > 0 { + res, err := s.chain.SendRawTransaction(hex) + if err != nil { + data.SendTxHex = hex + data.Error = &api.ApiError{Text: err.Error(), Public: true} + return sendTransactionTpl, data, nil + } + data.Status = "Transaction sent " + res + } + } + return sendTransactionTpl, data, nil +} + func getPagingRange(page int, total int) ([]int, int, int) { if total < 2 { return nil, 0, 0 diff --git a/server/public_test.go b/server/public_test.go index 791f546a..7f47cc9b 100644 --- a/server/public_test.go +++ b/server/public_test.go @@ -8,10 +8,10 @@ import ( "blockbook/common" "blockbook/db" "blockbook/tests/dbtestdata" - "io" "io/ioutil" "net/http" "net/http/httptest" + "net/url" "os" "strings" "testing" @@ -101,14 +101,27 @@ func closeAndDestroyPublicServer(t *testing.T, s *PublicServer, dbpath string) { os.RemoveAll(dbpath) } -func newGetRequest(url string, body io.Reader) *http.Request { - r, err := http.NewRequest("GET", url, body) +func newGetRequest(u string) *http.Request { + r, err := http.NewRequest("GET", u, nil) if err != nil { glog.Fatal(err) } return r } +func newPostRequest(u string, formdata ...string) *http.Request { + form := url.Values{} + for i := 0; i < len(formdata)-1; i += 2 { + form.Add(formdata[i], formdata[i+1]) + } + r, err := http.NewRequest("POST", u, strings.NewReader(form.Encode())) + if err != nil { + glog.Fatal(err) + } + r.Header.Add("Content-Type", "application/x-www-form-urlencoded") + return r +} + func httpTests(t *testing.T, ts *httptest.Server) { tests := []struct { name string @@ -119,7 +132,7 @@ func httpTests(t *testing.T, ts *httptest.Server) { }{ { name: "explorerTx", - r: newGetRequest(ts.URL+"/tx/fdd824a780cbb718eeb766eb05d83fdefc793a27082cd5e67f856d69798cf7db", nil), + r: newGetRequest(ts.URL + "/tx/fdd824a780cbb718eeb766eb05d83fdefc793a27082cd5e67f856d69798cf7db"), status: http.StatusOK, contentType: "text/html; charset=utf-8", body: []string{ @@ -135,7 +148,7 @@ func httpTests(t *testing.T, ts *httptest.Server) { }, { name: "explorerAddress", - r: newGetRequest(ts.URL+"/address/mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz", nil), + r: newGetRequest(ts.URL + "/address/mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz"), status: http.StatusOK, contentType: "text/html; charset=utf-8", body: []string{ @@ -155,7 +168,7 @@ func httpTests(t *testing.T, ts *httptest.Server) { }, { name: "explorerSpendingTx", - r: newGetRequest(ts.URL+"/spending/7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25/0", nil), + r: newGetRequest(ts.URL + "/spending/7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25/0"), status: http.StatusOK, contentType: "text/html; charset=utf-8", body: []string{ @@ -168,7 +181,7 @@ func httpTests(t *testing.T, ts *httptest.Server) { }, { name: "explorerSpendingTx - not found", - r: newGetRequest(ts.URL+"/spending/123be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25/0", nil), + r: newGetRequest(ts.URL + "/spending/123be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25/0"), status: http.StatusOK, contentType: "text/html; charset=utf-8", body: []string{ @@ -180,7 +193,7 @@ func httpTests(t *testing.T, ts *httptest.Server) { }, { name: "explorerBlocks", - r: newGetRequest(ts.URL+"/blocks", nil), + r: newGetRequest(ts.URL + "/blocks"), status: http.StatusOK, contentType: "text/html; charset=utf-8", body: []string{ @@ -196,7 +209,7 @@ func httpTests(t *testing.T, ts *httptest.Server) { }, { name: "explorerBlock", - r: newGetRequest(ts.URL+"/block/225494", nil), + r: newGetRequest(ts.URL + "/block/225494"), status: http.StatusOK, contentType: "text/html; charset=utf-8", body: []string{ @@ -212,7 +225,7 @@ func httpTests(t *testing.T, ts *httptest.Server) { }, { name: "explorerIndex", - r: newGetRequest(ts.URL+"/", nil), + r: newGetRequest(ts.URL + "/"), status: http.StatusOK, contentType: "text/html; charset=utf-8", body: []string{ @@ -226,7 +239,7 @@ func httpTests(t *testing.T, ts *httptest.Server) { }, { name: "explorerSearch block height", - r: newGetRequest(ts.URL+"/search?q=225494", nil), + r: newGetRequest(ts.URL + "/search?q=225494"), status: http.StatusOK, contentType: "text/html; charset=utf-8", body: []string{ @@ -242,7 +255,7 @@ func httpTests(t *testing.T, ts *httptest.Server) { }, { name: "explorerSearch block hash", - r: newGetRequest(ts.URL+"/search?q=00000000eb0443fd7dc4a1ed5c686a8e995057805f9a161d9a5a77a95e72b7b6", nil), + r: newGetRequest(ts.URL + "/search?q=00000000eb0443fd7dc4a1ed5c686a8e995057805f9a161d9a5a77a95e72b7b6"), status: http.StatusOK, contentType: "text/html; charset=utf-8", body: []string{ @@ -258,7 +271,7 @@ func httpTests(t *testing.T, ts *httptest.Server) { }, { name: "explorerSearch tx", - r: newGetRequest(ts.URL+"/search?q=fdd824a780cbb718eeb766eb05d83fdefc793a27082cd5e67f856d69798cf7db", nil), + r: newGetRequest(ts.URL + "/search?q=fdd824a780cbb718eeb766eb05d83fdefc793a27082cd5e67f856d69798cf7db"), status: http.StatusOK, contentType: "text/html; charset=utf-8", body: []string{ @@ -274,7 +287,7 @@ func httpTests(t *testing.T, ts *httptest.Server) { }, { name: "explorerSearch address", - r: newGetRequest(ts.URL+"/search?q=mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz", nil), + r: newGetRequest(ts.URL + "/search?q=mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz"), status: http.StatusOK, contentType: "text/html; charset=utf-8", body: []string{ @@ -294,7 +307,7 @@ func httpTests(t *testing.T, ts *httptest.Server) { }, { name: "explorerSearch not found", - r: newGetRequest(ts.URL+"/search?q=1234", nil), + r: newGetRequest(ts.URL + "/search?q=1234"), status: http.StatusOK, contentType: "text/html; charset=utf-8", body: []string{ @@ -304,9 +317,34 @@ func httpTests(t *testing.T, ts *httptest.Server) { ``, }, }, + { + name: "explorerSendTx", + r: newGetRequest(ts.URL + "/sendtx"), + status: http.StatusOK, + contentType: "text/html; charset=utf-8", + body: []string{ + `Fake Coin Explorer`, + `

Send Raw Transaction

`, + ``, + ``, + }, + }, + { + name: "explorerSendTx POST", + r: newPostRequest(ts.URL+"/sendtx", "hex", "12341234"), + status: http.StatusOK, + contentType: "text/html; charset=utf-8", + body: []string{ + `Fake Coin Explorer`, + `

Send Raw Transaction

`, + ``, + `
Not implemented
`, + ``, + }, + }, { name: "apiIndex", - r: newGetRequest(ts.URL+"/api", nil), + r: newGetRequest(ts.URL + "/api"), status: http.StatusOK, contentType: "application/json; charset=utf-8", body: []string{ @@ -318,7 +356,7 @@ func httpTests(t *testing.T, ts *httptest.Server) { }, { name: "apiBlockIndex", - r: newGetRequest(ts.URL+"/api/block-index/", nil), + r: newGetRequest(ts.URL + "/api/block-index/"), status: http.StatusOK, contentType: "application/json; charset=utf-8", body: []string{ @@ -327,7 +365,7 @@ func httpTests(t *testing.T, ts *httptest.Server) { }, { name: "apiTx", - r: newGetRequest(ts.URL+"/api/tx/05e2e48aeabdd9b75def7b48d756ba304713c2aba7b522bf9dbc893fc4231b07", nil), + r: newGetRequest(ts.URL + "/api/tx/05e2e48aeabdd9b75def7b48d756ba304713c2aba7b522bf9dbc893fc4231b07"), status: http.StatusOK, contentType: "application/json; charset=utf-8", body: []string{ @@ -336,7 +374,7 @@ func httpTests(t *testing.T, ts *httptest.Server) { }, { name: "apiTx - not found", - r: newGetRequest(ts.URL+"/api/tx/1232e48aeabdd9b75def7b48d756ba304713c2aba7b522bf9dbc893fc4231b07", nil), + r: newGetRequest(ts.URL + "/api/tx/1232e48aeabdd9b75def7b48d756ba304713c2aba7b522bf9dbc893fc4231b07"), status: http.StatusInternalServerError, contentType: "application/json; charset=utf-8", body: []string{ @@ -345,7 +383,7 @@ func httpTests(t *testing.T, ts *httptest.Server) { }, { name: "apiTxSpecific", - r: newGetRequest(ts.URL+"/api/tx-specific/00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840", nil), + r: newGetRequest(ts.URL + "/api/tx-specific/00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840"), status: http.StatusOK, contentType: "application/json; charset=utf-8", body: []string{ @@ -354,7 +392,7 @@ func httpTests(t *testing.T, ts *httptest.Server) { }, { name: "apiAddress", - r: newGetRequest(ts.URL+"/api/address/mv9uLThosiEnGRbVPS7Vhyw6VssbVRsiAw", nil), + r: newGetRequest(ts.URL + "/api/address/mv9uLThosiEnGRbVPS7Vhyw6VssbVRsiAw"), status: http.StatusOK, contentType: "application/json; charset=utf-8", body: []string{ @@ -363,7 +401,7 @@ func httpTests(t *testing.T, ts *httptest.Server) { }, { name: "apiBlock", - r: newGetRequest(ts.URL+"/api/block/225493", nil), + r: newGetRequest(ts.URL + "/api/block/225493"), status: http.StatusOK, contentType: "application/json; charset=utf-8", body: []string{ diff --git a/static/templates/base.html b/static/templates/base.html index 5c9e9d2c..e22278cd 100644 --- a/static/templates/base.html +++ b/static/templates/base.html @@ -76,6 +76,9 @@ Terms + + Send Transaction +