Implement Send Transaction in explorer
parent
95d27a7d1d
commit
4422661918
|
@ -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
|
||||
|
|
|
@ -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) {
|
|||
`</html>`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "explorerSendTx",
|
||||
r: newGetRequest(ts.URL + "/sendtx"),
|
||||
status: http.StatusOK,
|
||||
contentType: "text/html; charset=utf-8",
|
||||
body: []string{
|
||||
`<a href="/" class="nav-link">Fake Coin Explorer</a>`,
|
||||
`<h1>Send Raw Transaction</h1>`,
|
||||
`<textarea class="form-control" rows="8" name="hex"></textarea>`,
|
||||
`</html>`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "explorerSendTx POST",
|
||||
r: newPostRequest(ts.URL+"/sendtx", "hex", "12341234"),
|
||||
status: http.StatusOK,
|
||||
contentType: "text/html; charset=utf-8",
|
||||
body: []string{
|
||||
`<a href="/" class="nav-link">Fake Coin Explorer</a>`,
|
||||
`<h1>Send Raw Transaction</h1>`,
|
||||
`<textarea class="form-control" rows="8" name="hex">12341234</textarea>`,
|
||||
`<div class="alert alert-danger">Not implemented</div></div>`,
|
||||
`</html>`,
|
||||
},
|
||||
},
|
||||
{
|
||||
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{
|
||||
|
|
|
@ -76,6 +76,9 @@
|
|||
<a class="nav-link" href="{{- .TOSLink -}}" target="_blank" rel="noopener noreferrer">Terms</a>
|
||||
</li>
|
||||
</ul>
|
||||
<span class="d-none ml-md-auto d-md-flex navbar-form navbar-nav">
|
||||
<a href="/sendtx" class="nav-link">Send Transaction</a>
|
||||
</span>
|
||||
<ul class="navbar-nav flex-row ml-md-auto d-none d-md-flex">
|
||||
<li class="nav-item active">
|
||||
<a class="nav-link" rel="noopener noreferrer" href="http://shop.trezor.io">Don't have a TREZOR? Get one!</a>
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
{{define "specific" -}}
|
||||
<h1>Send Raw Transaction</h1>
|
||||
<form method="POST" action="/sendtx">
|
||||
<div class="form-group">
|
||||
<label for="exampleFormControlTextarea1">Raw transaction data</label>
|
||||
<textarea class="form-control" rows="8" name="hex">{{.SendTxHex}}</textarea>
|
||||
</div>
|
||||
<div class="form-group"><button type="submit" class="btn btn-primary">Send</button></div>
|
||||
</form>
|
||||
{{- if .Status -}}
|
||||
<div class="alert alert-success">{{.Status}}</div>
|
||||
{{- end -}}
|
||||
{{- if .Error -}}
|
||||
<div class="alert alert-danger">{{.Error.Text}}</div>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
|
@ -42,7 +42,7 @@
|
|||
{{template "txdetail" .}}
|
||||
</div>
|
||||
<div class="data-div">
|
||||
<h5>Transaction Data</h5>
|
||||
<h5>Raw Transaction</h5>
|
||||
<div class="alert alert-data" style="word-wrap: break-word; font-size: smaller;">
|
||||
<pre id="txSpecific"></pre>
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue