261 lines
5.6 KiB
Go
261 lines
5.6 KiB
Go
package geth
|
|
|
|
/*
|
|
#include <stddef.h>
|
|
#include <stdbool.h>
|
|
extern bool StatusServiceSignalEvent( const char *jsonEvent );
|
|
*/
|
|
import "C"
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/cnf/structhash"
|
|
"github.com/eapache/go-resiliency/semaphore"
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/les/status"
|
|
"github.com/robertkrimen/otto"
|
|
)
|
|
|
|
const (
|
|
EventTransactionQueued = "transaction.queued"
|
|
SendTransactionRequest = "eth_sendTransaction"
|
|
MessageIdKey = "message_id"
|
|
CellTicketKey = "cell_ticket"
|
|
)
|
|
|
|
func onSendTransactionRequest(queuedTx status.QueuedTx) {
|
|
requestCtx := context.Background()
|
|
requestQueue, err := GetNodeManager().JailedRequestQueue()
|
|
if err == nil {
|
|
requestCtx = requestQueue.PopQueuedTxContext(&queuedTx)
|
|
}
|
|
|
|
// request context obtained (if exists), safe to release the ticket
|
|
if ticket := cellTicketFromContext(requestCtx); ticket != nil {
|
|
ticket.Release()
|
|
}
|
|
|
|
event := GethEvent{
|
|
Type: EventTransactionQueued,
|
|
Event: SendTransactionEvent{
|
|
Id: string(queuedTx.Id),
|
|
Args: queuedTx.Args,
|
|
MessageId: messageIdFromContext(requestCtx),
|
|
},
|
|
}
|
|
|
|
body, _ := json.Marshal(&event)
|
|
C.StatusServiceSignalEvent(C.CString(string(body)))
|
|
}
|
|
|
|
func CompleteTransaction(id, password string) (common.Hash, error) {
|
|
lightEthereum, err := GetNodeManager().LightEthereumService()
|
|
if err != nil {
|
|
return common.Hash{}, err
|
|
}
|
|
|
|
backend := lightEthereum.StatusBackend
|
|
|
|
return backend.CompleteQueuedTransaction(status.QueuedTxId(id), password)
|
|
}
|
|
|
|
func messageIdFromContext(ctx context.Context) string {
|
|
if ctx == nil {
|
|
return ""
|
|
}
|
|
if messageId, ok := ctx.Value(MessageIdKey).(string); ok {
|
|
return messageId
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
func cellTicketFromContext(ctx context.Context) *semaphore.Semaphore {
|
|
if ctx == nil {
|
|
return nil
|
|
}
|
|
if sem, ok := ctx.Value(CellTicketKey).(*semaphore.Semaphore); ok {
|
|
return sem
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type JailedRequest struct {
|
|
method string
|
|
ctx context.Context
|
|
vm *otto.Otto
|
|
}
|
|
|
|
type JailedRequestQueue struct {
|
|
requests map[string]*JailedRequest
|
|
}
|
|
|
|
func NewJailedRequestsQueue() *JailedRequestQueue {
|
|
return &JailedRequestQueue{
|
|
requests: make(map[string]*JailedRequest),
|
|
}
|
|
}
|
|
|
|
func (q *JailedRequestQueue) PreProcessRequest(ticket *semaphore.Semaphore, vm *otto.Otto, req RPCCall) error {
|
|
// serialize access
|
|
err := ticket.Acquire()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
messageId := currentMessageId(vm.Context())
|
|
|
|
// save request context for reuse (by request handlers, such as queued transaction signal sender)
|
|
ctx := context.Background()
|
|
ctx = context.WithValue(ctx, "method", req.Method)
|
|
if len(messageId) > 0 {
|
|
ctx = context.WithValue(ctx, MessageIdKey, messageId)
|
|
}
|
|
|
|
// onSendTransactionRequest() will use context to obtain and release ticket
|
|
if req.Method == SendTransactionRequest {
|
|
ctx = context.WithValue(ctx, CellTicketKey, ticket)
|
|
} else {
|
|
ticket.Release()
|
|
}
|
|
q.saveRequestContext(vm, ctx, req)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (q *JailedRequestQueue) PostProcessRequest(vm *otto.Otto, req RPCCall) {
|
|
// set message id (if present in context)
|
|
messageId := currentMessageId(vm.Context())
|
|
if len(messageId) > 0 {
|
|
vm.Call("addContext", nil, messageId, MessageIdKey, messageId)
|
|
}
|
|
|
|
// set extra markers for queued transaction requests
|
|
if req.Method == SendTransactionRequest {
|
|
vm.Call("addContext", nil, messageId, SendTransactionRequest, true)
|
|
}
|
|
}
|
|
|
|
func (q *JailedRequestQueue) saveRequestContext(vm *otto.Otto, ctx context.Context, req RPCCall) {
|
|
hash := hashFromRPCCall(req)
|
|
|
|
if len(hash) == 0 { // no need to persist empty hash
|
|
return
|
|
}
|
|
|
|
q.requests[hash] = &JailedRequest{
|
|
method: req.Method,
|
|
ctx: ctx,
|
|
vm: vm,
|
|
}
|
|
}
|
|
|
|
func (q *JailedRequestQueue) GetQueuedTxContext(queuedTx *status.QueuedTx) context.Context {
|
|
hash := hashFromQueuedTx(queuedTx)
|
|
|
|
req, ok := q.requests[hash]
|
|
if ok {
|
|
return req.ctx
|
|
}
|
|
|
|
return context.Background()
|
|
}
|
|
|
|
func (q *JailedRequestQueue) PopQueuedTxContext(queuedTx *status.QueuedTx) context.Context {
|
|
hash := hashFromQueuedTx(queuedTx)
|
|
|
|
req, ok := q.requests[hash]
|
|
if ok {
|
|
delete(q.requests, hash)
|
|
return req.ctx
|
|
}
|
|
|
|
return context.Background()
|
|
}
|
|
|
|
// currentMessageId looks for `status.message_id` variable in current JS context
|
|
func currentMessageId(ctx otto.Context) string {
|
|
if statusObj, ok := ctx.Symbols["status"]; ok {
|
|
messageId, err := statusObj.Object().Get("message_id")
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
if messageId, err := messageId.ToString(); err == nil {
|
|
return messageId
|
|
}
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
type HashableSendRequest struct {
|
|
method string
|
|
from string
|
|
to string
|
|
value string
|
|
data string
|
|
}
|
|
|
|
func hashFromRPCCall(req RPCCall) string {
|
|
if req.Method != SendTransactionRequest { // no need to persist extra state for other requests
|
|
return ""
|
|
}
|
|
|
|
params, ok := req.Params[0].(map[string]interface{})
|
|
if !ok {
|
|
return ""
|
|
}
|
|
|
|
from, ok := params["from"].(string)
|
|
if !ok {
|
|
from = ""
|
|
}
|
|
|
|
to, ok := params["to"].(string)
|
|
if !ok {
|
|
to = ""
|
|
}
|
|
|
|
value, ok := params["value"].(string)
|
|
if !ok {
|
|
value = ""
|
|
}
|
|
|
|
data, ok := params["data"].(string)
|
|
if !ok {
|
|
data = ""
|
|
}
|
|
|
|
s := HashableSendRequest{
|
|
method: req.Method,
|
|
from: from,
|
|
to: to,
|
|
value: value,
|
|
data: data,
|
|
}
|
|
|
|
return fmt.Sprintf("%x", structhash.Sha1(s, 1))
|
|
}
|
|
|
|
func hashFromQueuedTx(queuedTx *status.QueuedTx) string {
|
|
value, err := queuedTx.Args.Value.MarshalJSON()
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
|
|
s := HashableSendRequest{
|
|
method: SendTransactionRequest,
|
|
from: queuedTx.Args.From.Hex(),
|
|
to: queuedTx.Args.To.Hex(),
|
|
value: string(bytes.Replace(value, []byte(`"`), []byte(""), 2)),
|
|
data: queuedTx.Args.Data,
|
|
}
|
|
|
|
return fmt.Sprintf("%x", structhash.Sha1(s, 1))
|
|
}
|