Port the transactions queue from go-ethereum to status-go (#261)

Now all transactions in both cases are waiting for CompleteTransaction or DiscardTransaction to proceed independently from their destination: upstream of local
This commit is contained in:
Adam Babik 2017-09-04 14:56:58 +02:00 committed by Ivan Tomilov
parent 680090bdf9
commit 8fb2424ea5
29 changed files with 2909 additions and 1039 deletions

View File

@ -46,7 +46,7 @@ statusgo-ios-simulator-mainnet: xgo
build/env.sh $(GOBIN)/xgo --image farazdagi/xgo-ios-simulator --go=$(GO) -out statusgo --dest=$(GOBIN) --targets=ios-9.3/framework -v $(shell build/mainnet-flags.sh) ./cmd/statusd
@echo "iOS framework cross compilation done (mainnet)."
ci:
ci: mock
build/env.sh go test -timeout 40m -v ./geth/api
build/env.sh go test -timeout 40m -v ./geth/common
build/env.sh go test -timeout 40m -v ./geth/jail
@ -111,6 +111,12 @@ lint:
@echo "Linter: gosimple\n--------------------"
@gometalinter --disable-all --deadline 45s --enable=gosimple extkeys cmd/... geth/... | grep -v -f ./static/config/linter_exclude_list.txt || echo "OK!"
mock-install:
go get -u github.com/golang/mock/mockgen
mock: mock-install
mockgen -source=geth/common/types.go -destination=geth/common/types_mock.go -package=common
test:
@build/env.sh echo "mode: set" > coverage-all.out
build/env.sh go test -coverprofile=coverage.out -covermode=set ./geth/api

View File

@ -9,6 +9,7 @@ import (
"gopkg.in/go-playground/validator.v9"
"github.com/status-im/status-go/geth/common"
"github.com/status-im/status-go/geth/log"
"github.com/status-im/status-go/geth/params"
"github.com/status-im/status-go/helpers/profiling"
)
@ -213,7 +214,7 @@ func Logout() *C.char {
//export CompleteTransaction
func CompleteTransaction(id, password *C.char) *C.char {
txHash, err := statusAPI.CompleteTransaction(C.GoString(id), C.GoString(password))
txHash, err := statusAPI.CompleteTransaction(common.QueuedTxID(C.GoString(id)), C.GoString(password))
errString := ""
if err != nil {
@ -226,7 +227,11 @@ func CompleteTransaction(id, password *C.char) *C.char {
Hash: txHash.Hex(),
Error: errString,
}
outBytes, _ := json.Marshal(&out)
outBytes, err := json.Marshal(&out)
if err != nil {
log.Error("failed to marshal CompleteTransaction output", "error", err.Error())
return makeJSONResponse(err)
}
return C.CString(string(outBytes))
}
@ -236,25 +241,42 @@ func CompleteTransactions(ids, password *C.char) *C.char {
out := common.CompleteTransactionsResult{}
out.Results = make(map[string]common.CompleteTransactionResult)
results := statusAPI.CompleteTransactions(C.GoString(ids), C.GoString(password))
for txID, result := range results {
txResult := common.CompleteTransactionResult{
ID: txID,
Hash: result.Hash.Hex(),
parsedIDs, err := common.ParseJSONArray(C.GoString(ids))
if err != nil {
out.Results["none"] = common.CompleteTransactionResult{
Error: err.Error(),
}
if result.Error != nil {
txResult.Error = result.Error.Error()
} else {
txIDs := make([]common.QueuedTxID, len(parsedIDs))
for i, id := range parsedIDs {
txIDs[i] = common.QueuedTxID(id)
}
results := statusAPI.CompleteTransactions(txIDs, C.GoString(password))
for txID, result := range results {
txResult := common.CompleteTransactionResult{
ID: string(txID),
Hash: result.Hash.Hex(),
}
if result.Error != nil {
txResult.Error = result.Error.Error()
}
out.Results[string(txID)] = txResult
}
out.Results[txID] = txResult
}
outBytes, _ := json.Marshal(&out)
outBytes, err := json.Marshal(&out)
if err != nil {
log.Error("failed to marshal CompleteTransactions output", "error", err.Error())
return makeJSONResponse(err)
}
return C.CString(string(outBytes))
}
//export DiscardTransaction
func DiscardTransaction(id *C.char) *C.char {
err := statusAPI.DiscardTransaction(C.GoString(id))
err := statusAPI.DiscardTransaction(common.QueuedTxID(C.GoString(id)))
errString := ""
if err != nil {
@ -266,7 +288,11 @@ func DiscardTransaction(id *C.char) *C.char {
ID: C.GoString(id),
Error: errString,
}
outBytes, _ := json.Marshal(&out)
outBytes, err := json.Marshal(&out)
if err != nil {
log.Error("failed to marshal DiscardTransaction output", "error", err.Error())
return makeJSONResponse(err)
}
return C.CString(string(outBytes))
}
@ -276,17 +302,34 @@ func DiscardTransactions(ids *C.char) *C.char {
out := common.DiscardTransactionsResult{}
out.Results = make(map[string]common.DiscardTransactionResult)
results := statusAPI.DiscardTransactions(C.GoString(ids))
for txID, result := range results {
txResult := common.DiscardTransactionResult{
ID: txID,
parsedIDs, err := common.ParseJSONArray(C.GoString(ids))
if err != nil {
out.Results["none"] = common.DiscardTransactionResult{
Error: err.Error(),
}
if result.Error != nil {
txResult.Error = result.Error.Error()
} else {
txIDs := make([]common.QueuedTxID, len(parsedIDs))
for i, id := range parsedIDs {
txIDs[i] = common.QueuedTxID(id)
}
results := statusAPI.DiscardTransactions(txIDs)
for txID, result := range results {
txResult := common.DiscardTransactionResult{
ID: string(txID),
}
if result.Error != nil {
txResult.Error = result.Error.Error()
}
out.Results[string(txID)] = txResult
}
out.Results[txID] = txResult
}
outBytes, _ := json.Marshal(&out)
outBytes, err := json.Marshal(&out)
if err != nil {
log.Error("failed to marshal DiscardTransactions output", "error", err.Error())
return makeJSONResponse(err)
}
return C.CString(string(outBytes))
}

View File

@ -15,7 +15,6 @@ import (
gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/les/status"
gethparams "github.com/ethereum/go-ethereum/params"
"github.com/status-im/status-go/geth/common"
@ -783,21 +782,15 @@ func testAccountLogout(t *testing.T) bool {
}
func testCompleteTransaction(t *testing.T) bool {
// obtain reference to status backend
lightEthereum, err := statusAPI.NodeManager().LightEthereumService()
if err != nil {
t.Errorf("Test failed: LES service is not running: %v", err)
return false
}
backend := lightEthereum.StatusBackend
txQueueManager := statusAPI.TxQueueManager()
txQueue := txQueueManager.TransactionQueue()
// reset queue
backend.TransactionQueue().Reset()
txQueue.Reset()
time.Sleep(5 * time.Second) // allow to sync
// log into account from which transactions will be sent
if err = statusAPI.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password); err != nil {
if err := statusAPI.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password); err != nil {
t.Errorf("cannot select account: %v", TestConfig.Account1.Address)
return false
}
@ -811,7 +804,7 @@ func testCompleteTransaction(t *testing.T) bool {
var txHash = ""
node.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
var envelope node.SignalEnvelope
if err = json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
return
}
@ -822,7 +815,7 @@ func testCompleteTransaction(t *testing.T) bool {
completeTxResponse := common.CompleteTransactionResult{}
rawResponse := CompleteTransaction(C.CString(event["id"].(string)), C.CString(TestConfig.Account1.Password))
if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &completeTxResponse); err != nil {
if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &completeTxResponse); err != nil {
t.Errorf("cannot decode RecoverAccount response (%s): %v", C.GoString(rawResponse), err)
}
@ -838,29 +831,31 @@ func testCompleteTransaction(t *testing.T) bool {
}
})
// this call blocks, up until Complete Transaction is called
txHashCheck, err := backend.SendTransaction(nil, status.SendTxArgs{
// this call blocks, up until Complete Transaction is called
txCheckHash, err := statusAPI.SendTransaction(nil, common.SendTxArgs{
From: common.FromAddress(TestConfig.Account1.Address),
To: common.ToAddress(TestConfig.Account2.Address),
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
})
if err != nil {
t.Errorf("Test failed: cannot send transaction: %v", err)
t.Errorf("Failed to SendTransaction: %s", err)
return false
}
<-queuedTxCompleted // make sure that complete transaction handler completes its magic, before we proceed
if txHash != txHashCheck.Hex() {
t.Errorf("Transaction hash returned from SendTransaction is invalid: expected %s, got %s", txHashCheck.Hex(), txHash)
if txHash != txCheckHash.Hex() {
t.Errorf("Transaction hash returned from SendTransaction is invalid: expected %s, got %s",
txCheckHash.Hex(), txHash)
return false
}
if reflect.DeepEqual(txHashCheck, gethcommon.Hash{}) {
if reflect.DeepEqual(txCheckHash, gethcommon.Hash{}) {
t.Error("Test failed: transaction was never queued or completed")
return false
}
if backend.TransactionQueue().Count() != 0 {
if txQueue.Count() != 0 {
t.Error("tx queue must be empty at this point")
return false
}
@ -869,16 +864,8 @@ func testCompleteTransaction(t *testing.T) bool {
}
func testCompleteMultipleQueuedTransactions(t *testing.T) bool {
// obtain reference to status backend
lightEthereum, err := statusAPI.NodeManager().LightEthereumService()
if err != nil {
t.Errorf("Test failed: LES service is not running: %v", err)
return false
}
backend := lightEthereum.StatusBackend
// reset queue
backend.TransactionQueue().Reset()
txQueue := statusAPI.TxQueueManager().TransactionQueue()
txQueue.Reset()
// log into account from which transactions will be sent
if err := statusAPI.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password); err != nil {
@ -910,7 +897,7 @@ func testCompleteMultipleQueuedTransactions(t *testing.T) bool {
// this call blocks, and should return when DiscardQueuedTransaction() for a given tx id is called
sendTx := func() {
txHashCheck, err := backend.SendTransaction(nil, status.SendTxArgs{
txHashCheck, err := statusAPI.SendTransaction(nil, common.SendTxArgs{
From: common.FromAddress(TestConfig.Account1.Address),
To: common.ToAddress(TestConfig.Account2.Address),
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
@ -946,7 +933,7 @@ func testCompleteMultipleQueuedTransactions(t *testing.T) bool {
}
results := resultsStruct.Results
if len(results) != (testTxCount+1) || results["invalid-tx-id"].Error != status.ErrQueuedTxIDNotFound.Error() {
if len(results) != (testTxCount+1) || results["invalid-tx-id"].Error != node.ErrQueuedTxIDNotFound.Error() {
t.Errorf("cannot complete txs: %v", results)
return
}
@ -971,7 +958,7 @@ func testCompleteMultipleQueuedTransactions(t *testing.T) bool {
time.Sleep(1 * time.Second) // make sure that tx complete signal propagates
for _, txID := range parsedIDs {
if backend.TransactionQueue().Has(status.QueuedTxID(txID)) {
if txQueue.Has(common.QueuedTxID(txID)) {
t.Errorf("txqueue should not have test tx at this point (it should be completed): %s", txID)
return
}
@ -1001,7 +988,7 @@ func testCompleteMultipleQueuedTransactions(t *testing.T) bool {
return false
}
if backend.TransactionQueue().Count() != 0 {
if txQueue.Count() != 0 {
t.Error("tx queue must be empty at this point")
return false
}
@ -1010,19 +997,11 @@ func testCompleteMultipleQueuedTransactions(t *testing.T) bool {
}
func testDiscardTransaction(t *testing.T) bool {
// obtain reference to status backend
lightEthereum, err := statusAPI.NodeManager().LightEthereumService()
if err != nil {
t.Errorf("Test failed: LES service is not running: %v", err)
return false
}
backend := lightEthereum.StatusBackend
// reset queue
backend.TransactionQueue().Reset()
txQueue := statusAPI.TxQueueManager().TransactionQueue()
txQueue.Reset()
// log into account from which transactions will be sent
if err = statusAPI.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password); err != nil {
if err := statusAPI.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password); err != nil {
t.Errorf("cannot select account: %v", TestConfig.Account1.Address)
return false
}
@ -1036,7 +1015,7 @@ func testDiscardTransaction(t *testing.T) bool {
txFailedEventCalled := false
node.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
var envelope node.SignalEnvelope
if err = json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
return
}
@ -1045,7 +1024,7 @@ func testDiscardTransaction(t *testing.T) bool {
txID = event["id"].(string)
t.Logf("transaction queued (will be discarded soon): {id: %s}\n", txID)
if !backend.TransactionQueue().Has(status.QueuedTxID(txID)) {
if !txQueue.Has(common.QueuedTxID(txID)) {
t.Errorf("txqueue should still have test tx: %s", txID)
return
}
@ -1054,7 +1033,7 @@ func testDiscardTransaction(t *testing.T) bool {
discardResponse := common.DiscardTransactionResult{}
rawResponse := DiscardTransaction(C.CString(txID))
if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &discardResponse); err != nil {
if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &discardResponse); err != nil {
t.Errorf("cannot decode RecoverAccount response (%s): %v", C.GoString(rawResponse), err)
}
@ -1064,14 +1043,14 @@ func testDiscardTransaction(t *testing.T) bool {
}
// try completing discarded transaction
_, err = statusAPI.CompleteTransaction(txID, TestConfig.Account1.Password)
if err != status.ErrQueuedTxIDNotFound {
_, err := statusAPI.CompleteTransaction(common.QueuedTxID(txID), TestConfig.Account1.Password)
if err != node.ErrQueuedTxIDNotFound {
t.Error("expects tx not found, but call to CompleteTransaction succeeded")
return
}
time.Sleep(1 * time.Second) // make sure that tx complete signal propagates
if backend.TransactionQueue().Has(status.QueuedTxID(txID)) {
if txQueue.Has(common.QueuedTxID(txID)) {
t.Errorf("txqueue should not have test tx at this point (it should be discarded): %s", txID)
return
}
@ -1084,7 +1063,7 @@ func testDiscardTransaction(t *testing.T) bool {
t.Logf("transaction return event received: {id: %s}\n", event["id"].(string))
receivedErrMessage := event["error_message"].(string)
expectedErrMessage := status.ErrQueuedTxDiscarded.Error()
expectedErrMessage := node.ErrQueuedTxDiscarded.Error()
if receivedErrMessage != expectedErrMessage {
t.Errorf("unexpected error message received: got %v", receivedErrMessage)
return
@ -1100,13 +1079,13 @@ func testDiscardTransaction(t *testing.T) bool {
}
})
// this call blocks, and should return when DiscardQueuedTransaction() is called
txHashCheck, err := backend.SendTransaction(nil, status.SendTxArgs{
// this call blocks, and should return when DiscardQueuedTransaction() is called
txHashCheck, err := statusAPI.SendTransaction(nil, common.SendTxArgs{
From: common.FromAddress(TestConfig.Account1.Address),
To: common.ToAddress(TestConfig.Account2.Address),
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
})
if err != status.ErrQueuedTxDiscarded {
if err != node.ErrQueuedTxDiscarded {
t.Errorf("expected error not thrown: %v", err)
return false
}
@ -1116,7 +1095,7 @@ func testDiscardTransaction(t *testing.T) bool {
return false
}
if backend.TransactionQueue().Count() != 0 {
if txQueue.Count() != 0 {
t.Error("tx queue must be empty at this point")
return false
}
@ -1130,16 +1109,8 @@ func testDiscardTransaction(t *testing.T) bool {
}
func testDiscardMultipleQueuedTransactions(t *testing.T) bool {
// obtain reference to status backend
lightEthereum, err := statusAPI.NodeManager().LightEthereumService()
if err != nil {
t.Errorf("Test failed: LES service is not running: %v", err)
return false
}
backend := lightEthereum.StatusBackend
// reset queue
backend.TransactionQueue().Reset()
txQueue := statusAPI.TxQueueManager().TransactionQueue()
txQueue.Reset()
// log into account from which transactions will be sent
if err := statusAPI.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password); err != nil {
@ -1166,7 +1137,7 @@ func testDiscardMultipleQueuedTransactions(t *testing.T) bool {
txID = event["id"].(string)
t.Logf("transaction queued (will be discarded soon): {id: %s}\n", txID)
if !backend.TransactionQueue().Has(status.QueuedTxID(txID)) {
if !txQueue.Has(common.QueuedTxID(txID)) {
t.Errorf("txqueue should still have test tx: %s", txID)
return
}
@ -1179,7 +1150,7 @@ func testDiscardMultipleQueuedTransactions(t *testing.T) bool {
t.Logf("transaction return event received: {id: %s}\n", event["id"].(string))
receivedErrMessage := event["error_message"].(string)
expectedErrMessage := status.ErrQueuedTxDiscarded.Error()
expectedErrMessage := node.ErrQueuedTxDiscarded.Error()
if receivedErrMessage != expectedErrMessage {
t.Errorf("unexpected error message received: got %v", receivedErrMessage)
return
@ -1198,14 +1169,14 @@ func testDiscardMultipleQueuedTransactions(t *testing.T) bool {
}
})
// this call blocks, and should return when DiscardQueuedTransaction() for a given tx id is called
// this call blocks, and should return when DiscardQueuedTransaction() for a given tx id is called
sendTx := func() {
txHashCheck, err := backend.SendTransaction(nil, status.SendTxArgs{
txHashCheck, err := statusAPI.SendTransaction(nil, common.SendTxArgs{
From: common.FromAddress(TestConfig.Account1.Address),
To: common.ToAddress(TestConfig.Account2.Address),
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
})
if err != status.ErrQueuedTxDiscarded {
if err != node.ErrQueuedTxDiscarded {
t.Errorf("expected error not thrown: %v", err)
return
}
@ -1236,7 +1207,7 @@ func testDiscardMultipleQueuedTransactions(t *testing.T) bool {
}
discardResults := discardResultsStruct.Results
if len(discardResults) != 1 || discardResults["invalid-tx-id"].Error != status.ErrQueuedTxIDNotFound.Error() {
if len(discardResults) != 1 || discardResults["invalid-tx-id"].Error != node.ErrQueuedTxIDNotFound.Error() {
t.Errorf("cannot discard txs: %v", discardResults)
return
}
@ -1258,7 +1229,7 @@ func testDiscardMultipleQueuedTransactions(t *testing.T) bool {
t.Errorf("tx id not set in result: expected id is %s", txID)
return
}
if txResult.Error != status.ErrQueuedTxIDNotFound.Error() {
if txResult.Error != node.ErrQueuedTxIDNotFound.Error() {
t.Errorf("invalid error for %s", txResult.Hash)
return
}
@ -1270,7 +1241,7 @@ func testDiscardMultipleQueuedTransactions(t *testing.T) bool {
time.Sleep(1 * time.Second) // make sure that tx complete signal propagates
for _, txID := range parsedIDs {
if backend.TransactionQueue().Has(status.QueuedTxID(txID)) {
if txQueue.Has(common.QueuedTxID(txID)) {
t.Errorf("txqueue should not have test tx at this point (it should be discarded): %s", txID)
return
}
@ -1299,7 +1270,7 @@ func testDiscardMultipleQueuedTransactions(t *testing.T) bool {
return false
}
if backend.TransactionQueue().Count() != 0 {
if txQueue.Count() != 0 {
t.Error("tx queue must be empty at this point")
return false
}

View File

@ -1,6 +1,8 @@
package api
import (
"context"
"github.com/ethereum/go-ethereum/accounts/keystore"
gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/status-im/status-go/geth/common"
@ -34,6 +36,11 @@ func (api *StatusAPI) JailManager() common.JailManager {
return api.b.JailManager()
}
// TxQueueManager returns reference to account manager
func (api *StatusAPI) TxQueueManager() common.TxQueueManager {
return api.b.TxQueueManager()
}
// StartNode start Status node, fails if node is already started
func (api *StatusAPI) StartNode(config *params.NodeConfig) error {
nodeStarted, err := api.b.StartNode(config)
@ -141,24 +148,29 @@ func (api *StatusAPI) Logout() error {
return api.b.AccountManager().Logout()
}
// SendTransaction creates a new transaction and waits until it's complete.
func (api *StatusAPI) SendTransaction(ctx context.Context, args common.SendTxArgs) (gethcommon.Hash, error) {
return api.b.SendTransaction(ctx, args)
}
// CompleteTransaction instructs backend to complete sending of a given transaction
func (api *StatusAPI) CompleteTransaction(id, password string) (gethcommon.Hash, error) {
return api.b.CompleteTransaction(id, password)
func (api *StatusAPI) CompleteTransaction(id common.QueuedTxID, password string) (gethcommon.Hash, error) {
return api.b.txQueueManager.CompleteTransaction(id, password)
}
// CompleteTransactions instructs backend to complete sending of multiple transactions
func (api *StatusAPI) CompleteTransactions(ids, password string) map[string]common.RawCompleteTransactionResult {
return api.b.CompleteTransactions(ids, password)
func (api *StatusAPI) CompleteTransactions(ids []common.QueuedTxID, password string) map[common.QueuedTxID]common.RawCompleteTransactionResult {
return api.b.txQueueManager.CompleteTransactions(ids, password)
}
// DiscardTransaction discards a given transaction from transaction queue
func (api *StatusAPI) DiscardTransaction(id string) error {
return api.b.DiscardTransaction(id)
func (api *StatusAPI) DiscardTransaction(id common.QueuedTxID) error {
return api.b.txQueueManager.DiscardTransaction(id)
}
// DiscardTransactions discards given multiple transactions from transaction queue
func (api *StatusAPI) DiscardTransactions(ids string) map[string]common.RawDiscardTransactionResult {
return api.b.DiscardTransactions(ids)
func (api *StatusAPI) DiscardTransactions(ids []common.QueuedTxID) map[common.QueuedTxID]common.RawDiscardTransactionResult {
return api.b.txQueueManager.DiscardTransactions(ids)
}
// JailParse creates a new jail cell context, with the given chatID as identifier.

View File

@ -1,6 +1,7 @@
package api
import (
"context"
"sync"
gethcommon "github.com/ethereum/go-ethereum/common"
@ -29,13 +30,14 @@ func NewStatusBackend() *StatusBackend {
nodeManager := node.NewNodeManager()
accountManager := node.NewAccountManager(nodeManager)
txQueueManager := node.NewTxQueueManager(nodeManager, accountManager)
return &StatusBackend{
nodeManager: nodeManager,
accountManager: accountManager,
jailManager: jail.New(nodeManager, accountManager),
jailManager: jail.New(nodeManager, accountManager, txQueueManager),
rpcManager: node.NewRPCManager(nodeManager),
txQueueManager: node.NewTxQueueManager(nodeManager, accountManager),
txQueueManager: txQueueManager,
}
}
@ -54,6 +56,11 @@ func (m *StatusBackend) JailManager() common.JailManager {
return m.jailManager
}
// TxQueueManager returns reference to jail
func (m *StatusBackend) TxQueueManager() common.TxQueueManager {
return m.txQueueManager
}
// IsNodeRunning confirm that node is running
func (m *StatusBackend) IsNodeRunning() bool {
return m.nodeManager.IsNodeRunning()
@ -73,6 +80,8 @@ func (m *StatusBackend) StartNode(config *params.NodeConfig) (<-chan struct{}, e
return nil, err
}
m.txQueueManager.Start()
m.nodeReady = make(chan struct{}, 1)
go m.onNodeStart(nodeStarted, m.nodeReady) // waits on nodeStarted, writes to backendReady
@ -112,6 +121,8 @@ func (m *StatusBackend) StopNode() (<-chan struct{}, error) {
return nil, err
}
m.txQueueManager.Stop()
backendStopped := make(chan struct{}, 1)
go func() {
<-nodeStopped
@ -172,23 +183,42 @@ func (m *StatusBackend) CallRPC(inputJSON string) string {
return m.rpcManager.Call(inputJSON)
}
// SendTransaction creates a new transaction and waits until it's complete.
func (m *StatusBackend) SendTransaction(ctx context.Context, args common.SendTxArgs) (gethcommon.Hash, error) {
if ctx == nil {
ctx = context.Background()
}
tx := m.txQueueManager.CreateTransaction(ctx, args)
if err := m.txQueueManager.QueueTransaction(tx); err != nil {
return gethcommon.Hash{}, err
}
if err := m.txQueueManager.WaitForTransaction(tx); err != nil {
return gethcommon.Hash{}, err
}
return tx.Hash, nil
}
// CompleteTransaction instructs backend to complete sending of a given transaction
func (m *StatusBackend) CompleteTransaction(id, password string) (gethcommon.Hash, error) {
func (m *StatusBackend) CompleteTransaction(id common.QueuedTxID, password string) (gethcommon.Hash, error) {
return m.txQueueManager.CompleteTransaction(id, password)
}
// CompleteTransactions instructs backend to complete sending of multiple transactions
func (m *StatusBackend) CompleteTransactions(ids, password string) map[string]common.RawCompleteTransactionResult {
func (m *StatusBackend) CompleteTransactions(ids []common.QueuedTxID, password string) map[common.QueuedTxID]common.RawCompleteTransactionResult {
return m.txQueueManager.CompleteTransactions(ids, password)
}
// DiscardTransaction discards a given transaction from transaction queue
func (m *StatusBackend) DiscardTransaction(id string) error {
func (m *StatusBackend) DiscardTransaction(id common.QueuedTxID) error {
return m.txQueueManager.DiscardTransaction(id)
}
// DiscardTransactions discards given multiple transactions from transaction queue
func (m *StatusBackend) DiscardTransactions(ids string) map[string]common.RawDiscardTransactionResult {
func (m *StatusBackend) DiscardTransactions(ids []common.QueuedTxID) map[common.QueuedTxID]common.RawDiscardTransactionResult {
return m.txQueueManager.DiscardTransactions(ids)
}
@ -208,10 +238,10 @@ func (m *StatusBackend) registerHandlers() error {
lightEthereum.StatusBackend.SetAccountsFilterHandler(m.accountManager.AccountsListRequestHandler())
log.Info("Registered handler", "fn", "AccountsFilterHandler")
lightEthereum.StatusBackend.SetTransactionQueueHandler(m.txQueueManager.TransactionQueueHandler())
m.txQueueManager.SetTransactionQueueHandler(m.txQueueManager.TransactionQueueHandler())
log.Info("Registered handler", "fn", "TransactionQueueHandler")
lightEthereum.StatusBackend.SetTransactionReturnHandler(m.txQueueManager.TransactionReturnHandler())
m.txQueueManager.SetTransactionReturnHandler(m.txQueueManager.TransactionReturnHandler())
log.Info("Registered handler", "fn", "TransactionReturnHandler")
return nil

View File

@ -49,7 +49,7 @@ func (s *BackendTestSuite) TestJailSendQueuedTransaction() {
}`
txCompletedSuccessfully := make(chan struct{})
txHashes := make(chan gethcommon.Hash)
txHashes := make(chan gethcommon.Hash, 1)
// replace transaction notification handler
requireMessageId := false
@ -67,9 +67,9 @@ func (s *BackendTestSuite) TestJailSendQueuedTransaction() {
} else {
require.Empty(messageId, "Message id is not required, but provided")
}
log.Info("Transaction queued (will be completed shortly)", "id", event["id"].(string))
txHash, err := s.backend.CompleteTransaction(event["id"].(string), TestConfig.Account1.Password)
txID := event["id"].(string)
txHash, err := s.backend.CompleteTransaction(common.QueuedTxID(txID), TestConfig.Account1.Password)
require.NoError(err, "cannot complete queued transaction[%v]", event["id"])
log.Info("Transaction complete", "URL", "https://ropsten.etherscan.io/tx/%s"+txHash.Hex())
@ -211,7 +211,8 @@ func (s *BackendTestSuite) TestContractDeployment() {
var txHash gethcommon.Hash
node.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
var envelope node.SignalEnvelope
err := json.Unmarshal([]byte(jsonEvent), &envelope)
var err error
err = json.Unmarshal([]byte(jsonEvent), &envelope)
require.NoError(err, fmt.Sprintf("cannot unmarshal JSON: %s", jsonEvent))
if envelope.Type == node.EventTransactionQueued {
@ -222,8 +223,8 @@ func (s *BackendTestSuite) TestContractDeployment() {
s.NoError(s.backend.AccountManager().SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password))
var err error
txHash, err = s.backend.CompleteTransaction(event["id"].(string), TestConfig.Account1.Password)
txID := event["id"].(string)
txHash, err = s.backend.CompleteTransaction(common.QueuedTxID(txID), TestConfig.Account1.Password)
if s.NoError(err, event["id"]) {
s.T().Logf("contract transaction complete, URL: %s", "https://ropsten.etherscan.io/tx/"+txHash.Hex())
}
@ -240,9 +241,13 @@ func (s *BackendTestSuite) TestContractDeployment() {
from: '` + TestConfig.Account1.Address + `',
data: '0x6060604052341561000c57fe5b5b60a58061001b6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680636ffa1caa14603a575bfe5b3415604157fe5b60556004808035906020019091905050606b565b6040518082815260200191505060405180910390f35b60008160020290505b9190505600a165627a7a72305820ccdadd737e4ac7039963b54cee5e5afb25fa859a275252bdcf06f653155228210029',
gas: '` + strconv.Itoa(params.DefaultGas) + `'
}, function (e, contract){
}, function (e, contract) {
// NOTE: The callback will fire twice!
// Once the contract has the transactionHash property set and once its deployed on an address.
if (!e) {
responseValue = contract.transactionHash
if (!contract.address) {
responseValue = contract.transactionHash;
}
}
})
`)
@ -757,7 +762,8 @@ func (s *BackendTestSuite) TestJailVMPersistence() {
//}
//var txHash common.Hash
txHash, err := s.backend.CompleteTransaction(event["id"].(string), TestConfig.Account1.Password)
txID := event["id"].(string)
txHash, err := s.backend.CompleteTransaction(common.QueuedTxID(txID), TestConfig.Account1.Password)
require.NoError(err, "cannot complete queued transaction[%v]: %v", event["id"], err)
s.T().Logf("Transaction complete: https://ropsten.etherscan.io/tx/%s", txHash.Hex())

View File

@ -102,6 +102,10 @@ func (s *BackendTestSuite) LightEthereumService() *les.LightEthereum {
return lightEthereum
}
func (s *BackendTestSuite) TxQueueManager() common.TxQueueManager {
return s.backend.TxQueueManager()
}
func (s *BackendTestSuite) RestartTestNode() {
require := s.Require()
require.NotNil(s.backend)
@ -365,12 +369,14 @@ func (s *BackendTestSuite) TestRaceConditions() {
},
func(config *params.NodeConfig) {
log.Info("CompleteTransactions()")
s.T().Logf("CompleteTransactions(), result: %v", s.backend.CompleteTransactions(`["id1","id2"]`, "password"))
ids := []common.QueuedTxID{"id1", "id2"}
s.T().Logf("CompleteTransactions(), result: %v", s.backend.CompleteTransactions(ids, "password"))
progress <- struct{}{}
},
func(config *params.NodeConfig) {
log.Info("DiscardTransactions()")
s.T().Logf("DiscardTransactions(), result: %v", s.backend.DiscardTransactions(`["id1","id2"]`))
ids := []common.QueuedTxID{"id1", "id2"}
s.T().Logf("DiscardTransactions(), result: %v", s.backend.DiscardTransactions(ids))
progress <- struct{}{}
},
}

View File

@ -10,7 +10,6 @@ import (
"github.com/ethereum/go-ethereum/accounts/keystore"
gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/les/status"
"github.com/status-im/status-go/geth/common"
"github.com/status-im/status-go/geth/log"
"github.com/status-im/status-go/geth/node"
@ -28,20 +27,16 @@ func (s *BackendTestSuite) TestSendContractTx() {
time.Sleep(TestConfig.Node.SyncSeconds * time.Second) // allow to sync
backend := s.LightEthereumService().StatusBackend
require.NotNil(backend)
// create an account
sampleAddress, _, _, err := s.backend.AccountManager().CreateAccount(TestConfig.Account1.Password)
require.NoError(err)
// make sure you panic if transaction complete doesn't return
completeQueuedTransaction := make(chan struct{}, 10)
common.PanicAfter(1*time.Minute, completeQueuedTransaction, s.T().Name())
common.PanicAfter(2*time.Minute, completeQueuedTransaction, s.T().Name())
// replace transaction notification handler
var txHash = gethcommon.Hash{}
var txHashCheck = gethcommon.Hash{}
var txHash gethcommon.Hash
node.SetDefaultNodeNotificationHandler(func(jsonEvent string) { // nolint :dupl
var envelope node.SignalEnvelope
err := json.Unmarshal([]byte(jsonEvent), &envelope)
@ -53,22 +48,37 @@ func (s *BackendTestSuite) TestSendContractTx() {
// the first call will fail (we are not logged in, but trying to complete tx)
log.Info("trying to complete with no user logged in")
txHash, err = s.backend.CompleteTransaction(event["id"].(string), TestConfig.Account1.Password)
s.EqualError(err, node.ErrNoAccountSelected.Error(),
fmt.Sprintf("expected error on queued transaction[%v] not thrown", event["id"]))
txHash, err = s.backend.CompleteTransaction(
common.QueuedTxID(event["id"].(string)),
TestConfig.Account1.Password,
)
s.EqualError(
err,
node.ErrNoAccountSelected.Error(),
fmt.Sprintf("expected error on queued transaction[%v] not thrown", event["id"]),
)
// the second call will also fail (we are logged in as different user)
log.Info("trying to complete with invalid user")
err = s.backend.AccountManager().SelectAccount(sampleAddress, TestConfig.Account1.Password)
s.NoError(err)
txHash, err = s.backend.CompleteTransaction(event["id"].(string), TestConfig.Account1.Password)
s.EqualError(err, status.ErrInvalidCompleteTxSender.Error(),
fmt.Sprintf("expected error on queued transaction[%v] not thrown", event["id"]))
txHash, err = s.backend.CompleteTransaction(
common.QueuedTxID(event["id"].(string)),
TestConfig.Account1.Password,
)
s.EqualError(
err,
node.ErrInvalidCompleteTxSender.Error(),
fmt.Sprintf("expected error on queued transaction[%v] not thrown", event["id"]),
)
// the third call will work as expected (as we are logged in with correct credentials)
log.Info("trying to complete with correct user, this should suceed")
log.Info("trying to complete with correct user, this should succeed")
s.NoError(s.backend.AccountManager().SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password))
txHash, err = s.backend.CompleteTransaction(event["id"].(string), TestConfig.Account1.Password)
txHash, err = s.backend.CompleteTransaction(
common.QueuedTxID(event["id"].(string)),
TestConfig.Account1.Password,
)
s.NoError(err, fmt.Sprintf("cannot complete queued transaction[%v]", event["id"]))
log.Info("contract transaction complete", "URL", "https://rinkeby.etherscan.io/tx/"+txHash.Hex())
@ -81,8 +91,7 @@ func (s *BackendTestSuite) TestSendContractTx() {
byteCode, err := hexutil.Decode(`0x6060604052341561000c57fe5b5b60a58061001b6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680636ffa1caa14603a575bfe5b3415604157fe5b60556004808035906020019091905050606b565b6040518082815260200191505060405180910390f35b60008160020290505b9190505600a165627a7a72305820ccdadd737e4ac7039963b54cee5e5afb25fa859a275252bdcf06f653155228210029`)
require.NoError(err)
// send transaction
txHashCheck, err = backend.SendTransaction(nil, status.SendTxArgs{
txHashCheck, err := s.backend.SendTransaction(nil, common.SendTxArgs{
From: common.FromAddress(TestConfig.Account1.Address),
To: nil, // marker, contract creation is expected
//Value: (*hexutil.Big)(new(big.Int).Mul(big.NewInt(1), gethcommon.Ether)),
@ -94,7 +103,7 @@ func (s *BackendTestSuite) TestSendContractTx() {
<-completeQueuedTransaction
s.Equal(txHashCheck.Hex(), txHash.Hex(), "transaction hash returned from SendTransaction is invalid")
s.False(reflect.DeepEqual(txHashCheck, gethcommon.Hash{}), "transaction was never queued or completed")
s.Zero(backend.TransactionQueue().Count(), "tx queue must be empty at this point")
s.Zero(s.TxQueueManager().TransactionQueue().Count(), "tx queue must be empty at this point")
}
func (s *BackendTestSuite) TestSendEtherTx() {
@ -119,7 +128,6 @@ func (s *BackendTestSuite) TestSendEtherTx() {
// replace transaction notification handler
var txHash = gethcommon.Hash{}
var txHashCheck = gethcommon.Hash{}
node.SetDefaultNodeNotificationHandler(func(jsonEvent string) { // nolint: dupl
var envelope node.SignalEnvelope
err := json.Unmarshal([]byte(jsonEvent), &envelope)
@ -131,22 +139,35 @@ func (s *BackendTestSuite) TestSendEtherTx() {
// the first call will fail (we are not logged in, but trying to complete tx)
log.Info("trying to complete with no user logged in")
txHash, err = s.backend.CompleteTransaction(event["id"].(string), TestConfig.Account1.Password)
s.EqualError(err, node.ErrNoAccountSelected.Error(),
fmt.Sprintf("expected error on queued transaction[%v] not thrown", event["id"]))
txHash, err = s.backend.CompleteTransaction(
common.QueuedTxID(event["id"].(string)),
TestConfig.Account1.Password,
)
s.EqualError(
err,
node.ErrNoAccountSelected.Error(),
fmt.Sprintf("expected error on queued transaction[%v] not thrown", event["id"]),
)
// the second call will also fail (we are logged in as different user)
log.Info("trying to complete with invalid user")
err = s.backend.AccountManager().SelectAccount(sampleAddress, TestConfig.Account1.Password)
s.NoError(err)
txHash, err = s.backend.CompleteTransaction(event["id"].(string), TestConfig.Account1.Password)
s.EqualError(err, status.ErrInvalidCompleteTxSender.Error(),
fmt.Sprintf("expected error on queued transaction[%v] not thrown", event["id"]))
txHash, err = s.backend.CompleteTransaction(
common.QueuedTxID(event["id"].(string)), TestConfig.Account1.Password)
s.EqualError(
err,
node.ErrInvalidCompleteTxSender.Error(),
fmt.Sprintf("expected error on queued transaction[%v] not thrown", event["id"]),
)
// the third call will work as expected (as we are logged in with correct credentials)
log.Info("trying to complete with correct user, this should suceed")
log.Info("trying to complete with correct user, this should succeed")
s.NoError(s.backend.AccountManager().SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password))
txHash, err = s.backend.CompleteTransaction(event["id"].(string), TestConfig.Account1.Password)
txHash, err = s.backend.CompleteTransaction(
common.QueuedTxID(event["id"].(string)),
TestConfig.Account1.Password,
)
s.NoError(err, fmt.Sprintf("cannot complete queued transaction[%v]", event["id"]))
log.Info("contract transaction complete", "URL", "https://rinkeby.etherscan.io/tx/"+txHash.Hex())
@ -155,8 +176,8 @@ func (s *BackendTestSuite) TestSendEtherTx() {
}
})
// this call blocks, up until Complete Transaction is called
txHashCheck, err = backend.SendTransaction(nil, status.SendTxArgs{
// this call blocks, up until Complete Transaction is called
txHashCheck, err := s.backend.SendTransaction(nil, common.SendTxArgs{
From: common.FromAddress(TestConfig.Account1.Address),
To: common.ToAddress(TestConfig.Account2.Address),
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
@ -166,7 +187,7 @@ func (s *BackendTestSuite) TestSendEtherTx() {
<-completeQueuedTransaction
s.Equal(txHashCheck.Hex(), txHash.Hex(), "transaction hash returned from SendTransaction is invalid")
s.False(reflect.DeepEqual(txHashCheck, gethcommon.Hash{}), "transaction was never queued or completed")
s.Zero(backend.TransactionQueue().Count(), "tx queue must be empty at this point")
s.Zero(s.backend.TxQueueManager().TransactionQueue().Count(), "tx queue must be empty at this point")
}
func (s *BackendTestSuite) TestDoubleCompleteQueuedTransactions() {
@ -189,7 +210,6 @@ func (s *BackendTestSuite) TestDoubleCompleteQueuedTransactions() {
common.PanicAfter(1*time.Minute, completeQueuedTransaction, s.T().Name())
// replace transaction notification handler
var txID string
txFailedEventCalled := false
txHash := gethcommon.Hash{}
node.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
@ -199,7 +219,7 @@ func (s *BackendTestSuite) TestDoubleCompleteQueuedTransactions() {
if envelope.Type == node.EventTransactionQueued {
event := envelope.Event.(map[string]interface{})
txID = event["id"].(string)
txID := common.QueuedTxID(event["id"].(string))
log.Info("transaction queued (will be failed and completed on the second call)", "id", txID)
// try with wrong password
@ -207,15 +227,12 @@ func (s *BackendTestSuite) TestDoubleCompleteQueuedTransactions() {
_, err = s.backend.CompleteTransaction(txID, TestConfig.Account1.Password+"wrong")
s.EqualError(err, keystore.ErrDecrypt.Error())
s.Equal(1, backend.TransactionQueue().Count(), "txqueue cannot be empty, as tx has failed")
s.Equal(1, s.TxQueueManager().TransactionQueue().Count(), "txqueue cannot be empty, as tx has failed")
// now try to complete transaction, but with the correct password
txHash, err = s.backend.CompleteTransaction(event["id"].(string), TestConfig.Account1.Password)
txHash, err = s.backend.CompleteTransaction(txID, TestConfig.Account1.Password)
s.NoError(err)
time.Sleep(1 * time.Second) // make sure that tx complete signal propagates
s.Equal(0, backend.TransactionQueue().Count(), "txqueue must be empty, as tx has completed")
log.Info("transaction complete", "URL", "https://rinkeby.etherscan.io/tx/"+txHash.Hex())
close(completeQueuedTransaction)
}
@ -235,8 +252,8 @@ func (s *BackendTestSuite) TestDoubleCompleteQueuedTransactions() {
}
})
// this call blocks, and should return on *second* attempt to CompleteTransaction (w/ the correct password)
txHashCheck, err := backend.SendTransaction(nil, status.SendTxArgs{
// this call blocks, and should return on *second* attempt to CompleteTransaction (w/ the correct password)
txHashCheck, err := s.backend.SendTransaction(nil, common.SendTxArgs{
From: common.FromAddress(TestConfig.Account1.Address),
To: common.ToAddress(TestConfig.Account2.Address),
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
@ -246,7 +263,7 @@ func (s *BackendTestSuite) TestDoubleCompleteQueuedTransactions() {
<-completeQueuedTransaction
s.Equal(txHashCheck.Hex(), txHash.Hex(), "transaction hash returned from SendTransaction is invalid")
s.False(reflect.DeepEqual(txHashCheck, gethcommon.Hash{}), "transaction was never queued or completed")
s.Zero(backend.TransactionQueue().Count(), "tx queue must be empty at this point")
s.Zero(s.backend.TxQueueManager().TransactionQueue().Count(), "tx queue must be empty at this point")
s.True(txFailedEventCalled, "expected tx failure signal is not received")
}
@ -263,7 +280,7 @@ func (s *BackendTestSuite) TestDiscardQueuedTransaction() {
require.NotNil(backend)
// reset queue
backend.TransactionQueue().Reset()
s.backend.TxQueueManager().TransactionQueue().Reset()
// log into account from which transactions will be sent
require.NoError(s.backend.AccountManager().SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password))
@ -273,7 +290,6 @@ func (s *BackendTestSuite) TestDiscardQueuedTransaction() {
common.PanicAfter(1*time.Minute, completeQueuedTransaction, s.T().Name())
// replace transaction notification handler
var txID string
txFailedEventCalled := false
node.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
var envelope node.SignalEnvelope
@ -282,10 +298,10 @@ func (s *BackendTestSuite) TestDiscardQueuedTransaction() {
if envelope.Type == node.EventTransactionQueued {
event := envelope.Event.(map[string]interface{})
txID = event["id"].(string)
txID := common.QueuedTxID(event["id"].(string))
log.Info("transaction queued (will be discarded soon)", "id", txID)
s.True(backend.TransactionQueue().Has(status.QueuedTxID(txID)), "txqueue should still have test tx")
s.True(s.backend.TxQueueManager().TransactionQueue().Has(txID), "txqueue should still have test tx")
// discard
err := s.backend.DiscardTransaction(txID)
@ -296,7 +312,7 @@ func (s *BackendTestSuite) TestDiscardQueuedTransaction() {
s.EqualError(err, "transaction hash not found", "expects tx not found, but call to CompleteTransaction succeeded")
time.Sleep(1 * time.Second) // make sure that tx complete signal propagates
s.False(backend.TransactionQueue().Has(status.QueuedTxID(txID)),
s.False(s.backend.TxQueueManager().TransactionQueue().Has(txID),
fmt.Sprintf("txqueue should not have test tx at this point (it should be discarded): %s", txID))
close(completeQueuedTransaction)
@ -307,7 +323,7 @@ func (s *BackendTestSuite) TestDiscardQueuedTransaction() {
log.Info("transaction return event received", "id", event["id"].(string))
receivedErrMessage := event["error_message"].(string)
expectedErrMessage := status.ErrQueuedTxDiscarded.Error()
expectedErrMessage := node.ErrQueuedTxDiscarded.Error()
s.Equal(receivedErrMessage, expectedErrMessage)
receivedErrCode := event["error_code"].(string)
@ -317,17 +333,17 @@ func (s *BackendTestSuite) TestDiscardQueuedTransaction() {
}
})
// this call blocks, and should return when DiscardQueuedTransaction() is called
txHashCheck, err := backend.SendTransaction(nil, status.SendTxArgs{
// this call blocks, and should return when DiscardQueuedTransaction() is called
txHashCheck, err := s.backend.SendTransaction(nil, common.SendTxArgs{
From: common.FromAddress(TestConfig.Account1.Address),
To: common.ToAddress(TestConfig.Account2.Address),
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
})
s.EqualError(err, status.ErrQueuedTxDiscarded.Error(), "transaction is expected to be discarded")
s.EqualError(err, node.ErrQueuedTxDiscarded.Error(), "transaction is expected to be discarded")
<-completeQueuedTransaction
s.True(reflect.DeepEqual(txHashCheck, gethcommon.Hash{}), "transaction returned hash, while it shouldn't")
s.Zero(backend.TransactionQueue().Count(), "tx queue must be empty at this point")
s.Zero(s.backend.TxQueueManager().TransactionQueue().Count(), "tx queue must be empty at this point")
s.True(txFailedEventCalled, "expected tx failure signal is not received")
}
@ -338,31 +354,29 @@ func (s *BackendTestSuite) TestCompleteMultipleQueuedTransactions() {
s.StartTestBackend(params.RopstenNetworkID)
defer s.StopTestBackend()
time.Sleep(TestConfig.Node.SyncSeconds * time.Second) // allow to sync
// allow to sync
time.Sleep(TestConfig.Node.SyncSeconds * time.Second)
backend := s.LightEthereumService().StatusBackend
require.NotNil(backend)
// reset queue
backend.TransactionQueue().Reset()
s.TxQueueManager().TransactionQueue().Reset()
// log into account from which transactions will be sent
require.NoError(s.backend.AccountManager().SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password))
err := s.backend.AccountManager().SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password)
require.NoError(err)
// make sure you panic if transaction complete doesn't return
testTxCount := 3
txIDs := make(chan string, testTxCount)
txIDs := make(chan common.QueuedTxID, testTxCount)
allTestTxCompleted := make(chan struct{}, 1)
// replace transaction notification handler
node.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
var txID string
var envelope node.SignalEnvelope
err := json.Unmarshal([]byte(jsonEvent), &envelope)
s.NoError(err, fmt.Sprintf("cannot unmarshal JSON: %s", jsonEvent))
if envelope.Type == node.EventTransactionQueued {
event := envelope.Event.(map[string]interface{})
txID = event["id"].(string)
txID := common.QueuedTxID(event["id"].(string))
log.Info("transaction queued (will be completed in a single call, once aggregated)", "id", txID)
txIDs <- txID
@ -371,7 +385,7 @@ func (s *BackendTestSuite) TestCompleteMultipleQueuedTransactions() {
// this call blocks, and should return when DiscardQueuedTransaction() for a given tx id is called
sendTx := func() {
txHashCheck, err := backend.SendTransaction(nil, status.SendTxArgs{
txHashCheck, err := s.backend.SendTransaction(nil, common.SendTxArgs{
From: common.FromAddress(TestConfig.Account1.Address),
To: common.ToAddress(TestConfig.Account2.Address),
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
@ -381,16 +395,9 @@ func (s *BackendTestSuite) TestCompleteMultipleQueuedTransactions() {
}
// wait for transactions, and complete them in a single call
completeTxs := func(txIDStrings string) {
var parsedIDs []string
err := json.Unmarshal([]byte(txIDStrings), &parsedIDs)
s.NoError(err)
parsedIDs = append(parsedIDs, "invalid-tx-id")
updatedTxIDStrings, _ := json.Marshal(parsedIDs)
// complete
results := s.backend.CompleteTransactions(string(updatedTxIDStrings), TestConfig.Account1.Password)
completeTxs := func(txIDs []common.QueuedTxID) {
txIDs = append(txIDs, "invalid-tx-id")
results := s.backend.CompleteTransactions(txIDs, TestConfig.Account1.Password)
require.Len(results, testTxCount+1)
require.EqualError(results["invalid-tx-id"].Error, "transaction hash not found")
@ -399,29 +406,29 @@ func (s *BackendTestSuite) TestCompleteMultipleQueuedTransactions() {
txResult.Error != nil && txID != "invalid-tx-id",
"invalid error for %s", txID,
)
require.False(
txResult.Hash.Hex() == "0x0000000000000000000000000000000000000000000000000000000000000000" && txID != "invalid-tx-id",
txResult.Hash == (gethcommon.Hash{}) && txID != "invalid-tx-id",
"invalid hash (expected non empty hash): %s", txID,
)
log.Info("transaction complete", "URL", "https://rinkeby.etherscan.io/tx/"+txResult.Hash.Hex())
}
time.Sleep(1 * time.Second) // make sure that tx complete signal propagates
for _, txID := range parsedIDs {
for _, txID := range txIDs {
require.False(
backend.TransactionQueue().Has(status.QueuedTxID(txID)),
s.backend.TxQueueManager().TransactionQueue().Has(txID),
"txqueue should not have test tx at this point (it should be completed)",
)
}
}
go func() {
var txIDStrings []string
ids := make([]common.QueuedTxID, testTxCount)
for i := 0; i < testTxCount; i++ {
txIDStrings = append(txIDStrings, <-txIDs)
ids[i] = <-txIDs
}
txIDJSON, _ := json.Marshal(txIDStrings)
completeTxs(string(txIDJSON))
completeTxs(ids)
allTestTxCompleted <- struct{}{}
}()
@ -437,7 +444,7 @@ func (s *BackendTestSuite) TestCompleteMultipleQueuedTransactions() {
require.Fail("test timed out")
}
require.Empty(backend.TransactionQueue().Count())
require.Zero(s.TxQueueManager().TransactionQueue().Count(), "queue should be empty")
}
func (s *BackendTestSuite) TestDiscardMultipleQueuedTransactions() {
@ -453,29 +460,29 @@ func (s *BackendTestSuite) TestDiscardMultipleQueuedTransactions() {
require.NotNil(backend)
// reset queue
backend.TransactionQueue().Reset()
s.backend.TxQueueManager().TransactionQueue().Reset()
// log into account from which transactions will be sent
require.NoError(s.backend.AccountManager().SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password))
// make sure you panic if transaction complete doesn't return
testTxCount := 3
txIDs := make(chan string, testTxCount)
txIDs := make(chan common.QueuedTxID, testTxCount)
allTestTxDiscarded := make(chan struct{}, 1)
// replace transaction notification handler
txFailedEventCallCount := 0
node.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
var txID string
var envelope node.SignalEnvelope
err := json.Unmarshal([]byte(jsonEvent), &envelope)
s.NoError(err)
if envelope.Type == node.EventTransactionQueued {
event := envelope.Event.(map[string]interface{})
txID = event["id"].(string)
txID := common.QueuedTxID(event["id"].(string))
log.Info("transaction queued (will be discarded soon)", "id", txID)
s.True(backend.TransactionQueue().Has(status.QueuedTxID(txID)), "txqueue should still have test tx")
s.True(s.backend.TxQueueManager().TransactionQueue().Has(txID),
"txqueue should still have test tx")
txIDs <- txID
}
@ -484,7 +491,7 @@ func (s *BackendTestSuite) TestDiscardMultipleQueuedTransactions() {
log.Info("transaction return event received", "id", event["id"].(string))
receivedErrMessage := event["error_message"].(string)
expectedErrMessage := status.ErrQueuedTxDiscarded.Error()
expectedErrMessage := node.ErrQueuedTxDiscarded.Error()
s.Equal(receivedErrMessage, expectedErrMessage)
receivedErrCode := event["error_code"].(string)
@ -499,32 +506,27 @@ func (s *BackendTestSuite) TestDiscardMultipleQueuedTransactions() {
// this call blocks, and should return when DiscardQueuedTransaction() for a given tx id is called
sendTx := func() {
txHashCheck, err := backend.SendTransaction(nil, status.SendTxArgs{
txHashCheck, err := s.backend.SendTransaction(nil, common.SendTxArgs{
From: common.FromAddress(TestConfig.Account1.Address),
To: common.ToAddress(TestConfig.Account2.Address),
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
})
s.EqualError(err, status.ErrQueuedTxDiscarded.Error())
s.EqualError(err, node.ErrQueuedTxDiscarded.Error())
s.True(reflect.DeepEqual(txHashCheck, gethcommon.Hash{}), "transaction returned hash, while it shouldn't")
}
// wait for transactions, and discard immediately
discardTxs := func(txIDStrings string) {
var parsedIDs []string
err := json.Unmarshal([]byte(txIDStrings), &parsedIDs)
s.NoError(err)
parsedIDs = append(parsedIDs, "invalid-tx-id")
updatedTxIDStrings, _ := json.Marshal(parsedIDs)
discardTxs := func(txIDs []common.QueuedTxID) {
txIDs = append(txIDs, "invalid-tx-id")
// discard
discardResults := s.backend.DiscardTransactions(string(updatedTxIDStrings))
discardResults := s.backend.DiscardTransactions(txIDs)
require.Len(discardResults, 1, "cannot discard txs: %v", discardResults)
require.Error(discardResults["invalid-tx-id"].Error, "transaction hash not found", "cannot discard txs: %v", discardResults)
// try completing discarded transaction
completeResults := s.backend.CompleteTransactions(string(updatedTxIDStrings), TestConfig.Account1.Password)
completeResults := s.backend.CompleteTransactions(txIDs, TestConfig.Account1.Password)
require.Len(completeResults, testTxCount+1, "unexpected number of errors (call to CompleteTransaction should not succeed)")
for _, txResult := range completeResults {
@ -533,18 +535,22 @@ func (s *BackendTestSuite) TestDiscardMultipleQueuedTransactions() {
}
time.Sleep(1 * time.Second) // make sure that tx complete signal propagates
for _, txID := range parsedIDs {
require.False(backend.TransactionQueue().Has(status.QueuedTxID(txID)), "txqueue should not have test txs at this point (it should be discarded): %s", txID)
for _, txID := range txIDs {
require.False(
s.backend.TxQueueManager().TransactionQueue().Has(txID),
"txqueue should not have test tx at this point (it should be discarded): %s",
txID,
)
}
}
go func() {
var txIDStrings []string
ids := make([]common.QueuedTxID, testTxCount)
for i := 0; i < testTxCount; i++ {
txIDStrings = append(txIDStrings, <-txIDs)
ids[i] = <-txIDs
}
txIDJSON, _ := json.Marshal(txIDStrings)
discardTxs(string(txIDJSON))
discardTxs(ids)
}()
// send multiple transactions
@ -559,7 +565,7 @@ func (s *BackendTestSuite) TestDiscardMultipleQueuedTransactions() {
require.Fail("test timed out")
}
require.Empty(backend.TransactionQueue().Count(), "tx queue must be empty at this point")
require.Zero(s.backend.TxQueueManager().TransactionQueue().Count(), "tx queue must be empty at this point")
}
func (s *BackendTestSuite) TestNonExistentQueuedTransactions() {
@ -581,7 +587,7 @@ func (s *BackendTestSuite) TestNonExistentQueuedTransactions() {
// try completing non-existing transaction
_, err := s.backend.CompleteTransaction("some-bad-transaction-id", TestConfig.Account1.Password)
s.Error(err, "error expected and not received")
s.EqualError(err, status.ErrQueuedTxIDNotFound.Error())
s.EqualError(err, node.ErrQueuedTxIDNotFound.Error())
}
func (s *BackendTestSuite) TestEvictionOfQueuedTransactions() {
@ -595,15 +601,15 @@ func (s *BackendTestSuite) TestEvictionOfQueuedTransactions() {
require.NotNil(backend)
// reset queue
backend.TransactionQueue().Reset()
s.backend.TxQueueManager().TransactionQueue().Reset()
// log into account from which transactions will be sent
require.NoError(s.backend.AccountManager().SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password))
txQueue := backend.TransactionQueue()
txQueue := s.backend.TxQueueManager().TransactionQueue()
var i = 0
txIDs := [status.DefaultTxQueueCap + 5 + 10]status.QueuedTxID{}
backend.SetTransactionQueueHandler(func(queuedTx status.QueuedTx) {
txIDs := [node.DefaultTxQueueCap + 5 + 10]common.QueuedTxID{}
s.backend.TxQueueManager().SetTransactionQueueHandler(func(queuedTx *common.QueuedTx) {
log.Info("tx enqueued", "i", i+1, "queue size", txQueue.Count(), "id", queuedTx.ID)
txIDs[i] = queuedTx.ID
i++
@ -612,25 +618,25 @@ func (s *BackendTestSuite) TestEvictionOfQueuedTransactions() {
s.Zero(txQueue.Count(), "transaction count should be zero")
for i := 0; i < 10; i++ {
go backend.SendTransaction(nil, status.SendTxArgs{}) // nolint: errcheck
go s.backend.SendTransaction(nil, common.SendTxArgs{}) // nolint: errcheck
}
time.Sleep(1 * time.Second)
log.Info(fmt.Sprintf("Number of transactions queued: %d. Queue size (shouldn't be more than %d): %d",
i, status.DefaultTxQueueCap, txQueue.Count()))
i, node.DefaultTxQueueCap, txQueue.Count()))
s.Equal(10, txQueue.Count(), "transaction count should be 10")
for i := 0; i < status.DefaultTxQueueCap+5; i++ { // stress test by hitting with lots of goroutines
go backend.SendTransaction(nil, status.SendTxArgs{}) // nolint: errcheck
for i := 0; i < node.DefaultTxQueueCap+5; i++ { // stress test by hitting with lots of goroutines
go s.backend.SendTransaction(nil, common.SendTxArgs{}) // nolint: errcheck
}
time.Sleep(3 * time.Second)
require.True(txQueue.Count() <= status.DefaultTxQueueCap, "transaction count should be %d (or %d): got %d", status.DefaultTxQueueCap, status.DefaultTxQueueCap-1, txQueue.Count())
require.True(txQueue.Count() <= node.DefaultTxQueueCap, "transaction count should be %d (or %d): got %d", node.DefaultTxQueueCap, node.DefaultTxQueueCap-1, txQueue.Count())
for _, txID := range txIDs {
txQueue.Remove(txID)
}
require.Empty(txQueue.Count(), "transaction count should be zero: %d", txQueue.Count())
require.Zero(txQueue.Count(), "transaction count should be zero: %d", txQueue.Count())
}

View File

@ -2,6 +2,7 @@ package common
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
@ -11,8 +12,8 @@ import (
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/les"
"github.com/ethereum/go-ethereum/les/status"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/rpc"
whisper "github.com/ethereum/go-ethereum/whisper/whisperv5"
@ -153,25 +154,98 @@ type RawDiscardTransactionResult struct {
Error error
}
// QueuedTxID queued transaction identifier
type QueuedTxID string
// QueuedTx holds enough information to complete the queued transaction.
type QueuedTx struct {
ID QueuedTxID
Hash common.Hash
Context context.Context
Args SendTxArgs
Done chan struct{}
Discard chan struct{}
Err error
}
// SendTxArgs represents the arguments to submit a new transaction into the transaction pool.
type SendTxArgs struct {
From common.Address `json:"from"`
To *common.Address `json:"to"`
Gas *hexutil.Big `json:"gas"`
GasPrice *hexutil.Big `json:"gasPrice"`
Value *hexutil.Big `json:"value"`
Data hexutil.Bytes `json:"data"`
Nonce *hexutil.Uint64 `json:"nonce"`
}
// EnqueuedTxHandler is a function that receives queued/pending transactions, when they get queued
type EnqueuedTxHandler func(*QueuedTx)
// EnqueuedTxReturnHandler is a function that receives response when tx is complete (both on success and error)
type EnqueuedTxReturnHandler func(*QueuedTx, error)
// TxQueue is a queue of transactions.
type TxQueue interface {
// Remove removes a transaction from the queue.
Remove(id QueuedTxID)
// Reset resets the state of the queue.
Reset()
// Count returns a number of transactions in the queue.
Count() int
// Has returns true if a transaction is in the queue.
Has(id QueuedTxID) bool
}
// TxQueueManager defines expected methods for managing transaction queue
type TxQueueManager interface {
// Start starts accepting new transaction in the queue.
Start()
// Stop stops accepting new transactions in the queue.
Stop()
// TransactionQueue returns a transaction queue.
TransactionQueue() TxQueue
// CreateTransactoin creates a new transaction.
CreateTransaction(ctx context.Context, args SendTxArgs) *QueuedTx
// QueueTransaction adds a new transaction to the queue.
QueueTransaction(tx *QueuedTx) error
// WaitForTransactions blocks until transaction is completed, discarded or timed out.
WaitForTransaction(tx *QueuedTx) error
// NotifyOnQueuedTxReturn notifies a handler when a transaction returns.
NotifyOnQueuedTxReturn(queuedTx *QueuedTx, err error)
// TransactionQueueHandler returns handler that processes incoming tx queue requests
TransactionQueueHandler() func(queuedTx status.QueuedTx)
TransactionQueueHandler() func(queuedTx *QueuedTx)
// TODO(adam): might be not needed
SetTransactionQueueHandler(fn EnqueuedTxHandler)
// TODO(adam): might be not needed
SetTransactionReturnHandler(fn EnqueuedTxReturnHandler)
// TransactionReturnHandler returns handler that processes responses from internal tx manager
TransactionReturnHandler() func(queuedTx *status.QueuedTx, err error)
TransactionReturnHandler() func(queuedTx *QueuedTx, err error)
// CompleteTransaction instructs backend to complete sending of a given transaction
CompleteTransaction(id, password string) (common.Hash, error)
CompleteTransaction(id QueuedTxID, password string) (common.Hash, error)
// CompleteTransactions instructs backend to complete sending of multiple transactions
CompleteTransactions(ids, password string) map[string]RawCompleteTransactionResult
CompleteTransactions(ids []QueuedTxID, password string) map[QueuedTxID]RawCompleteTransactionResult
// DiscardTransaction discards a given transaction from transaction queue
DiscardTransaction(id string) error
DiscardTransaction(id QueuedTxID) error
// DiscardTransactions discards given multiple transactions from transaction queue
DiscardTransactions(ids string) map[string]RawDiscardTransactionResult
DiscardTransactions(ids []QueuedTxID) map[QueuedTxID]RawDiscardTransactionResult
}
// JailCell represents single jail cell, which is basically a JavaScript VM.

842
geth/common/types_mock.go Normal file
View File

@ -0,0 +1,842 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: geth/common/types.go
// Package common is a generated GoMock package.
package common
import (
context "context"
accounts "github.com/ethereum/go-ethereum/accounts"
keystore "github.com/ethereum/go-ethereum/accounts/keystore"
common "github.com/ethereum/go-ethereum/common"
les "github.com/ethereum/go-ethereum/les"
node "github.com/ethereum/go-ethereum/node"
rpc "github.com/ethereum/go-ethereum/rpc"
whisperv5 "github.com/ethereum/go-ethereum/whisper/whisperv5"
gomock "github.com/golang/mock/gomock"
otto "github.com/robertkrimen/otto"
params "github.com/status-im/status-go/geth/params"
reflect "reflect"
)
// MockNodeManager is a mock of NodeManager interface
type MockNodeManager struct {
ctrl *gomock.Controller
recorder *MockNodeManagerMockRecorder
}
// MockNodeManagerMockRecorder is the mock recorder for MockNodeManager
type MockNodeManagerMockRecorder struct {
mock *MockNodeManager
}
// NewMockNodeManager creates a new mock instance
func NewMockNodeManager(ctrl *gomock.Controller) *MockNodeManager {
mock := &MockNodeManager{ctrl: ctrl}
mock.recorder = &MockNodeManagerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockNodeManager) EXPECT() *MockNodeManagerMockRecorder {
return m.recorder
}
// StartNode mocks base method
func (m *MockNodeManager) StartNode(config *params.NodeConfig) (<-chan struct{}, error) {
ret := m.ctrl.Call(m, "StartNode", config)
ret0, _ := ret[0].(<-chan struct{})
ret1, _ := ret[1].(error)
return ret0, ret1
}
// StartNode indicates an expected call of StartNode
func (mr *MockNodeManagerMockRecorder) StartNode(config interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartNode", reflect.TypeOf((*MockNodeManager)(nil).StartNode), config)
}
// StopNode mocks base method
func (m *MockNodeManager) StopNode() (<-chan struct{}, error) {
ret := m.ctrl.Call(m, "StopNode")
ret0, _ := ret[0].(<-chan struct{})
ret1, _ := ret[1].(error)
return ret0, ret1
}
// StopNode indicates an expected call of StopNode
func (mr *MockNodeManagerMockRecorder) StopNode() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StopNode", reflect.TypeOf((*MockNodeManager)(nil).StopNode))
}
// RestartNode mocks base method
func (m *MockNodeManager) RestartNode() (<-chan struct{}, error) {
ret := m.ctrl.Call(m, "RestartNode")
ret0, _ := ret[0].(<-chan struct{})
ret1, _ := ret[1].(error)
return ret0, ret1
}
// RestartNode indicates an expected call of RestartNode
func (mr *MockNodeManagerMockRecorder) RestartNode() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RestartNode", reflect.TypeOf((*MockNodeManager)(nil).RestartNode))
}
// ResetChainData mocks base method
func (m *MockNodeManager) ResetChainData() (<-chan struct{}, error) {
ret := m.ctrl.Call(m, "ResetChainData")
ret0, _ := ret[0].(<-chan struct{})
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ResetChainData indicates an expected call of ResetChainData
func (mr *MockNodeManagerMockRecorder) ResetChainData() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResetChainData", reflect.TypeOf((*MockNodeManager)(nil).ResetChainData))
}
// IsNodeRunning mocks base method
func (m *MockNodeManager) IsNodeRunning() bool {
ret := m.ctrl.Call(m, "IsNodeRunning")
ret0, _ := ret[0].(bool)
return ret0
}
// IsNodeRunning indicates an expected call of IsNodeRunning
func (mr *MockNodeManagerMockRecorder) IsNodeRunning() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsNodeRunning", reflect.TypeOf((*MockNodeManager)(nil).IsNodeRunning))
}
// NodeConfig mocks base method
func (m *MockNodeManager) NodeConfig() (*params.NodeConfig, error) {
ret := m.ctrl.Call(m, "NodeConfig")
ret0, _ := ret[0].(*params.NodeConfig)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// NodeConfig indicates an expected call of NodeConfig
func (mr *MockNodeManagerMockRecorder) NodeConfig() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NodeConfig", reflect.TypeOf((*MockNodeManager)(nil).NodeConfig))
}
// Node mocks base method
func (m *MockNodeManager) Node() (*node.Node, error) {
ret := m.ctrl.Call(m, "Node")
ret0, _ := ret[0].(*node.Node)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Node indicates an expected call of Node
func (mr *MockNodeManagerMockRecorder) Node() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Node", reflect.TypeOf((*MockNodeManager)(nil).Node))
}
// PopulateStaticPeers mocks base method
func (m *MockNodeManager) PopulateStaticPeers() error {
ret := m.ctrl.Call(m, "PopulateStaticPeers")
ret0, _ := ret[0].(error)
return ret0
}
// PopulateStaticPeers indicates an expected call of PopulateStaticPeers
func (mr *MockNodeManagerMockRecorder) PopulateStaticPeers() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PopulateStaticPeers", reflect.TypeOf((*MockNodeManager)(nil).PopulateStaticPeers))
}
// AddPeer mocks base method
func (m *MockNodeManager) AddPeer(url string) error {
ret := m.ctrl.Call(m, "AddPeer", url)
ret0, _ := ret[0].(error)
return ret0
}
// AddPeer indicates an expected call of AddPeer
func (mr *MockNodeManagerMockRecorder) AddPeer(url interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddPeer", reflect.TypeOf((*MockNodeManager)(nil).AddPeer), url)
}
// LightEthereumService mocks base method
func (m *MockNodeManager) LightEthereumService() (*les.LightEthereum, error) {
ret := m.ctrl.Call(m, "LightEthereumService")
ret0, _ := ret[0].(*les.LightEthereum)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// LightEthereumService indicates an expected call of LightEthereumService
func (mr *MockNodeManagerMockRecorder) LightEthereumService() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LightEthereumService", reflect.TypeOf((*MockNodeManager)(nil).LightEthereumService))
}
// WhisperService mocks base method
func (m *MockNodeManager) WhisperService() (*whisperv5.Whisper, error) {
ret := m.ctrl.Call(m, "WhisperService")
ret0, _ := ret[0].(*whisperv5.Whisper)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// WhisperService indicates an expected call of WhisperService
func (mr *MockNodeManagerMockRecorder) WhisperService() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WhisperService", reflect.TypeOf((*MockNodeManager)(nil).WhisperService))
}
// AccountManager mocks base method
func (m *MockNodeManager) AccountManager() (*accounts.Manager, error) {
ret := m.ctrl.Call(m, "AccountManager")
ret0, _ := ret[0].(*accounts.Manager)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// AccountManager indicates an expected call of AccountManager
func (mr *MockNodeManagerMockRecorder) AccountManager() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AccountManager", reflect.TypeOf((*MockNodeManager)(nil).AccountManager))
}
// AccountKeyStore mocks base method
func (m *MockNodeManager) AccountKeyStore() (*keystore.KeyStore, error) {
ret := m.ctrl.Call(m, "AccountKeyStore")
ret0, _ := ret[0].(*keystore.KeyStore)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// AccountKeyStore indicates an expected call of AccountKeyStore
func (mr *MockNodeManagerMockRecorder) AccountKeyStore() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AccountKeyStore", reflect.TypeOf((*MockNodeManager)(nil).AccountKeyStore))
}
// RPCClient mocks base method
func (m *MockNodeManager) RPCClient() (*rpc.Client, error) {
ret := m.ctrl.Call(m, "RPCClient")
ret0, _ := ret[0].(*rpc.Client)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// RPCClient indicates an expected call of RPCClient
func (mr *MockNodeManagerMockRecorder) RPCClient() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RPCClient", reflect.TypeOf((*MockNodeManager)(nil).RPCClient))
}
// RPCServer mocks base method
func (m *MockNodeManager) RPCServer() (*rpc.Server, error) {
ret := m.ctrl.Call(m, "RPCServer")
ret0, _ := ret[0].(*rpc.Server)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// RPCServer indicates an expected call of RPCServer
func (mr *MockNodeManagerMockRecorder) RPCServer() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RPCServer", reflect.TypeOf((*MockNodeManager)(nil).RPCServer))
}
// MockAccountManager is a mock of AccountManager interface
type MockAccountManager struct {
ctrl *gomock.Controller
recorder *MockAccountManagerMockRecorder
}
// MockAccountManagerMockRecorder is the mock recorder for MockAccountManager
type MockAccountManagerMockRecorder struct {
mock *MockAccountManager
}
// NewMockAccountManager creates a new mock instance
func NewMockAccountManager(ctrl *gomock.Controller) *MockAccountManager {
mock := &MockAccountManager{ctrl: ctrl}
mock.recorder = &MockAccountManagerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockAccountManager) EXPECT() *MockAccountManagerMockRecorder {
return m.recorder
}
// CreateAccount mocks base method
func (m *MockAccountManager) CreateAccount(password string) (string, string, string, error) {
ret := m.ctrl.Call(m, "CreateAccount", password)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(string)
ret2, _ := ret[2].(string)
ret3, _ := ret[3].(error)
return ret0, ret1, ret2, ret3
}
// CreateAccount indicates an expected call of CreateAccount
func (mr *MockAccountManagerMockRecorder) CreateAccount(password interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateAccount", reflect.TypeOf((*MockAccountManager)(nil).CreateAccount), password)
}
// CreateChildAccount mocks base method
func (m *MockAccountManager) CreateChildAccount(parentAddress, password string) (string, string, error) {
ret := m.ctrl.Call(m, "CreateChildAccount", parentAddress, password)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(string)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
}
// CreateChildAccount indicates an expected call of CreateChildAccount
func (mr *MockAccountManagerMockRecorder) CreateChildAccount(parentAddress, password interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateChildAccount", reflect.TypeOf((*MockAccountManager)(nil).CreateChildAccount), parentAddress, password)
}
// RecoverAccount mocks base method
func (m *MockAccountManager) RecoverAccount(password, mnemonic string) (string, string, error) {
ret := m.ctrl.Call(m, "RecoverAccount", password, mnemonic)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(string)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
}
// RecoverAccount indicates an expected call of RecoverAccount
func (mr *MockAccountManagerMockRecorder) RecoverAccount(password, mnemonic interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecoverAccount", reflect.TypeOf((*MockAccountManager)(nil).RecoverAccount), password, mnemonic)
}
// VerifyAccountPassword mocks base method
func (m *MockAccountManager) VerifyAccountPassword(keyStoreDir, address, password string) (*keystore.Key, error) {
ret := m.ctrl.Call(m, "VerifyAccountPassword", keyStoreDir, address, password)
ret0, _ := ret[0].(*keystore.Key)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// VerifyAccountPassword indicates an expected call of VerifyAccountPassword
func (mr *MockAccountManagerMockRecorder) VerifyAccountPassword(keyStoreDir, address, password interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyAccountPassword", reflect.TypeOf((*MockAccountManager)(nil).VerifyAccountPassword), keyStoreDir, address, password)
}
// SelectAccount mocks base method
func (m *MockAccountManager) SelectAccount(address, password string) error {
ret := m.ctrl.Call(m, "SelectAccount", address, password)
ret0, _ := ret[0].(error)
return ret0
}
// SelectAccount indicates an expected call of SelectAccount
func (mr *MockAccountManagerMockRecorder) SelectAccount(address, password interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectAccount", reflect.TypeOf((*MockAccountManager)(nil).SelectAccount), address, password)
}
// ReSelectAccount mocks base method
func (m *MockAccountManager) ReSelectAccount() error {
ret := m.ctrl.Call(m, "ReSelectAccount")
ret0, _ := ret[0].(error)
return ret0
}
// ReSelectAccount indicates an expected call of ReSelectAccount
func (mr *MockAccountManagerMockRecorder) ReSelectAccount() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReSelectAccount", reflect.TypeOf((*MockAccountManager)(nil).ReSelectAccount))
}
// SelectedAccount mocks base method
func (m *MockAccountManager) SelectedAccount() (*SelectedExtKey, error) {
ret := m.ctrl.Call(m, "SelectedAccount")
ret0, _ := ret[0].(*SelectedExtKey)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// SelectedAccount indicates an expected call of SelectedAccount
func (mr *MockAccountManagerMockRecorder) SelectedAccount() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectedAccount", reflect.TypeOf((*MockAccountManager)(nil).SelectedAccount))
}
// Logout mocks base method
func (m *MockAccountManager) Logout() error {
ret := m.ctrl.Call(m, "Logout")
ret0, _ := ret[0].(error)
return ret0
}
// Logout indicates an expected call of Logout
func (mr *MockAccountManagerMockRecorder) Logout() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Logout", reflect.TypeOf((*MockAccountManager)(nil).Logout))
}
// AccountsListRequestHandler mocks base method
func (m *MockAccountManager) AccountsListRequestHandler() func([]common.Address) []common.Address {
ret := m.ctrl.Call(m, "AccountsListRequestHandler")
ret0, _ := ret[0].(func([]common.Address) []common.Address)
return ret0
}
// AccountsListRequestHandler indicates an expected call of AccountsListRequestHandler
func (mr *MockAccountManagerMockRecorder) AccountsListRequestHandler() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AccountsListRequestHandler", reflect.TypeOf((*MockAccountManager)(nil).AccountsListRequestHandler))
}
// AddressToDecryptedAccount mocks base method
func (m *MockAccountManager) AddressToDecryptedAccount(address, password string) (accounts.Account, *keystore.Key, error) {
ret := m.ctrl.Call(m, "AddressToDecryptedAccount", address, password)
ret0, _ := ret[0].(accounts.Account)
ret1, _ := ret[1].(*keystore.Key)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
}
// AddressToDecryptedAccount indicates an expected call of AddressToDecryptedAccount
func (mr *MockAccountManagerMockRecorder) AddressToDecryptedAccount(address, password interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddressToDecryptedAccount", reflect.TypeOf((*MockAccountManager)(nil).AddressToDecryptedAccount), address, password)
}
// MockRPCManager is a mock of RPCManager interface
type MockRPCManager struct {
ctrl *gomock.Controller
recorder *MockRPCManagerMockRecorder
}
// MockRPCManagerMockRecorder is the mock recorder for MockRPCManager
type MockRPCManagerMockRecorder struct {
mock *MockRPCManager
}
// NewMockRPCManager creates a new mock instance
func NewMockRPCManager(ctrl *gomock.Controller) *MockRPCManager {
mock := &MockRPCManager{ctrl: ctrl}
mock.recorder = &MockRPCManagerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockRPCManager) EXPECT() *MockRPCManagerMockRecorder {
return m.recorder
}
// Call mocks base method
func (m *MockRPCManager) Call(inputJSON string) string {
ret := m.ctrl.Call(m, "Call", inputJSON)
ret0, _ := ret[0].(string)
return ret0
}
// Call indicates an expected call of Call
func (mr *MockRPCManagerMockRecorder) Call(inputJSON interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Call", reflect.TypeOf((*MockRPCManager)(nil).Call), inputJSON)
}
// MockTxQueue is a mock of TxQueue interface
type MockTxQueue struct {
ctrl *gomock.Controller
recorder *MockTxQueueMockRecorder
}
// MockTxQueueMockRecorder is the mock recorder for MockTxQueue
type MockTxQueueMockRecorder struct {
mock *MockTxQueue
}
// NewMockTxQueue creates a new mock instance
func NewMockTxQueue(ctrl *gomock.Controller) *MockTxQueue {
mock := &MockTxQueue{ctrl: ctrl}
mock.recorder = &MockTxQueueMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockTxQueue) EXPECT() *MockTxQueueMockRecorder {
return m.recorder
}
// Remove mocks base method
func (m *MockTxQueue) Remove(id QueuedTxID) {
m.ctrl.Call(m, "Remove", id)
}
// Remove indicates an expected call of Remove
func (mr *MockTxQueueMockRecorder) Remove(id interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockTxQueue)(nil).Remove), id)
}
// Reset mocks base method
func (m *MockTxQueue) Reset() {
m.ctrl.Call(m, "Reset")
}
// Reset indicates an expected call of Reset
func (mr *MockTxQueueMockRecorder) Reset() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Reset", reflect.TypeOf((*MockTxQueue)(nil).Reset))
}
// Count mocks base method
func (m *MockTxQueue) Count() int {
ret := m.ctrl.Call(m, "Count")
ret0, _ := ret[0].(int)
return ret0
}
// Count indicates an expected call of Count
func (mr *MockTxQueueMockRecorder) Count() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Count", reflect.TypeOf((*MockTxQueue)(nil).Count))
}
// Has mocks base method
func (m *MockTxQueue) Has(id QueuedTxID) bool {
ret := m.ctrl.Call(m, "Has", id)
ret0, _ := ret[0].(bool)
return ret0
}
// Has indicates an expected call of Has
func (mr *MockTxQueueMockRecorder) Has(id interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Has", reflect.TypeOf((*MockTxQueue)(nil).Has), id)
}
// MockTxQueueManager is a mock of TxQueueManager interface
type MockTxQueueManager struct {
ctrl *gomock.Controller
recorder *MockTxQueueManagerMockRecorder
}
// MockTxQueueManagerMockRecorder is the mock recorder for MockTxQueueManager
type MockTxQueueManagerMockRecorder struct {
mock *MockTxQueueManager
}
// NewMockTxQueueManager creates a new mock instance
func NewMockTxQueueManager(ctrl *gomock.Controller) *MockTxQueueManager {
mock := &MockTxQueueManager{ctrl: ctrl}
mock.recorder = &MockTxQueueManagerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockTxQueueManager) EXPECT() *MockTxQueueManagerMockRecorder {
return m.recorder
}
// Start mocks base method
func (m *MockTxQueueManager) Start() {
m.ctrl.Call(m, "Start")
}
// Start indicates an expected call of Start
func (mr *MockTxQueueManagerMockRecorder) Start() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*MockTxQueueManager)(nil).Start))
}
// Stop mocks base method
func (m *MockTxQueueManager) Stop() {
m.ctrl.Call(m, "Stop")
}
// Stop indicates an expected call of Stop
func (mr *MockTxQueueManagerMockRecorder) Stop() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stop", reflect.TypeOf((*MockTxQueueManager)(nil).Stop))
}
// TransactionQueue mocks base method
func (m *MockTxQueueManager) TransactionQueue() TxQueue {
ret := m.ctrl.Call(m, "TransactionQueue")
ret0, _ := ret[0].(TxQueue)
return ret0
}
// TransactionQueue indicates an expected call of TransactionQueue
func (mr *MockTxQueueManagerMockRecorder) TransactionQueue() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TransactionQueue", reflect.TypeOf((*MockTxQueueManager)(nil).TransactionQueue))
}
// CreateTransaction mocks base method
func (m *MockTxQueueManager) CreateTransaction(ctx context.Context, args SendTxArgs) *QueuedTx {
ret := m.ctrl.Call(m, "CreateTransaction", ctx, args)
ret0, _ := ret[0].(*QueuedTx)
return ret0
}
// CreateTransaction indicates an expected call of CreateTransaction
func (mr *MockTxQueueManagerMockRecorder) CreateTransaction(ctx, args interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateTransaction", reflect.TypeOf((*MockTxQueueManager)(nil).CreateTransaction), ctx, args)
}
// QueueTransaction mocks base method
func (m *MockTxQueueManager) QueueTransaction(tx *QueuedTx) error {
ret := m.ctrl.Call(m, "QueueTransaction", tx)
ret0, _ := ret[0].(error)
return ret0
}
// QueueTransaction indicates an expected call of QueueTransaction
func (mr *MockTxQueueManagerMockRecorder) QueueTransaction(tx interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueueTransaction", reflect.TypeOf((*MockTxQueueManager)(nil).QueueTransaction), tx)
}
// WaitForTransaction mocks base method
func (m *MockTxQueueManager) WaitForTransaction(tx *QueuedTx) error {
ret := m.ctrl.Call(m, "WaitForTransaction", tx)
ret0, _ := ret[0].(error)
return ret0
}
// WaitForTransaction indicates an expected call of WaitForTransaction
func (mr *MockTxQueueManagerMockRecorder) WaitForTransaction(tx interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitForTransaction", reflect.TypeOf((*MockTxQueueManager)(nil).WaitForTransaction), tx)
}
// NotifyOnQueuedTxReturn mocks base method
func (m *MockTxQueueManager) NotifyOnQueuedTxReturn(queuedTx *QueuedTx, err error) {
m.ctrl.Call(m, "NotifyOnQueuedTxReturn", queuedTx, err)
}
// NotifyOnQueuedTxReturn indicates an expected call of NotifyOnQueuedTxReturn
func (mr *MockTxQueueManagerMockRecorder) NotifyOnQueuedTxReturn(queuedTx, err interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NotifyOnQueuedTxReturn", reflect.TypeOf((*MockTxQueueManager)(nil).NotifyOnQueuedTxReturn), queuedTx, err)
}
// TransactionQueueHandler mocks base method
func (m *MockTxQueueManager) TransactionQueueHandler() func(*QueuedTx) {
ret := m.ctrl.Call(m, "TransactionQueueHandler")
ret0, _ := ret[0].(func(*QueuedTx))
return ret0
}
// TransactionQueueHandler indicates an expected call of TransactionQueueHandler
func (mr *MockTxQueueManagerMockRecorder) TransactionQueueHandler() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TransactionQueueHandler", reflect.TypeOf((*MockTxQueueManager)(nil).TransactionQueueHandler))
}
// SetTransactionQueueHandler mocks base method
func (m *MockTxQueueManager) SetTransactionQueueHandler(fn EnqueuedTxHandler) {
m.ctrl.Call(m, "SetTransactionQueueHandler", fn)
}
// SetTransactionQueueHandler indicates an expected call of SetTransactionQueueHandler
func (mr *MockTxQueueManagerMockRecorder) SetTransactionQueueHandler(fn interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetTransactionQueueHandler", reflect.TypeOf((*MockTxQueueManager)(nil).SetTransactionQueueHandler), fn)
}
// SetTransactionReturnHandler mocks base method
func (m *MockTxQueueManager) SetTransactionReturnHandler(fn EnqueuedTxReturnHandler) {
m.ctrl.Call(m, "SetTransactionReturnHandler", fn)
}
// SetTransactionReturnHandler indicates an expected call of SetTransactionReturnHandler
func (mr *MockTxQueueManagerMockRecorder) SetTransactionReturnHandler(fn interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetTransactionReturnHandler", reflect.TypeOf((*MockTxQueueManager)(nil).SetTransactionReturnHandler), fn)
}
// TransactionReturnHandler mocks base method
func (m *MockTxQueueManager) TransactionReturnHandler() func(*QueuedTx, error) {
ret := m.ctrl.Call(m, "TransactionReturnHandler")
ret0, _ := ret[0].(func(*QueuedTx, error))
return ret0
}
// TransactionReturnHandler indicates an expected call of TransactionReturnHandler
func (mr *MockTxQueueManagerMockRecorder) TransactionReturnHandler() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TransactionReturnHandler", reflect.TypeOf((*MockTxQueueManager)(nil).TransactionReturnHandler))
}
// CompleteTransaction mocks base method
func (m *MockTxQueueManager) CompleteTransaction(id QueuedTxID, password string) (common.Hash, error) {
ret := m.ctrl.Call(m, "CompleteTransaction", id, password)
ret0, _ := ret[0].(common.Hash)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CompleteTransaction indicates an expected call of CompleteTransaction
func (mr *MockTxQueueManagerMockRecorder) CompleteTransaction(id, password interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CompleteTransaction", reflect.TypeOf((*MockTxQueueManager)(nil).CompleteTransaction), id, password)
}
// CompleteTransactions mocks base method
func (m *MockTxQueueManager) CompleteTransactions(ids []QueuedTxID, password string) map[QueuedTxID]RawCompleteTransactionResult {
ret := m.ctrl.Call(m, "CompleteTransactions", ids, password)
ret0, _ := ret[0].(map[QueuedTxID]RawCompleteTransactionResult)
return ret0
}
// CompleteTransactions indicates an expected call of CompleteTransactions
func (mr *MockTxQueueManagerMockRecorder) CompleteTransactions(ids, password interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CompleteTransactions", reflect.TypeOf((*MockTxQueueManager)(nil).CompleteTransactions), ids, password)
}
// DiscardTransaction mocks base method
func (m *MockTxQueueManager) DiscardTransaction(id QueuedTxID) error {
ret := m.ctrl.Call(m, "DiscardTransaction", id)
ret0, _ := ret[0].(error)
return ret0
}
// DiscardTransaction indicates an expected call of DiscardTransaction
func (mr *MockTxQueueManagerMockRecorder) DiscardTransaction(id interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DiscardTransaction", reflect.TypeOf((*MockTxQueueManager)(nil).DiscardTransaction), id)
}
// DiscardTransactions mocks base method
func (m *MockTxQueueManager) DiscardTransactions(ids []QueuedTxID) map[QueuedTxID]RawDiscardTransactionResult {
ret := m.ctrl.Call(m, "DiscardTransactions", ids)
ret0, _ := ret[0].(map[QueuedTxID]RawDiscardTransactionResult)
return ret0
}
// DiscardTransactions indicates an expected call of DiscardTransactions
func (mr *MockTxQueueManagerMockRecorder) DiscardTransactions(ids interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DiscardTransactions", reflect.TypeOf((*MockTxQueueManager)(nil).DiscardTransactions), ids)
}
// MockJailCell is a mock of JailCell interface
type MockJailCell struct {
ctrl *gomock.Controller
recorder *MockJailCellMockRecorder
}
// MockJailCellMockRecorder is the mock recorder for MockJailCell
type MockJailCellMockRecorder struct {
mock *MockJailCell
}
// NewMockJailCell creates a new mock instance
func NewMockJailCell(ctrl *gomock.Controller) *MockJailCell {
mock := &MockJailCell{ctrl: ctrl}
mock.recorder = &MockJailCellMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockJailCell) EXPECT() *MockJailCellMockRecorder {
return m.recorder
}
// Set mocks base method
func (m *MockJailCell) Set(arg0 string, arg1 interface{}) error {
ret := m.ctrl.Call(m, "Set", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// Set indicates an expected call of Set
func (mr *MockJailCellMockRecorder) Set(arg0, arg1 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Set", reflect.TypeOf((*MockJailCell)(nil).Set), arg0, arg1)
}
// Get mocks base method
func (m *MockJailCell) Get(arg0 string) (otto.Value, error) {
ret := m.ctrl.Call(m, "Get", arg0)
ret0, _ := ret[0].(otto.Value)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Get indicates an expected call of Get
func (mr *MockJailCellMockRecorder) Get(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockJailCell)(nil).Get), arg0)
}
// Run mocks base method
func (m *MockJailCell) Run(arg0 string) (otto.Value, error) {
ret := m.ctrl.Call(m, "Run", arg0)
ret0, _ := ret[0].(otto.Value)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Run indicates an expected call of Run
func (mr *MockJailCellMockRecorder) Run(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Run", reflect.TypeOf((*MockJailCell)(nil).Run), arg0)
}
// RunOnLoop mocks base method
func (m *MockJailCell) RunOnLoop(arg0 string) (otto.Value, error) {
ret := m.ctrl.Call(m, "RunOnLoop", arg0)
ret0, _ := ret[0].(otto.Value)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// RunOnLoop indicates an expected call of RunOnLoop
func (mr *MockJailCellMockRecorder) RunOnLoop(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RunOnLoop", reflect.TypeOf((*MockJailCell)(nil).RunOnLoop), arg0)
}
// MockJailManager is a mock of JailManager interface
type MockJailManager struct {
ctrl *gomock.Controller
recorder *MockJailManagerMockRecorder
}
// MockJailManagerMockRecorder is the mock recorder for MockJailManager
type MockJailManagerMockRecorder struct {
mock *MockJailManager
}
// NewMockJailManager creates a new mock instance
func NewMockJailManager(ctrl *gomock.Controller) *MockJailManager {
mock := &MockJailManager{ctrl: ctrl}
mock.recorder = &MockJailManagerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockJailManager) EXPECT() *MockJailManagerMockRecorder {
return m.recorder
}
// Parse mocks base method
func (m *MockJailManager) Parse(chatID, js string) string {
ret := m.ctrl.Call(m, "Parse", chatID, js)
ret0, _ := ret[0].(string)
return ret0
}
// Parse indicates an expected call of Parse
func (mr *MockJailManagerMockRecorder) Parse(chatID, js interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Parse", reflect.TypeOf((*MockJailManager)(nil).Parse), chatID, js)
}
// Call mocks base method
func (m *MockJailManager) Call(chatID, path, args string) string {
ret := m.ctrl.Call(m, "Call", chatID, path, args)
ret0, _ := ret[0].(string)
return ret0
}
// Call indicates an expected call of Call
func (mr *MockJailManagerMockRecorder) Call(chatID, path, args interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Call", reflect.TypeOf((*MockJailManager)(nil).Call), chatID, path, args)
}
// NewJailCell mocks base method
func (m *MockJailManager) NewJailCell(id string) (JailCell, error) {
ret := m.ctrl.Call(m, "NewJailCell", id)
ret0, _ := ret[0].(JailCell)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// NewJailCell indicates an expected call of NewJailCell
func (mr *MockJailManagerMockRecorder) NewJailCell(id interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewJailCell", reflect.TypeOf((*MockJailManager)(nil).NewJailCell), id)
}
// GetJailCell mocks base method
func (m *MockJailManager) GetJailCell(chatID string) (JailCell, error) {
ret := m.ctrl.Call(m, "GetJailCell", chatID)
ret0, _ := ret[0].(JailCell)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetJailCell indicates an expected call of GetJailCell
func (mr *MockJailManagerMockRecorder) GetJailCell(chatID interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetJailCell", reflect.TypeOf((*MockJailManager)(nil).GetJailCell), chatID)
}
// BaseJS mocks base method
func (m *MockJailManager) BaseJS(js string) {
m.ctrl.Call(m, "BaseJS", js)
}
// BaseJS indicates an expected call of BaseJS
func (mr *MockJailManagerMockRecorder) BaseJS(js interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BaseJS", reflect.TypeOf((*MockJailManager)(nil).BaseJS), js)
}

View File

@ -3,16 +3,12 @@ package jail
import (
"context"
"encoding/json"
"math/big"
"time"
gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc"
"github.com/robertkrimen/otto"
"github.com/status-im/status-go/geth/common"
"github.com/status-im/status-go/geth/params"
)
// ExecutionPolicy provides a central container for the executions of RPCCall requests for both
@ -20,131 +16,70 @@ import (
type ExecutionPolicy struct {
nodeManager common.NodeManager
accountManager common.AccountManager
txQueueManager common.TxQueueManager
}
// NewExecutionPolicy returns a new instance of ExecutionPolicy.
func NewExecutionPolicy(nodeManager common.NodeManager, accountManager common.AccountManager) *ExecutionPolicy {
func NewExecutionPolicy(
nodeManager common.NodeManager, accountManager common.AccountManager, txQueueManager common.TxQueueManager,
) *ExecutionPolicy {
return &ExecutionPolicy{
nodeManager: nodeManager,
accountManager: accountManager,
txQueueManager: txQueueManager,
}
}
// Execute handles a received RPC call.
func (ep *ExecutionPolicy) Execute(req common.RPCCall, call otto.FunctionCall) (*otto.Object, error) {
switch req.Method {
case params.SendTransactionMethodName:
return ep.executeSendTransaction(req, call)
default:
return ep.executeOtherTransaction(req, call)
}
}
// ExecuteSendTransaction defines a function to execute RPC requests for eth_sendTransaction method only.
func (ep *ExecutionPolicy) ExecuteSendTransaction(req common.RPCCall, call otto.FunctionCall) (*otto.Object, error) {
config, err := ep.nodeManager.NodeConfig()
func (ep *ExecutionPolicy) executeSendTransaction(req common.RPCCall, call otto.FunctionCall) (*otto.Object, error) {
res, err := call.Otto.Object(`({"jsonrpc":"2.0"})`)
if err != nil {
return nil, err
}
if config.UpstreamConfig.Enabled {
return ep.executeRemoteSendTransaction(req, call)
}
res.Set("id", req.ID)
return ep.executeLocalSendTransaction(req, call)
}
// executeRemoteSendTransaction defines a function to execute RPC method eth_sendTransaction over the upstream server.
func (ep *ExecutionPolicy) executeRemoteSendTransaction(req common.RPCCall, call otto.FunctionCall) (*otto.Object, error) {
config, err := ep.nodeManager.NodeConfig()
messageID, err := preProcessRequest(call.Otto, req)
if err != nil {
return nil, err
}
selectedAcct, err := ep.accountManager.SelectedAccount()
if err != nil {
// TODO(adam): check if context is used
ctx := context.WithValue(context.Background(), common.MessageIDKey, messageID)
args := sendTxArgsFromRPCCall(req)
tx := ep.txQueueManager.CreateTransaction(ctx, args)
if err := ep.txQueueManager.QueueTransaction(tx); err != nil {
return nil, err
}
client, err := ep.nodeManager.RPCClient()
if err != nil {
if err := ep.txQueueManager.WaitForTransaction(tx); err != nil {
return nil, err
}
fromAddr, err := req.ParseFromAddress()
if err != nil {
return nil, err
}
// invoke post processing
postProcessRequest(call.Otto, req, messageID)
toAddr, err := req.ParseToAddress()
if err != nil {
return nil, err
}
// @TODO(adam): which one is actually used?
res.Set("result", tx.Hash.Hex())
res.Set("hash", tx.Hash.Hex())
// We need to request a new transaction nounce from upstream node.
ctx, canceller := context.WithTimeout(context.Background(), time.Minute)
defer canceller()
var num hexutil.Uint
if err := client.CallContext(ctx, &num, "eth_getTransactionCount", fromAddr, "pending"); err != nil {
return nil, err
}
nonce := uint64(num)
gas := (*big.Int)(req.ParseGas())
dataVal := []byte(req.ParseData())
priceVal := (*big.Int)(req.ParseValue())
gasPrice := (*big.Int)(req.ParseGasPrice())
chainID := big.NewInt(int64(config.NetworkID))
tx := types.NewTransaction(nonce, toAddr, priceVal, gas, gasPrice, dataVal)
txs, err := types.SignTx(tx, types.NewEIP155Signer(chainID), selectedAcct.AccountKey.PrivateKey)
if err != nil {
return nil, err
}
// Attempt to get the hex version of the transaction.
txBytes, err := rlp.EncodeToBytes(txs)
if err != nil {
return nil, err
}
//TODO(influx6): Should we use a single context with a higher timeout, say 3-5 minutes
// for calls to rpcClient?
ctx2, canceler2 := context.WithTimeout(context.Background(), time.Minute)
defer canceler2()
var result json.RawMessage
if err := client.CallContext(ctx2, &result, "eth_sendRawTransaction", gethcommon.ToHex(txBytes)); err != nil {
return nil, err
}
resp, err := call.Otto.Object(`({"jsonrpc":"2.0"})`)
if err != nil {
return nil, err
}
resp.Set("id", req.ID)
resp.Set("result", result)
resp.Set("hash", txs.Hash().String())
return resp, nil
}
// executeLocalSendTransaction defines a function which handles execution of RPC method over the internal rpc server
// from the eth.LightClient. It specifically caters to process eth_sendTransaction.
func (ep *ExecutionPolicy) executeLocalSendTransaction(req common.RPCCall, call otto.FunctionCall) (*otto.Object, error) {
resp, err := call.Otto.Object(`({"jsonrpc":"2.0"})`)
if err != nil {
return nil, err
}
resp.Set("id", req.ID)
txHash, err := processRPCCall(ep.nodeManager, req, call)
resp.Set("result", txHash.Hex())
if err != nil {
resp = newErrorResponse(call.Otto, -32603, err.Error(), &req.ID).Object()
return resp, nil
}
return resp, nil
return res, nil
}
// ExecuteOtherTransaction defines a function which handles the processing of non `eth_sendTransaction`
// rpc request to the internal node server.
func (ep *ExecutionPolicy) ExecuteOtherTransaction(req common.RPCCall, call otto.FunctionCall) (*otto.Object, error) {
func (ep *ExecutionPolicy) executeOtherTransaction(req common.RPCCall, call otto.FunctionCall) (*otto.Object, error) {
client, err := ep.nodeManager.RPCClient()
if err != nil {
return nil, common.StopRPCCallError{Err: err}
@ -207,3 +142,66 @@ func (ep *ExecutionPolicy) ExecuteOtherTransaction(req common.RPCCall, call otto
return resp, nil
}
// preProcessRequest pre-processes a given RPC call to a given Otto VM
func preProcessRequest(vm *otto.Otto, req common.RPCCall) (string, error) {
messageID := currentMessageID(vm.Context())
return messageID, nil
}
// postProcessRequest post-processes a given RPC call to a given Otto VM
func postProcessRequest(vm *otto.Otto, req common.RPCCall, messageID string) {
if len(messageID) > 0 {
vm.Call("addContext", nil, messageID, common.MessageIDKey, messageID) // nolint: errcheck
}
// set extra markers for queued transaction requests
if req.Method == params.SendTransactionMethodName {
vm.Call("addContext", nil, messageID, params.SendTransactionMethodName, true) // nolint: errcheck
}
}
// 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 ""
}
func sendTxArgsFromRPCCall(req common.RPCCall) common.SendTxArgs {
// no need to persist extra state for other requests
if req.Method != params.SendTransactionMethodName {
return common.SendTxArgs{}
}
var err error
var fromAddr, toAddr gethcommon.Address
fromAddr, err = req.ParseFromAddress()
if err != nil {
fromAddr = gethcommon.HexToAddress("0x0")
}
toAddr, err = req.ParseToAddress()
if err != nil {
toAddr = gethcommon.HexToAddress("0x0")
}
return common.SendTxArgs{
To: &toAddr,
From: fromAddr,
Value: req.ParseValue(),
Data: req.ParseData(),
Gas: req.ParseGas(),
GasPrice: req.ParseGasPrice(),
}
}

View File

@ -1,18 +1,14 @@
package jail
import (
"context"
"encoding/json"
"errors"
"fmt"
"sync"
gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/les/status"
"github.com/robertkrimen/otto"
"github.com/status-im/status-go/geth/common"
"github.com/status-im/status-go/geth/log"
"github.com/status-im/status-go/geth/params"
"github.com/status-im/status-go/static"
)
@ -31,6 +27,7 @@ type Jail struct {
sync.RWMutex
nodeManager common.NodeManager
accountManager common.AccountManager
txQueueManager common.TxQueueManager
policy *ExecutionPolicy
cells map[string]*Cell // jail supports running many isolated instances of jailed runtime
baseJSCode string // JavaScript used to initialize all new cells with
@ -38,12 +35,15 @@ type Jail struct {
// New returns new Jail environment with the associated NodeManager and
// AccountManager.
func New(nodeManager common.NodeManager, accountManager common.AccountManager) *Jail {
func New(
nodeManager common.NodeManager, accountManager common.AccountManager, txQueueManager common.TxQueueManager,
) *Jail {
return &Jail{
nodeManager: nodeManager,
accountManager: accountManager,
txQueueManager: txQueueManager,
cells: make(map[string]*Cell),
policy: NewExecutionPolicy(nodeManager, accountManager),
policy: NewExecutionPolicy(nodeManager, accountManager, txQueueManager),
}
}
@ -176,18 +176,12 @@ func (jail *Jail) Send(call otto.FunctionCall) (response otto.Value) {
// Execute the requests.
for _, req := range reqs {
var resErr error
var res *otto.Object
log.Info("executing a request via jail.policy", "method", req.Method, "params", req.Params)
res, err := jail.policy.Execute(req, call)
log.Info("response from the request", "err", err)
switch req.Method {
case params.SendTransactionMethodName:
res, resErr = jail.policy.ExecuteSendTransaction(req, call)
default:
res, resErr = jail.policy.ExecuteOtherTransaction(req, call)
}
if resErr != nil {
switch resErr.(type) {
if err != nil {
switch err.(type) {
case common.StopRPCCallError:
return newErrorResponse(call.Otto, -32603, err.Error(), nil)
default:
@ -214,99 +208,6 @@ func (jail *Jail) Send(call otto.FunctionCall) (response otto.Value) {
return response
}
//==================================================================================================================================
func processRPCCall(manager common.NodeManager, req common.RPCCall, call otto.FunctionCall) (gethcommon.Hash, error) {
lightEthereum, err := manager.LightEthereumService()
if err != nil {
return gethcommon.Hash{}, err
}
backend := lightEthereum.StatusBackend
messageID, err := preProcessRequest(call.Otto, req)
if err != nil {
return gethcommon.Hash{}, err
}
// onSendTransactionRequest() will use context to obtain and release ticket
ctx := context.Background()
ctx = context.WithValue(ctx, common.MessageIDKey, messageID)
// this call blocks, up until Complete Transaction is called
txHash, err := backend.SendTransaction(ctx, sendTxArgsFromRPCCall(req))
if err != nil {
return gethcommon.Hash{}, err
}
// invoke post processing
postProcessRequest(call.Otto, req, messageID)
return txHash, nil
}
// preProcessRequest pre-processes a given RPC call to a given Otto VM
func preProcessRequest(vm *otto.Otto, req common.RPCCall) (string, error) {
messageID := currentMessageID(vm.Context())
return messageID, nil
}
// postProcessRequest post-processes a given RPC call to a given Otto VM
func postProcessRequest(vm *otto.Otto, req common.RPCCall, messageID string) {
if len(messageID) > 0 {
vm.Call("addContext", nil, messageID, common.MessageIDKey, messageID) // nolint: errcheck
}
// set extra markers for queued transaction requests
if req.Method == params.SendTransactionMethodName {
vm.Call("addContext", nil, messageID, params.SendTransactionMethodName, true) // nolint: errcheck
}
}
func sendTxArgsFromRPCCall(req common.RPCCall) status.SendTxArgs {
if req.Method != params.SendTransactionMethodName { // no need to persist extra state for other requests
return status.SendTxArgs{}
}
var err error
var fromAddr, toAddr gethcommon.Address
fromAddr, err = req.ParseFromAddress()
if err != nil {
fromAddr = gethcommon.HexToAddress("0x0")
}
toAddr, err = req.ParseToAddress()
if err != nil {
toAddr = gethcommon.HexToAddress("0x0")
}
return status.SendTxArgs{
To: &toAddr,
From: fromAddr,
Value: req.ParseValue(),
Data: req.ParseData(),
Gas: req.ParseGas(),
GasPrice: req.ParseGasPrice(),
}
}
// 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 ""
}
//==========================================================================================================
func newErrorResponse(vm *otto.Otto, code int, msg string, id interface{}) otto.Value {

View File

@ -55,7 +55,9 @@ func (s *JailRPCTestSuite) SetupTest() {
acctman := node.NewAccountManager(nodeman)
require.NotNil(acctman)
policy := jail.NewExecutionPolicy(nodeman, acctman)
txQueueManager := node.NewTxQueueManager(nodeman, acctman)
policy := jail.NewExecutionPolicy(nodeman, acctman, txQueueManager)
require.NotNil(policy)
s.Policy = policy
@ -145,7 +147,7 @@ func (s *JailRPCTestSuite) TestSendTransaction() {
selectErr := s.Account.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password)
require.NoError(selectErr)
res, err := s.Policy.ExecuteSendTransaction(request, odFunc)
res, err := s.Policy.Execute(request, odFunc)
require.NoError(err)
result, err := res.Get("result")

View File

@ -38,7 +38,7 @@ func (s *JailTestSuite) SetupTest() {
accountManager := node.NewAccountManager(nodeManager)
require.NotNil(accountManager)
jail := jail.New(nodeManager, accountManager)
jail := jail.New(nodeManager, accountManager, nil)
require.NotNil(jail)
s.jail = jail

View File

@ -13,7 +13,6 @@ import (
"net/url"
"github.com/ethereum/go-ethereum/les/status"
"github.com/status-im/status-go/geth/common"
"github.com/status-im/status-go/geth/log"
"github.com/status-im/status-go/geth/params"
@ -100,7 +99,7 @@ func (c *RPCManager) Call(inputJSON string) string {
select {
case out := <-outputJSON:
return out
case <-time.After((status.DefaultTxSendCompletionTimeout + 10) * time.Minute): // give up eventually
case <-time.After((DefaultTxSendCompletionTimeout + 10) * time.Minute): // give up eventually
// pass
}

View File

@ -1,194 +1,256 @@
package node
import (
"context"
"errors"
"fmt"
"sync"
"time"
"github.com/ethereum/go-ethereum/accounts/keystore"
gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/les/status"
"github.com/status-im/status-go/geth/common"
"github.com/status-im/status-go/geth/log"
)
const (
// EventTransactionQueued is triggered when send transaction request is queued
EventTransactionQueued = "transaction.queued"
// EventTransactionFailed is triggered when send transaction request fails
EventTransactionFailed = "transaction.failed"
// SendTxDefaultErrorCode is sent by default, when error is not nil, but type is unknown/unexpected.
SendTxDefaultErrorCode = SendTransactionDefaultErrorCode
// DefaultTxQueueCap defines how many items can be queued.
DefaultTxQueueCap = int(35)
// DefaultTxSendQueueCap defines how many items can be passed to sendTransaction() w/o blocking.
DefaultTxSendQueueCap = int(70)
// DefaultTxSendCompletionTimeout defines how many seconds to wait before returning result in sentTransaction().
DefaultTxSendCompletionTimeout = 300
)
// Send transaction response codes
const (
SendTransactionNoErrorCode = "0"
SendTransactionDefaultErrorCode = "1"
SendTransactionPasswordErrorCode = "2"
SendTransactionTimeoutErrorCode = "3"
SendTransactionDiscardedErrorCode = "4"
var (
ErrQueuedTxIDNotFound = errors.New("transaction hash not found")
ErrQueuedTxTimedOut = errors.New("transaction sending timed out")
ErrQueuedTxDiscarded = errors.New("transaction has been discarded")
ErrInvalidCompleteTxSender = errors.New("transaction can only be completed by the same account which created it")
)
var txReturnCodes = map[error]string{ // deliberately strings, in case more meaningful codes are to be returned
nil: SendTransactionNoErrorCode,
keystore.ErrDecrypt: SendTransactionPasswordErrorCode,
status.ErrQueuedTxTimedOut: SendTransactionTimeoutErrorCode,
status.ErrQueuedTxDiscarded: SendTransactionDiscardedErrorCode,
// TxQueue is capped container that holds pending transactions
type TxQueue struct {
transactions map[common.QueuedTxID]*common.QueuedTx
mu sync.RWMutex // to guard transactions map
evictableIDs chan common.QueuedTxID
enqueueTicker chan struct{}
incomingPool chan *common.QueuedTx
// when this channel is closed, all queue channels processing must cease (incoming queue, processing queued items etc)
stopped chan struct{}
stoppedGroup sync.WaitGroup // to make sure that all routines are stopped
// when items are enqueued notify subscriber
txEnqueueHandler common.EnqueuedTxHandler
// when tx is returned (either successfully or with error) notify subscriber
txReturnHandler common.EnqueuedTxReturnHandler
}
// TxQueueManager provides means to manage internal Status Backend (injected into LES)
type TxQueueManager struct {
nodeManager common.NodeManager
accountManager common.AccountManager
}
func NewTxQueueManager(nodeManager common.NodeManager, accountManager common.AccountManager) *TxQueueManager {
return &TxQueueManager{
nodeManager: nodeManager,
accountManager: accountManager,
// NewTransactionQueue make new transaction queue
func NewTransactionQueue() *TxQueue {
log.Info("initializing transaction queue")
return &TxQueue{
transactions: make(map[common.QueuedTxID]*common.QueuedTx),
evictableIDs: make(chan common.QueuedTxID, DefaultTxQueueCap), // will be used to evict in FIFO
enqueueTicker: make(chan struct{}),
incomingPool: make(chan *common.QueuedTx, DefaultTxSendQueueCap),
}
}
// CompleteTransaction instructs backend to complete sending of a given transaction
func (m *TxQueueManager) CompleteTransaction(id, password string) (gethcommon.Hash, error) {
lightEthereum, err := m.nodeManager.LightEthereumService()
if err != nil {
return gethcommon.Hash{}, err
// Start starts enqueue and eviction loops
func (q *TxQueue) Start() {
log.Info("starting transaction queue")
if q.stopped != nil {
return
}
backend := lightEthereum.StatusBackend
q.stopped = make(chan struct{})
q.stoppedGroup.Add(2)
selectedAccount, err := m.accountManager.SelectedAccount()
if err != nil {
return gethcommon.Hash{}, err
}
ctx := context.Background()
ctx = context.WithValue(ctx, status.SelectedAccountKey, selectedAccount.Hex())
return backend.CompleteQueuedTransaction(ctx, status.QueuedTxID(id), password)
go q.evictionLoop()
go q.enqueueLoop()
}
// CompleteTransactions instructs backend to complete sending of multiple transactions
func (m *TxQueueManager) CompleteTransactions(ids, password string) map[string]common.RawCompleteTransactionResult {
results := make(map[string]common.RawCompleteTransactionResult)
// Stop stops transaction enqueue and eviction loops
func (q *TxQueue) Stop() {
log.Info("stopping transaction queue")
parsedIDs, err := common.ParseJSONArray(ids)
if err != nil {
results["none"] = common.RawCompleteTransactionResult{
Error: err,
}
return results
if q.stopped == nil {
return
}
for _, txID := range parsedIDs {
txHash, txErr := m.CompleteTransaction(txID, password)
results[txID] = common.RawCompleteTransactionResult{
Hash: txHash,
Error: txErr,
close(q.stopped) // stops all processing loops (enqueue, eviction etc)
q.stoppedGroup.Wait()
q.stopped = nil
log.Info("finally stopped transaction queue")
}
// evictionLoop frees up queue to accommodate another transaction item
func (q *TxQueue) evictionLoop() {
defer HaltOnPanic()
evict := func() {
if len(q.transactions) >= DefaultTxQueueCap { // eviction is required to accommodate another/last item
q.Remove(<-q.evictableIDs)
}
}
return results
}
// DiscardTransaction discards a given transaction from transaction queue
func (m *TxQueueManager) DiscardTransaction(id string) error {
lightEthereum, err := m.nodeManager.LightEthereumService()
if err != nil {
return err
}
backend := lightEthereum.StatusBackend
return backend.DiscardQueuedTransaction(status.QueuedTxID(id))
}
// DiscardTransactions discards given multiple transactions from transaction queue
func (m *TxQueueManager) DiscardTransactions(ids string) map[string]common.RawDiscardTransactionResult {
var parsedIDs []string
results := make(map[string]common.RawDiscardTransactionResult)
parsedIDs, err := common.ParseJSONArray(ids)
if err != nil {
results["none"] = common.RawDiscardTransactionResult{
Error: err,
}
return results
}
for _, txID := range parsedIDs {
err := m.DiscardTransaction(txID)
if err != nil {
results[txID] = common.RawDiscardTransactionResult{
Error: err,
}
}
}
return results
}
// SendTransactionEvent is a signal sent on a send transaction request
type SendTransactionEvent struct {
ID string `json:"id"`
Args status.SendTxArgs `json:"args"`
MessageID string `json:"message_id"`
}
// TransactionQueueHandler returns handler that processes incoming tx queue requests
func (m *TxQueueManager) TransactionQueueHandler() func(queuedTx status.QueuedTx) {
return func(queuedTx status.QueuedTx) {
SendSignal(SignalEnvelope{
Type: EventTransactionQueued,
Event: SendTransactionEvent{
ID: string(queuedTx.ID),
Args: queuedTx.Args,
MessageID: common.MessageIDFromContext(queuedTx.Context),
},
})
}
}
// ReturnSendTransactionEvent is a JSON returned whenever transaction send is returned
type ReturnSendTransactionEvent struct {
ID string `json:"id"`
Args status.SendTxArgs `json:"args"`
MessageID string `json:"message_id"`
ErrorMessage string `json:"error_message"`
ErrorCode string `json:"error_code"`
}
// TransactionReturnHandler returns handler that processes responses from internal tx manager
func (m *TxQueueManager) TransactionReturnHandler() func(queuedTx *status.QueuedTx, err error) {
return func(queuedTx *status.QueuedTx, err error) {
if err == nil {
for {
select {
case <-time.After(250 * time.Millisecond): // do not wait for manual ticks, check queue regularly
evict()
case <-q.enqueueTicker: // when manually requested
evict()
case <-q.stopped:
log.Info("transaction queue's eviction loop stopped")
q.stoppedGroup.Done()
return
}
}
}
// discard notifications with empty tx
if queuedTx == nil {
// enqueueLoop process incoming enqueue requests
func (q *TxQueue) enqueueLoop() {
defer HaltOnPanic()
// enqueue incoming transactions
for {
select {
case queuedTx := <-q.incomingPool:
log.Info("transaction enqueued requested", "tx", queuedTx.ID)
q.Enqueue(queuedTx)
log.Info("transaction enqueued", "tx", queuedTx.ID)
case <-q.stopped:
log.Info("transaction queue's enqueue loop stopped")
q.stoppedGroup.Done()
return
}
// error occurred, signal up to application
SendSignal(SignalEnvelope{
Type: EventTransactionFailed,
Event: ReturnSendTransactionEvent{
ID: string(queuedTx.ID),
Args: queuedTx.Args,
MessageID: common.MessageIDFromContext(queuedTx.Context),
ErrorMessage: err.Error(),
ErrorCode: m.sendTransactionErrorCode(err),
},
})
}
}
func (m *TxQueueManager) sendTransactionErrorCode(err error) string {
if code, ok := txReturnCodes[err]; ok {
return code
// Reset is to be used in tests only, as it simply creates new transaction map, w/o any cleanup of the previous one
func (q *TxQueue) Reset() {
q.mu.Lock()
defer q.mu.Unlock()
q.transactions = make(map[common.QueuedTxID]*common.QueuedTx)
q.evictableIDs = make(chan common.QueuedTxID, DefaultTxQueueCap)
}
// EnqueueAsync enqueues incoming transaction in async manner, returns as soon as possible
func (q *TxQueue) EnqueueAsync(tx *common.QueuedTx) error {
q.incomingPool <- tx
return nil
}
// Enqueue enqueues incoming transaction
func (q *TxQueue) Enqueue(tx *common.QueuedTx) error {
log.Info(fmt.Sprintf("enqueue transaction: %s", tx.ID))
if q.txEnqueueHandler == nil { //discard, until handler is provided
log.Info("there is no txEnqueueHandler")
return nil
}
return SendTxDefaultErrorCode
log.Info("before enqueueTicker")
q.enqueueTicker <- struct{}{} // notify eviction loop that we are trying to insert new item
log.Info("before evictableIDs")
q.evictableIDs <- tx.ID // this will block when we hit DefaultTxQueueCap
log.Info("after evictableIDs")
q.mu.Lock()
q.transactions[tx.ID] = tx
q.mu.Unlock()
// notify handler
log.Info("calling txEnqueueHandler")
q.txEnqueueHandler(tx)
return nil
}
// Get returns transaction by transaction identifier
func (q *TxQueue) Get(id common.QueuedTxID) (*common.QueuedTx, error) {
q.mu.RLock()
defer q.mu.RUnlock()
if tx, ok := q.transactions[id]; ok {
return tx, nil
}
return nil, ErrQueuedTxIDNotFound
}
// Remove removes transaction by transaction identifier
func (q *TxQueue) Remove(id common.QueuedTxID) {
q.mu.Lock()
defer q.mu.Unlock()
delete(q.transactions, id)
}
// Count returns number of currently queued transactions
func (q *TxQueue) Count() int {
q.mu.RLock()
defer q.mu.RUnlock()
return len(q.transactions)
}
// Has checks whether transaction with a given identifier exists in queue
func (q *TxQueue) Has(id common.QueuedTxID) bool {
q.mu.RLock()
defer q.mu.RUnlock()
_, ok := q.transactions[id]
return ok
}
// SetEnqueueHandler sets callback handler, that is triggered on enqueue operation
func (q *TxQueue) SetEnqueueHandler(fn common.EnqueuedTxHandler) {
q.txEnqueueHandler = fn
}
// SetTxReturnHandler sets callback handler, that is triggered when transaction is finished executing
func (q *TxQueue) SetTxReturnHandler(fn common.EnqueuedTxReturnHandler) {
q.txReturnHandler = fn
}
// NotifyOnQueuedTxReturn is invoked when transaction is ready to return
// Transaction can be in error state, or executed successfully at this point.
func (q *TxQueue) NotifyOnQueuedTxReturn(queuedTx *common.QueuedTx, err error) {
if q == nil {
return
}
// discard, if transaction is not found
if queuedTx == nil {
return
}
// on success, remove item from the queue and stop propagating
if err == nil {
q.Remove(queuedTx.ID)
return
}
// error occurred, send upward notification
if q.txReturnHandler == nil { // discard, until handler is provided
return
}
// remove from queue on any error (except for transient ones) and propagate
transientErrs := map[error]bool{
keystore.ErrDecrypt: true, // wrong password
ErrInvalidCompleteTxSender: true, // completing tx create from another account
}
if !transientErrs[err] { // remove only on unrecoverable errors
q.Remove(queuedTx.ID)
}
// notify handler
q.txReturnHandler(queuedTx, err)
}

View File

@ -0,0 +1,379 @@
package node
import (
"context"
"math/big"
"time"
"github.com/ethereum/go-ethereum/accounts/keystore"
gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/les/status"
"github.com/ethereum/go-ethereum/rlp"
"github.com/pborman/uuid"
"github.com/status-im/status-go/geth/common"
"github.com/status-im/status-go/geth/log"
)
const (
// EventTransactionQueued is triggered when send transaction request is queued
EventTransactionQueued = "transaction.queued"
// EventTransactionFailed is triggered when send transaction request fails
EventTransactionFailed = "transaction.failed"
// SendTxDefaultErrorCode is sent by default, when error is not nil, but type is unknown/unexpected.
SendTxDefaultErrorCode = SendTransactionDefaultErrorCode
)
// Send transaction response codes
const (
SendTransactionNoErrorCode = "0"
SendTransactionDefaultErrorCode = "1"
SendTransactionPasswordErrorCode = "2"
SendTransactionTimeoutErrorCode = "3"
SendTransactionDiscardedErrorCode = "4"
)
var txReturnCodes = map[error]string{ // deliberately strings, in case more meaningful codes are to be returned
nil: SendTransactionNoErrorCode,
keystore.ErrDecrypt: SendTransactionPasswordErrorCode,
ErrQueuedTxTimedOut: SendTransactionTimeoutErrorCode,
ErrQueuedTxDiscarded: SendTransactionDiscardedErrorCode,
}
// TxQueueManager provides means to manage internal Status Backend (injected into LES)
type TxQueueManager struct {
nodeManager common.NodeManager
accountManager common.AccountManager
txQueue *TxQueue
}
// NewTxQueueManager returns a new TxQueueManager.
func NewTxQueueManager(nodeManager common.NodeManager, accountManager common.AccountManager) *TxQueueManager {
return &TxQueueManager{
nodeManager: nodeManager,
accountManager: accountManager,
txQueue: NewTransactionQueue(),
}
}
// Start starts accepting new transactions into the queue.
func (m *TxQueueManager) Start() {
log.Info("start TxQueueManager")
m.txQueue.Start()
}
// Stop stops accepting new transactions into the queue.
func (m *TxQueueManager) Stop() {
log.Info("stop TxQueueManager")
m.txQueue.Stop()
}
// TransactionQueue returns a reference to the queue.
func (m *TxQueueManager) TransactionQueue() common.TxQueue {
return m.txQueue
}
// CreateTransaction returns a transaction object.
func (m *TxQueueManager) CreateTransaction(ctx context.Context, args common.SendTxArgs) *common.QueuedTx {
return &common.QueuedTx{
ID: common.QueuedTxID(uuid.New()),
Hash: gethcommon.Hash{},
Context: ctx,
Args: args,
Done: make(chan struct{}, 1),
Discard: make(chan struct{}, 1),
}
}
// QueueTransaction puts a transaction into the queue.
func (m *TxQueueManager) QueueTransaction(tx *common.QueuedTx) error {
to := "<nil>"
if tx.Args.To != nil {
to = tx.Args.To.Hex()
}
log.Info("queue a new transaction", "id", tx.ID, "from", tx.Args.From.Hex(), "to", to)
return m.txQueue.Enqueue(tx)
}
// WaitForTransaction adds a transaction to the queue and blocks
// until it's completed, discarded or times out.
func (m *TxQueueManager) WaitForTransaction(tx *common.QueuedTx) error {
log.Info("wait for transaction", "id", tx.ID)
// now wait up until transaction is:
// - completed (via CompleteQueuedTransaction),
// - discarded (via DiscardQueuedTransaction)
// - or times out
select {
case <-tx.Done:
m.NotifyOnQueuedTxReturn(tx, tx.Err)
return tx.Err
case <-tx.Discard:
m.NotifyOnQueuedTxReturn(tx, ErrQueuedTxDiscarded)
return ErrQueuedTxDiscarded
case <-time.After(DefaultTxSendCompletionTimeout * time.Second):
m.NotifyOnQueuedTxReturn(tx, ErrQueuedTxTimedOut)
return ErrQueuedTxTimedOut
}
}
// NotifyOnQueuedTxReturn calls a handler when a transaction resolves.
func (m *TxQueueManager) NotifyOnQueuedTxReturn(queuedTx *common.QueuedTx, err error) {
m.txQueue.NotifyOnQueuedTxReturn(queuedTx, err)
}
// CompleteTransaction instructs backend to complete sending of a given transaction.
// TODO(adam): investigate a possible bug that calling this method multiple times with the same Transaction ID
// results in sending multiple transactions.
func (m *TxQueueManager) CompleteTransaction(id common.QueuedTxID, password string) (gethcommon.Hash, error) {
log.Info("complete transaction", "id", id)
queuedTx, err := m.txQueue.Get(id)
if err != nil {
log.Warn("could not get a queued transaction", "err", err)
return gethcommon.Hash{}, err
}
selectedAccount, err := m.accountManager.SelectedAccount()
if err != nil {
log.Warn("failed to get a selected account", "err", err)
return gethcommon.Hash{}, err
}
// make sure that only account which created the tx can complete it
if queuedTx.Args.From.Hex() != selectedAccount.Address.Hex() {
log.Warn("queued transaction does not belong to the selected account", "err", ErrInvalidCompleteTxSender)
m.NotifyOnQueuedTxReturn(queuedTx, ErrInvalidCompleteTxSender)
return gethcommon.Hash{}, ErrInvalidCompleteTxSender
}
config, err := m.nodeManager.NodeConfig()
if err != nil {
log.Warn("could not get a node config", "err", err)
return gethcommon.Hash{}, err
}
// Send the transaction finally.
var hash gethcommon.Hash
var txErr error
if config.UpstreamConfig.Enabled {
hash, txErr = m.completeRemoteTransaction(queuedTx, password)
} else {
hash, txErr = m.completeLocalTransaction(queuedTx, password)
}
// when incorrect sender tries to complete the account,
// notify and keep tx in queue (so that correct sender can complete)
if txErr == keystore.ErrDecrypt {
log.Warn("failed to complete transaction", "err", txErr)
m.NotifyOnQueuedTxReturn(queuedTx, txErr)
return hash, txErr
}
log.Info("finally completed transaction", "id", queuedTx.ID, "hash", hash, "err", txErr)
queuedTx.Hash = hash
queuedTx.Err = txErr
queuedTx.Done <- struct{}{} // sendTransaction() waits on this, notify so that it can return
return hash, txErr
}
func (m *TxQueueManager) completeLocalTransaction(queuedTx *common.QueuedTx, password string) (gethcommon.Hash, error) {
log.Info("complete transaction using local node", "id", queuedTx.ID)
les, err := m.nodeManager.LightEthereumService()
if err != nil {
return gethcommon.Hash{}, err
}
return les.StatusBackend.SendTransaction(context.Background(), status.SendTxArgs(queuedTx.Args), password)
}
func (m *TxQueueManager) completeRemoteTransaction(queuedTx *common.QueuedTx, password string) (gethcommon.Hash, error) {
log.Info("complete transaction using upstream node", "id", queuedTx.ID)
var emptyHash gethcommon.Hash
config, err := m.nodeManager.NodeConfig()
if err != nil {
return emptyHash, err
}
selectedAcct, err := m.accountManager.SelectedAccount()
if err != nil {
return emptyHash, err
}
client, err := m.nodeManager.RPCClient()
if err != nil {
return emptyHash, err
}
// We need to request a new transaction nounce from upstream node.
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
var txCount hexutil.Uint
if callErr := client.CallContext(ctx, &txCount, "eth_getTransactionCount", queuedTx.Args.From, "pending"); callErr != nil {
return emptyHash, callErr
}
chainID := big.NewInt(int64(config.NetworkID))
nonce := uint64(txCount)
gas := (*big.Int)(queuedTx.Args.Gas)
gasPrice := (*big.Int)(queuedTx.Args.GasPrice)
dataVal := []byte(queuedTx.Args.Data)
priceVal := (*big.Int)(queuedTx.Args.Value)
tx := types.NewTransaction(nonce, *queuedTx.Args.To, priceVal, gas, gasPrice, dataVal)
signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), selectedAcct.AccountKey.PrivateKey)
if err != nil {
return emptyHash, err
}
txBytes, err := rlp.EncodeToBytes(signedTx)
if err != nil {
return emptyHash, err
}
ctx2, cancel2 := context.WithTimeout(context.Background(), time.Minute)
defer cancel2()
if err := client.CallContext(ctx2, nil, "eth_sendRawTransaction", gethcommon.ToHex(txBytes)); err != nil {
return emptyHash, err
}
return signedTx.Hash(), nil
}
// CompleteTransactions instructs backend to complete sending of multiple transactions
func (m *TxQueueManager) CompleteTransactions(ids []common.QueuedTxID, password string) map[common.QueuedTxID]common.RawCompleteTransactionResult {
results := make(map[common.QueuedTxID]common.RawCompleteTransactionResult)
for _, txID := range ids {
txHash, txErr := m.CompleteTransaction(txID, password)
results[txID] = common.RawCompleteTransactionResult{
Hash: txHash,
Error: txErr,
}
}
return results
}
// DiscardTransaction discards a given transaction from transaction queue
func (m *TxQueueManager) DiscardTransaction(id common.QueuedTxID) error {
queuedTx, err := m.txQueue.Get(id)
if err != nil {
return err
}
// remove from queue, before notifying SendTransaction
m.txQueue.Remove(queuedTx.ID)
// allow SendTransaction to return
queuedTx.Err = ErrQueuedTxDiscarded
queuedTx.Discard <- struct{}{} // sendTransaction() waits on this, notify so that it can return
return nil
}
// DiscardTransactions discards given multiple transactions from transaction queue
func (m *TxQueueManager) DiscardTransactions(ids []common.QueuedTxID) map[common.QueuedTxID]common.RawDiscardTransactionResult {
results := make(map[common.QueuedTxID]common.RawDiscardTransactionResult)
for _, txID := range ids {
err := m.DiscardTransaction(txID)
if err != nil {
results[txID] = common.RawDiscardTransactionResult{
Error: err,
}
}
}
return results
}
// SendTransactionEvent is a signal sent on a send transaction request
type SendTransactionEvent struct {
ID string `json:"id"`
Args common.SendTxArgs `json:"args"`
MessageID string `json:"message_id"`
}
// TransactionQueueHandler returns handler that processes incoming tx queue requests
func (m *TxQueueManager) TransactionQueueHandler() func(queuedTx *common.QueuedTx) {
return func(queuedTx *common.QueuedTx) {
log.Info("calling TransactionQueueHandler")
SendSignal(SignalEnvelope{
Type: EventTransactionQueued,
Event: SendTransactionEvent{
ID: string(queuedTx.ID),
Args: queuedTx.Args,
MessageID: common.MessageIDFromContext(queuedTx.Context),
},
})
}
}
// SetTransactionQueueHandler sets a handler that will be called
// when a new transaction is enqueued.
func (m *TxQueueManager) SetTransactionQueueHandler(fn common.EnqueuedTxHandler) {
m.txQueue.SetEnqueueHandler(fn)
}
// ReturnSendTransactionEvent is a JSON returned whenever transaction send is returned
type ReturnSendTransactionEvent struct {
ID string `json:"id"`
Args common.SendTxArgs `json:"args"`
MessageID string `json:"message_id"`
ErrorMessage string `json:"error_message"`
ErrorCode string `json:"error_code"`
}
// TransactionReturnHandler returns handler that processes responses from internal tx manager
func (m *TxQueueManager) TransactionReturnHandler() func(queuedTx *common.QueuedTx, err error) {
return func(queuedTx *common.QueuedTx, err error) {
if err == nil {
return
}
// discard notifications with empty tx
if queuedTx == nil {
return
}
// error occurred, signal up to application
SendSignal(SignalEnvelope{
Type: EventTransactionFailed,
Event: ReturnSendTransactionEvent{
ID: string(queuedTx.ID),
Args: queuedTx.Args,
MessageID: common.MessageIDFromContext(queuedTx.Context),
ErrorMessage: err.Error(),
ErrorCode: m.sendTransactionErrorCode(err),
},
})
}
}
func (m *TxQueueManager) sendTransactionErrorCode(err error) string {
if code, ok := txReturnCodes[err]; ok {
return code
}
return SendTxDefaultErrorCode
}
// SetTransactionReturnHandler sets a handler that will be called
// when a transaction is about to return or when a recoverable error occured.
// Recoverable error is, for instance, wrong password.
func (m *TxQueueManager) SetTransactionReturnHandler(fn common.EnqueuedTxReturnHandler) {
m.txQueue.SetTxReturnHandler(fn)
}

View File

@ -0,0 +1,215 @@
package node
import (
"context"
"errors"
"testing"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/stretchr/testify/suite"
"github.com/golang/mock/gomock"
"github.com/status-im/status-go/geth/common"
"github.com/status-im/status-go/geth/params"
. "github.com/status-im/status-go/geth/testing"
)
var errTxAssumedSent = errors.New("assume tx is done")
func TestTxQueueTestSuite(t *testing.T) {
suite.Run(t, new(TxQueueTestSuite))
}
type TxQueueTestSuite struct {
suite.Suite
nodeManagerMockCtrl *gomock.Controller
nodeManagerMock *common.MockNodeManager
accountManagerMockCtrl *gomock.Controller
accountManagerMock *common.MockAccountManager
}
func (s *TxQueueTestSuite) SetupTest() {
s.nodeManagerMockCtrl = gomock.NewController(s.T())
s.accountManagerMockCtrl = gomock.NewController(s.T())
s.nodeManagerMock = common.NewMockNodeManager(s.nodeManagerMockCtrl)
s.accountManagerMock = common.NewMockAccountManager(s.accountManagerMockCtrl)
}
func (s *TxQueueTestSuite) TearDownTest() {
s.nodeManagerMockCtrl.Finish()
s.accountManagerMockCtrl.Finish()
}
func (s *TxQueueTestSuite) TestCompleteTransaction() {
s.accountManagerMock.EXPECT().SelectedAccount().Return(&common.SelectedExtKey{
Address: common.FromAddress(TestConfig.Account1.Address),
}, nil)
s.nodeManagerMock.EXPECT().NodeConfig().Return(
params.NewNodeConfig("/tmp", params.RopstenNetworkID, true),
)
// TODO(adam): StatusBackend as an interface would allow a better solution.
// As we want to avoid network connection, we mock LES with a known error
// and treat as success.
s.nodeManagerMock.EXPECT().LightEthereumService().Return(nil, errTxAssumedSent)
txQueueManager := NewTxQueueManager(s.nodeManagerMock, s.accountManagerMock)
txQueueManager.Start()
defer txQueueManager.Stop()
tx := txQueueManager.CreateTransaction(context.Background(), common.SendTxArgs{
From: common.FromAddress(TestConfig.Account1.Address),
To: common.ToAddress(TestConfig.Account2.Address),
})
// TransactionQueueHandler is required to enqueue a transaction.
txQueueManager.SetTransactionQueueHandler(func(queuedTx *common.QueuedTx) {
s.Equal(tx.ID, queuedTx.ID)
})
txQueueManager.SetTransactionReturnHandler(func(queuedTx *common.QueuedTx, err error) {
s.Equal(tx.ID, queuedTx.ID)
s.Equal(errTxAssumedSent, err)
})
err := txQueueManager.QueueTransaction(tx)
s.NoError(err)
go func() {
_, err := txQueueManager.CompleteTransaction(tx.ID, TestConfig.Account1.Password)
s.Equal(errTxAssumedSent, err)
}()
err = txQueueManager.WaitForTransaction(tx)
s.Equal(errTxAssumedSent, err)
// Check that error is assigned to the transaction.
s.Equal(errTxAssumedSent, tx.Err)
// Transaction should be already removed from the queue.
s.False(txQueueManager.TransactionQueue().Has(tx.ID))
}
func (s *TxQueueTestSuite) TestAccountMismatch() {
s.accountManagerMock.EXPECT().SelectedAccount().Return(&common.SelectedExtKey{
Address: common.FromAddress(TestConfig.Account2.Address),
}, nil)
txQueueManager := NewTxQueueManager(s.nodeManagerMock, s.accountManagerMock)
txQueueManager.Start()
defer txQueueManager.Stop()
tx := txQueueManager.CreateTransaction(context.Background(), common.SendTxArgs{
From: common.FromAddress(TestConfig.Account1.Address),
To: common.ToAddress(TestConfig.Account2.Address),
})
// TransactionQueueHandler is required to enqueue a transaction.
txQueueManager.SetTransactionQueueHandler(func(queuedTx *common.QueuedTx) {
s.Equal(tx.ID, queuedTx.ID)
})
// Missmatched address is a recoverable error, that's why
// the return handler is called.
txQueueManager.SetTransactionReturnHandler(func(queuedTx *common.QueuedTx, err error) {
s.Equal(tx.ID, queuedTx.ID)
s.Equal(ErrInvalidCompleteTxSender, err)
s.Nil(tx.Err)
})
err := txQueueManager.QueueTransaction(tx)
s.NoError(err)
_, err = txQueueManager.CompleteTransaction(tx.ID, TestConfig.Account1.Password)
s.Equal(err, ErrInvalidCompleteTxSender)
// Transaction should stay in the queue as mismatched accounts
// is a recoverable error.
s.True(txQueueManager.TransactionQueue().Has(tx.ID))
}
func (s *TxQueueTestSuite) TestInvalidPassword() {
s.accountManagerMock.EXPECT().SelectedAccount().Return(&common.SelectedExtKey{
Address: common.FromAddress(TestConfig.Account1.Address),
}, nil)
s.nodeManagerMock.EXPECT().NodeConfig().Return(
params.NewNodeConfig("/tmp", params.RopstenNetworkID, true),
)
// Set ErrDecrypt error response as expected with a wrong password.
s.nodeManagerMock.EXPECT().LightEthereumService().Return(nil, keystore.ErrDecrypt)
txQueueManager := NewTxQueueManager(s.nodeManagerMock, s.accountManagerMock)
txQueueManager.Start()
defer txQueueManager.Stop()
tx := txQueueManager.CreateTransaction(context.Background(), common.SendTxArgs{
From: common.FromAddress(TestConfig.Account1.Address),
To: common.ToAddress(TestConfig.Account2.Address),
})
// TransactionQueueHandler is required to enqueue a transaction.
txQueueManager.SetTransactionQueueHandler(func(queuedTx *common.QueuedTx) {
s.Equal(tx.ID, queuedTx.ID)
})
// Missmatched address is a revocable error, that's why
// the return handler is called.
txQueueManager.SetTransactionReturnHandler(func(queuedTx *common.QueuedTx, err error) {
s.Equal(tx.ID, queuedTx.ID)
s.Equal(keystore.ErrDecrypt, err)
s.Nil(tx.Err)
})
err := txQueueManager.QueueTransaction(tx)
s.NoError(err)
_, err = txQueueManager.CompleteTransaction(tx.ID, "invalid-password")
s.Equal(err, keystore.ErrDecrypt)
// Transaction should stay in the queue as mismatched accounts
// is a recoverable error.
s.True(txQueueManager.TransactionQueue().Has(tx.ID))
}
func (s *TxQueueTestSuite) TestDiscardTransaction() {
txQueueManager := NewTxQueueManager(s.nodeManagerMock, s.accountManagerMock)
txQueueManager.Start()
defer txQueueManager.Stop()
tx := txQueueManager.CreateTransaction(context.Background(), common.SendTxArgs{
From: common.FromAddress(TestConfig.Account1.Address),
To: common.ToAddress(TestConfig.Account2.Address),
})
// TransactionQueueHandler is required to enqueue a transaction.
txQueueManager.SetTransactionQueueHandler(func(queuedTx *common.QueuedTx) {
s.Equal(tx.ID, queuedTx.ID)
})
txQueueManager.SetTransactionReturnHandler(func(queuedTx *common.QueuedTx, err error) {
s.Equal(tx.ID, queuedTx.ID)
s.Equal(ErrQueuedTxDiscarded, err)
})
err := txQueueManager.QueueTransaction(tx)
s.NoError(err)
go func() {
err := txQueueManager.DiscardTransaction(tx.ID)
s.NoError(err)
}()
err = txQueueManager.WaitForTransaction(tx)
s.Equal(ErrQueuedTxDiscarded, err)
// Check that error is assigned to the transaction.
s.Equal(ErrQueuedTxDiscarded, tx.Err)
// Transaction should be already removed from the queue.
s.False(txQueueManager.TransactionQueue().Has(tx.ID))
}

View File

@ -36,7 +36,6 @@ import (
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/les/status"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/params"
@ -1181,34 +1180,14 @@ func submitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (c
return tx.Hash(), nil
}
// SendTransaction queues transactions, to be fulfilled by CompleteQueuedTransaction()
func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args SendTxArgs) (common.Hash, error) {
backend := s.b.GetStatusBackend()
if backend != nil {
return backend.SendTransaction(ctx, status.SendTxArgs(args))
}
return common.Hash{}, ErrStatusBackendNotInited
}
// CompleteQueuedTransaction creates a transaction by unpacking queued transaction, signs it and submits to the
// transaction pool.
func (s *PublicTransactionPoolAPI) CompleteQueuedTransaction(ctx context.Context, args SendTxArgs, passphrase string) (common.Hash, error) {
func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args SendTxArgs, passphrase string) (common.Hash, error) {
// Set some sanity defaults and terminate on failure
if err := args.setDefaults(ctx, s.b); err != nil {
return common.Hash{}, err
}
// make sure that only account which created the tx can complete it
selectedAccountAddress := "0x0"
if address, ok := ctx.Value(status.SelectedAccountKey).(string); ok {
selectedAccountAddress = address
}
if args.From.Hex() != selectedAccountAddress {
log.Info("Failed to complete tx", "from", args.From.Hex(), "selected account", selectedAccountAddress)
return common.Hash{}, status.ErrInvalidCompleteTxSender
}
// Look up the wallet containing the requested signer
account := accounts.Account{Address: args.From}

View File

@ -3,14 +3,11 @@ package ethapi
import (
"errors"
"math/big"
"time"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/les/status"
"github.com/ethereum/go-ethereum/log"
"github.com/pborman/uuid"
"golang.org/x/net/context"
)
@ -20,8 +17,7 @@ type StatusBackend struct {
bcapi *PublicBlockChainAPI // Wrapper around the blockchain to access chain data
txapi *PublicTransactionPoolAPI // Wrapper around the transaction pool to access transaction data
txQueue *status.TxQueue
am *status.AccountManager
am *status.AccountManager
}
var (
@ -32,50 +28,13 @@ var (
func NewStatusBackend(apiBackend Backend) *StatusBackend {
log.Info("StatusIM: backend service inited")
return &StatusBackend{
eapi: NewPublicEthereumAPI(apiBackend),
bcapi: NewPublicBlockChainAPI(apiBackend),
txapi: NewPublicTransactionPoolAPI(apiBackend, new(AddrLocker)),
txQueue: status.NewTransactionQueue(),
am: status.NewAccountManager(apiBackend.AccountManager()),
eapi: NewPublicEthereumAPI(apiBackend),
bcapi: NewPublicBlockChainAPI(apiBackend),
txapi: NewPublicTransactionPoolAPI(apiBackend, new(AddrLocker)),
am: status.NewAccountManager(apiBackend.AccountManager()),
}
}
// Start starts status backend
func (b *StatusBackend) Start() {
log.Info("StatusIM: started as LES sub-protocol")
b.txQueue.Start()
}
// Stop stops status backend
func (b *StatusBackend) Stop() {
log.Info("StatusIM: stopped as LES sub-protocol")
b.txQueue.Stop()
}
// NotifyOnQueuedTxReturn notifies any registered handlers that transaction is ready to return
func (b *StatusBackend) NotifyOnQueuedTxReturn(queuedTx *status.QueuedTx, err error) {
if b == nil {
return
}
b.txQueue.NotifyOnQueuedTxReturn(queuedTx, err)
}
// SetTransactionReturnHandler sets a callback that is triggered when transaction is ready to return
func (b *StatusBackend) SetTransactionReturnHandler(fn status.EnqueuedTxReturnHandler) {
b.txQueue.SetTxReturnHandler(fn)
}
// SetTransactionQueueHandler sets a callback that is triggered when transaction is enqueued
func (b *StatusBackend) SetTransactionQueueHandler(fn status.EnqueuedTxHandler) {
b.txQueue.SetEnqueueHandler(fn)
}
// TransactionQueue returns reference to transaction queue
func (b *StatusBackend) TransactionQueue() *status.TxQueue {
return b.txQueue
}
// SetAccountsFilterHandler sets a callback that is triggered when account list is requested
func (b *StatusBackend) SetAccountsFilterHandler(fn status.AccountsFilterHandler) {
b.am.SetAccountsFilterHandler(fn)
@ -87,7 +46,7 @@ func (b *StatusBackend) AccountManager() *status.AccountManager {
}
// SendTransaction wraps call to PublicTransactionPoolAPI.SendTransaction
func (b *StatusBackend) SendTransaction(ctx context.Context, args status.SendTxArgs) (common.Hash, error) {
func (b *StatusBackend) SendTransaction(ctx context.Context, args status.SendTxArgs, passphrase string) (common.Hash, error) {
if ctx == nil {
ctx = context.Background()
}
@ -98,81 +57,7 @@ func (b *StatusBackend) SendTransaction(ctx context.Context, args status.SendTxA
}
}
queuedTx := &status.QueuedTx{
ID: status.QueuedTxID(uuid.New()),
Hash: common.Hash{},
Context: ctx,
Args: status.SendTxArgs(args),
Done: make(chan struct{}, 1),
Discard: make(chan struct{}, 1),
}
// send transaction to pending pool, w/o blocking
b.txQueue.EnqueueAsync(queuedTx)
// now wait up until transaction is:
// - completed (via CompleteQueuedTransaction),
// - discarded (via DiscardQueuedTransaction)
// - or times out
select {
case <-queuedTx.Done:
b.NotifyOnQueuedTxReturn(queuedTx, queuedTx.Err)
return queuedTx.Hash, queuedTx.Err
case <-queuedTx.Discard:
b.NotifyOnQueuedTxReturn(queuedTx, status.ErrQueuedTxDiscarded)
return queuedTx.Hash, queuedTx.Err
case <-time.After(status.DefaultTxSendCompletionTimeout * time.Second):
b.NotifyOnQueuedTxReturn(queuedTx, status.ErrQueuedTxTimedOut)
return common.Hash{}, status.ErrQueuedTxTimedOut
}
return queuedTx.Hash, nil
}
// CompleteQueuedTransaction wraps call to PublicTransactionPoolAPI.CompleteQueuedTransaction
func (b *StatusBackend) CompleteQueuedTransaction(ctx context.Context, id status.QueuedTxID, passphrase string) (common.Hash, error) {
queuedTx, err := b.txQueue.Get(id)
if err != nil {
return common.Hash{}, err
}
hash, err := b.txapi.CompleteQueuedTransaction(ctx, SendTxArgs(queuedTx.Args), passphrase)
// on password error, notify the app, and keep tx in queue (so that CompleteQueuedTransaction() can be resent)
if err == keystore.ErrDecrypt {
b.NotifyOnQueuedTxReturn(queuedTx, err)
return hash, err // SendTransaction is still blocked
}
// when incorrect sender tries to complete the account, notify and keep tx in queue (so that correct sender can complete)
if err == status.ErrInvalidCompleteTxSender {
b.NotifyOnQueuedTxReturn(queuedTx, err)
return hash, err // SendTransaction is still blocked
}
// allow SendTransaction to return
queuedTx.Hash = hash
queuedTx.Err = err
queuedTx.Done <- struct{}{} // sendTransaction() waits on this, notify so that it can return
return hash, err
}
// DiscardQueuedTransaction discards queued transaction forcing SendTransaction to return
func (b *StatusBackend) DiscardQueuedTransaction(id status.QueuedTxID) error {
queuedTx, err := b.txQueue.Get(id)
if err != nil {
return err
}
// remove from queue, before notifying SendTransaction
b.TransactionQueue().Remove(queuedTx.ID)
// allow SendTransaction to return
queuedTx.Err = status.ErrQueuedTxDiscarded
queuedTx.Discard <- struct{}{} // sendTransaction() waits on this, notify so that it can return
return nil
return b.txapi.SendTransaction(ctx, SendTxArgs(args), passphrase)
}
// EstimateGas uses underlying blockchain API to obtain gas for a given tx arguments

View File

@ -213,7 +213,6 @@ func (s *LightEthereum) Start(srvr *p2p.Server) error {
s.netRPCService = ethapi.NewPublicNetAPI(srvr, s.networkId)
s.serverPool.start(srvr, lesTopic(s.blockchain.Genesis().Hash()))
s.protocolManager.Start()
s.StatusBackend.Start()
return nil
}
@ -231,8 +230,6 @@ func (s *LightEthereum) Stop() error {
s.chainDb.Close()
close(s.shutdownChan)
s.StatusBackend.Stop()
return nil
}

View File

@ -5,6 +5,8 @@ import (
"github.com/ethereum/go-ethereum/common"
)
const SelectedAccountKey = "selected_account"
// AccountManager abstracts both internal account manager and extra filter status backend requires
type AccountManager struct {
am *accounts.Manager

View File

@ -1,278 +0,0 @@
package status
import (
"errors"
"os"
"runtime/debug"
"sync"
"time"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
"golang.org/x/net/context"
)
const (
DefaultTxQueueCap = int(35) // how many items can be queued
DefaultTxSendQueueCap = int(70) // how many items can be passed to sendTransaction() w/o blocking
DefaultTxSendCompletionTimeout = 300 // how many seconds to wait before returning result in sentTransaction()
SelectedAccountKey = "selected_account"
)
var (
ErrQueuedTxIDNotFound = errors.New("transaction hash not found")
ErrQueuedTxTimedOut = errors.New("transaction sending timed out")
ErrQueuedTxDiscarded = errors.New("transaction has been discarded")
ErrInvalidCompleteTxSender = errors.New("transaction can only be completed by the same account which created it")
)
// TxQueue is capped container that holds pending transactions
type TxQueue struct {
transactions map[QueuedTxID]*QueuedTx
mu sync.RWMutex // to guard transactions map
evictableIDs chan QueuedTxID
enqueueTicker chan struct{}
incomingPool chan *QueuedTx
// when this channel is closed, all queue channels processing must cease (incoming queue, processing queued items etc)
stopped chan struct{}
stoppedGroup sync.WaitGroup // to make sure that all routines are stopped
// when items are enqueued notify subscriber
txEnqueueHandler EnqueuedTxHandler
// when tx is returned (either successfully or with error) notify subscriber
txReturnHandler EnqueuedTxReturnHandler
}
// QueuedTx holds enough information to complete the queued transaction.
type QueuedTx struct {
ID QueuedTxID
Hash common.Hash
Context context.Context
Args SendTxArgs
Done chan struct{}
Discard chan struct{}
Err error
}
// QueuedTxID queued transaction identifier
type QueuedTxID string
// EnqueuedTxHandler is a function that receives queued/pending transactions, when they get queued
type EnqueuedTxHandler func(QueuedTx)
// EnqueuedTxReturnHandler is a function that receives response when tx is complete (both on success and error)
type EnqueuedTxReturnHandler func(queuedTx *QueuedTx, err error)
// SendTxArgs represents the arguments to submit a new transaction into the transaction pool.
type SendTxArgs struct {
From common.Address `json:"from"`
To *common.Address `json:"to"`
Gas *hexutil.Big `json:"gas"`
GasPrice *hexutil.Big `json:"gasPrice"`
Value *hexutil.Big `json:"value"`
Data hexutil.Bytes `json:"data"`
Nonce *hexutil.Uint64 `json:"nonce"`
}
// NewTransactionQueue make new transaction queue
func NewTransactionQueue() *TxQueue {
log.Info("StatusIM: initializing transaction queue")
return &TxQueue{
transactions: make(map[QueuedTxID]*QueuedTx),
evictableIDs: make(chan QueuedTxID, DefaultTxQueueCap), // will be used to evict in FIFO
enqueueTicker: make(chan struct{}),
incomingPool: make(chan *QueuedTx, DefaultTxSendQueueCap),
}
}
// Start starts enqueue and eviction loops
func (q *TxQueue) Start() {
log.Info("StatusIM: starting transaction queue")
q.stopped = make(chan struct{})
q.stoppedGroup.Add(2)
go q.evictionLoop()
go q.enqueueLoop()
}
// Stop stops transaction enqueue and eviction loops
func (q *TxQueue) Stop() {
log.Info("StatusIM: stopping transaction queue")
close(q.stopped) // stops all processing loops (enqueue, eviction etc)
q.stoppedGroup.Wait()
}
// HaltOnPanic recovers from panic, logs issue, and exits
func HaltOnPanic() {
if r := recover(); r != nil {
log.Error("Runtime PANIC!!!", "error", r, "stack", string(debug.Stack()))
time.Sleep(5 * time.Second) // allow logger to flush logs
os.Exit(1)
}
}
// evictionLoop frees up queue to accommodate another transaction item
func (q *TxQueue) evictionLoop() {
defer HaltOnPanic()
evict := func() {
if len(q.transactions) >= DefaultTxQueueCap { // eviction is required to accommodate another/last item
q.Remove(<-q.evictableIDs)
}
}
for {
select {
case <-time.After(250 * time.Millisecond): // do not wait for manual ticks, check queue regularly
evict()
case <-q.enqueueTicker: // when manually requested
evict()
case <-q.stopped:
log.Info("StatusIM: transaction queue's eviction loop stopped")
q.stoppedGroup.Done()
return
}
}
}
// enqueueLoop process incoming enqueue requests
func (q *TxQueue) enqueueLoop() {
defer HaltOnPanic()
// enqueue incoming transactions
for {
select {
case queuedTx := <-q.incomingPool:
log.Info("StatusIM: transaction enqueued requested", "tx", queuedTx.ID)
q.Enqueue(queuedTx)
log.Info("StatusIM: transaction enqueued", "tx", queuedTx.ID)
case <-q.stopped:
log.Info("StatusIM: transaction queue's enqueue loop stopped")
q.stoppedGroup.Done()
return
}
}
}
// Reset is to be used in tests only, as it simply creates new transaction map, w/o any cleanup of the previous one
func (q *TxQueue) Reset() {
q.mu.Lock()
defer q.mu.Unlock()
q.transactions = make(map[QueuedTxID]*QueuedTx)
q.evictableIDs = make(chan QueuedTxID, DefaultTxQueueCap)
}
// EnqueueAsync enqueues incoming transaction in async manner, returns as soon as possible
func (q *TxQueue) EnqueueAsync(tx *QueuedTx) error {
q.incomingPool <- tx
return nil
}
// Enqueue enqueues incoming transaction
func (q *TxQueue) Enqueue(tx *QueuedTx) error {
if q.txEnqueueHandler == nil { //discard, until handler is provided
return nil
}
q.enqueueTicker <- struct{}{} // notify eviction loop that we are trying to insert new item
q.evictableIDs <- tx.ID // this will block when we hit DefaultTxQueueCap
q.mu.Lock()
q.transactions[tx.ID] = tx
q.mu.Unlock()
// notify handler
q.txEnqueueHandler(*tx)
return nil
}
// Get returns transaction by transaction identifier
func (q *TxQueue) Get(id QueuedTxID) (*QueuedTx, error) {
q.mu.RLock()
defer q.mu.RUnlock()
if tx, ok := q.transactions[id]; ok {
return tx, nil
}
return nil, ErrQueuedTxIDNotFound
}
// Remove removes transaction by transaction identifier
func (q *TxQueue) Remove(id QueuedTxID) {
q.mu.Lock()
defer q.mu.Unlock()
delete(q.transactions, id)
}
// Count returns number of currently queued transactions
func (q *TxQueue) Count() int {
q.mu.RLock()
defer q.mu.RUnlock()
return len(q.transactions)
}
// Has checks whether transaction with a given identifier exists in queue
func (q *TxQueue) Has(id QueuedTxID) bool {
q.mu.RLock()
defer q.mu.RUnlock()
_, ok := q.transactions[id]
return ok
}
// SetEnqueueHandler sets callback handler, that is triggered on enqueue operation
func (q *TxQueue) SetEnqueueHandler(fn EnqueuedTxHandler) {
q.txEnqueueHandler = fn
}
// SetTxReturnHandler sets callback handler, that is triggered when transaction is finished executing
func (q *TxQueue) SetTxReturnHandler(fn EnqueuedTxReturnHandler) {
q.txReturnHandler = fn
}
// NotifyOnQueuedTxReturn is invoked when transaction is ready to return
// Transaction can be in error state, or executed successfully at this point.
func (q *TxQueue) NotifyOnQueuedTxReturn(queuedTx *QueuedTx, err error) {
if q == nil {
return
}
// discard, if transaction is not found
if queuedTx == nil {
return
}
// on success, remove item from the queue and stop propagating
if err == nil {
q.Remove(queuedTx.ID)
return
}
// error occurred, send upward notification
if q.txReturnHandler == nil { // discard, until handler is provided
return
}
// remove from queue on any error (except for transient ones) and propagate
transientErrs := map[error]bool{
keystore.ErrDecrypt: true, // wrong password
ErrInvalidCompleteTxSender: true, // completing tx create from another account
}
if !transientErrs[err] { // remove only on unrecoverable errors
q.Remove(queuedTx.ID)
}
// notify handler
q.txReturnHandler(queuedTx, err)
}

View File

@ -0,0 +1,17 @@
package status
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
// SendTxArgs represents the arguments to submit a new transaction into the transaction pool.
type SendTxArgs struct {
From common.Address `json:"from"`
To *common.Address `json:"to"`
Gas *hexutil.Big `json:"gas"`
GasPrice *hexutil.Big `json:"gasPrice"`
Value *hexutil.Big `json:"value"`
Data hexutil.Bytes `json:"data"`
Nonce *hexutil.Uint64 `json:"nonce"`
}

282
vendor/github.com/golang/mock/gomock/call.go generated vendored Normal file
View File

@ -0,0 +1,282 @@
// Copyright 2010 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gomock
import (
"fmt"
"reflect"
"strconv"
"strings"
)
// Call represents an expected call to a mock.
type Call struct {
t TestReporter // for triggering test failures on invalid call setup
receiver interface{} // the receiver of the method call
method string // the name of the method
methodType reflect.Type // the type of the method
args []Matcher // the args
rets []interface{} // the return values (if any)
origin string // file and line number of call setup
preReqs []*Call // prerequisite calls
// Expectations
minCalls, maxCalls int
numCalls int // actual number made
// Actions
doFunc reflect.Value
setArgs map[int]reflect.Value
}
// AnyTimes allows the expectation to be called 0 or more times
func (c *Call) AnyTimes() *Call {
c.minCalls, c.maxCalls = 0, 1e8 // close enough to infinity
return c
}
// MinTimes requires the call to occur at least n times. If AnyTimes or MaxTimes have not been called, MinTimes also
// sets the maximum number of calls to infinity.
func (c *Call) MinTimes(n int) *Call {
c.minCalls = n
if c.maxCalls == 1 {
c.maxCalls = 1e8
}
return c
}
// MaxTimes limits the number of calls to n times. If AnyTimes or MinTimes have not been called, MaxTimes also
// sets the minimum number of calls to 0.
func (c *Call) MaxTimes(n int) *Call {
c.maxCalls = n
if c.minCalls == 1 {
c.minCalls = 0
}
return c
}
// Do declares the action to run when the call is matched.
// It takes an interface{} argument to support n-arity functions.
func (c *Call) Do(f interface{}) *Call {
// TODO: Check arity and types here, rather than dying badly elsewhere.
c.doFunc = reflect.ValueOf(f)
return c
}
func (c *Call) Return(rets ...interface{}) *Call {
mt := c.methodType
if len(rets) != mt.NumOut() {
c.t.Fatalf("wrong number of arguments to Return for %T.%v: got %d, want %d [%s]",
c.receiver, c.method, len(rets), mt.NumOut(), c.origin)
}
for i, ret := range rets {
if got, want := reflect.TypeOf(ret), mt.Out(i); got == want {
// Identical types; nothing to do.
} else if got == nil {
// Nil needs special handling.
switch want.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
// ok
default:
c.t.Fatalf("argument %d to Return for %T.%v is nil, but %v is not nillable [%s]",
i, c.receiver, c.method, want, c.origin)
}
} else if got.AssignableTo(want) {
// Assignable type relation. Make the assignment now so that the generated code
// can return the values with a type assertion.
v := reflect.New(want).Elem()
v.Set(reflect.ValueOf(ret))
rets[i] = v.Interface()
} else {
c.t.Fatalf("wrong type of argument %d to Return for %T.%v: %v is not assignable to %v [%s]",
i, c.receiver, c.method, got, want, c.origin)
}
}
c.rets = rets
return c
}
func (c *Call) Times(n int) *Call {
c.minCalls, c.maxCalls = n, n
return c
}
// SetArg declares an action that will set the nth argument's value,
// indirected through a pointer. Or, in the case of a slice, SetArg
// will copy value's elements into the nth argument.
func (c *Call) SetArg(n int, value interface{}) *Call {
if c.setArgs == nil {
c.setArgs = make(map[int]reflect.Value)
}
mt := c.methodType
// TODO: This will break on variadic methods.
// We will need to check those at invocation time.
if n < 0 || n >= mt.NumIn() {
c.t.Fatalf("SetArg(%d, ...) called for a method with %d args [%s]",
n, mt.NumIn(), c.origin)
}
// Permit setting argument through an interface.
// In the interface case, we don't (nay, can't) check the type here.
at := mt.In(n)
switch at.Kind() {
case reflect.Ptr:
dt := at.Elem()
if vt := reflect.TypeOf(value); !vt.AssignableTo(dt) {
c.t.Fatalf("SetArg(%d, ...) argument is a %v, not assignable to %v [%s]",
n, vt, dt, c.origin)
}
case reflect.Interface:
// nothing to do
case reflect.Slice:
// nothing to do
default:
c.t.Fatalf("SetArg(%d, ...) referring to argument of non-pointer non-interface non-slice type %v",
n, at, c.origin)
}
c.setArgs[n] = reflect.ValueOf(value)
return c
}
// isPreReq returns true if other is a direct or indirect prerequisite to c.
func (c *Call) isPreReq(other *Call) bool {
for _, preReq := range c.preReqs {
if other == preReq || preReq.isPreReq(other) {
return true
}
}
return false
}
// After declares that the call may only match after preReq has been exhausted.
func (c *Call) After(preReq *Call) *Call {
if c == preReq {
c.t.Fatalf("A call isn't allowed to be its own prerequisite")
}
if preReq.isPreReq(c) {
c.t.Fatalf("Loop in call order: %v is a prerequisite to %v (possibly indirectly).", c, preReq)
}
c.preReqs = append(c.preReqs, preReq)
return c
}
// Returns true if the minimum number of calls have been made.
func (c *Call) satisfied() bool {
return c.numCalls >= c.minCalls
}
// Returns true iff the maximum number of calls have been made.
func (c *Call) exhausted() bool {
return c.numCalls >= c.maxCalls
}
func (c *Call) String() string {
args := make([]string, len(c.args))
for i, arg := range c.args {
args[i] = arg.String()
}
arguments := strings.Join(args, ", ")
return fmt.Sprintf("%T.%v(%s) %s", c.receiver, c.method, arguments, c.origin)
}
// Tests if the given call matches the expected call.
// If yes, returns nil. If no, returns error with message explaining why it does not match.
func (c *Call) matches(args []interface{}) error {
if len(args) != len(c.args) {
return fmt.Errorf("Expected call at %s has the wrong number of arguments. Got: %s, want: %s",
c.origin, strconv.Itoa(len(args)), strconv.Itoa(len(c.args)))
}
for i, m := range c.args {
if !m.Matches(args[i]) {
return fmt.Errorf("Expected call at %s doesn't match the argument at index %s.\nGot: %v\nWant: %v\n",
c.origin, strconv.Itoa(i), args[i], m)
}
}
// Check that all prerequisite calls have been satisfied.
for _, preReqCall := range c.preReqs {
if !preReqCall.satisfied() {
return fmt.Errorf("Expected call at %s doesn't have a prerequisite call satisfied:\n%v\nshould be called before:\n%v",
c.origin, preReqCall, c)
}
}
return nil
}
// dropPrereqs tells the expected Call to not re-check prerequisite calls any
// longer, and to return its current set.
func (c *Call) dropPrereqs() (preReqs []*Call) {
preReqs = c.preReqs
c.preReqs = nil
return
}
func (c *Call) call(args []interface{}) (rets []interface{}, action func()) {
c.numCalls++
// Actions
if c.doFunc.IsValid() {
doArgs := make([]reflect.Value, len(args))
ft := c.doFunc.Type()
for i := 0; i < len(args); i++ {
if args[i] != nil {
doArgs[i] = reflect.ValueOf(args[i])
} else {
// Use the zero value for the arg.
doArgs[i] = reflect.Zero(ft.In(i))
}
}
action = func() { c.doFunc.Call(doArgs) }
}
for n, v := range c.setArgs {
switch reflect.TypeOf(args[n]).Kind() {
case reflect.Slice:
setSlice(args[n], v)
default:
reflect.ValueOf(args[n]).Elem().Set(v)
}
}
rets = c.rets
if rets == nil {
// Synthesize the zero value for each of the return args' types.
mt := c.methodType
rets = make([]interface{}, mt.NumOut())
for i := 0; i < mt.NumOut(); i++ {
rets[i] = reflect.Zero(mt.Out(i)).Interface()
}
}
return
}
// InOrder declares that the given calls should occur in order.
func InOrder(calls ...*Call) {
for i := 1; i < len(calls); i++ {
calls[i].After(calls[i-1])
}
}
func setSlice(arg interface{}, v reflect.Value) {
va := reflect.ValueOf(arg)
for i := 0; i < v.Len(); i++ {
va.Index(i).Set(v.Index(i))
}
}

86
vendor/github.com/golang/mock/gomock/callset.go generated vendored Normal file
View File

@ -0,0 +1,86 @@
// Copyright 2011 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gomock
import (
"errors"
"fmt"
)
// callSet represents a set of expected calls, indexed by receiver and method
// name.
type callSet map[interface{}]map[string][]*Call
// Add adds a new expected call.
func (cs callSet) Add(call *Call) {
methodMap, ok := cs[call.receiver]
if !ok {
methodMap = make(map[string][]*Call)
cs[call.receiver] = methodMap
}
methodMap[call.method] = append(methodMap[call.method], call)
}
// Remove removes an expected call.
func (cs callSet) Remove(call *Call) {
methodMap, ok := cs[call.receiver]
if !ok {
return
}
sl := methodMap[call.method]
for i, c := range sl {
if c == call {
// quick removal; we don't need to maintain call order
if len(sl) > 1 {
sl[i] = sl[len(sl)-1]
}
methodMap[call.method] = sl[:len(sl)-1]
break
}
}
}
// FindMatch searches for a matching call. Returns error with explanation message if no call matched.
func (cs callSet) FindMatch(receiver interface{}, method string, args []interface{}) (*Call, error) {
methodMap, ok := cs[receiver]
if !ok {
return nil, errors.New("there are no expected method calls for that receiver")
}
calls, ok := methodMap[method]
if !ok {
return nil, fmt.Errorf("there are no expected calls of the method: %s for that receiver", method)
}
// Search through the unordered set of calls expected on a method on a
// receiver.
callsErrors := ""
for _, call := range calls {
// A call should not normally still be here if exhausted,
// but it can happen if, for instance, .Times(0) was used.
// Pretend the call doesn't match.
if call.exhausted() {
callsErrors += "\nThe call was exhausted."
continue
}
err := call.matches(args)
if err != nil {
callsErrors += "\n" + err.Error()
} else {
return call, nil
}
}
return nil, fmt.Errorf(callsErrors)
}

193
vendor/github.com/golang/mock/gomock/controller.go generated vendored Normal file
View File

@ -0,0 +1,193 @@
// Copyright 2010 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// GoMock - a mock framework for Go.
//
// Standard usage:
// (1) Define an interface that you wish to mock.
// type MyInterface interface {
// SomeMethod(x int64, y string)
// }
// (2) Use mockgen to generate a mock from the interface.
// (3) Use the mock in a test:
// func TestMyThing(t *testing.T) {
// mockCtrl := gomock.NewController(t)
// defer mockCtrl.Finish()
//
// mockObj := something.NewMockMyInterface(mockCtrl)
// mockObj.EXPECT().SomeMethod(4, "blah")
// // pass mockObj to a real object and play with it.
// }
//
// By default, expected calls are not enforced to run in any particular order.
// Call order dependency can be enforced by use of InOrder and/or Call.After.
// Call.After can create more varied call order dependencies, but InOrder is
// often more convenient.
//
// The following examples create equivalent call order dependencies.
//
// Example of using Call.After to chain expected call order:
//
// firstCall := mockObj.EXPECT().SomeMethod(1, "first")
// secondCall := mockObj.EXPECT().SomeMethod(2, "second").After(firstCall)
// mockObj.EXPECT().SomeMethod(3, "third").After(secondCall)
//
// Example of using InOrder to declare expected call order:
//
// gomock.InOrder(
// mockObj.EXPECT().SomeMethod(1, "first"),
// mockObj.EXPECT().SomeMethod(2, "second"),
// mockObj.EXPECT().SomeMethod(3, "third"),
// )
//
// TODO:
// - Handle different argument/return types (e.g. ..., chan, map, interface).
package gomock
import (
"fmt"
"reflect"
"runtime"
"sync"
)
// A TestReporter is something that can be used to report test failures.
// It is satisfied by the standard library's *testing.T.
type TestReporter interface {
Errorf(format string, args ...interface{})
Fatalf(format string, args ...interface{})
}
// A Controller represents the top-level control of a mock ecosystem.
// It defines the scope and lifetime of mock objects, as well as their expectations.
// It is safe to call Controller's methods from multiple goroutines.
type Controller struct {
mu sync.Mutex
t TestReporter
expectedCalls callSet
}
func NewController(t TestReporter) *Controller {
return &Controller{
t: t,
expectedCalls: make(callSet),
}
}
func (ctrl *Controller) RecordCall(receiver interface{}, method string, args ...interface{}) *Call {
recv := reflect.ValueOf(receiver)
for i := 0; i < recv.Type().NumMethod(); i++ {
if recv.Type().Method(i).Name == method {
return ctrl.RecordCallWithMethodType(receiver, method, recv.Method(i).Type(), args...)
}
}
ctrl.t.Fatalf("gomock: failed finding method %s on %T", method, receiver)
// In case t.Fatalf does not panic.
panic(fmt.Sprintf("gomock: failed finding method %s on %T", method, receiver))
}
func (ctrl *Controller) RecordCallWithMethodType(receiver interface{}, method string, methodType reflect.Type, args ...interface{}) *Call {
// TODO: check arity, types.
margs := make([]Matcher, len(args))
for i, arg := range args {
if m, ok := arg.(Matcher); ok {
margs[i] = m
} else if arg == nil {
// Handle nil specially so that passing a nil interface value
// will match the typed nils of concrete args.
margs[i] = Nil()
} else {
margs[i] = Eq(arg)
}
}
ctrl.mu.Lock()
defer ctrl.mu.Unlock()
origin := callerInfo(2)
call := &Call{t: ctrl.t, receiver: receiver, method: method, methodType: methodType, args: margs, origin: origin, minCalls: 1, maxCalls: 1}
ctrl.expectedCalls.Add(call)
return call
}
func (ctrl *Controller) Call(receiver interface{}, method string, args ...interface{}) []interface{} {
ctrl.mu.Lock()
defer ctrl.mu.Unlock()
expected, err := ctrl.expectedCalls.FindMatch(receiver, method, args)
if err != nil {
origin := callerInfo(2)
ctrl.t.Fatalf("Unexpected call to %T.%v(%v) at %s because: %s", receiver, method, args, origin, err)
}
// Two things happen here:
// * the matching call no longer needs to check prerequite calls,
// * and the prerequite calls are no longer expected, so remove them.
preReqCalls := expected.dropPrereqs()
for _, preReqCall := range preReqCalls {
ctrl.expectedCalls.Remove(preReqCall)
}
rets, action := expected.call(args)
if expected.exhausted() {
ctrl.expectedCalls.Remove(expected)
}
// Don't hold the lock while doing the call's action (if any)
// so that actions may execute concurrently.
// We use the deferred Unlock to capture any panics that happen above;
// here we add a deferred Lock to balance it.
ctrl.mu.Unlock()
defer ctrl.mu.Lock()
if action != nil {
action()
}
return rets
}
func (ctrl *Controller) Finish() {
ctrl.mu.Lock()
defer ctrl.mu.Unlock()
// If we're currently panicking, probably because this is a deferred call,
// pass through the panic.
if err := recover(); err != nil {
panic(err)
}
// Check that all remaining expected calls are satisfied.
failures := false
for _, methodMap := range ctrl.expectedCalls {
for _, calls := range methodMap {
for _, call := range calls {
if !call.satisfied() {
ctrl.t.Errorf("missing call(s) to %v", call)
failures = true
}
}
}
}
if failures {
ctrl.t.Fatalf("aborting test due to missing call(s)")
}
}
func callerInfo(skip int) string {
if _, file, line, ok := runtime.Caller(skip + 1); ok {
return fmt.Sprintf("%s:%d", file, line)
}
return "unknown file"
}

99
vendor/github.com/golang/mock/gomock/matchers.go generated vendored Normal file
View File

@ -0,0 +1,99 @@
//go:generate mockgen -destination mock_matcher/mock_matcher.go github.com/golang/mock/gomock Matcher
// Copyright 2010 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gomock
import (
"fmt"
"reflect"
)
// A Matcher is a representation of a class of values.
// It is used to represent the valid or expected arguments to a mocked method.
type Matcher interface {
// Matches returns whether x is a match.
Matches(x interface{}) bool
// String describes what the matcher matches.
String() string
}
type anyMatcher struct{}
func (anyMatcher) Matches(x interface{}) bool {
return true
}
func (anyMatcher) String() string {
return "is anything"
}
type eqMatcher struct {
x interface{}
}
func (e eqMatcher) Matches(x interface{}) bool {
return reflect.DeepEqual(e.x, x)
}
func (e eqMatcher) String() string {
return fmt.Sprintf("is equal to %v", e.x)
}
type nilMatcher struct{}
func (nilMatcher) Matches(x interface{}) bool {
if x == nil {
return true
}
v := reflect.ValueOf(x)
switch v.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map,
reflect.Ptr, reflect.Slice:
return v.IsNil()
}
return false
}
func (nilMatcher) String() string {
return "is nil"
}
type notMatcher struct {
m Matcher
}
func (n notMatcher) Matches(x interface{}) bool {
return !n.m.Matches(x)
}
func (n notMatcher) String() string {
// TODO: Improve this if we add a NotString method to the Matcher interface.
return "not(" + n.m.String() + ")"
}
// Constructors
func Any() Matcher { return anyMatcher{} }
func Eq(x interface{}) Matcher { return eqMatcher{x} }
func Nil() Matcher { return nilMatcher{} }
func Not(x interface{}) Matcher {
if m, ok := x.(Matcher); ok {
return notMatcher{m}
}
return notMatcher{Eq(x)}
}

View File

@ -0,0 +1,56 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/golang/mock/gomock (interfaces: Matcher)
package mock_gomock
import (
gomock "github.com/golang/mock/gomock"
reflect "reflect"
)
// MockMatcher is a mock of Matcher interface
type MockMatcher struct {
ctrl *gomock.Controller
recorder *MockMatcherMockRecorder
}
// MockMatcherMockRecorder is the mock recorder for MockMatcher
type MockMatcherMockRecorder struct {
mock *MockMatcher
}
// NewMockMatcher creates a new mock instance
func NewMockMatcher(ctrl *gomock.Controller) *MockMatcher {
mock := &MockMatcher{ctrl: ctrl}
mock.recorder = &MockMatcherMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockMatcher) EXPECT() *MockMatcherMockRecorder {
return m.recorder
}
// Matches mocks base method
func (m *MockMatcher) Matches(arg0 interface{}) bool {
ret := m.ctrl.Call(m, "Matches", arg0)
ret0, _ := ret[0].(bool)
return ret0
}
// Matches indicates an expected call of Matches
func (mr *MockMatcherMockRecorder) Matches(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Matches", reflect.TypeOf((*MockMatcher)(nil).Matches), arg0)
}
// String mocks base method
func (m *MockMatcher) String() string {
ret := m.ctrl.Call(m, "String")
ret0, _ := ret[0].(string)
return ret0
}
// String indicates an expected call of String
func (mr *MockMatcherMockRecorder) String() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "String", reflect.TypeOf((*MockMatcher)(nil).String))
}