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:
parent
680090bdf9
commit
8fb2424ea5
8
Makefile
8
Makefile
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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{}{}
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -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}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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"`
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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)}
|
||||
}
|
|
@ -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))
|
||||
}
|
Loading…
Reference in New Issue